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 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