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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4575d6b7451a1081528ebc8c930b54bf69f115f3
4
- data.tar.gz: 28a716ec22511dc7c9ec3618c0c57583dc912231
3
+ metadata.gz: cc9f2d167dfb88b6c0b630bb57e537af41f1551a
4
+ data.tar.gz: 515300ca5d54dda0d0ba4c61c32dcd568c569f5f
5
5
  SHA512:
6
- metadata.gz: 653e5ae559782293971fc577d837de76c871fc05cefc915f538c83fe36dc36dab32154fbfadb0682a098c0285109ed6e0d19a3801a4884fc62be6592294cc6c1
7
- data.tar.gz: ccbc2d905bc14ba9ce85612c7b50404423f0da3a09cfa02ed1c2af01f004cd8e689e2517f27de5b05bfd65617fcaaa3929f249eaaae64dbec4b6244f7d673cd2
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 inpiring this project.
103
+ Special thanks to Mitchell Gerrard for inspiring this project.
@@ -1,107 +1,109 @@
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
- @representation[first_vertex] << second_vertex
7
- else
8
- raise ArgumentError, "One of the vertices referenced does not exist in the graph"
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
- def remove_edge(first_vertex, second_vertex)
13
- if edge_exists?(first_vertex, second_vertex)
14
- @representation[first_vertex].delete(second_vertex)
15
- else
16
- raise ArgumentError, "That edge does not exist in the graph"
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
- def neighbors_of_vertex(name)
21
- if vertex_exists?(name)
22
- @representation[name]
23
- else
24
- raise ArgumentError, "That vertex does not exist in the graph"
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
- 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)
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
- def transpose!
44
- reversed_edges = []
45
- edges.each do |edge|
46
- remove_edge(edge.first, edge.last)
47
- reversed_edges << [edge.last, edge.first]
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
- reversed_edges.each do |edge|
50
- add_edge(edge.first, edge.last)
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
- def reachable_by_two_path(start)
55
- if vertex_exists?(start)
56
- reached_vertices = @representation[start]
57
- reached_vertices.each do |vertex|
58
- reached_vertices += @representation[vertex]
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
- def square
67
- graph = self.clone
67
+ def square
68
+ graph = self.clone
68
69
 
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)
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
- graph
76
- end
79
+ def dfs
80
+ discovered = []
81
+ time = 0
82
+ output = {}
83
+ vertices.each { |vertex| output[vertex] = {} }
77
84
 
78
- def dfs
79
- discovered = []
80
- time = 0
81
- output = {}
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
- dfs_helper = lambda do |u| #only u can do u, so do it!
85
- discovered << u
86
- time += 1
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
- neighbors_of_vertex(u).each do |neighbor|
90
- dfs_helper.call neighbor unless discovered.include?(neighbor)
94
+ time += 1
95
+ output[u][:finish] = time
91
96
  end
92
97
 
93
- time += 1
94
- output[u][:finish] = time
95
- end
98
+ vertices.each do |vertex|
99
+ dfs_helper.call vertex unless discovered.include?(vertex)
100
+ end
96
101
 
97
- vertices.each do |vertex|
98
- dfs_helper.call vertex unless discovered.include?(vertex)
102
+ output
99
103
  end
100
104
 
101
- output
102
- end
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
@@ -1,79 +1,81 @@
1
1
  # this class should not be invoked directly
2
- class Graph
3
- def initialize(hash = {})
4
- @representation = hash
5
- end
2
+ module Graphunk
3
+ class Graph
4
+ def initialize(hash = {})
5
+ @representation = hash
6
+ end
6
7
 
7
- def vertices
8
- @representation.keys
9
- end
8
+ def vertices
9
+ @representation.keys
10
+ end
10
11
 
11
- def edges
12
- [].tap do |edge_constructor|
13
- vertices.each do |vertex|
14
- @representation[vertex].each do |neighbor|
15
- edge_constructor << [vertex, neighbor]
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
- def add_vertex(name)
22
- unless vertex_exists?(name)
23
- @representation[name] = []
24
- else
25
- raise ArgumentError, "Vertex already exists"
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
- def add_vertices(*names)
30
- if (names & vertices).count == 0
31
- names.each { |name| add_vertex(name) }
32
- else
33
- raise ArgumentError, "One or more of the given vertices already exists"
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
- def remove_vertex(name)
38
- if vertex_exists?(name)
39
- edges.each do |edge|
40
- remove_edge(edge.first, edge.last) if edge.include?(name)
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
- def neighbors_of_vertex(name)
49
- if vertex_exists?(name)
50
- edges.select { |edge| edge.include? name }.map do |edge|
51
- edge.first == name ? edge.last : edge.first
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
- def edges_on_vertex(name)
59
- if vertex_exists?(name)
60
- edges.select { |edge| edge.include?(name) }
61
- else
62
- raise ArgumentError, "That vertex does not exist in the graph"
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
- def edge_exists?(first_vertex, second_vertex)
67
- edges.include?(order_vertices(first_vertex, second_vertex))
68
- end
67
+ def edge_exists?(first_vertex, second_vertex)
68
+ edges.include?(order_vertices(first_vertex, second_vertex))
69
+ end
69
70
 
70
- def vertex_exists?(name)
71
- vertices.include?(name)
72
- end
71
+ def vertex_exists?(name)
72
+ vertices.include?(name)
73
+ end
73
74
 
74
- private
75
+ private
75
76
 
76
- def order_vertices(v, u)
77
- [v, u].sort
77
+ def order_vertices(v, u)
78
+ [v, u].sort
79
+ end
78
80
  end
79
81
  end
@@ -1,120 +1,120 @@
1
- require 'set'
2
-
3
- class UndirectedGraph < Graph
4
- def add_edge(first_vertex, second_vertex)
5
- if edge_exists?(first_vertex, second_vertex)
6
- raise ArgumentError, "This edge already exists"
7
- elsif vertex_exists?(first_vertex) && vertex_exists?(second_vertex)
8
- ordered_vertices = order_vertices(first_vertex, second_vertex)
9
- @representation[ordered_vertices.first] << ordered_vertices.last
10
- else
11
- raise ArgumentError, "One of the vertices referenced does not exist in the graph"
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
- def remove_edge(first_vertex, second_vertex)
16
- if edge_exists?(first_vertex, second_vertex)
17
- ordered_vertices = order_vertices(first_vertex, second_vertex)
18
- @representation[ordered_vertices.first].delete(ordered_vertices.last)
19
- else
20
- raise ArgumentError, "That edge does not exist in the graph"
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
- def lexicographic_bfs
25
- sets = [vertices]
26
- output_vertices = []
23
+ def lexicographic_bfs
24
+ sets = [vertices]
25
+ output_vertices = []
27
26
 
28
- until sets.empty?
29
- v = sets.first.delete_at(0)
30
- sets.delete_at(0) if sets.first.empty?
31
- output_vertices << v
32
- replaced = []
33
- neighbors_of_vertex(v).each do |neighbor|
34
- s = sets.select{ |set| set.include?(neighbor) }.first
35
- if s
36
- if replaced.include?(s)
37
- t = sets[sets.find_index(s)-1]
38
- else
39
- t = []
40
- sets.insert(sets.find_index(s), t)
41
- replaced << s
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
- output_vertices
51
- end
49
+ output_vertices
50
+ end
52
51
 
53
- def clique?(vertex_list)
54
- clique = true
55
- vertex_list.each do |vertex|
56
- unless (neighbors_of_vertex(vertex) & vertex_list).sort == (vertex_list - [vertex]).sort
57
- clique = false
58
- break
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
- def chordal?
65
- chordal = true
66
- (lexicographic_ordering = lexicographic_bfs.reverse).each_with_index do |v, i|
67
- successors_of_v = lexicographic_ordering[i, lexicographic_ordering.size]
68
- unless clique?([v] | (neighbors_of_vertex(v) & successors_of_v))
69
- chordal = false
70
- break
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
- def bipartite?
83
- colors = Hash.new
84
- d = Hash.new
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
- start = vertices.first
93
- colors[start] = "gray"
94
- partition[start] = 1
95
- d[start] = 0
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
- stack = []
98
- stack.push(start)
96
+ stack = []
97
+ stack.push(start)
99
98
 
100
- until stack.empty?
101
- vertex = stack.pop
102
- neighbors_of_vertex(vertex).each do |neighbor|
103
- if partition[neighbor] == partition[vertex]
104
- return false
105
- else
106
- if colors[neighbor] == "white"
107
- colors[neighbor] == "gray"
108
- d[neighbor] = d[vertex] + 1
109
- partition[neighbor] = 3 - partition[vertex]
110
- stack.push(neighbor)
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
- true
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
- class WeightedGraph < Graph
2
+ module Graphunk
3
+ class WeightedGraph < Graph
3
4
 
4
- attr_reader :weights
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
- 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
- @representation[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"
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
- def remove_edge(v, u)
16
- if edge_exists?(v, u)
17
- ordered_vertices = order_vertices(v, u)
18
- @representation[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"
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
- def edge_weight(edge)
26
- @weights[edge]
27
- end
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
- def adjust_weight(v, u, w)
30
- if edge_exists?(v, u)
31
- @weights[[v,u]] = w
32
- else
33
- raise ArgumentError, "That edge does not exist in the graph"
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
- # Prim's Algorithm
38
- def minimum_spanning_tree
39
- forest = WeightedUndirectedGraph.new
40
- forest.add_vertex(vertices.first)
41
- until forest.vertices.sort == vertices.sort
42
- minimum_edge = edges_to_examine(forest.vertices).min_by { |edge| weights[edge] }
43
- vertex_to_add = forest.vertices.include?(minimum_edge.first) ? minimum_edge.last : minimum_edge.first
44
- forest.add_vertex(vertex_to_add)
45
- forest.add_edge(minimum_edge.first, minimum_edge.last, weights[minimum_edge])
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
- private
55
+ private
51
56
 
52
- def edges_to_examine(vertices)
53
- [].tap do |examinable|
54
- edges.each do |edge|
55
- examinable << edge if (edge & vertices).count == 1
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
- def remove_weight(v, u)
61
- @weights.delete(order_vertices(v, u))
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
@@ -3,3 +3,4 @@ require 'graphunk/undirected_graph'
3
3
  require 'graphunk/directed_graph'
4
4
  require 'graphunk/weighted_graph'
5
5
  require 'graphunk/weighted_undirected_graph'
6
+ require 'graphunk/weighted_directed_graph'
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.1
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