graphunk 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +67 -0
- data/lib/graphunk/directed_graph.rb +107 -0
- data/lib/{graph.rb → graphunk/graph.rb} +22 -2
- data/lib/{undirected_graph.rb → graphunk/undirected_graph.rb} +0 -25
- data/lib/graphunk/weighted_graph.rb +10 -0
- data/lib/graphunk/weighted_undirected_graph.rb +59 -0
- data/lib/graphunk.rb +5 -2
- data/license.md +21 -0
- metadata +9 -5
- data/lib/directed_graph.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2fbceaea640b3b555fba08b526a211d0078a3dfd
|
4
|
+
data.tar.gz: 63fd21c8a5e0bf2b5f90de2cc9c848780f7e520a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ffd51fc13f3a6c5d9e24ed05ed97f5958c53921b4f28099a924c40fc03006e0fa1a8d618f870fad83e6600ca0975f992b6a343d7d66ec3facc0e5d69114ff99f
|
7
|
+
data.tar.gz: 91a642dc7941b6697855302dcb5a8924f6864d7401d1b6ad44de9a7a3f5eb89c2bf9a4a5f2fa2998ed0604df706341e6134cccda09c92e60735ada49103abd5f
|
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Graphunk
|
2
|
+
|
3
|
+
Graphunk defines simple and fully-tested graph classes in Ruby which you can use to perform graph-theoretical operations.
|
4
|
+
|
5
|
+
## Defining a Graph
|
6
|
+
|
7
|
+
There are two kinds of graphs supported by this system, directed graphs and undirected graphs.
|
8
|
+
|
9
|
+
Graphs inherit from Hash, so you can define a graph in the same way you would define a Hash:
|
10
|
+
|
11
|
+
```
|
12
|
+
UndirectedGraph[
|
13
|
+
'a' => ['b','c'],
|
14
|
+
'b' => ['c', 'd', 'e'],
|
15
|
+
'c' => ['d'],
|
16
|
+
'd' => ['e'],
|
17
|
+
'e' => []
|
18
|
+
]
|
19
|
+
```
|
20
|
+
|
21
|
+
Each key is a string representing a vertex, and the value is a list
|
22
|
+
of strings which represent neighbor vertices of the key.
|
23
|
+
|
24
|
+
In an undirected graph, edges are not represented redundantly, meaning
|
25
|
+
that the edge a-b in the above case is defined by "b" being a member of "a's" list, but
|
26
|
+
"a" is not a member of "b's" list. The add_edge method takes care of ordering automatically.
|
27
|
+
|
28
|
+
In a directed graph, the order in an edge matters. A construction of a directed graph
|
29
|
+
might look like this:
|
30
|
+
|
31
|
+
```
|
32
|
+
DirectedGraph[
|
33
|
+
'a' => ['b','c'],
|
34
|
+
'b' => ['a'],
|
35
|
+
'c' => ['d'],
|
36
|
+
'd' => []
|
37
|
+
]
|
38
|
+
```
|
39
|
+
|
40
|
+
Graphs can also be built by individually adding edges and vertices.
|
41
|
+
|
42
|
+
```
|
43
|
+
graph = UndirectedGraph.new
|
44
|
+
graph.add_vertex('a')
|
45
|
+
graph.add_vertex('b')
|
46
|
+
graph.add_edge('a','b')
|
47
|
+
```
|
48
|
+
|
49
|
+
## Testing
|
50
|
+
|
51
|
+
To run the test suite simply execute:
|
52
|
+
|
53
|
+
```
|
54
|
+
rspec
|
55
|
+
```
|
56
|
+
|
57
|
+
## Future Work
|
58
|
+
|
59
|
+
- More algorithms
|
60
|
+
- Make the Graph constructor more "safe"
|
61
|
+
- Support for flow networks
|
62
|
+
|
63
|
+
## Credits
|
64
|
+
|
65
|
+
All code (c) Evan Hemsley 2014
|
66
|
+
|
67
|
+
Special thanks to Mitchell Gerrard for inpiring this project.
|
@@ -0,0 +1,107 @@
|
|
1
|
+
class DirectedGraph < Graph
|
2
|
+
def add_edge(first_vertex, second_vertex)
|
3
|
+
if edge_exists?(first_vertex, second_vertex)
|
4
|
+
raise ArgumentError, "This edge already exists"
|
5
|
+
elsif vertex_exists?(first_vertex) && vertex_exists?(second_vertex)
|
6
|
+
self[first_vertex] << second_vertex
|
7
|
+
else
|
8
|
+
raise ArgumentError, "One of the vertices referenced does not exist in the graph"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def remove_edge(first_vertex, second_vertex)
|
13
|
+
if edge_exists?(first_vertex, second_vertex)
|
14
|
+
self[first_vertex].delete(second_vertex)
|
15
|
+
else
|
16
|
+
raise ArgumentError, "That edge does not exist in the graph"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def neighbors_of_vertex(name)
|
21
|
+
if vertex_exists?(name)
|
22
|
+
self[name]
|
23
|
+
else
|
24
|
+
raise ArgumentError, "That vertex does not exist in the graph"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def edge_exists?(first_vertex, second_vertex)
|
29
|
+
edges.include?([first_vertex, second_vertex])
|
30
|
+
end
|
31
|
+
|
32
|
+
def transpose
|
33
|
+
graph = DirectedGraph.new
|
34
|
+
vertices.each do |vertex|
|
35
|
+
graph.add_vertex(vertex)
|
36
|
+
end
|
37
|
+
edges.each do |edge|
|
38
|
+
graph.add_edge(edge.last, edge.first)
|
39
|
+
end
|
40
|
+
graph
|
41
|
+
end
|
42
|
+
|
43
|
+
def transpose!
|
44
|
+
reversed_edges = []
|
45
|
+
edges.each do |edge|
|
46
|
+
remove_edge(edge.first, edge.last)
|
47
|
+
reversed_edges << [edge.last, edge.first]
|
48
|
+
end
|
49
|
+
reversed_edges.each do |edge|
|
50
|
+
add_edge(edge.first, edge.last)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def reachable_by_two_path(start)
|
55
|
+
if vertex_exists?(start)
|
56
|
+
reached_vertices = self[start]
|
57
|
+
reached_vertices.each do |vertex|
|
58
|
+
reached_vertices += self[vertex]
|
59
|
+
end
|
60
|
+
reached_vertices.uniq
|
61
|
+
else
|
62
|
+
raise ArgumentError, "That vertex does not exist in the graph"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def square
|
67
|
+
graph = self.clone
|
68
|
+
|
69
|
+
vertices.each do |vertex|
|
70
|
+
(reachable_by_two_path(vertex) - [vertex]).each do |reachable|
|
71
|
+
graph.add_edge(vertex, reachable) unless edge_exists?(vertex, reachable)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
graph
|
76
|
+
end
|
77
|
+
|
78
|
+
def dfs
|
79
|
+
discovered = []
|
80
|
+
time = 0
|
81
|
+
output = {}
|
82
|
+
vertices.each { |vertex| output[vertex] = {} }
|
83
|
+
|
84
|
+
dfs_helper = lambda do |u| #only u can do u, so do it!
|
85
|
+
discovered << u
|
86
|
+
time += 1
|
87
|
+
output[u][:start] = time
|
88
|
+
|
89
|
+
neighbors_of_vertex(u).each do |neighbor|
|
90
|
+
dfs_helper.call neighbor unless discovered.include?(neighbor)
|
91
|
+
end
|
92
|
+
|
93
|
+
time += 1
|
94
|
+
output[u][:finish] = time
|
95
|
+
end
|
96
|
+
|
97
|
+
vertices.each do |vertex|
|
98
|
+
dfs_helper.call vertex unless discovered.include?(vertex)
|
99
|
+
end
|
100
|
+
|
101
|
+
output
|
102
|
+
end
|
103
|
+
|
104
|
+
def topological_sort
|
105
|
+
dfs.sort_by { |vertex, times| times[:finish] }.map(&:first).reverse
|
106
|
+
end
|
107
|
+
end
|
@@ -24,8 +24,8 @@ class Graph < Hash
|
|
24
24
|
|
25
25
|
def remove_vertex(name)
|
26
26
|
if vertex_exists?(name)
|
27
|
-
|
28
|
-
|
27
|
+
edges.each do |edge|
|
28
|
+
remove_edge(edge.first, edge.last) if edge.include?(name)
|
29
29
|
end
|
30
30
|
self.delete(name)
|
31
31
|
else
|
@@ -33,6 +33,16 @@ class Graph < Hash
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
+
def neighbors_of_vertex(name)
|
37
|
+
if vertex_exists?(name)
|
38
|
+
edges.select { |edge| edge.include? name }.map do |edge|
|
39
|
+
edge.first == name ? edge.last : edge.first
|
40
|
+
end
|
41
|
+
else
|
42
|
+
raise ArgumentError, "That vertex does not exist in the graph"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
36
46
|
def edges_on_vertex(name)
|
37
47
|
if vertex_exists?(name)
|
38
48
|
edges.select { |edge| edge.include?(name) }
|
@@ -41,7 +51,17 @@ class Graph < Hash
|
|
41
51
|
end
|
42
52
|
end
|
43
53
|
|
54
|
+
def edge_exists?(first_vertex, second_vertex)
|
55
|
+
edges.include?(order_vertices(first_vertex, second_vertex))
|
56
|
+
end
|
57
|
+
|
44
58
|
def vertex_exists?(name)
|
45
59
|
vertices.include?(name)
|
46
60
|
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def order_vertices(v, u)
|
65
|
+
[v, u].sort
|
66
|
+
end
|
47
67
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'graph'
|
2
1
|
require 'set'
|
3
2
|
|
4
3
|
class UndirectedGraph < Graph
|
@@ -22,24 +21,6 @@ class UndirectedGraph < Graph
|
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
25
|
-
def neighbors_of_vertex(name)
|
26
|
-
if vertex_exists?(name)
|
27
|
-
edges.select { |edge| edge.include? name }.map do |edge|
|
28
|
-
if edge.first == name
|
29
|
-
edge.last
|
30
|
-
else
|
31
|
-
edge.first
|
32
|
-
end
|
33
|
-
end
|
34
|
-
else
|
35
|
-
raise ArgumentError, "That vertex does not exist in the graph"
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def edge_exists?(first_vertex, second_vertex)
|
40
|
-
edges.include?(order_vertices(first_vertex, second_vertex))
|
41
|
-
end
|
42
|
-
|
43
24
|
def lexicographic_bfs
|
44
25
|
sets = [vertices]
|
45
26
|
output_vertices = []
|
@@ -136,10 +117,4 @@ class UndirectedGraph < Graph
|
|
136
117
|
|
137
118
|
true
|
138
119
|
end
|
139
|
-
|
140
|
-
private
|
141
|
-
|
142
|
-
def order_vertices(first_vertex, second_vertex)
|
143
|
-
[first_vertex, second_vertex].sort
|
144
|
-
end
|
145
120
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class WeightedUndirectedGraph < WeightedGraph
|
2
|
+
|
3
|
+
def add_edge(v, u, w)
|
4
|
+
if edge_exists?(v, u)
|
5
|
+
raise ArgumentError, "This edge already exists"
|
6
|
+
elsif vertex_exists?(v) && vertex_exists?(u)
|
7
|
+
ordered_vertices = order_vertices(v, u)
|
8
|
+
self[ordered_vertices.first] << ordered_vertices.last
|
9
|
+
weights[ordered_vertices] = w
|
10
|
+
else
|
11
|
+
raise ArgumentError, "One of the vertices referenced does not exist in the graph"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def remove_edge(v, u)
|
16
|
+
if edge_exists?(v, u)
|
17
|
+
ordered_vertices = order_vertices(v, u)
|
18
|
+
self[ordered_vertices.first].delete(ordered_vertices.last)
|
19
|
+
remove_weight(v, u)
|
20
|
+
else
|
21
|
+
raise ArgumentError, "That edge does not exist in the graph"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def adjust_weight(v, u, w)
|
26
|
+
if edge_exists?(v, u)
|
27
|
+
weights[[v,u]] = w
|
28
|
+
else
|
29
|
+
raise ArgumentError, "That edge does not exist in the graph"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Prim's Algorithm
|
34
|
+
def minimum_spanning_tree
|
35
|
+
forest = WeightedUndirectedGraph.new
|
36
|
+
forest.add_vertex(vertices.first)
|
37
|
+
until forest.vertices.sort == vertices.sort
|
38
|
+
minimum_edge = edges_to_examine(forest.vertices).min_by { |edge| weights[edge] }
|
39
|
+
vertex_to_add = forest.vertices.include?(minimum_edge.first) ? minimum_edge.last : minimum_edge.first
|
40
|
+
forest.add_vertex(vertex_to_add)
|
41
|
+
forest.add_edge(minimum_edge.first, minimum_edge.last, weights[minimum_edge])
|
42
|
+
end
|
43
|
+
forest
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def edges_to_examine(vertices)
|
49
|
+
[].tap do |examinable|
|
50
|
+
edges.each do |edge|
|
51
|
+
examinable << edge if (edge & vertices).count == 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def remove_weight(v, u)
|
57
|
+
weights.delete(order_vertices(v, u))
|
58
|
+
end
|
59
|
+
end
|
data/lib/graphunk.rb
CHANGED
data/license.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) [2014] [Evan Hemsley]
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphunk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Hemsley
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-03-04 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: This gem defines graph classes which are useful in various mathematical
|
14
14
|
applications.
|
@@ -17,10 +17,14 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
-
-
|
21
|
-
- lib/graph.rb
|
20
|
+
- README.md
|
22
21
|
- lib/graphunk.rb
|
23
|
-
- lib/
|
22
|
+
- lib/graphunk/directed_graph.rb
|
23
|
+
- lib/graphunk/graph.rb
|
24
|
+
- lib/graphunk/undirected_graph.rb
|
25
|
+
- lib/graphunk/weighted_graph.rb
|
26
|
+
- lib/graphunk/weighted_undirected_graph.rb
|
27
|
+
- license.md
|
24
28
|
homepage: https://github.com/ehemsley/graphunk
|
25
29
|
licenses:
|
26
30
|
- MIT
|
data/lib/directed_graph.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
require 'graph'
|
2
|
-
class DirectedGraph < Graph
|
3
|
-
def add_edge(first_vertex, second_vertex)
|
4
|
-
if edge_exists?(first_vertex, second_vertex)
|
5
|
-
raise ArgumentError, "This edge already exists"
|
6
|
-
elsif vertex_exists?(first_vertex) && vertex_exists?(second_vertex)
|
7
|
-
self[first_vertex] << second_vertex
|
8
|
-
else
|
9
|
-
raise ArgumentError, "One of the vertices referenced does not exist in the graph"
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def remove_edge(first_vertex, second_vertex)
|
14
|
-
if edge_exists?(first_vertex, second_vertex)
|
15
|
-
self[first_vertex].delete(second_vertex)
|
16
|
-
else
|
17
|
-
raise ArgumentError, "That edge does not exist in the graph"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def neighbors_of_vertex(name)
|
22
|
-
if vertex_exists?(name)
|
23
|
-
self[name]
|
24
|
-
else
|
25
|
-
raise ArgumentError, "That vertex does not exist in the graph"
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def edge_exists?(first_vertex, second_vertex)
|
30
|
-
edges.include?([first_vertex, second_vertex])
|
31
|
-
end
|
32
|
-
end
|