yargraph 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7eaedaa62ff94b250c68ca0dfe9a4127e390b5d8
4
+ data.tar.gz: 757a7b6dfd85305d6df76963bd2ebc0d2bf863bc
5
+ SHA512:
6
+ metadata.gz: d9a65b8e7038068406bf52ec4b248814c9d13dfffef5de3ba5cffceff5b2c644442e568dd13fd664a2f48f8610d3878823cfbd889c54446c7944bbbc84c217f1
7
+ data.tar.gz: d0e755409c82c0ad7cbb4d7528b6d39beb2a857c8966e72f83879a83a19c2cfff38b9e9e120f21ad940e31205176d3c3cd67094e313322a68e81d0638ab75a94
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+ gem 'ds'
6
+
7
+ # Add dependencies to develop your gem here.
8
+ # Include everything needed to run rake, tests, features, etc.
9
+ group :development do
10
+ gem "rspec", ">= 2.8.0"
11
+ gem "rdoc", ">= 3.12"
12
+ gem "bundler", ">= 1.0"
13
+ gem "jeweler", ">= 1.8.7"
14
+ end
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2014 Ben J. Woodcroft
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,18 @@
1
+ # Yargraph
2
+
3
+ Yet another Ruby graphing library. Implements some [graph](http://en.wikipedia.org/wiki/Graph_theory)/vertex/edge related algorithms. Currently operates only on undirected graphs.
4
+
5
+ * find all [Hamiltonian cycles](http://en.wikipedia.org/wiki/Hamiltonian_cycle) in a graph using an exponential time algorithm (`hamiltonian_cycles`, dynamic programming method of Bellman, Held, and Karp).
6
+ * find edges that are a part of all Hamiltonian cycles (```edges_in_all_hamiltonian_cycles```, requires exponential time so may be _very_ slow)
7
+ * find only some edges that are a part of all Hamiltonian cycles (```some_edges_in_all_hamiltonian_cycles```, faster but may not find all edges)
8
+
9
+ Soon to be implemented:
10
+ * finding [bridges](http://en.wikipedia.org/wiki/Bridge_%28graph_theory%29) (```bridges```, requires linear time using Schmidt's [chain decompositions method](http://dx.doi.org/10.1016%2Fj.ipl.2013.01.016))
11
+ * determining [3-edge-connectivity](http://en.wikipedia.org/wiki/K-edge-connected_graph) and if 3-edge-connected (but not 4- or more), determine pairs of edges whose removal disconnects the graph (```three_edge_connected?```, ```three_edge_connections```, algorithm runs in O(n^2))
12
+
13
+ Contributions are most welcome.
14
+
15
+ ## Copyright
16
+ Copyright (c) 2014 Ben J. Woodcroft. See LICENSE.txt for
17
+ further details.
18
+
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "yargraph"
18
+ gem.homepage = "http://github.com/wwood/yargraph"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Pure Ruby graph algorithms}
21
+ gem.description = %Q{Pure Ruby graph algorithms, particularly e.g. Hamiltonian cycles}
22
+ gem.email = "donttrustben near gmail.com"
23
+ gem.authors = ["Ben J. Woodcroft"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rdoc/task'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "yargraph #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,454 @@
1
+ require 'set'
2
+ require 'ds'
3
+
4
+ module Yargraph
5
+ class OperationalLimitReachedException < Exception; end
6
+
7
+ class UndirectedGraph
8
+ # vertices is a Set
9
+ attr_accessor :vertices
10
+
11
+ # edges is an EdgeSet
12
+ attr_accessor :edges
13
+ # Contained within is a Hash of vertex1 => Set: vertex2, vertex3, ..
14
+ # If vertex1 => Set: vertex2, then vertex2 => Set: vertex1, ..
15
+ class EdgeSet
16
+ include Enumerable
17
+
18
+ def initialize
19
+ @edges = {}
20
+ end
21
+
22
+ def add_edge(v1, v2)
23
+ @edges[v1] ||= Set.new
24
+ @edges[v2] ||= Set.new
25
+ @edges[v1] << v2
26
+ @edges[v2] << v1
27
+ end
28
+
29
+ # Return an array of neighbours
30
+ def neighbours(v)
31
+ e = @edges[v]
32
+ return [] if e.nil?
33
+ return e.to_a
34
+ end
35
+
36
+ # Return a Set of vertices that are neighbours of the given
37
+ # vertex, or an empty Set if there are none
38
+ def [](vertex)
39
+ e = @edges[vertex]
40
+ if e
41
+ return e
42
+ else
43
+ return Set.new
44
+ end
45
+ end
46
+
47
+ def each
48
+ already_seen_vertices = Set.new
49
+ @edges.each do |v1, neighbours|
50
+ neighbours.each do |v2|
51
+ yield v1, v2 unless already_seen_vertices.include?(v2)
52
+ end
53
+ already_seen_vertices << v1
54
+ end
55
+ end
56
+
57
+ def length
58
+ count = 0
59
+ each do
60
+ count += 1
61
+ end
62
+ return count
63
+ end
64
+
65
+ # Is there an edge between v1 and v2?
66
+ def edge?(v1,v2)
67
+ e = @edges[v1]
68
+ return false if e.nil?
69
+ return e.include?(v2)
70
+ end
71
+
72
+ def empty?
73
+ @edges.each do |v, neighbours|
74
+ return false unless neighbours.empty?
75
+ end
76
+ return true
77
+ end
78
+ end
79
+
80
+
81
+ def initialize
82
+ @vertices = Set.new
83
+ @edges = EdgeSet.new
84
+ end
85
+
86
+ # Add an edge between two vertices, adding the vertices in the
87
+ # edge to the graph if they aren't already contained within it.
88
+ def add_edge(vertex1, vertex2)
89
+ @vertices << vertex1
90
+ @vertices << vertex2
91
+ @edges.add_edge vertex1, vertex2
92
+ return
93
+ end
94
+
95
+ # Add a vertex to the graph
96
+ def add_vertex(vertex)
97
+ @vertices << vertex
98
+ end
99
+
100
+ # Return an Enumerable collection of vertices that
101
+ # are directly connected to the given vertex
102
+ def neighbours(vertex)
103
+ @edges.neighbours(vertex)
104
+ end
105
+
106
+ def delete_edge(v1,v2)
107
+ @edges[v1].delete v2
108
+ @edges[v2].delete v1
109
+ end
110
+
111
+ # Yield a pair of vertices for each edge
112
+ def each_edge
113
+ @edges.each do |v1, v2|
114
+ yield v1, v2
115
+ end
116
+ end
117
+
118
+ def edge?(v1,v2)
119
+ @edges.edge?(v1,v2)
120
+ end
121
+
122
+ def copy
123
+ another = UndirectedGraph.new
124
+ @vertices.each do |v|
125
+ another.add_vertex v
126
+ end
127
+ each_edge do |v1, v2|
128
+ another.add_edge v1, v2
129
+ end
130
+ return another
131
+ end
132
+
133
+ # Run depth first search, returning an array of Hamiltonian paths.
134
+ # Or, if a block is given, yield each Hamiltonian path that comes
135
+ # along (in no defined order), and don't return the array (to potentially
136
+ # save RAM).
137
+ #
138
+ # The operational limit is used to make sure this algorithm doesn't
139
+ # get out of hand - only this many 'operations' are used when traversing
140
+ # the graph, as in #hamiltonian_paths_brute_force. When nil, there is no operational
141
+ # limit
142
+ def hamiltonian_cycles_brute_force(operational_limit=nil)
143
+ stack = DS::Stack.new
144
+ return [] if @vertices.empty?
145
+
146
+ origin_vertex = @vertices.to_a[0]
147
+ hamiltonians = []
148
+ num_operations = 0
149
+
150
+ path = Path.new
151
+ path << origin_vertex
152
+ stack.push path
153
+ while path = stack.pop
154
+ last_vertex = path[path.length-1]
155
+ if last_vertex == origin_vertex and path.length > 1
156
+ # Cycle of some sort detected. Is it Hamiltonian?
157
+ if path.length == vertices.length + 1
158
+ # Found a Hamiltonian path. Yield or save it for later
159
+ hpath = path.copy[0...(path.length-1)]
160
+ if block_given?
161
+ yield hpath
162
+ else
163
+ hamiltonians << hpath
164
+ end
165
+ else
166
+ # non-Hamiltonian path found. Ignore
167
+ end
168
+
169
+ elsif path.find_index(last_vertex) != path.length - 1
170
+ # Found a loop, go no further
171
+
172
+ else
173
+ # No loop, just another regular thing.
174
+ neighbours(last_vertex).each do |neighbour|
175
+ unless operational_limit.nil?
176
+ num_operations += 1
177
+ if num_operations > operational_limit
178
+ raise OperationalLimitReachedException
179
+ end
180
+ end
181
+ new_path = Path.new(path.copy+[neighbour])
182
+ stack.push new_path
183
+ end
184
+ end
185
+ end
186
+
187
+ return hamiltonians
188
+ end
189
+
190
+ # Use dynamic programming to find all the Hamiltonian cycles in this graph
191
+ def hamiltonian_cycles_dynamic_programming(operational_limit=nil)
192
+ stack = DS::Stack.new
193
+ return [] if @vertices.empty?
194
+
195
+ origin_vertex = @vertices.to_a[0]
196
+ hamiltonians = []
197
+ num_operations = 0
198
+
199
+ # This hash keeps track of subproblems that have already been
200
+ # solved. ie is there a path through vertices that ends in the
201
+ # endpoint
202
+ # Hash of [vertex_set,endpoint] => Array of Path objects.
203
+ # If no path is found, then the key is false
204
+ # The endpoint is not stored in the vertex set to make the programming
205
+ # easier.
206
+ dp_cache = {}
207
+
208
+ # First problem is the whole problem. We get the Hamiltonian paths,
209
+ # and then after reject those paths that are not cycles.
210
+ initial_vertex_set = Set.new(@vertices.reject{|v| v==origin_vertex})
211
+ initial_problem = [initial_vertex_set, origin_vertex]
212
+ stack.push initial_problem
213
+
214
+ while next_problem = stack.pop
215
+ vertices = next_problem[0]
216
+ destination = next_problem[1]
217
+
218
+ if dp_cache[next_problem]
219
+ # No need to do anything - problem already solved
220
+
221
+ elsif vertices.empty?
222
+ # The bottom of the problem. Only return a path
223
+ # if there is an edge between the destination and the origin
224
+ # node
225
+ if edge?(destination, origin_vertex)
226
+ path = Path.new [destination]
227
+ dp_cache[next_problem] = [path]
228
+ else
229
+ # Reached dead end
230
+ dp_cache[next_problem] = false
231
+ end
232
+
233
+ else
234
+ # This is an unsolved problem and there are at least 2 vertices in the vertex set.
235
+ # Work out which vertices in the set are neighbours
236
+ neighs = Set.new neighbours(destination)
237
+ possibilities = neighs.intersection(vertices)
238
+ if possibilities.length > 0
239
+ # There is still the possibility to go further into this unsolved problem
240
+ subproblems_unsolved = []
241
+ subproblems = []
242
+
243
+ possibilities.each do |new_destination|
244
+ new_vertex_set = Set.new(vertices.to_a.reject{|v| v==new_destination})
245
+ subproblem = [new_vertex_set, new_destination]
246
+
247
+ subproblems.push subproblem
248
+ if !dp_cache.key?(subproblem)
249
+ subproblems_unsolved.push subproblem
250
+ end
251
+ end
252
+
253
+ # if solved all the subproblems, then we can make a decision about this problem
254
+ if subproblems_unsolved.empty?
255
+ answers = []
256
+ subproblems.each do |problem|
257
+ paths = dp_cache[problem]
258
+ if paths == false
259
+ # Nothing to see here
260
+ else
261
+ # Add the found sub-paths to the set of answers
262
+ paths.each do |path|
263
+ answers.push Path.new(path+[destination])
264
+ end
265
+ end
266
+ end
267
+
268
+ if answers.empty?
269
+ # No paths have been found here
270
+ dp_cache[next_problem] = false
271
+ else
272
+ dp_cache[next_problem] = answers
273
+ end
274
+ else
275
+ # More problems to be solved before a decision can be made
276
+ stack.push next_problem #We have only delayed solving this problem, need to keep going in the future
277
+ subproblems_unsolved.each do |prob|
278
+ unless operational_limit.nil?
279
+ num_operations += 1
280
+ raise OperationalLimitReachedException if num_operations > operational_limit
281
+ end
282
+ stack.push prob
283
+ end
284
+ end
285
+
286
+ else
287
+ # No neighbours in the set, so reached a dead end, can go no further
288
+ dp_cache[next_problem] = false
289
+ end
290
+ end
291
+ end
292
+
293
+ if block_given?
294
+ dp_cache[initial_problem].each do |hpath|
295
+ yield hpath
296
+ end
297
+ return
298
+ else
299
+ return dp_cache[initial_problem]
300
+ end
301
+ end
302
+ alias_method :hamiltonian_cycles, :hamiltonian_cycles_dynamic_programming
303
+
304
+
305
+ # Return an array of edges (edges being an array of 2 vertices)
306
+ # that correspond to edges that are found in all Hamiltonian paths.
307
+ # This method might be quite slow because it requires finding all Hamiltonian
308
+ # paths, which implies solving the (NP-complete) Hamiltonian path problem.
309
+ #
310
+ # There is probably no polynomial time way to implement this method anyway, see
311
+ # http://cstheory.stackexchange.com/questions/20413/is-there-an-efficient-algorithm-for-finding-edges-that-are-part-of-all-hamiltoni
312
+ #
313
+ # The operational limit is used to make sure this algorithm doesn't
314
+ # get out of hand - only this many 'operations' are used when traversing
315
+ # the graph, as in #hamiltonian_cycles_brute_force
316
+ def edges_in_all_hamiltonian_cycles(operational_limit=nil)
317
+ hedges = nil
318
+ hamiltonian_cycles do |path|
319
+ # Convert the path to a hash v1->v2, v2->v3. Can't have collisions because the path is Hamiltonian
320
+ edge_hash = {}
321
+ path.each_with_index do |v, i|
322
+ unless i == path.length-1
323
+ edge_hash[v] = path[i+1]
324
+ end
325
+ end
326
+ edge_hash[path[path.length-1]] = path[0] #Add the final wrap around edge
327
+
328
+ if hedges.nil?
329
+ # First Hpath found
330
+ hedges = edge_hash
331
+ else
332
+ # Use a process of elimination, removing all edges that
333
+ # aren't in hedges or this new Hpath
334
+ hedges.select! do |v1, v2|
335
+ edge_hash[v1] == v2 or edge_hash[v2] = v1
336
+ end
337
+ # If no edges fit the bill, then we are done
338
+ return [] if hedges.empty?
339
+ end
340
+ end
341
+ return [] if hedges.nil? #no Hpaths found in the graph
342
+ return hedges.to_a
343
+ end
344
+
345
+ # If #edges_in_all_hamiltonian_cycles is too slow, the method
346
+ # here is faster, but is not guaranteed to find every edge that is
347
+ # part of Hamiltonian cycles. This method proceeds under the assumption
348
+ # that the graph has at least 1 Hamiltonian cycle, but may stumble
349
+ # across evidence to the contrary.
350
+ #
351
+ # Returns an instance of EdgeSearchResult where #edges_in_all are those
352
+ # edges that are in all hamiltonian cycles, and #edges_in_none are those
353
+ # edges that are not in any hamiltonian cycles. While
354
+ def some_edges_in_all_hamiltonian_cycles
355
+ stack = DS::Stack.new
356
+ result = EdgeSearchResult.new
357
+
358
+ # As we are deleting edges, make a deep copy to start with
359
+ g = copy
360
+
361
+ # Fill up the stack, in reverse to ease testing
362
+ g.vertices.to_a.reverse.each do |v|
363
+ stack.push v
364
+ end
365
+
366
+ while v = stack.pop
367
+ all_neighbours = g.neighbours(v)
368
+ ham_neighbours = result.hamiltonian_neighbours(v)
369
+ # p v
370
+ # p all_neighbours
371
+ # p ham_neighbours
372
+
373
+ # If a vertex contains 1 or 0 total neighbours, then the graph cannot contain
374
+ # any hamcycles (in contrast, degree 1 doesn't preclude hampaths).
375
+ if all_neighbours.length < 2
376
+ result.contains_hamcycle = false
377
+
378
+ elsif all_neighbours.length == 2
379
+ # If a vertex has degree 2 then both edges must be a part of the hamcycle
380
+ all_neighbours.each do |n|
381
+ unless result.edges_in_all.edge?(v,n)
382
+ result.edges_in_all.add_edge(v,n)
383
+ stack.push n #now need to re-evalute the neighbour, as its neighbourhood is changed
384
+ end
385
+
386
+ # if an edge be and must not be in all hamcycles, then the graph is not Hamiltonian.
387
+ # Are there any concrete examples of this? Possibly.
388
+ if result.edges_in_all[v].include?(n) and result.edges_in_none[v].include?(n)
389
+ result.contains_hamcycle = false
390
+ end
391
+ end
392
+
393
+ elsif ham_neighbours.length >= 2
394
+ # There cannot be any further hamcycle edges from this vertex, so the rest of the edges
395
+ # cannot be a part of _any_ hamcycle
396
+ all_neighbours.each do |n|
397
+ next if ham_neighbours.include?(n)
398
+
399
+ result.edges_in_none.add_edge(v,n)
400
+ g.delete_edge(v,n)
401
+ stack.push n #reconsider the neighbour
402
+ end
403
+
404
+ else
405
+ # Anything else that can be done cheaply?
406
+ # Maybe edges that create non-Hamiltonian cycles when only considering edges
407
+ # that are in all Hamiltonian cycles -> these cannot be in any hamcycle
408
+
409
+ end
410
+ #p stack
411
+ end
412
+ #p result
413
+
414
+ return result
415
+ end
416
+
417
+ class EdgeSearchResult
418
+ # EdgeSets of edges that are contained in, or not contained in all Hamiltonian cycles.
419
+ attr_accessor :edges_in_all, :edges_in_none
420
+
421
+ # True, false, or dunno (nil), does the graph contain one or more Hamiltonian cycles?
422
+ attr_accessor :contains_hamcycle
423
+
424
+ def initialize
425
+ @edges_in_all = EdgeSet.new
426
+ @edges_in_none = EdgeSet.new
427
+ end
428
+
429
+ # Return an Set of neighbours that must be next or previous in a hamiltonian cycle (& path?)
430
+ def hamiltonian_neighbours(vertex)
431
+ @edges_in_all[vertex]
432
+ end
433
+ end
434
+
435
+ class Path < Array
436
+ def copy
437
+ Path.new(self)
438
+ end
439
+ end
440
+
441
+ # # Return a "chain decomposition" as defined by Jens M. Schmidt, "A simple
442
+ # # test on 2-vertex- and 2-edge-connectivity"
443
+ # def chain_decomposition
444
+ # end
445
+ #
446
+ # class ChainDecomposition
447
+ # # A Hash of edge objects (arrays of objects)objects. Each element of the array
448
+ # # is
449
+ # attr_accessor :edges_to_chainsets
450
+ #
451
+ # def number_of_
452
+ # end
453
+ end
454
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'yargraph'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
@@ -0,0 +1,228 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ class GraphTesting
4
+ def self.generate_undirected(edges)
5
+ g = Yargraph::UndirectedGraph.new
6
+ edges.each do |edge|
7
+ g.add_edge edge[0], edge[1]
8
+ end
9
+ return g
10
+ end
11
+
12
+ def self.sorted_edges(edges)
13
+ edges.collect{|edge| edge.sort}.sort
14
+ end
15
+
16
+ # return an array of cycles the same as the original
17
+ # set, except that they have been rotated until so the min element
18
+ # is the first element
19
+ def self.sort_cycles(cycles)
20
+ cycles.collect do |cycle|
21
+ [cycle, cycle.reverse].collect do |cyc|
22
+ m = cyc.min
23
+ i = cyc.find_index(m)
24
+ cyc.rotate(i)
25
+ end.sort[0]
26
+ end
27
+ end
28
+ end
29
+
30
+ CYCLE_FINDING_METHODS = [
31
+ :hamiltonian_cycles_dynamic_programming,
32
+ :hamiltonian_cycles_brute_force,
33
+ ]
34
+
35
+ describe "Yargraph" do
36
+ it 'should do neighbours' do
37
+ g = GraphTesting.generate_undirected([
38
+ [0,1],
39
+ [1,2],
40
+ [2,0]
41
+ ])
42
+ g.neighbours(0).sort.should == [1,2]
43
+ g.neighbours(1).sort.should == [0,2]
44
+
45
+ g = GraphTesting.generate_undirected([
46
+ [0,1],
47
+ [1,2],
48
+ ])
49
+ g.neighbours(2).sort.should == [1]
50
+ end
51
+
52
+ it "should find hamiltonian cycles 1" do
53
+ g = GraphTesting.generate_undirected([
54
+ [0,1],
55
+ [1,2],
56
+ [2,0]
57
+ ])
58
+ CYCLE_FINDING_METHODS.each do |method|
59
+ cycles = GraphTesting.sort_cycles(g.send(method))
60
+ cycles.should == GraphTesting.sort_cycles([
61
+ [1,2,0],
62
+ [2,1,0],
63
+ ])
64
+ end
65
+ end
66
+
67
+ it "should find hamiltonian cycles 2" do
68
+ g = GraphTesting.generate_undirected([
69
+ [1,2],
70
+ [1,3],
71
+ [2,4],
72
+ [2,3],
73
+ [3,6],
74
+ [4,5],
75
+ [4,6],
76
+ [5,6],
77
+ ])
78
+ paths = [
79
+ [2,4,5,6,3,1],
80
+ ]
81
+ revpaths = paths.collect do |path|
82
+ (path[1..path.length]+[path[0]]).reverse
83
+ end
84
+
85
+ CYCLE_FINDING_METHODS.each do |method|
86
+ GraphTesting.sort_cycles(g.send(method)).should ==
87
+ GraphTesting.sort_cycles(paths+revpaths)
88
+ end
89
+ end
90
+
91
+ it 'should operated within limits' do
92
+ g = GraphTesting.generate_undirected([
93
+ [1,2],
94
+ [1,3],
95
+ [2,4],
96
+ [2,3],
97
+ [3,6],
98
+ [4,5],
99
+ [4,6],
100
+ [5,6],
101
+ ])
102
+ CYCLE_FINDING_METHODS.each do |method|
103
+ expect {
104
+ g.send(method, 4)
105
+ }.to raise_error(Yargraph::OperationalLimitReachedException)
106
+ end
107
+ end
108
+
109
+ it 'should find edges in all hamiltonian cycles' do
110
+ g = GraphTesting.generate_undirected([
111
+ [1,2],
112
+ [1,3],
113
+ [2,4],
114
+ [2,3],
115
+ [3,6],
116
+ [4,5],
117
+ [4,6],
118
+ [5,6],
119
+ ])
120
+ GraphTesting.sorted_edges(g.edges_in_all_hamiltonian_cycles).should ==
121
+ GraphTesting.sorted_edges([
122
+ [1,2],
123
+ [2,4],
124
+ [4,5],
125
+ [5,6],
126
+ [6,3],
127
+ [3,1],
128
+ ])
129
+ end
130
+
131
+ it 'should find some all-hamiltonian edges first' do
132
+ g = GraphTesting.generate_undirected([
133
+ [0,1],
134
+ [1,2],
135
+ [2,0]
136
+ ])
137
+ edgeset_results = g.some_edges_in_all_hamiltonian_cycles
138
+ edgeset_results.contains_hamcycle.should == nil
139
+ GraphTesting.sorted_edges(edgeset_results.edges_in_all.to_a).should ==
140
+ GraphTesting.sorted_edges([
141
+ [0,1],
142
+ [1,2],
143
+ [2,0],
144
+ ])
145
+ GraphTesting.sorted_edges(edgeset_results.edges_in_none.to_a).should == []
146
+ end
147
+
148
+ it 'some all-hamiltonian edges should say when it falsifies the assumption' do
149
+ g = GraphTesting.generate_undirected([
150
+ [0,1],
151
+ [1,2],
152
+ ])
153
+ edgeset_results = g.some_edges_in_all_hamiltonian_cycles
154
+ edgeset_results.contains_hamcycle.should == false
155
+ GraphTesting.sorted_edges(edgeset_results.edges_in_all.to_a).should ==
156
+ GraphTesting.sorted_edges([
157
+ [0,1],
158
+ [1,2],
159
+ ])
160
+ GraphTesting.sorted_edges(edgeset_results.edges_in_none.to_a).should == []
161
+ end
162
+
163
+
164
+ it 'some all-hamiltonian edges should not choose all edges when not all are right' do
165
+ g = GraphTesting.generate_undirected([
166
+ [0,1],
167
+ [1,2],
168
+ [2,3],
169
+ [3,0],
170
+
171
+ [0,2],
172
+ [1,3],
173
+ ]) #This graph has hamiltonian paths but no edges are in every hamiltonian cycle
174
+ edgeset_results = g.some_edges_in_all_hamiltonian_cycles
175
+ edgeset_results.contains_hamcycle.should == nil
176
+ GraphTesting.sorted_edges(edgeset_results.edges_in_all.to_a).should == []
177
+ GraphTesting.sorted_edges(edgeset_results.edges_in_none.to_a).should == []
178
+ end
179
+
180
+ it 'some all-hamiltonian edges should choose none when none are right' do
181
+ g = GraphTesting.generate_undirected([
182
+ [0,1],
183
+ [1,2],
184
+ [2,3],
185
+ [3,0],
186
+
187
+ [0,2],
188
+ [1,3],
189
+ ]) #This graph has hamiltonian paths but no edges are in every hamiltonian cycle
190
+ edgeset_results = g.some_edges_in_all_hamiltonian_cycles
191
+ edgeset_results.contains_hamcycle.should == nil
192
+ GraphTesting.sorted_edges(edgeset_results.edges_in_all.to_a).should == []
193
+ GraphTesting.sorted_edges(edgeset_results.edges_in_none.to_a).should == []
194
+ end
195
+
196
+ it 'some all-hamiltonian edges should iterate properly' do
197
+ g = GraphTesting.generate_undirected([
198
+ [0,1],
199
+ [1,2],
200
+
201
+ [3,4],
202
+ [4,5],
203
+
204
+ [0,3],
205
+ [1,4],
206
+ [2,5],
207
+ ]) #This graph requires removal of edges to discover more h_edges
208
+ edgeset_results = g.some_edges_in_all_hamiltonian_cycles
209
+ edgeset_results.contains_hamcycle.should == nil
210
+
211
+ GraphTesting.sorted_edges(edgeset_results.edges_in_all.to_a).should ==
212
+ GraphTesting.sorted_edges([
213
+ [0,1],
214
+ [1,2],
215
+
216
+ [3,4],
217
+ [4,5],
218
+
219
+ [0,3],
220
+ #[1,4],
221
+ [2,5],
222
+ ])
223
+ GraphTesting.sorted_edges(edgeset_results.edges_in_none.to_a).should ==
224
+ GraphTesting.sorted_edges([
225
+ [1,4],
226
+ ])
227
+ end
228
+ end
@@ -0,0 +1,63 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+ # stub: yargraph 0.0.1 ruby lib
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "yargraph"
9
+ s.version = "0.0.1"
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.require_paths = ["lib"]
13
+ s.authors = ["Ben J. Woodcroft"]
14
+ s.date = "2014-05-30"
15
+ s.description = "Pure Ruby graph algorithms, particularly e.g. Hamiltonian cycles"
16
+ s.email = "donttrustben near gmail.com"
17
+ s.extra_rdoc_files = [
18
+ "LICENSE.txt",
19
+ "README.md"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".rspec",
24
+ "Gemfile",
25
+ "LICENSE.txt",
26
+ "README.md",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "lib/yargraph.rb",
30
+ "spec/spec_helper.rb",
31
+ "spec/yargraph_spec.rb",
32
+ "yargraph.gemspec"
33
+ ]
34
+ s.homepage = "http://github.com/wwood/yargraph"
35
+ s.licenses = ["MIT"]
36
+ s.rubygems_version = "2.2.2"
37
+ s.summary = "Pure Ruby graph algorithms"
38
+
39
+ if s.respond_to? :specification_version then
40
+ s.specification_version = 4
41
+
42
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
43
+ s.add_runtime_dependency(%q<ds>, [">= 0"])
44
+ s.add_development_dependency(%q<rspec>, [">= 2.8.0"])
45
+ s.add_development_dependency(%q<rdoc>, [">= 3.12"])
46
+ s.add_development_dependency(%q<bundler>, [">= 1.0"])
47
+ s.add_development_dependency(%q<jeweler>, [">= 1.8.7"])
48
+ else
49
+ s.add_dependency(%q<ds>, [">= 0"])
50
+ s.add_dependency(%q<rspec>, [">= 2.8.0"])
51
+ s.add_dependency(%q<rdoc>, [">= 3.12"])
52
+ s.add_dependency(%q<bundler>, [">= 1.0"])
53
+ s.add_dependency(%q<jeweler>, [">= 1.8.7"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<ds>, [">= 0"])
57
+ s.add_dependency(%q<rspec>, [">= 2.8.0"])
58
+ s.add_dependency(%q<rdoc>, [">= 3.12"])
59
+ s.add_dependency(%q<bundler>, [">= 1.0"])
60
+ s.add_dependency(%q<jeweler>, [">= 1.8.7"])
61
+ end
62
+ end
63
+
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yargraph
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ben J. Woodcroft
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ds
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.8.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.8.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rdoc
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.12'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.12'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: jeweler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 1.8.7
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.8.7
83
+ description: Pure Ruby graph algorithms, particularly e.g. Hamiltonian cycles
84
+ email: donttrustben near gmail.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files:
88
+ - LICENSE.txt
89
+ - README.md
90
+ files:
91
+ - ".document"
92
+ - ".rspec"
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - VERSION
98
+ - lib/yargraph.rb
99
+ - spec/spec_helper.rb
100
+ - spec/yargraph_spec.rb
101
+ - yargraph.gemspec
102
+ homepage: http://github.com/wwood/yargraph
103
+ licenses:
104
+ - MIT
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.2.2
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Pure Ruby graph algorithms
126
+ test_files: []