graphunk 0.4.1 → 0.5.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 +8 -6
- data/lib/graphunk/directed_graph.rb +81 -79
- data/lib/graphunk/graph.rb +57 -55
- data/lib/graphunk/undirected_graph.rb +94 -94
- data/lib/graphunk/weighted_directed_graph.rb +116 -0
- data/lib/graphunk/weighted_graph.rb +8 -5
- data/lib/graphunk/weighted_undirected_graph.rb +54 -48
- data/lib/graphunk.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cc9f2d167dfb88b6c0b630bb57e537af41f1551a
|
4
|
+
data.tar.gz: 515300ca5d54dda0d0ba4c61c32dcd568c569f5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 811bb6a91750487945b8b4098061c79f5a5038787d8306aefe1a97ea208b316710b6fe5b327a06e07579fbc4a24dd08135f4ba3cc7459075ce0d6ee47ee44319
|
7
|
+
data.tar.gz: 7e83dbe57d445d2729f0a68a3999c4ce85ad457102f4dac6d3f61d6f0c7ff56b2163188fe4264ae8bb14ecd6e1799edc1090498aaa7eedfdda7eb0f81e39e015
|
data/README.md
CHANGED
@@ -9,7 +9,7 @@ Graphunk defines simple and fully-tested graph classes in Ruby which you can use
|
|
9
9
|
Graphs are internally represented as a hash, so you can define a graph similarly to how you would define a Hash:
|
10
10
|
|
11
11
|
```
|
12
|
-
UndirectedGraph.new({
|
12
|
+
Graphunk::UndirectedGraph.new({
|
13
13
|
'a' => ['b','c'],
|
14
14
|
'b' => ['c', 'd', 'e'],
|
15
15
|
'c' => ['d'],
|
@@ -29,7 +29,7 @@ In a directed graph, the order in an edge matters. A construction of a directed
|
|
29
29
|
might look like this:
|
30
30
|
|
31
31
|
```
|
32
|
-
DirectedGraph.new({
|
32
|
+
Graphunk::DirectedGraph.new({
|
33
33
|
'a' => ['b','c'],
|
34
34
|
'b' => ['a'],
|
35
35
|
'c' => ['d'],
|
@@ -40,7 +40,7 @@ DirectedGraph.new({
|
|
40
40
|
Graphs can also be built by individually adding edges and vertices.
|
41
41
|
|
42
42
|
```
|
43
|
-
graph = UndirectedGraph.new
|
43
|
+
graph = Graphunk::UndirectedGraph.new
|
44
44
|
graph.add_vertex('a')
|
45
45
|
graph.add_vertex('b')
|
46
46
|
graph.add_edge('a','b')
|
@@ -53,7 +53,7 @@ Weighted graphs have an additional property: each edge must specify a numerical
|
|
53
53
|
To construct a weighted graph, you must pass in the vertex and edge information as well as the weights:
|
54
54
|
|
55
55
|
```
|
56
|
-
WeightedUndirectedGraph.new({
|
56
|
+
Graphunk::WeightedUndirectedGraph.new({
|
57
57
|
'a' => ['b','c'],
|
58
58
|
'b' => ['c', 'd', 'e'],
|
59
59
|
'c' => ['d'],
|
@@ -73,13 +73,15 @@ WeightedUndirectedGraph.new({
|
|
73
73
|
|
74
74
|
You can also build them by adding vertices and edges.
|
75
75
|
```
|
76
|
-
graph = WeightedUndirectedGraph.new
|
76
|
+
graph = Graphunk::WeightedUndirectedGraph.new
|
77
77
|
graph.add_vertex('a')
|
78
78
|
graph.add_vertex('b')
|
79
79
|
graph.add_edge('a','b',3)
|
80
80
|
```
|
81
81
|
Now the edge 'a-b' will have a weight of 3.
|
82
82
|
|
83
|
+
WeightedDirectedGraph behaves similarly.
|
84
|
+
|
83
85
|
## Testing
|
84
86
|
|
85
87
|
To run the test suite simply execute:
|
@@ -98,4 +100,4 @@ rspec
|
|
98
100
|
|
99
101
|
All code (c) Evan Hemsley 2014
|
100
102
|
|
101
|
-
Special thanks to Mitchell Gerrard for
|
103
|
+
Special thanks to Mitchell Gerrard for inspiring this project.
|
@@ -1,107 +1,109 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
module Graphunk
|
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
|
+
@representation[first_vertex] << second_vertex
|
8
|
+
else
|
9
|
+
raise ArgumentError, "One of the vertices referenced does not exist in the graph"
|
10
|
+
end
|
9
11
|
end
|
10
|
-
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
def remove_edge(first_vertex, second_vertex)
|
14
|
+
if edge_exists?(first_vertex, second_vertex)
|
15
|
+
@representation[first_vertex].delete(second_vertex)
|
16
|
+
else
|
17
|
+
raise ArgumentError, "That edge does not exist in the graph"
|
18
|
+
end
|
17
19
|
end
|
18
|
-
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
def neighbors_of_vertex(name)
|
22
|
+
if vertex_exists?(name)
|
23
|
+
@representation[name]
|
24
|
+
else
|
25
|
+
raise ArgumentError, "That vertex does not exist in the graph"
|
26
|
+
end
|
25
27
|
end
|
26
|
-
end
|
27
28
|
|
28
|
-
|
29
|
-
|
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)
|
29
|
+
def edge_exists?(first_vertex, second_vertex)
|
30
|
+
edges.include?([first_vertex, second_vertex])
|
39
31
|
end
|
40
|
-
graph
|
41
|
-
end
|
42
32
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
33
|
+
def transpose
|
34
|
+
graph = DirectedGraph.new
|
35
|
+
vertices.each do |vertex|
|
36
|
+
graph.add_vertex(vertex)
|
37
|
+
end
|
38
|
+
edges.each do |edge|
|
39
|
+
graph.add_edge(edge.last, edge.first)
|
40
|
+
end
|
41
|
+
graph
|
48
42
|
end
|
49
|
-
|
50
|
-
|
43
|
+
|
44
|
+
def transpose!
|
45
|
+
reversed_edges = []
|
46
|
+
edges.each do |edge|
|
47
|
+
remove_edge(edge.first, edge.last)
|
48
|
+
reversed_edges << [edge.last, edge.first]
|
49
|
+
end
|
50
|
+
reversed_edges.each do |edge|
|
51
|
+
add_edge(edge.first, edge.last)
|
52
|
+
end
|
51
53
|
end
|
52
|
-
end
|
53
54
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
def reachable_by_two_path(start)
|
56
|
+
if vertex_exists?(start)
|
57
|
+
reached_vertices = @representation[start]
|
58
|
+
reached_vertices.each do |vertex|
|
59
|
+
reached_vertices += @representation[vertex]
|
60
|
+
end
|
61
|
+
reached_vertices.uniq
|
62
|
+
else
|
63
|
+
raise ArgumentError, "That vertex does not exist in the graph"
|
59
64
|
end
|
60
|
-
reached_vertices.uniq
|
61
|
-
else
|
62
|
-
raise ArgumentError, "That vertex does not exist in the graph"
|
63
65
|
end
|
64
|
-
end
|
65
66
|
|
66
|
-
|
67
|
-
|
67
|
+
def square
|
68
|
+
graph = self.clone
|
68
69
|
|
69
|
-
|
70
|
-
|
71
|
-
|
70
|
+
vertices.each do |vertex|
|
71
|
+
(reachable_by_two_path(vertex) - [vertex]).each do |reachable|
|
72
|
+
graph.add_edge(vertex, reachable) unless edge_exists?(vertex, reachable)
|
73
|
+
end
|
72
74
|
end
|
75
|
+
|
76
|
+
graph
|
73
77
|
end
|
74
78
|
|
75
|
-
|
76
|
-
|
79
|
+
def dfs
|
80
|
+
discovered = []
|
81
|
+
time = 0
|
82
|
+
output = {}
|
83
|
+
vertices.each { |vertex| output[vertex] = {} }
|
77
84
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
vertices.each { |vertex| output[vertex] = {} }
|
85
|
+
dfs_helper = lambda do |u| #only u can do u, so do it!
|
86
|
+
discovered << u
|
87
|
+
time += 1
|
88
|
+
output[u][:start] = time
|
83
89
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
output[u][:start] = time
|
90
|
+
neighbors_of_vertex(u).each do |neighbor|
|
91
|
+
dfs_helper.call neighbor unless discovered.include?(neighbor)
|
92
|
+
end
|
88
93
|
|
89
|
-
|
90
|
-
|
94
|
+
time += 1
|
95
|
+
output[u][:finish] = time
|
91
96
|
end
|
92
97
|
|
93
|
-
|
94
|
-
|
95
|
-
|
98
|
+
vertices.each do |vertex|
|
99
|
+
dfs_helper.call vertex unless discovered.include?(vertex)
|
100
|
+
end
|
96
101
|
|
97
|
-
|
98
|
-
dfs_helper.call vertex unless discovered.include?(vertex)
|
102
|
+
output
|
99
103
|
end
|
100
104
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
def topological_sort
|
105
|
-
dfs.sort_by { |vertex, times| times[:finish] }.map(&:first).reverse
|
105
|
+
def topological_sort
|
106
|
+
dfs.sort_by { |vertex, times| times[:finish] }.map(&:first).reverse
|
107
|
+
end
|
106
108
|
end
|
107
109
|
end
|
data/lib/graphunk/graph.rb
CHANGED
@@ -1,79 +1,81 @@
|
|
1
1
|
# this class should not be invoked directly
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
module Graphunk
|
3
|
+
class Graph
|
4
|
+
def initialize(hash = {})
|
5
|
+
@representation = hash
|
6
|
+
end
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
def vertices
|
9
|
+
@representation.keys
|
10
|
+
end
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
def edges
|
13
|
+
[].tap do |edge_constructor|
|
14
|
+
vertices.each do |vertex|
|
15
|
+
@representation[vertex].each do |neighbor|
|
16
|
+
edge_constructor << [vertex, neighbor]
|
17
|
+
end
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
19
|
-
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
def add_vertex(name)
|
23
|
+
unless vertex_exists?(name)
|
24
|
+
@representation[name] = []
|
25
|
+
else
|
26
|
+
raise ArgumentError, "Vertex already exists"
|
27
|
+
end
|
26
28
|
end
|
27
|
-
end
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
def add_vertices(*names)
|
31
|
+
if (names & vertices).count == 0
|
32
|
+
names.each { |name| add_vertex(name) }
|
33
|
+
else
|
34
|
+
raise ArgumentError, "One or more of the given vertices already exists"
|
35
|
+
end
|
34
36
|
end
|
35
|
-
end
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
def remove_vertex(name)
|
39
|
+
if vertex_exists?(name)
|
40
|
+
edges.each do |edge|
|
41
|
+
remove_edge(edge.first, edge.last) if edge.include?(name)
|
42
|
+
end
|
43
|
+
@representation.delete(name)
|
44
|
+
else
|
45
|
+
raise ArgumentError, "That vertex does not exist in the graph"
|
41
46
|
end
|
42
|
-
@representation.delete(name)
|
43
|
-
else
|
44
|
-
raise ArgumentError, "That vertex does not exist in the graph"
|
45
47
|
end
|
46
|
-
end
|
47
48
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
def neighbors_of_vertex(name)
|
50
|
+
if vertex_exists?(name)
|
51
|
+
edges.select { |edge| edge.include? name }.map do |edge|
|
52
|
+
edge.first == name ? edge.last : edge.first
|
53
|
+
end
|
54
|
+
else
|
55
|
+
raise ArgumentError, "That vertex does not exist in the graph"
|
52
56
|
end
|
53
|
-
else
|
54
|
-
raise ArgumentError, "That vertex does not exist in the graph"
|
55
57
|
end
|
56
|
-
end
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
59
|
+
def edges_on_vertex(name)
|
60
|
+
if vertex_exists?(name)
|
61
|
+
edges.select { |edge| edge.include?(name) }
|
62
|
+
else
|
63
|
+
raise ArgumentError, "That vertex does not exist in the graph"
|
64
|
+
end
|
63
65
|
end
|
64
|
-
end
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
67
|
+
def edge_exists?(first_vertex, second_vertex)
|
68
|
+
edges.include?(order_vertices(first_vertex, second_vertex))
|
69
|
+
end
|
69
70
|
|
70
|
-
|
71
|
-
|
72
|
-
|
71
|
+
def vertex_exists?(name)
|
72
|
+
vertices.include?(name)
|
73
|
+
end
|
73
74
|
|
74
|
-
|
75
|
+
private
|
75
76
|
|
76
|
-
|
77
|
-
|
77
|
+
def order_vertices(v, u)
|
78
|
+
[v, u].sort
|
79
|
+
end
|
78
80
|
end
|
79
81
|
end
|
@@ -1,120 +1,120 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
module Graphunk
|
2
|
+
class UndirectedGraph < 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
|
+
ordered_vertices = order_vertices(first_vertex, second_vertex)
|
8
|
+
@representation[ordered_vertices.first] << ordered_vertices.last
|
9
|
+
else
|
10
|
+
raise ArgumentError, "One of the vertices referenced does not exist in the graph"
|
11
|
+
end
|
12
12
|
end
|
13
|
-
end
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
14
|
+
def remove_edge(first_vertex, second_vertex)
|
15
|
+
if edge_exists?(first_vertex, second_vertex)
|
16
|
+
ordered_vertices = order_vertices(first_vertex, second_vertex)
|
17
|
+
@representation[ordered_vertices.first].delete(ordered_vertices.last)
|
18
|
+
else
|
19
|
+
raise ArgumentError, "That edge does not exist in the graph"
|
20
|
+
end
|
21
21
|
end
|
22
|
-
end
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
def lexicographic_bfs
|
24
|
+
sets = [vertices]
|
25
|
+
output_vertices = []
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
27
|
+
until sets.empty?
|
28
|
+
v = sets.first.delete_at(0)
|
29
|
+
sets.delete_at(0) if sets.first.empty?
|
30
|
+
output_vertices << v
|
31
|
+
replaced = []
|
32
|
+
neighbors_of_vertex(v).each do |neighbor|
|
33
|
+
s = sets.select{ |set| set.include?(neighbor) }.first
|
34
|
+
if s
|
35
|
+
if replaced.include?(s)
|
36
|
+
t = sets[sets.find_index(s)-1]
|
37
|
+
else
|
38
|
+
t = []
|
39
|
+
sets.insert(sets.find_index(s), t)
|
40
|
+
replaced << s
|
41
|
+
end
|
42
|
+
s.delete(neighbor)
|
43
|
+
t << neighbor
|
44
|
+
sets.delete(s) if s.empty?
|
42
45
|
end
|
43
|
-
s.delete(neighbor)
|
44
|
-
t << neighbor
|
45
|
-
sets.delete(s) if s.empty?
|
46
46
|
end
|
47
47
|
end
|
48
|
-
end
|
49
48
|
|
50
|
-
|
51
|
-
|
49
|
+
output_vertices
|
50
|
+
end
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
52
|
+
def clique?(vertex_list)
|
53
|
+
clique = true
|
54
|
+
vertex_list.each do |vertex|
|
55
|
+
unless (neighbors_of_vertex(vertex) & vertex_list).sort == (vertex_list - [vertex]).sort
|
56
|
+
clique = false
|
57
|
+
break
|
58
|
+
end
|
59
59
|
end
|
60
|
+
clique
|
60
61
|
end
|
61
|
-
clique
|
62
|
-
end
|
63
62
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
63
|
+
def chordal?
|
64
|
+
chordal = true
|
65
|
+
(lexicographic_ordering = lexicographic_bfs.reverse).each_with_index do |v, i|
|
66
|
+
successors_of_v = lexicographic_ordering[i, lexicographic_ordering.size]
|
67
|
+
unless clique?([v] | (neighbors_of_vertex(v) & successors_of_v))
|
68
|
+
chordal = false
|
69
|
+
break
|
70
|
+
end
|
71
71
|
end
|
72
|
+
chordal
|
72
73
|
end
|
73
|
-
chordal
|
74
|
-
end
|
75
|
-
alias_method :triangulated?, :chordal?
|
76
|
-
|
77
|
-
def complete?
|
78
|
-
n = vertices.count
|
79
|
-
edges.count == (n * (n-1) / 2)
|
80
|
-
end
|
74
|
+
alias_method :triangulated?, :chordal?
|
81
75
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
partition = Hash.new
|
86
|
-
vertices.each do |vertex|
|
87
|
-
colors[vertex] = "white"
|
88
|
-
d[vertex] = Float::INFINITY
|
89
|
-
partition[vertex] = 0
|
76
|
+
def complete?
|
77
|
+
n = vertices.count
|
78
|
+
edges.count == (n * (n-1) / 2)
|
90
79
|
end
|
91
80
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
81
|
+
def bipartite?
|
82
|
+
colors = Hash.new
|
83
|
+
d = Hash.new
|
84
|
+
partition = Hash.new
|
85
|
+
vertices.each do |vertex|
|
86
|
+
colors[vertex] = "white"
|
87
|
+
d[vertex] = Float::INFINITY
|
88
|
+
partition[vertex] = 0
|
89
|
+
end
|
90
|
+
|
91
|
+
start = vertices.first
|
92
|
+
colors[start] = "gray"
|
93
|
+
partition[start] = 1
|
94
|
+
d[start] = 0
|
96
95
|
|
97
|
-
|
98
|
-
|
96
|
+
stack = []
|
97
|
+
stack.push(start)
|
99
98
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
99
|
+
until stack.empty?
|
100
|
+
vertex = stack.pop
|
101
|
+
neighbors_of_vertex(vertex).each do |neighbor|
|
102
|
+
if partition[neighbor] == partition[vertex]
|
103
|
+
return false
|
104
|
+
else
|
105
|
+
if colors[neighbor] == "white"
|
106
|
+
colors[neighbor] == "gray"
|
107
|
+
d[neighbor] = d[vertex] + 1
|
108
|
+
partition[neighbor] = 3 - partition[vertex]
|
109
|
+
stack.push(neighbor)
|
110
|
+
end
|
111
111
|
end
|
112
112
|
end
|
113
|
+
stack.pop
|
114
|
+
colors[vertex] = "black"
|
113
115
|
end
|
114
|
-
stack.pop
|
115
|
-
colors[vertex] = "black"
|
116
|
-
end
|
117
116
|
|
118
|
-
|
117
|
+
true
|
118
|
+
end
|
119
119
|
end
|
120
120
|
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module Graphunk
|
2
|
+
class WeightedDirectedGraph < WeightedGraph
|
3
|
+
|
4
|
+
def add_edge(v, u, w)
|
5
|
+
if edge_exists?(v, u)
|
6
|
+
raise ArgumentError, "This edge already exists"
|
7
|
+
elsif vertex_exists?(v) && vertex_exists?(u)
|
8
|
+
@representation[v] << u
|
9
|
+
@weights[[v,u]] = 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
|
+
@representation[v].delete(u)
|
18
|
+
@weights.delete([v,u])
|
19
|
+
else
|
20
|
+
raise ArgumentError, "That edge does not exist in the graph"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def edge_weight(v, u)
|
25
|
+
if edge_exists?(v,u)
|
26
|
+
@weights[[v,u]]
|
27
|
+
else
|
28
|
+
raise ArgumentError, "That edge does not exist in the graph"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def adjust_weight(v, u, w)
|
33
|
+
if edge_exists?(v, u)
|
34
|
+
@weights[[v,u]] = w
|
35
|
+
else
|
36
|
+
raise ArgumentError, "That edge does not exist in the graph"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def neighbors_of_vertex(v)
|
41
|
+
if vertex_exists?(v)
|
42
|
+
@representation[v]
|
43
|
+
else
|
44
|
+
raise ArgumentError, "That vertex does not exist in the graph"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def shortest_path_distance(v, u)
|
49
|
+
if vertex_exists?(u)
|
50
|
+
single_source_shortest_path_distances(v)[u]
|
51
|
+
else
|
52
|
+
raise ArgumentError, "A specified vertex does not exist in the graph"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def shortest_path(v, u)
|
57
|
+
if vertex_exists?(u)
|
58
|
+
[].tap do |s|
|
59
|
+
previous = single_source_shortest_path_previous(v)
|
60
|
+
while previous[u]
|
61
|
+
s.insert(0, u)
|
62
|
+
u = previous[u]
|
63
|
+
end
|
64
|
+
s.insert(0, v)
|
65
|
+
end
|
66
|
+
else
|
67
|
+
raise ArgumentError, "A specified vertex does not exist in the graph"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# Dijsktra's Algorithm
|
74
|
+
def single_source_shortest_path(source)
|
75
|
+
raise ArgumentError, "A specified vertex does not exist in the graph" unless vertex_exists?(source)
|
76
|
+
|
77
|
+
distance = {}
|
78
|
+
previous = {}
|
79
|
+
vertices.each do |vertex|
|
80
|
+
distance[vertex] = Float::INFINITY
|
81
|
+
previous[vertex] = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
distance[source] = 0
|
85
|
+
q = vertices
|
86
|
+
|
87
|
+
until q.empty?
|
88
|
+
u = q.delete(q.min_by { |vertex| distance[vertex] })
|
89
|
+
break if distance[u] == Float::INFINITY
|
90
|
+
|
91
|
+
neighbors_of_vertex(u).each do |v|
|
92
|
+
alt = distance[u] + edge_weight(u, v)
|
93
|
+
if alt < distance[v]
|
94
|
+
distance[v] = alt
|
95
|
+
previous[v] = u
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
{distance: distance, previous: previous}
|
101
|
+
end
|
102
|
+
|
103
|
+
def single_source_shortest_path_distances(source)
|
104
|
+
single_source_shortest_path(source)[:distance]
|
105
|
+
end
|
106
|
+
|
107
|
+
def single_source_shortest_path_previous(source)
|
108
|
+
single_source_shortest_path(source)[:previous]
|
109
|
+
end
|
110
|
+
|
111
|
+
def edge_exists?(v, u)
|
112
|
+
edges.include?([v,u])
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -1,10 +1,13 @@
|
|
1
1
|
#this class should not be invoked directly
|
2
|
-
|
2
|
+
module Graphunk
|
3
|
+
class WeightedGraph < Graph
|
3
4
|
|
4
|
-
|
5
|
+
attr_reader :weights
|
6
|
+
|
7
|
+
def initialize(hash = {}, weights = {})
|
8
|
+
@representation = hash
|
9
|
+
@weights = weights
|
10
|
+
end
|
5
11
|
|
6
|
-
def initialize(hash = {}, weights = {})
|
7
|
-
@representation = hash
|
8
|
-
@weights = weights
|
9
12
|
end
|
10
13
|
end
|
@@ -1,63 +1,69 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
module Graphunk
|
2
|
+
class WeightedUndirectedGraph < WeightedGraph
|
3
|
+
|
4
|
+
def add_edge(v, u, w)
|
5
|
+
if edge_exists?(v, u)
|
6
|
+
raise ArgumentError, "This edge already exists"
|
7
|
+
elsif vertex_exists?(v) && vertex_exists?(u)
|
8
|
+
ordered_vertices = order_vertices(v, u)
|
9
|
+
@representation[ordered_vertices.first] << ordered_vertices.last
|
10
|
+
@weights[ordered_vertices] = w
|
11
|
+
else
|
12
|
+
raise ArgumentError, "One of the vertices referenced does not exist in the graph"
|
13
|
+
end
|
12
14
|
end
|
13
|
-
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
def remove_edge(v, u)
|
17
|
+
if edge_exists?(v, u)
|
18
|
+
ordered_vertices = order_vertices(v, u)
|
19
|
+
@representation[ordered_vertices.first].delete(ordered_vertices.last)
|
20
|
+
remove_weight(v, u)
|
21
|
+
else
|
22
|
+
raise ArgumentError, "That edge does not exist in the graph"
|
23
|
+
end
|
22
24
|
end
|
23
|
-
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
def edge_weight(v, u)
|
27
|
+
if edge_exists?(v,u)
|
28
|
+
@weights[order_vertices(v,u)]
|
29
|
+
else
|
30
|
+
raise ArgumentError, "That edge does not exist in the graph"
|
31
|
+
end
|
32
|
+
end
|
28
33
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
def adjust_weight(v, u, w)
|
35
|
+
if edge_exists?(v, u)
|
36
|
+
@weights[order_vertices(v,u)] = w
|
37
|
+
else
|
38
|
+
raise ArgumentError, "That edge does not exist in the graph"
|
39
|
+
end
|
34
40
|
end
|
35
|
-
end
|
36
41
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
# Prim's Algorithm
|
43
|
+
def minimum_spanning_tree
|
44
|
+
forest = WeightedUndirectedGraph.new
|
45
|
+
forest.add_vertex(vertices.first)
|
46
|
+
until forest.vertices.sort == vertices.sort
|
47
|
+
minimum_edge = edges_to_examine(forest.vertices).min_by { |edge| weights[edge] }
|
48
|
+
vertex_to_add = forest.vertices.include?(minimum_edge.first) ? minimum_edge.last : minimum_edge.first
|
49
|
+
forest.add_vertex(vertex_to_add)
|
50
|
+
forest.add_edge(minimum_edge.first, minimum_edge.last, weights[minimum_edge])
|
51
|
+
end
|
52
|
+
forest
|
46
53
|
end
|
47
|
-
forest
|
48
|
-
end
|
49
54
|
|
50
|
-
|
55
|
+
private
|
51
56
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
57
|
+
def edges_to_examine(vertices)
|
58
|
+
[].tap do |examinable|
|
59
|
+
edges.each do |edge|
|
60
|
+
examinable << edge if (edge & vertices).count == 1
|
61
|
+
end
|
56
62
|
end
|
57
63
|
end
|
58
|
-
end
|
59
64
|
|
60
|
-
|
61
|
-
|
65
|
+
def remove_weight(v, u)
|
66
|
+
@weights.delete(order_vertices(v, u))
|
67
|
+
end
|
62
68
|
end
|
63
69
|
end
|
data/lib/graphunk.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphunk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Hemsley
|
@@ -62,6 +62,7 @@ files:
|
|
62
62
|
- lib/graphunk/directed_graph.rb
|
63
63
|
- lib/graphunk/graph.rb
|
64
64
|
- lib/graphunk/undirected_graph.rb
|
65
|
+
- lib/graphunk/weighted_directed_graph.rb
|
65
66
|
- lib/graphunk/weighted_graph.rb
|
66
67
|
- lib/graphunk/weighted_undirected_graph.rb
|
67
68
|
- license.md
|