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