graphunk 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|