graphunk 0.2.1 → 0.3.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: 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