yargraph 0.0.1

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.
@@ -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: []