From c657923be4f79721e9bfaf82ed38edce36f76137 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 06:50:42 +0200 Subject: [PATCH 01/44] bumped a VERSION file --- Rakefile | 24 ++++++++++++------------ VERSION | 1 + 2 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 VERSION diff --git a/Rakefile b/Rakefile index b7fcf95..02da44c 100644 --- a/Rakefile +++ b/Rakefile @@ -38,18 +38,18 @@ Rake::RDocTask.new do |rdoc| rdoc.rdoc_files.include('lib/**/*.rb') end -require 'spec/rake/spectask' -Spec::Rake::SpecTask.new(:spec) do |spec| - spec.libs << 'lib' << 'spec' - spec.spec_files = FileList['spec/**/*_spec.rb'] -end +#require 'spec/rake/spectask' +#Spec::Rake::SpecTask.new(:spec) do |spec| + #spec.libs << 'lib' << 'spec' + #spec.spec_files = FileList['spec/**/*_spec.rb'] +#end -Spec::Rake::SpecTask.new(:rcov) do |spec| - spec.libs << 'lib' << 'spec' - spec.pattern = 'spec/**/*_spec.rb' - spec.rcov = true -end +#Spec::Rake::SpecTask.new(:rcov) do |spec| + #spec.libs << 'lib' << 'spec' + #spec.pattern = 'spec/**/*_spec.rb' + #spec.rcov = true +#end -task :spec => :check_dependencies +#task :spec => :check_dependencies -task :default => :spec +#task :default => :spec diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 From 05c7fd742e71bf0a69f4e0308bf5f964bc6d1d7f Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 07:01:26 +0200 Subject: [PATCH 02/44] Version bump to 1.0.1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 3eefcb9..7dea76e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0 +1.0.1 From cbab08977326b984387fc171242f295d48b963d7 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 07:18:32 +0200 Subject: [PATCH 03/44] 1.9.x compatibility --- lib/graphy/directed_graph.rb | 2 +- lib/graphy/graph.rb | 18 ++++++++++++------ lib/graphy/graph_api.rb | 26 +++++++++++++------------- lib/graphy/labels.rb | 18 ++++++++++++------ 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/lib/graphy/directed_graph.rb b/lib/graphy/directed_graph.rb index 44a6662..825d0bc 100644 --- a/lib/graphy/directed_graph.rb +++ b/lib/graphy/directed_graph.rb @@ -3,7 +3,7 @@ module Graphy class DirectedGraph < Graph autoload :Algorithms, "graphy/directed_graph/algorithms" - autoload :Distance, "graphy/directed_graph/distance" + autoload :Distance, "graphy/directed_graph/distance" def initialize(*params) args = (params.pop if params.last.kind_of? Hash) || {} diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb index 7c8ba0d..f190119 100644 --- a/lib/graphy/graph.rb +++ b/lib/graphy/graph.rb @@ -278,18 +278,24 @@ def merge(other) def +(other) result = self.class.new(self) case other - when Graphy::Graph : result.merge(other) - when Graphy::Arc : result.add_edge!(other) - else result.add_vertex!(other) + when Graphy::Graph + result.merge(other) + when Graphy::Arc + result.add_edge!(other) + else + result.add_vertex!(other) end end # Remove all vertices in the specified right hand side graph def -(other) case other - when Graphy::Graph : induced_subgraph(vertices - other.vertices) - when Graphy::Arc : self.class.new(self).remove_edge!(other) - else self.class.new(self).remove_vertex!(other) + when Graphy::Graph + induced_subgraph(vertices - other.vertices) + when Graphy::Arc + self.class.new(self).remove_edge!(other) + else + self.class.new(self).remove_vertex!(other) end end diff --git a/lib/graphy/graph_api.rb b/lib/graphy/graph_api.rb index 2f08acf..0c71898 100644 --- a/lib/graphy/graph_api.rb +++ b/lib/graphy/graph_api.rb @@ -1,9 +1,9 @@ module Graphy - + # This defines the minimum set of functions required to make a graph class that can # use the algorithms defined by this library module GraphAPI - + # Each implementation module must implement the following routines # * directed?() # Is the graph directed? # * add_vertex!(v,l=nil) # Add a vertex to the graph and return the graph, l is an optional label @@ -14,17 +14,17 @@ module GraphAPI # * edges() # Returns an array of all edges # * edge_class() # Returns the class used to store edges def self.included(klass) - [:directed?,:add_vertex!,:add_edge!,:remove_vertex!,:remove_edge!,:vertices,:edges,:edge_class].each do |meth| - raise "Must implement #{meth}" unless klass.instance_methods.include?(meth.to_s) - end - - klass.class_eval do - # Is this right? - alias remove_arc! remove_edge! - alias add_arc! add_edge! - alias arcs edges - alias arc_class edge_class - end + [:directed?,:add_vertex!,:add_edge!,:remove_vertex!,:remove_edge!,:vertices,:edges,:edge_class].each do |meth| + raise "Must implement #{meth}" unless klass.instance_methods.include?(meth.to_s) + end + + klass.class_eval do + # Is this right? + alias remove_arc! remove_edge! + alias add_arc! add_edge! + alias arcs edges + alias arc_class edge_class + end end end end diff --git a/lib/graphy/labels.rb b/lib/graphy/labels.rb index 2fb477d..602ea08 100644 --- a/lib/graphy/labels.rb +++ b/lib/graphy/labels.rb @@ -54,9 +54,12 @@ def edge_label_dict() @edge_labels ||= {}; end def cost(u,v=nil,weight=nil) u.kind_of?(Arc) ? weight = v : u = edge_class[u,v] case weight - when Proc : weight.call(u) - when nil : self[u] - else self[u][weight] + when Proc + weight.call(u) + when nil + self[u] + else + self[u][weight] end end @@ -66,9 +69,12 @@ def cost(u,v=nil,weight=nil) # A function to set properties specified by the user. def property_set(u,name,value) case name - when Proc : name.call(value) - when nil : self[u] = value - else self[u][name] = value + when Proc + name.call(value) + when nil + self[u] = value + else + self[u][name] = value end end From d544be3b447b7ccfe524a77b2c547c434b816529 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 07:35:40 +0200 Subject: [PATCH 04/44] readme update --- README.markdown | 77 ++++++++++++++++++++++------------------- lib/graphy/graph_api.rb | 4 +-- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/README.markdown b/README.markdown index 56ecba9..c7be66f 100644 --- a/README.markdown +++ b/README.markdown @@ -1,7 +1,7 @@ Graphy: A Graph Theory Library for Ruby ======================================= -A framework for graph data structures and algorithms. +**A framework for graph data structures and algorithms.** This library is based on [GRATR][1] (itself a fork of [RGL][2]). @@ -23,12 +23,16 @@ These are based on more general algorithm patterns: * Djikstra's Algorithm * Lexicographic Search -The Tour --------- +## A quick Tour ### Arcs -There are two Arc classes, `Graphy::Arc` and `Graphy::Edge`. +There are two vertices bound classes, `Graphy::Arc` and `Graphy::Edge`. The +former defines directional edges, the latter undirected edges. + +### Vertices + +Vertices can be any `Object`. ### Graph Types @@ -55,23 +59,23 @@ different features and constraints: ### Data Structures -Use the `Graphy::AdjacencyGraph` module provides a generalized adjacency -list and an edge list adaptor. +In order to modelize data structures, make use of the `Graphy::AdjacencyGraph` +module which provides a generalized adjacency list and an edge list adaptor. The `Graphy::Digraph` class is the general purpose "swiss army knife" of graph classes, most of the other classes are just modifications to this class. It is optimized for efficient access to just the out-edges, fast vertex insertion and removal at the cost of extra space overhead, etc. -Example Usage -------------- +## Example Usage -Require the library: +Using IRB, first require the library: + $ irb require 'graphy' If you'd like to include all the classes in the current scope (so you -don't have to prefix with `GraphTeory::`), just: +don't have to prefix with `Graphy::`), just: include Graphy @@ -80,7 +84,7 @@ Let's play with the library a bit in IRB: >> dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] => Graphy::Digraph[[2, 3], [1, 6], [2, 4], [4, 5], [1, 2], [6, 4]] -A few properties of the graph +A few properties of the graph we just created: >> dg.directed? => true @@ -97,36 +101,45 @@ Every object could be a vertex, even the class object `Object`: >> dg.vertex?(Object) => false + >> UndirectedGraph.new(dg).edges.sort.to_s - => "(1=2)(2=3)(2=4)(4=5)(1=6)(4=6)" + => "[Graphy::Edge[1,2,nil], Graphy::Edge[2,3,nil], Graphy::Edge[2,4,nil], + Graphy::Edge[4,5,nil], Graphy::Edge[1,6,nil], Graphy::Edge[6,4,nil]]" Add inverse edge `(4-2)` to directed graph: >> dg.add_edge!(4,2) - => GRATR::Digraph[[2, 3], [1, 6], [4, 2], [2, 4], [4, 5], [1, 2], [6, 4]] - -`(4-2) == (2-4)` in the undirected graph: + => Graphy::DirectedGraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], + Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[4,2,nil], + Graphy::Arc[6,4,nil]] + +`(4-2) == (2-4)` in the undirected graph (4-2 doesn't show up): >> UndirectedGraph.new(dg).edges.sort.to_s - => "(1=2)(2=3)(2=4)(4=5)(1=6)(4=6)" + => "[Graphy::Edge[1,2,nil], Graphy::Edge[2,3,nil], Graphy::Edge[2,4,nil], + Graphy::Edge[4,5,nil], Graphy::Edge[1,6,nil], Graphy::Edge[6,4,nil]]" -`(4-2) != (2-4)` in directed graphs: +`(4-2) != (2-4)` in directed graphs (both show up): >> dg.edges.sort.to_s - => "(1-2)(1-6)(2-3)(2-4)(4-2)(4-5)(6-4)" + => "[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], + Graphy::Arc[2,4,nil], Graphy::Arc[4,2,nil], Graphy::Arc[4,5,nil], + Graphy::Arc[6,4,nil]]" + >> dg.remove_edge! 4,2 - => GRATR::Digraph[[2, 3], [1, 6], [2, 4], [4, 5], [1, 2], [6, 4]] + => Graphy::DirectedGraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], + Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[6,4,nil]] Topological sorting is realized with an iterator: >> dg.topsort - => [1, 2, 3, 6, 4, 5] + => [1, 6, 2, 4, 5, 3] >> y = 0; dg.topsort { |v| y += v }; y => 21 You can use DOT to visualize the graph: - >> require 'graph/dot' + >> require 'graphy/dot' >> dg.write_to_graphic_file('jpg','visualize') Here's an example showing the module inheritance hierarchy: @@ -140,8 +153,7 @@ Here's an example showing the module inheritance hierarchy: Look for more in the examples directory. -History -------- +## History This library is based on [GRATR][1] by Shawn Garbett (itself a fork of Horst Duchene's RGL library) which is heavily influenced by the Boost @@ -149,31 +161,26 @@ Graph Library (BGL). This fork attempts to modernize and extend the API and tests. -References ----------- +## References For more information on Graph Theory, you may want to read: -* The [documentation][3] for the Boost Graph Library -* [The Dictionary of Algorithms and Data Structures][4] +* the [documentation][3] for the Boost Graph Library +* [the Dictionary of Algorithms and Data Structures][4] -Credits -------- +## Credits See CREDITS.markdown -TODO ----- +## TODO See TODO.markdown -CHANGELOG ---------- +## CHANGELOG See CHANGELOG.markdown -License -------- +## License See LICENSE diff --git a/lib/graphy/graph_api.rb b/lib/graphy/graph_api.rb index 0c71898..0b61ca6 100644 --- a/lib/graphy/graph_api.rb +++ b/lib/graphy/graph_api.rb @@ -14,8 +14,8 @@ module GraphAPI # * edges() # Returns an array of all edges # * edge_class() # Returns the class used to store edges def self.included(klass) - [:directed?,:add_vertex!,:add_edge!,:remove_vertex!,:remove_edge!,:vertices,:edges,:edge_class].each do |meth| - raise "Must implement #{meth}" unless klass.instance_methods.include?(meth.to_s) + [:directed?, :add_vertex!, :add_edge!, :remove_vertex!, :remove_edge!, :vertices, :edges, :edge_class].each do |meth| + raise "Must implement #{meth}" unless klass.instance_methods.include?(meth) end klass.class_eval do From d35c01e410882838abdadacd4bfb51b97e0f6e9c Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 08:02:35 +0200 Subject: [PATCH 05/44] cleanup, pre-yardoc --- README.markdown | 8 +- lib/graphy/common.rb | 20 +-- lib/graphy/directed_graph.rb | 17 +- lib/graphy/directed_graph/algorithms.rb | 27 ++- lib/graphy/directed_graph/distance.rb | 10 +- lib/graphy/graph.rb | 229 ++++++++++++++++-------- lib/graphy/graph_api.rb | 26 +-- 7 files changed, 214 insertions(+), 123 deletions(-) diff --git a/README.markdown b/README.markdown index c7be66f..8c2b7f1 100644 --- a/README.markdown +++ b/README.markdown @@ -39,19 +39,19 @@ Vertices can be any `Object`. There are a number of different graph types, each of which provide different features and constraints: -`Graphy::Digraph` and it's pseudonym `Graphy::DirectedGraph`: +`Graphy::Digraph` and its alias `Graphy::DirectedGraph`: -* Single directed edges between vertices +* Single directed edges (arcs) between vertices * Loops are forbidden `Graphy::DirectedPseudoGraph`: -* Multiple directed edges between vertices +* Multiple directed edges (arcs) between vertices * Loops are forbidden `Graphy::DirectedMultiGraph`: -* Multiple directed edges between vertices +* Multiple directed edges (arcs) between vertices * Loops on vertices `Graphy::UndirectedGraph`, `Graphy::UndirectedPseudoGraph`, and diff --git a/lib/graphy/common.rb b/lib/graphy/common.rb index bc35ac5..dbdda0d 100644 --- a/lib/graphy/common.rb +++ b/lib/graphy/common.rb @@ -1,11 +1,11 @@ module Graphy - # This class defines a cycle graph of size n + + # This class defines a cycle graph of size n. # This is easily done by using the base Graph # class and implemeting the minimum methods needed to # make it work. This is a good example to look - # at for making one's own graph classes + # at for making one's own graph classes. class Cycle - def initialize(n) @size = n; end def directed?() false; end def vertices() (1..@size).to_a; end @@ -15,13 +15,13 @@ def edge?(u,v=nil) vertex?(u) && vertex?(v) && ((v-u == 1) or (u==@size && v=1)) end def edges() Array.new(@size) {|i| Graphy::Edge[i+1, (i+1)==@size ? 1 : i+2]}; end - end + end # Cycle - # This class defines a complete graph of size n + # This class defines a complete graph of size n. # This is easily done by using the base Graph # class and implemeting the minimum methods needed to # make it work. This is a good example to look - # at for making one's own graph classes + # at for making one's own graph classes. class Complete < Cycle def initialize(n) @size = n; @edges = nil; end def edges @@ -35,8 +35,6 @@ def edge?(u,v=nil) u, v = [u.source, v.target] if u.kind_of? Graphy::Arc vertex?(u) && vertex?(v) end - end # Complete - - - -end # Graphy + + end # Complete +end # Graphy diff --git a/lib/graphy/directed_graph.rb b/lib/graphy/directed_graph.rb index 825d0bc..d7cbfe5 100644 --- a/lib/graphy/directed_graph.rb +++ b/lib/graphy/directed_graph.rb @@ -10,7 +10,8 @@ def initialize(*params) args[:algorithmic_category] = DirectedGraph::Algorithms super *(params << args) end - end + + end # DirectedGraph # DirectedGraph is just an alias for Digraph should one desire Digraph = DirectedGraph @@ -18,20 +19,24 @@ def initialize(*params) # This is a Digraph that allows for parallel edges, but does not # allow loops class DirectedPseudoGraph < DirectedGraph + def initialize(*params) args = (params.pop if params.last.kind_of? Hash) || {} args[:parallel_edges] = true super *(params << args) - end - end + end + + end # DirectedPseudoGraph # This is a Digraph that allows for parallel edges and loops class DirectedMultiGraph < DirectedPseudoGraph + def initialize(*params) args = (params.pop if params.last.kind_of? Hash) || {} args[:loops] = true super *(params << args) - end - end + end + + end # DirectedMultiGraph -end +end # Graphy diff --git a/lib/graphy/directed_graph/algorithms.rb b/lib/graphy/directed_graph/algorithms.rb index a672f2e..dbdbeab 100644 --- a/lib/graphy/directed_graph/algorithms.rb +++ b/lib/graphy/directed_graph/algorithms.rb @@ -1,16 +1,15 @@ module Graphy - # + # Digraph is a directed graph which is a finite set of vertices # and a finite set of edges connecting vertices. It cannot contain parallel # edges going from the same source vertex to the same target. It also # cannot contain loops, i.e. edges that go have the same vertex for source # and target. # - # DirectedPseudoGraph is a class that allows for parallel edges, and a + # DirectedPseudoGraph is a class that allows for parallel edges, and # DirectedMultiGraph is a class that allows for parallel edges and loops # as well. class DirectedGraph - module Algorithms include Search @@ -19,12 +18,20 @@ module Algorithms include ChinesePostman # A directed graph is directed by definition + # + # @return [Boolean] always true def directed?() true; end # A digraph uses the Arc class for edges + # + # @return [Graphy::MultiArc, Graphy::Arc] `Graphy::MultiArc` if the graph allows for parallel edges, + # `Graphy::Arc` otherwise. def edge_class() @parallel_edges ? Graphy::MultiArc : Graphy::Arc; end # Reverse all edges in a graph + # + # @return [DirectedGraph] a copy of the receiver for which the direction of edges has + # been inverted. def reversal result = self.class.new edges.inject(result) {|a,e| a << e.reverse} @@ -32,7 +39,9 @@ def reversal result end - # Return true if the Graph is oriented. + # Check whether the Graph is oriented or not. + # + # @return [Boolean] def oriented? e = edges re = e.map {|x| x.reverse} @@ -40,6 +49,8 @@ def oriented? end # Balanced is when the out edge count is equal to the in edge count + # + # @return [Boolean] def balanced?(v) out_degree(v) == in_degree(v); end # Returns out_degree(v) - in_degree(v) @@ -59,9 +70,7 @@ def community(node, direction) def descendants(node) community(node, :out); end def ancestors(node) community(node, :in ); end def family(node) community(node, :all); end - - end - - end -end + end # Algorithms + end # DirectedGraph +end # Graphy diff --git a/lib/graphy/directed_graph/distance.rb b/lib/graphy/directed_graph/distance.rb index a84925d..01498e8 100644 --- a/lib/graphy/directed_graph/distance.rb +++ b/lib/graphy/directed_graph/distance.rb @@ -1,7 +1,5 @@ module Graphy - class DirectedGraph - module Distance # Shortest path from Jorgen Band-Jensen and Gregory Gutin, @@ -153,10 +151,8 @@ def floyd_warshall(weight=nil, zero=0) end end [c, path, delta] - end # floyd_warshall - + end + end # Distance - - end - + end # DirectedGraph end # Graphy diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb index f190119..52c6159 100644 --- a/lib/graphy/graph.rb +++ b/lib/graphy/graph.rb @@ -1,18 +1,19 @@ module Graphy - + # Using the functions required by the GraphAPI, it implements all the # basic functions of a Graph class by using only functions in GraphAPI. # An actual implementation still needs to be done, as in Digraph or # UndirectedGraph. class Graph + include Enumerable include Labels - + def self.[](*a) self.new.from_array(*a); end def initialize(*params) raise ArgumentError if params.any? do |p| - !(p.kind_of? Graphy::Graph or p.kind_of? Array or p.kind_of? Hash) + !(p.kind_of? Graphy::Graph or p.kind_of? Array or p.kind_of? Hash) end args = params.last || {} class << self @@ -24,7 +25,7 @@ class << self end implementation_initialize(*params) end - + # Shortcut for creating a Graph # # Example: Graphy::Graph[1,2, 2,3, 2,4, 4,5].edges.to_a.to_s => @@ -36,14 +37,14 @@ def from_array(*a) if a.size == 1 and a[0].kind_of? Hash # Convert to edge class a[0].each do |k,v| -#FIXME, edge class shouldn't be assume here!!! + #FIXME, edge class shouldn't be assume here!!! if edge_class.include? Graphy::ArcNumber add_edge!(edge_class[k[0],k[1],nil,v]) else add_edge!(edge_class[k[0],k[1],v]) end end -#FIXME, edge class shouldn't be assume here!!! + #FIXME, edge class shouldn't be assume here!!! elsif a[0].kind_of? Graphy::Arc a.each{|e| add_edge!(e); self[e] = e.label} elsif a.size % 2 == 0 @@ -53,24 +54,36 @@ def from_array(*a) end self end - + # Non destructive version of add_vertex!, returns modified copy of Graph - def add_vertex(v, l=nil) x=self.class.new(self); x.add_vertex!(v,l); end - + def add_vertex(v, l=nil) + x = self.class.new(self) + x.add_vertex!(v,l) + end + # Non destructive version add_edge!, returns modified copy of Graph - def add_edge(u, v=nil, l=nil) x=self.class.new(self); x.add_edge!(u,v,l); end + def add_edge(u, v=nil, l=nil) + x = self.class.new(self) + x.add_edge!(u,v,l) + end alias add_arc add_edge - + # Non destructive version of remove_vertex!, returns modified copy of Graph - def remove_vertex(v) x=self.class.new(self); x.remove_vertex!(v); end + def remove_vertex(v) + x = self.class.new(self) + x.remove_vertex!(v) + end # Non destructive version of remove_edge!, returns modified copy of Graph - def remove_edge(u,v=nil) x=self.class.new(self); x.remove_edge!(u,v); end + def remove_edge(u,v=nil) + x = self.class.new(self) + x.remove_edge!(u,v) + end alias remove_arc remove_edge - + # Return Array of adjacent portions of the Graph # x can either be a vertex an edge. - # options specifies parameters about the adjacency search + # options specifies parameters about the adjacency search: # :type can be either :edges or :vertices (default). # :direction can be :in, :out(default) or :all. # @@ -85,54 +98,83 @@ def adjacent(x, options={}) (options[:type] == :edges ? edges : to_a).select {|u| adjacent?(x,u,d)} end -#FIXME, THIS IS A HACK AROUND A SERIOUS PROBLEM + #FIXME, THIS IS A HACK AROUND A SERIOUS PROBLEM alias graph_adjacent adjacent - + # Add all objects in _a_ to the vertex set. - def add_vertices!(*a) a.each {|v| add_vertex! v}; self; end - - # See add_vertices! + def add_vertices!(*a) + a.each {|v| add_vertex! v} + self + end - def add_vertices(*a) x=self.class.new(self); x.add_vertices(*a); self; end + # See add_vertices! + def add_vertices(*a) + x = self.class.new(self) + x.add_vertices(*a) + self + end # Add all edges in the _edges_ Enumerable to the edge set. Elements of the # Enumerable can be both two-element arrays or instances of DirectedArc or # UnDirectedArc. - def add_edges!(*args) args.each { |edge| add_edge!(edge) }; self; end + def add_edges!(*args) + args.each { |edge| add_edge!(edge) } + self + end alias add_arcs! add_edges! - + # See add_edge! - def add_edges(*a) x=self.class.new(self); x.add_edges!(*a); self; end + def add_edges(*a) + x = self.class.new(self) + x.add_edges!(*a) + self + end alias add_arcs add_edges # Remove all vertices specified by the Enumerable a from the graph by # calling remove_vertex!. - def remove_vertices!(*a) a.each { |v| remove_vertex! v }; end - + def remove_vertices!(*a) + a.each { |v| remove_vertex! v } + end + # See remove_vertices! - def remove_vertices(*a) x=self.class.new(self); x.remove_vertices(*a); end + def remove_vertices(*a) + x = self.class.new(self) + x.remove_vertices(*a) + end # Remove all vertices edges by the Enumerable a from the graph by # calling remove_edge! - def remove_edges!(*a) a.each { |e| remove_edges! e }; end + def remove_edges!(*a) + a.each { |e| remove_edges! e } + end alias remove_arcs! remove_edges! # See remove_edges - def remove_edges(*a) x=self.class.new(self); x.remove_edges(*a); end + def remove_edges(*a) + x = self.class.new(self) + x.remove_edges(*a) + end alias remove_arcs remove_edges # Execute given block for each vertex, provides for methods in Enumerable - def each(&block) vertices.each(&block); end + def each(&block) + vertices.each(&block) + end # Returns true if _v_ is a vertex of the graph. # This is a default implementation that is of O(n) average complexity. # If a subclass uses a hash to store vertices, then this can be # made into an O(1) average complexity operation. - def vertex?(v) vertices.include?(v); end - + def vertex?(v) + vertices.include?(v) + end + # Returns true if u or (u,v) is an Arc of the graph. - def edge?(*arg) edges.include?(edge_convert(*args)); end + def edge?(*arg) + edges.include?(edge_convert(*args)) + end alias arc? edge? # Tests two objects to see if they are adjacent. @@ -165,98 +207,129 @@ def adjacent?(source, target, direction=:all) end # Returns true if the graph has no vertex, i.e. num_vertices == 0. - def empty?() vertices.size.zero?; end + def empty?() + vertices.size.zero? + end # Returns true if the given object is a vertex or Arc in the Graph. - # - def include?(x) x.kind_of?(Graphy::Arc) ? edge?(x) : vertex?(x); end + def include?(x) + x.is_a?(Graphy::Arc) ? edge?(x) : vertex?(x) + end # Returns the neighboorhood of the given vertex (or Arc) # This is equivalent to adjacent, but bases type on the type of object. # direction can be :all, :in, or :out def neighborhood(x, direction = :all) - adjacent(x, :direction => direction, :type => ((x.kind_of? Graphy::Arc) ? :edges : :vertices )) + adjacent(x, :direction => direction, :type => ((x.is_a? Graphy::Arc) ? :edges : :vertices )) end - + # Union of all neighborhoods of vertices (or edges) in the Enumerable x minus the contents of x # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: Theory, Algorithms and Applications_, pg 4 def set_neighborhood(x, direction = :all) - x.inject(Set.new) {|a,v| a.merge(neighborhood(v,direction))}.reject {|v2| x.include?(v2)} + x.inject(Set.new) { |a,v| a.merge(neighborhood(v, idirection))}.reject { |v2| x.include?(v2) } end - + # Union of all set_neighborhoods reachable in p edges # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: Theory, Algorithms and Applications_, pg 46 - def closed_pth_neighborhood(w,p,direction=:all) + def closed_pth_neighborhood(w, p, direction = :all) if p <= 0 w elsif p == 1 - (w + set_neighborhood(w,direction)).uniq + (w + set_neighborhood(w, direction)).uniq else n = set_neighborhood(w, direction) - (w + n + closed_pth_neighborhood(n,p-1,direction)).uniq + (w + n + closed_pth_neighborhood(n, p-1, direction)).uniq end end - + # Returns the neighboorhoods reachable in p steps from every vertex (or edge) # in the Enumerable x # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: Theory, Algorithms and Applications_, pg 46 - def open_pth_neighborhood(x, p, direction=:all) + def open_pth_neighborhood(x, p, direction = :all) if p <= 0 x elsif p == 1 set_neighborhood(x,direction) else - set_neighborhood(open_pth_neighborhood(x, p-1, direction),direction) - closed_pth_neighborhood(x,p-1,direction) + set_neighborhood(open_pth_neighborhood(x, p-1, direction),direction) - closed_pth_neighborhood(x, p-1, direction) end end - + # Returns the number of out-edges (for directed graphs) or the number of # incident edges (for undirected graphs) of vertex _v_. - def out_degree(v) adjacent(v, :direction => :out).size; end + def out_degree(v) + adjacent(v, :direction => :out).size + end # Returns the number of in-edges (for directed graphs) or the number of # incident edges (for undirected graphs) of vertex _v_. - def in_degree(v) adjacent(v, :direction => :in ).size; end + def in_degree(v) + adjacent(v, :direction => :in ).size + end # Returns the sum of the number in and out edges for a vertex - def degree(v) in_degree(v) + out_degree(v); end + def degree(v) + in_degree(v) + out_degree(v) + end # Minimum in-degree - def min_in_degree() to_a.map {|v| in_degree(v)}.min; end + def min_in_degree() + to_a.map {|v| in_degree(v)}.min + end # Minimum out-degree - def min_out_degree() to_a.map {|v| out_degree(v)}.min; end + def min_out_degree() + to_a.map {|v| out_degree(v)}.min + end # Minimum degree of all vertexes - def min_degree() [min_in_degree, min_out_degree].min; end + def min_degree() + [min_in_degree, min_out_degree].min + end # Maximum in-degree - def max_in_degree() vertices.map {|v| in_degree(v)}.max; end + def max_in_degree() + vertices.map { |v| in_degree(v)}.max + end # Maximum out-degree - def max_out_degree() vertices.map {|v| out_degree(v)}.max; end + def max_out_degree() + vertices.map { |v| out_degree(v)}.max + end # Minimum degree of all vertexes - def max_degree() [max_in_degree, max_out_degree].max; end + def max_degree() + [max_in_degree, max_out_degree].max + end # Regular - def regular?() min_degree == max_degree; end + def regular?() + min_degree == max_degree + end # Returns the number of vertices. - def size() vertices.size; end + def size() + vertices.size + end # Synonym for size. - def num_vertices() vertices.size; end + def num_vertices() + vertices.size + end # Returns the number of edges. - def num_edges() edges.size; end + def num_edges() + edges.size + end # Utility method to show a string representation of the edges of the graph. - def to_s() edges.to_s; end + def to_s() + edges.to_s + end # Equality is defined to be same set of edges and directed? - def eql?(g) - return false unless g.kind_of? Graphy::Graph + def eql?(g) + return false unless g.is_a? Graphy::Graph (g.directed? == self.directed?) and (vertices.sort == g.vertices.sort) and @@ -264,13 +337,15 @@ def eql?(g) end # Synonym for eql? - def ==(rhs) eql?(rhs); end + def ==(rhs) + eql?(rhs) + end # Merge another graph into this one def merge(other) - other.vertices.each {|v| add_vertex!(v) } - other.edges.each {|e| add_edge!(e) } - other.edges.each {|e| add_edge!(e.reverse) } if directed? and !other.directed? + other.vertices.each { |v| add_vertex!(v) } + other.edges.each { |e| add_edge!(e) } + other.edges.each { |e| add_edge!(e.reverse) } if directed? and !other.directed? self end @@ -300,13 +375,15 @@ def -(other) end # A synonym for add_edge! - def <<(edge) add_edge!(edge); end + def <<(edge) + add_edge!(edge) + end # Return the complement of the current graph def complement vertices.inject(self.class.new) do |a,v| a.add_vertex!(v) - vertices.each {|v2| a.add_edge!(v,v2) unless edge?(v,v2) }; a + vertices.each { |v2| a.add_edge!(v, v2) unless edge?(v, v2) }; a end end @@ -316,15 +393,17 @@ def induced_subgraph(v) ( v.include?(e.source) and v.include?(e.target) ) ? (a << e) : a end; end - + def inspect - l = vertices.select {|v| self[v]}.map {|u| "vertex_label_set(#{u.inspect},#{self[u].inspect})"}.join('.') + l = vertices.select { |v| self[v]}.map { |u| "vertex_label_set(#{u.inspect}, #{self[u].inspect})"}.join('.') self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l && l != '' ? '.'+l : '') end - - private - def edge_convert(*args) args[0].kind_of?(Graphy::Arc) ? args[0] : edge_class[*args]; end - end # Graph + private + def edge_convert(*args) + args[0].is_a?(Graphy::Arc) ? args[0] : edge_class[*args] + end + + end # Graph end # Graphy diff --git a/lib/graphy/graph_api.rb b/lib/graphy/graph_api.rb index 0b61ca6..1d3309f 100644 --- a/lib/graphy/graph_api.rb +++ b/lib/graphy/graph_api.rb @@ -4,15 +4,18 @@ module Graphy # use the algorithms defined by this library module GraphAPI - # Each implementation module must implement the following routines - # * directed?() # Is the graph directed? - # * add_vertex!(v,l=nil) # Add a vertex to the graph and return the graph, l is an optional label - # * add_edge!(u,v=nil,l=nil) # Add an edge to the graph and return the graph. u can be an Arc or Edge or u,v is an edge pair. last parameter is an optional label - # * remove_vertex!(v) # Remove a vertex to the graph and return the graph - # * remove_edge!(u,v=nil) # Remove an edge from the graph and return the graph - # * vertices() # Returns an array of of all vertices - # * edges() # Returns an array of all edges - # * edge_class() # Returns the class used to store edges + # Each implementation module must implement the following routines: + # * directed?() # Is the graph directed? + # * add_vertex!(v,l=nil) # Add a vertex to the graph and return the graph, l is an optional label. + # * add_edge!(u,v=nil,l=nil) # Add an edge to the graph and return the graph. u can be an Arc or Edge + # or u,v is an edge pair. The last parameter is an optional label. + # * remove_vertex!(v) # Remove a vertex to the graph and return the graph. + # * remove_edge!(u,v=nil) # Remove an edge from the graph and return the graph. + # * vertices() # Returns an array of of all vertices. + # * edges() # Returns an array of all edges. + # * edge_class() # Returns the class used to store edges. + # + # @raise if the API is not completely implemented def self.included(klass) [:directed?, :add_vertex!, :add_edge!, :remove_vertex!, :remove_edge!, :vertices, :edges, :edge_class].each do |meth| raise "Must implement #{meth}" unless klass.instance_methods.include?(meth) @@ -26,5 +29,6 @@ def self.included(klass) alias arc_class edge_class end end - end -end + + end # GraphAPI +end # Graphy From ebcd7c43df3aa565825040986a8ee06bc9d824a9 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 08:14:07 +0200 Subject: [PATCH 06/44] Version bump to 0.5.2 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 7dea76e..cb0c939 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.1 +0.5.2 From 40f4631eba7d03d8f89bad0ae5a38f07ac0adc01 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 08:18:00 +0200 Subject: [PATCH 07/44] some more cleanup, v0.5.2 --- lib/graphy/adjacency_graph.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/graphy/adjacency_graph.rb b/lib/graphy/adjacency_graph.rb index db400ff..e500376 100644 --- a/lib/graphy/adjacency_graph.rb +++ b/lib/graphy/adjacency_graph.rb @@ -16,7 +16,10 @@ class ArrayWithAdd < Array # :nodoc: def implementation_initialize(*params) @vertex_dict = Hash.new clear_all_labels - + + # FIXME: could definitely make use of the activesupport helper + # extract_options! and facets' reverse_merge! technique + # to handle parameters args = (params.pop if params.last.kind_of? Hash) || {} # Basic configuration of adjacency @@ -47,11 +50,11 @@ def implementation_initialize(*params) end # Returns true if v is a vertex of this Graph - # An O(1) implementation of vertex? + # (an "O(1)" implementation of vertex?) def vertex?(v) @vertex_dict.has_key?(v); end # Returns true if [u,v] or u is an Arc - # An O(1) implementation + # (an "O(1)" implementation of edge?) def edge?(u, v=nil) u, v = u.source, u.target if u.kind_of? Graphy::Arc vertex?(u) and @vertex_dict[u].include?(v) @@ -86,7 +89,7 @@ def add_edge!(u, v=nil, l=nil, n=nil) # Removes a given vertex from the graph def remove_vertex!(v) -# FIXME This is broken for multi graphs + # FIXME This is broken for multi graphs @vertex_dict.delete(v) @vertex_dict.each_value { |adjList| adjList.delete(v) } @vertex_dict.keys.each do |u| @@ -137,8 +140,8 @@ def edges end; a end.to_a end - -# FIXME, EFFED UP + + # FIXME, EFFED UP def adjacent(x, options={}) options[:direction] ||= :out if !x.kind_of?(Graphy::Arc) and (options[:direction] == :out || !directed?) From 032b07ea183794b87a5c5d529f88880a6add6216 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 08:30:39 +0200 Subject: [PATCH 08/44] some more code cleanup --- README.markdown | 5 +-- lib/graphy/adjacency_graph.rb | 56 ++++++++++++++----------- lib/graphy/common.rb | 47 +++++++++++++++------ lib/graphy/directed_graph/algorithms.rb | 36 +++++++++++----- lib/graphy/directed_graph/distance.rb | 39 +++++++++-------- 5 files changed, 115 insertions(+), 68 deletions(-) diff --git a/README.markdown b/README.markdown index 8c2b7f1..5a55803 100644 --- a/README.markdown +++ b/README.markdown @@ -3,8 +3,6 @@ Graphy: A Graph Theory Library for Ruby **A framework for graph data structures and algorithms.** -This library is based on [GRATR][1] (itself a fork of [RGL][2]). - Graph algorithms currently provided are: * Topological Sort @@ -156,7 +154,7 @@ Look for more in the examples directory. ## History This library is based on [GRATR][1] by Shawn Garbett (itself a fork of -Horst Duchene's RGL library) which is heavily influenced by the Boost +Horst Duchene's [RGL][2] library) which is heavily influenced by the [Boost][3] Graph Library (BGL). This fork attempts to modernize and extend the API and tests. @@ -188,3 +186,4 @@ See LICENSE [2]: http://rgl.rubyforge.org [3]: http://www.boost.org/libs/graph/doc [4]: http://www.nist.gov/dads/HTML/graph.html + diff --git a/lib/graphy/adjacency_graph.rb b/lib/graphy/adjacency_graph.rb index e500376..d2626df 100644 --- a/lib/graphy/adjacency_graph.rb +++ b/lib/graphy/adjacency_graph.rb @@ -20,7 +20,7 @@ def implementation_initialize(*params) # FIXME: could definitely make use of the activesupport helper # extract_options! and facets' reverse_merge! technique # to handle parameters - args = (params.pop if params.last.kind_of? Hash) || {} + args = (params.pop if params.last.is_a? Hash) || {} # Basic configuration of adjacency @allow_loops = args[:loops] || false @@ -32,7 +32,7 @@ def implementation_initialize(*params) end # Copy any given graph into this graph - params.select {|p| p.kind_of? Graphy::Graph}.each do |g| + params.select { |p| p.is_a? Graphy::Graph }.each do |g| g.edges.each do |e| add_edge!(e) edge_label_set(e, edge_label(e)) if edge_label(e) @@ -43,25 +43,27 @@ def implementation_initialize(*params) end # Add all array edges specified - params.select {|p| p.kind_of? Array}.each do |a| - 0.step(a.size-1, 2) {|i| add_edge!(a[i], a[i+1])} + params.select { |p| p.is_a? Array}.each do |a| + 0.step(a.size-1, 2) { |i| add_edge!(a[i], a[i+1])} end end # Returns true if v is a vertex of this Graph # (an "O(1)" implementation of vertex?) - def vertex?(v) @vertex_dict.has_key?(v); end + def vertex?(v) + @vertex_dict.has_key?(v) + end # Returns true if [u,v] or u is an Arc # (an "O(1)" implementation of edge?) - def edge?(u, v=nil) - u, v = u.source, u.target if u.kind_of? Graphy::Arc + def edge?(u, v = nil) + u, v = u.source, u.target if u.is_a? Graphy::Arc vertex?(u) and @vertex_dict[u].include?(v) end # Adds a vertex to the graph with an optional label - def add_vertex!(vertex, label=nil) + def add_vertex!(vertex, label = nil) @vertex_dict[vertex] ||= @edgelist_class.new self[vertex] = label if label self @@ -71,18 +73,19 @@ def add_vertex!(vertex, label=nil) # Can be called in two basic ways, label is optional # * add_edge!(Arc[source,target], "Label") # * add_edge!(source,target, "Label") - def add_edge!(u, v=nil, l=nil, n=nil) + def add_edge!(u, v = nil, l = nil, n = nil) n = u.number if u.class.include? ArcNumber and n.nil? - u, v, l = u.source, u.target, u.label if u.kind_of? Graphy::Arc + u, v, l = u.source, u.target, u.label if u.is_a? Graphy::Arc return self if not @allow_loops and u == v - n = (@next_edge_number+=1) unless n if @parallel_edges - add_vertex!(u); add_vertex!(v) + n = (@next_edge_number += 1) unless n if @parallel_edges + add_vertex!(u) + add_vertex!(v) @vertex_dict[u].add(v) (@edge_number[u] ||= @edgelist_class.new).add(n) if @parallel_edges unless directed? @vertex_dict[v].add(u) (@edge_number[v] ||= @edgelist_class.new).add(n) if @parallel_edges - end + end self[n ? edge_class[u,v,n] : edge_class[u,v]] = l if l self end @@ -92,7 +95,7 @@ def remove_vertex!(v) # FIXME This is broken for multi graphs @vertex_dict.delete(v) @vertex_dict.each_value { |adjList| adjList.delete(v) } - @vertex_dict.keys.each do |u| + @vertex_dict.keys.each do |u| delete_label(edge_class[u,v]) delete_label(edge_class[v,u]) end @@ -102,8 +105,8 @@ def remove_vertex!(v) # Removes an edge from the graph, can be called with source and target or with # and object of Graphy::Arc derivation - def remove_edge!(u, v=nil) - unless u.kind_of? Graphy::Arc + def remove_edge!(u, v = nil) + unless u.is_a? Graphy::Arc raise ArgumentError if @parallel_edges u = edge_class[u,v] end @@ -122,7 +125,9 @@ def remove_edge!(u, v=nil) end # Returns an array of vertices that the graph has - def vertices() @vertex_dict.keys; end + def vertices() + @vertex_dict.keys + end # Returns an array of edges, most likely of class Arc or Edge depending # upon the type of graph @@ -130,26 +135,27 @@ def edges @vertex_dict.keys.inject(Set.new) do |a,v| if @parallel_edges and @edge_number[v] @vertex_dict[v].zip(@edge_number[v]).each do |w| - s,t,n = v,w[0],w[1] - a.add( edge_class[ s,t,n, edge_label(s,t,n) ] ) + s, t, n = v, w[0], w[1] + a.add(edge_class[s, t, n, edge_label(s, t, n)]) end else @vertex_dict[v].each do |w| - a.add(edge_class[v,w,edge_label(v,w)]) + a.add(edge_class[v, w, edge_label(v, w)]) end - end; a + end + a end.to_a end # FIXME, EFFED UP - def adjacent(x, options={}) + def adjacent(x, options = {}) options[:direction] ||= :out - if !x.kind_of?(Graphy::Arc) and (options[:direction] == :out || !directed?) + if !x.is_a?(Graphy::Arc) and (options[:direction] == :out || !directed?) if options[:type] == :edges i = -1 @parallel_edges ? - @vertex_dict[x].map {|v| e=edge_class[x,v,@edge_number[x][i+=1]]; e.label = self[e]; e} : - @vertex_dict[x].map {|v| e=edge_class[x,v]; e.label = self[e]; e} + @vertex_dict[x].map { |v| e=edge_class[x, v, @edge_number[x][i+=1]]; e.label = self[e]; e} : + @vertex_dict[x].map { |v| e=edge_class[x, v]; e.label = self[e]; e} else @vertex_dict[x].to_a end diff --git a/lib/graphy/common.rb b/lib/graphy/common.rb index dbdda0d..495d51f 100644 --- a/lib/graphy/common.rb +++ b/lib/graphy/common.rb @@ -6,15 +6,30 @@ module Graphy # make it work. This is a good example to look # at for making one's own graph classes. class Cycle - def initialize(n) @size = n; end - def directed?() false; end - def vertices() (1..@size).to_a; end - def vertex?(v) v > 0 and v <= @size; end - def edge?(u,v=nil) - u, v = [u.source, v.target] if u.kind_of? Graphy::Arc - vertex?(u) && vertex?(v) && ((v-u == 1) or (u==@size && v=1)) + def initialize(n) + @size = n; + end + + def directed?() + false + end + + def vertices() + (1..@size).to_a + end + + def vertex?(v) + v > 0 and v <= @size + end + + def edge?(u,v = nil) + u, v = [u.source, v.target] if u.is_a? Graphy::Arc + vertex?(u) && vertex?(v) && ((v-u == 1) or (u == @size && v = 1)) + end + + def edges() + Array.new(@size) { |i| Graphy::Edge[i+1, (i+1) == @size ? 1 : i+2]} end - def edges() Array.new(@size) {|i| Graphy::Edge[i+1, (i+1)==@size ? 1 : i+2]}; end end # Cycle # This class defines a complete graph of size n. @@ -23,15 +38,21 @@ def edges() Array.new(@size) {|i| Graphy::Edge[i+1, (i+1)==@size ? 1 : i+2]}; en # make it work. This is a good example to look # at for making one's own graph classes. class Complete < Cycle - def initialize(n) @size = n; @edges = nil; end + def initialize(n) + @size = n + @edges = nil + end + def edges - return @edges if @edges # Cache edges + return @edges if @edges # cache edges @edges = [] @size.times do |u| - @size.times {|v| @edges << Graphy::Edge[u+1, v+1]} - end; @edges + @size.times { |v| @edges << Graphy::Edge[u+1, v+1]} + end + @edges end - def edge?(u,v=nil) + + def edge?(u, v = nil) u, v = [u.source, v.target] if u.kind_of? Graphy::Arc vertex?(u) && vertex?(v) end diff --git a/lib/graphy/directed_graph/algorithms.rb b/lib/graphy/directed_graph/algorithms.rb index dbdbeab..420b736 100644 --- a/lib/graphy/directed_graph/algorithms.rb +++ b/lib/graphy/directed_graph/algorithms.rb @@ -20,13 +20,17 @@ module Algorithms # A directed graph is directed by definition # # @return [Boolean] always true - def directed?() true; end + def directed?() + true + end # A digraph uses the Arc class for edges # # @return [Graphy::MultiArc, Graphy::Arc] `Graphy::MultiArc` if the graph allows for parallel edges, # `Graphy::Arc` otherwise. - def edge_class() @parallel_edges ? Graphy::MultiArc : Graphy::Arc; end + def edge_class() + @parallel_edges ? Graphy::MultiArc : Graphy::Arc + end # Reverse all edges in a graph # @@ -34,7 +38,7 @@ def edge_class() @parallel_edges ? Graphy::MultiArc : Graphy::Arc; end # been inverted. def reversal result = self.class.new - edges.inject(result) {|a,e| a << e.reverse} + edges.inject(result) { |a,e| a << e.reverse} vertices.each { |v| result.add_vertex!(v) unless result.vertex?(v) } result end @@ -44,17 +48,21 @@ def reversal # @return [Boolean] def oriented? e = edges - re = e.map {|x| x.reverse} - not e.any? {|x| re.include?(x)} + re = e.map { |x| x.reverse} + not e.any? { |x| re.include?(x)} end # Balanced is when the out edge count is equal to the in edge count # # @return [Boolean] - def balanced?(v) out_degree(v) == in_degree(v); end + def balanced?(v) + out_degree(v) == in_degree(v) + end # Returns out_degree(v) - in_degree(v) - def delta(v) out_degree(v) - in_degree(v); end + def delta(v) + out_degree(v) - in_degree(v) + end def community(node, direction) nodes, stack = {}, adjacent(node, :direction => direction) @@ -67,9 +75,17 @@ def community(node, direction) nodes.values end - def descendants(node) community(node, :out); end - def ancestors(node) community(node, :in ); end - def family(node) community(node, :all); end + def descendants(node) + community(node, :out) + end + + def ancestors(node) + community(node, :in) + end + + def family(node) + community(node, :all) + end end # Algorithms end # DirectedGraph diff --git a/lib/graphy/directed_graph/distance.rb b/lib/graphy/directed_graph/distance.rb index 01498e8..fed2ebb 100644 --- a/lib/graphy/directed_graph/distance.rb +++ b/lib/graphy/directed_graph/distance.rb @@ -20,15 +20,16 @@ module Distance # distance. A missing vertex from the hash is an infinite distance # # Complexity O(n+m) - def shortest_path(start, weight=nil, zero=0) - dist = {start => zero}; path = {} + def shortest_path(start, weight = nil, zero = 0) + dist = {start => zero} + path = {} topsort(start) do |vi| next if vi == start - dist[vi],path[vi] = adjacent(vi, :direction => :in).map do |vj| + dist[vi], path[vi] = adjacent(vi, :direction => :in).map do |vj| [dist[vj] + cost(vj,vi,weight), vj] - end.min {|a,b| a[0] <=> b[0]} + end.min { |a,b| a[0] <=> b[0]} end; - dist.keys.size == vertices.size ? [dist,path] : nil + dist.keys.size == vertices.size ? [dist, path] : nil end # shortest_path # Algorithm from Jorgen Band-Jensen and Gregory Gutin, @@ -53,18 +54,20 @@ def shortest_path(start, weight=nil, zero=0) # # O(n*log(n) + m) complexity def dijkstras_algorithm(s, weight = nil, zero = 0) - q = vertices; distance = { s => zero }; path = {} + q = vertices; distance = { s => zero } + path = {} while not q.empty? - v = (q & distance.keys).inject(nil) {|a,k| (!a.nil?) && (distance[a] < distance[k]) ? a : k} + v = (q & distance.keys).inject(nil) { |a,k| (!a.nil?) && (distance[a] < distance[k]) ? a : k} q.delete(v) (q & adjacent(v)).each do |u| c = cost(v,u,weight) - if distance[u].nil? or distance[u] > (c+distance[v]) + if distance[u].nil? or distance[u] > (c + distance[v]) distance[u] = c + distance[v] path[u] = v end end - end; [distance, path] + end + [distance, path] end # dijkstras_algorithm # Algorithm from Jorgen Band-Jensen and Gregory Gutin, @@ -88,19 +91,21 @@ def dijkstras_algorithm(s, weight = nil, zero = 0) # # O(nm) complexity def bellman_ford_moore(start, weight = nil, zero = 0) - distance = { start => zero }; path = {} + distance = { start => zero } + path = {} 2.upto(vertices.size) do edges.each do |e| - u,v = e[0],e[1] + u, v = e[0], e[1] unless distance[u].nil? - c = cost(u, v, weight)+distance[u] + c = cost(u, v, weight) + distance[u] if distance[v].nil? or c < distance[v] distance[v] = c path[v] = u end end end - end; [distance, path] + end + [distance, path] end # bellman_ford_moore # This uses the Floyd-Warshall algorithm to efficiently find @@ -126,10 +131,10 @@ def bellman_ford_moore(start, weight = nil, zero = 0) # about the math system and fully functional duck typing. # # O(n^3) complexity in time. - def floyd_warshall(weight=nil, zero=0) - c = Hash.new {|h,k| h[k] = Hash.new} - path = Hash.new {|h,k| h[k] = Hash.new} - delta = Hash.new {|h,k| h[k] = 0} + def floyd_warshall(weight = nil, zero = 0) + c = Hash.new { |h,k| h[k] = Hash.new} + path = Hash.new { |h,k| h[k] = Hash.new} + delta = Hash.new { |h,k| h[k] = 0} edges.each do |e| delta[e.source] += 1 delta[e.target] -= 1 From b3f8b98761b9cc46114276af24f86b9f4576b4b8 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 08:54:24 +0200 Subject: [PATCH 09/44] 1.8/1.9 compatibility --- lib/graphy.rb | 20 ++++++++++---------- lib/graphy/graph_api.rb | 5 ++++- lib/graphy/ruby_compatibility.rb | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 lib/graphy/ruby_compatibility.rb diff --git a/lib/graphy.rb b/lib/graphy.rb index 0bf4b79..39337e5 100644 --- a/lib/graphy.rb +++ b/lib/graphy.rb @@ -42,16 +42,16 @@ module Graphy autoload :DirectedPseudoGraph, 'graphy/directed_graph' autoload :DirectedMultiGraph, 'graphy/directed_graph' - autoload :Dot, 'graphy/dot' - autoload :Edge, 'graphy/edge' - autoload :Graph, 'graphy/graph' - autoload :GraphAPI, 'graphy/graph_api' - autoload :Labels, 'graphy/labels' - autoload :MaximumFlow, 'graphy/maximum_flow' - autoload :Rdot, 'graphy/rdot' - autoload :Search, 'graphy/search' - autoload :StrongComponents, 'graphy/strong_components' - autoload :UndirectedGraph, 'graphy/undirected_graph' + autoload :Dot, 'graphy/dot' + autoload :Edge, 'graphy/edge' + autoload :Graph, 'graphy/graph' + autoload :GraphAPI, 'graphy/graph_api' + autoload :Labels, 'graphy/labels' + autoload :MaximumFlow, 'graphy/maximum_flow' + autoload :Rdot, 'graphy/rdot' + autoload :Search, 'graphy/search' + autoload :StrongComponents, 'graphy/strong_components' + autoload :UndirectedGraph, 'graphy/undirected_graph' end # Vendored libraries diff --git a/lib/graphy/graph_api.rb b/lib/graphy/graph_api.rb index 1d3309f..c31e202 100644 --- a/lib/graphy/graph_api.rb +++ b/lib/graphy/graph_api.rb @@ -17,7 +17,10 @@ module GraphAPI # # @raise if the API is not completely implemented def self.included(klass) - [:directed?, :add_vertex!, :add_edge!, :remove_vertex!, :remove_edge!, :vertices, :edges, :edge_class].each do |meth| + API_methods = [:directed?, :add_vertex!, :add_edge!, :remove_vertex!, :remove_edge!, :vertices, :edges, :edge_class] + ruby_18 { API_methods.each { |m| m.to_s } } + + API_methods.each do |meth| raise "Must implement #{meth}" unless klass.instance_methods.include?(meth) end diff --git a/lib/graphy/ruby_compatibility.rb b/lib/graphy/ruby_compatibility.rb new file mode 100644 index 0000000..853f06d --- /dev/null +++ b/lib/graphy/ruby_compatibility.rb @@ -0,0 +1,17 @@ +if RUBY_VERSION < "1.9" + def ruby_18 + yield + end + + def ruby_19 + false + end +else + def ruby_18 + false + end + + def ruby_19 + yield + end +end From 25b2164d090bb67d404de0a1e2ee1f10661e962a Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 13:30:25 +0200 Subject: [PATCH 10/44] turned classes into modules so as to be a little bit more flexible --- README.markdown | 2 +- lib/graphy.rb | 8 ++ lib/graphy/adjacency_graph.rb | 6 +- lib/graphy/common.rb | 10 +-- lib/graphy/directed_graph.rb | 61 +++++++++------ lib/graphy/directed_graph/algorithms.rb | 6 +- lib/graphy/directed_graph/distance.rb | 2 +- lib/graphy/dot.rb | 2 +- lib/graphy/graph.rb | 90 +++++++++++++---------- lib/graphy/graph_api.rb | 14 ++-- lib/graphy/helpers.rb | 13 ++++ lib/graphy/labels.rb | 21 +++--- lib/graphy/maximum_flow.rb | 3 +- lib/graphy/undirected_graph.rb | 9 ++- lib/graphy/undirected_graph/algorithms.rb | 10 +-- 15 files changed, 155 insertions(+), 102 deletions(-) create mode 100644 lib/graphy/helpers.rb diff --git a/README.markdown b/README.markdown index 5a55803..1fcdbc6 100644 --- a/README.markdown +++ b/README.markdown @@ -69,7 +69,7 @@ insertion and removal at the cost of extra space overhead, etc. Using IRB, first require the library: - $ irb + require 'rubygems' # only if you are using ruby 1.8.x require 'graphy' If you'd like to include all the classes in the current scope (so you diff --git a/lib/graphy.rb b/lib/graphy.rb index 39337e5..146c704 100644 --- a/lib/graphy.rb +++ b/lib/graphy.rb @@ -29,6 +29,14 @@ require 'set' module Graphy + # ruby stdlib extensions + require 'ext/ext' + # ruby 1.8.x/1.9.x compatibility + require 'graphy/ruby_compatibility' + + require 'graphy/helpers' + + # Graphy internals autoload :AdjacencyGraph, 'graphy/adjacency_graph' autoload :Arc, 'graphy/arc' autoload :ArcNumber, 'graphy/arc_number' diff --git a/lib/graphy/adjacency_graph.rb b/lib/graphy/adjacency_graph.rb index d2626df..509bc88 100644 --- a/lib/graphy/adjacency_graph.rb +++ b/lib/graphy/adjacency_graph.rb @@ -2,7 +2,8 @@ module Graphy # This provides the basic routines needed to implement the Digraph, # UndirectedGraph, PseudoGraph, DirectedPseudoGraph, MultiGraph and - # DirectedPseudoGraph class. + # DirectedPseudoGraph classes, through Graph, under the control of + # the GraphAPI. module AdjacencyGraph class ArrayWithAdd < Array # :nodoc: @@ -46,7 +47,6 @@ def implementation_initialize(*params) params.select { |p| p.is_a? Array}.each do |a| 0.step(a.size-1, 2) { |i| add_edge!(a[i], a[i+1])} end - end # Returns true if v is a vertex of this Graph @@ -125,7 +125,7 @@ def remove_edge!(u, v = nil) end # Returns an array of vertices that the graph has - def vertices() + def vertices @vertex_dict.keys end diff --git a/lib/graphy/common.rb b/lib/graphy/common.rb index 495d51f..5a7e3ff 100644 --- a/lib/graphy/common.rb +++ b/lib/graphy/common.rb @@ -5,16 +5,16 @@ module Graphy # class and implemeting the minimum methods needed to # make it work. This is a good example to look # at for making one's own graph classes. - class Cycle + module Cycle def initialize(n) @size = n; end - def directed?() + def directed? false end - def vertices() + def vertices (1..@size).to_a end @@ -27,7 +27,7 @@ def edge?(u,v = nil) vertex?(u) && vertex?(v) && ((v-u == 1) or (u == @size && v = 1)) end - def edges() + def edges Array.new(@size) { |i| Graphy::Edge[i+1, (i+1) == @size ? 1 : i+2]} end end # Cycle @@ -37,7 +37,7 @@ def edges() # class and implemeting the minimum methods needed to # make it work. This is a good example to look # at for making one's own graph classes. - class Complete < Cycle + module Complete < Cycle def initialize(n) @size = n @edges = nil diff --git a/lib/graphy/directed_graph.rb b/lib/graphy/directed_graph.rb index d7cbfe5..1b95a9b 100644 --- a/lib/graphy/directed_graph.rb +++ b/lib/graphy/directed_graph.rb @@ -1,42 +1,61 @@ module Graphy - - class DirectedGraph < Graph + + module DirectedGraph + include Graph autoload :Algorithms, "graphy/directed_graph/algorithms" autoload :Distance, "graphy/directed_graph/distance" + + #def self.included? base + ## FIXME: don't understand why but that's not triggered + ## by include Digraph so the [] class methods is never + ## added to the receiver :( + #puts "Digraph included" + #base.extend(ClassMethods) + #end + + ## using my helper: + #extends_host_with :ClassMethods + + module ClassMethods + def [](*a) + puts self + self.new.from_array(*a) + end + end def initialize(*params) args = (params.pop if params.last.kind_of? Hash) || {} args[:algorithmic_category] = DirectedGraph::Algorithms super *(params << args) end - end # DirectedGraph # DirectedGraph is just an alias for Digraph should one desire Digraph = DirectedGraph - # This is a Digraph that allows for parallel edges, but does not - # allow loops - class DirectedPseudoGraph < DirectedGraph + # This is a Digraph that allows for paral#lel edges, but does not + ## allow loops + #module DirectedPseudoGraph + #include DirectedGraph - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:parallel_edges] = true - super *(params << args) - end - - end # DirectedPseudoGraph + #def initialize(*params) + #args = (params.pop if params.last.kind_of? Hash) || {} + #args[:parallel_edges] = true + #super *(params << args) + #end - # This is a Digraph that allows for parallel edges and loops - class DirectedMultiGraph < DirectedPseudoGraph + #end # DirectedPseudoGraph - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:loops] = true - super *(params << args) - end + ## This is a Digraph that allows for parallel edges and loops + #module DirectedMultiGraph + #include DirectedPseudoGraph - end # DirectedMultiGraph + #def initialize(*params) + #args = (params.pop if params.last.kind_of? Hash) || {} + #args[:loops] = true + #super *(params << args) + #end + #end # DirectedMultiGraph end # Graphy diff --git a/lib/graphy/directed_graph/algorithms.rb b/lib/graphy/directed_graph/algorithms.rb index 420b736..d75f172 100644 --- a/lib/graphy/directed_graph/algorithms.rb +++ b/lib/graphy/directed_graph/algorithms.rb @@ -9,7 +9,7 @@ module Graphy # DirectedPseudoGraph is a class that allows for parallel edges, and # DirectedMultiGraph is a class that allows for parallel edges and loops # as well. - class DirectedGraph + module DirectedGraph module Algorithms include Search @@ -20,7 +20,7 @@ module Algorithms # A directed graph is directed by definition # # @return [Boolean] always true - def directed?() + def directed? true end @@ -28,7 +28,7 @@ def directed?() # # @return [Graphy::MultiArc, Graphy::Arc] `Graphy::MultiArc` if the graph allows for parallel edges, # `Graphy::Arc` otherwise. - def edge_class() + def edge_class @parallel_edges ? Graphy::MultiArc : Graphy::Arc end diff --git a/lib/graphy/directed_graph/distance.rb b/lib/graphy/directed_graph/distance.rb index fed2ebb..97a7ba8 100644 --- a/lib/graphy/directed_graph/distance.rb +++ b/lib/graphy/directed_graph/distance.rb @@ -1,5 +1,5 @@ module Graphy - class DirectedGraph + module DirectedGraph module Distance # Shortest path from Jorgen Band-Jensen and Gregory Gutin, diff --git a/lib/graphy/dot.rb b/lib/graphy/dot.rb index 4ed142c..6cbf39a 100644 --- a/lib/graphy/dot.rb +++ b/lib/graphy/dot.rb @@ -1,5 +1,5 @@ module Graphy - class Graph + module Graph # Return a DOT::DOTDigraph for directed graphs or a DOT::DOTSubgraph for an # undirected Graph. _params_ can contain any graph property specified in diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb index 52c6159..9991830 100644 --- a/lib/graphy/graph.rb +++ b/lib/graphy/graph.rb @@ -4,23 +4,34 @@ module Graphy # basic functions of a Graph class by using only functions in GraphAPI. # An actual implementation still needs to be done, as in Digraph or # UndirectedGraph. - class Graph + module Graph include Enumerable include Labels - def self.[](*a) self.new.from_array(*a); end + # usi#ng my helper: + #extends_host_with :ClassMethods + + #module ClassMethods + #def self.[](*a) + #puts self + #self.new.from_array(*a) + #end + #end + include InitArray def initialize(*params) + puts "trapped" + puts self raise ArgumentError if params.any? do |p| - !(p.kind_of? Graphy::Graph or p.kind_of? Array or p.kind_of? Hash) + !(p.is_a? Graphy::Graph or p.is_a? Array or p.is_a? Hash) end args = params.last || {} class << self self - end.class_eval do - include( args[:implementation] ? args[:implementation] : AdjacencyGraph ) - include( args[:algorithmic_category] ? args[:algorithmic_category] : Digraph ) + end.module_eval do + include(args[:implementation] ? args[:implementation] : AdjacencyGraph) + include(args[:algorithmic_category] ? args[:algorithmic_category] : Digraph ) include GraphAPI end implementation_initialize(*params) @@ -31,10 +42,10 @@ class << self # Example: Graphy::Graph[1,2, 2,3, 2,4, 4,5].edges.to_a.to_s => # "(1-2)(2-3)(2-4)(4-5)" # - # Or as a Hash for specifying lables + # Or as a Hash for specifying labels # Graphy::Graph[ [:a,:b] => 3, [:b,:c] => 4 ] (Note: Do not use for Multi or Pseudo graphs) def from_array(*a) - if a.size == 1 and a[0].kind_of? Hash + if a.size == 1 and a[0].is_a? Hash # Convert to edge class a[0].each do |k,v| #FIXME, edge class shouldn't be assume here!!! @@ -45,8 +56,8 @@ def from_array(*a) end end #FIXME, edge class shouldn't be assume here!!! - elsif a[0].kind_of? Graphy::Arc - a.each{|e| add_edge!(e); self[e] = e.label} + elsif a[0].is_a? Graphy::Arc + a.each{ |e| add_edge!(e); self[e] = e.label} elsif a.size % 2 == 0 0.step(a.size-1, 2) {|i| add_edge!(a[i], a[i+1])} else @@ -56,13 +67,13 @@ def from_array(*a) end # Non destructive version of add_vertex!, returns modified copy of Graph - def add_vertex(v, l=nil) + def add_vertex(v, l = nil) x = self.class.new(self) x.add_vertex!(v,l) end # Non destructive version add_edge!, returns modified copy of Graph - def add_edge(u, v=nil, l=nil) + def add_edge(u, v = nil, l = nil) x = self.class.new(self) x.add_edge!(u,v,l) end @@ -75,7 +86,7 @@ def remove_vertex(v) end # Non destructive version of remove_edge!, returns modified copy of Graph - def remove_edge(u,v=nil) + def remove_edge(u,v = nil) x = self.class.new(self) x.remove_edge!(u,v) end @@ -92,9 +103,9 @@ def adjacent(x, options={}) d = directed? ? (options[:direction] || :out) : :all # Discharge the easy ones first - return [x.source] if x.kind_of? Arc and options[:type] == :vertices and d == :in - return [x.target] if x.kind_of? Arc and options[:type] == :vertices and d == :out - return [x.source, x.target] if x.kind_of? Arc and options[:type] != :edges and d == :all + return [x.source] if x.is_a? Arc and options[:type] == :vertices and d == :in + return [x.target] if x.is_a? Arc and options[:type] == :vertices and d == :out + return [x.source, x.target] if x.is_a? Arc and options[:type] != :edges and d == :all (options[:type] == :edges ? edges : to_a).select {|u| adjacent?(x,u,d)} end @@ -184,10 +195,10 @@ def edge?(*arg) # all adjacent objects in a graph to a given object. Here the concern is primarily on seeing # if two objects touch. For vertexes, any edge between the two will usually do, but the direction # can be specified if need be. - def adjacent?(source, target, direction=:all) - if source.kind_of? Graphy::Arc + def adjacent?(source, target, direction = :all) + if source.is_a? Graphy::Arc raise NoArcError unless edge? source - if target.kind_of? Graphy::Arc + if target.is_a? Graphy::Arc raise NoArcError unless edge? target (direction != :out and source.source == target.target) or (direction != :in and source.target == target.source) else @@ -196,7 +207,7 @@ def adjacent?(source, target, direction=:all) end else raise NoVertexError unless vertex? source - if target.kind_of? Graphy::Arc + if target.is_a? Graphy::Arc raise NoArcError unless edge? target (direction != :out and source == target.target) or (direction != :in and source == target.source) else @@ -207,7 +218,7 @@ def adjacent?(source, target, direction=:all) end # Returns true if the graph has no vertex, i.e. num_vertices == 0. - def empty?() + def empty? vertices.size.zero? end @@ -264,7 +275,7 @@ def out_degree(v) # Returns the number of in-edges (for directed graphs) or the number of # incident edges (for undirected graphs) of vertex _v_. def in_degree(v) - adjacent(v, :direction => :in ).size + adjacent(v, :direction => :in).size end # Returns the sum of the number in and out edges for a vertex @@ -273,59 +284,59 @@ def degree(v) end # Minimum in-degree - def min_in_degree() + def min_in_degree to_a.map {|v| in_degree(v)}.min end # Minimum out-degree - def min_out_degree() + def min_out_degree to_a.map {|v| out_degree(v)}.min end # Minimum degree of all vertexes - def min_degree() + def min_degree [min_in_degree, min_out_degree].min end # Maximum in-degree - def max_in_degree() + def max_in_degree vertices.map { |v| in_degree(v)}.max end # Maximum out-degree - def max_out_degree() + def max_out_degree vertices.map { |v| out_degree(v)}.max end # Minimum degree of all vertexes - def max_degree() + def max_degree [max_in_degree, max_out_degree].max end # Regular - def regular?() + def regular? min_degree == max_degree end # Returns the number of vertices. - def size() + def size vertices.size end # Synonym for size. - def num_vertices() + def num_vertices vertices.size end # Returns the number of edges. - def num_edges() + def num_edges edges.size end # Utility method to show a string representation of the edges of the graph. - def to_s() - edges.to_s - end + #def to_s + #edges.to_s + #end # Equality is defined to be same set of edges and directed? def eql?(g) @@ -394,10 +405,11 @@ def induced_subgraph(v) end; end - def inspect - l = vertices.select { |v| self[v]}.map { |u| "vertex_label_set(#{u.inspect}, #{self[u].inspect})"}.join('.') - self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l && l != '' ? '.'+l : '') - end + #def inspect + #puts self.inspect + ##l = vertices.select { |v| self[v]}.map { |u| "vertex_label_set(#{u.inspect}, #{self[u].inspect})"}.join('.') + ##self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l && l != '' ? '.'+l : '') + #end private diff --git a/lib/graphy/graph_api.rb b/lib/graphy/graph_api.rb index c31e202..b4d987b 100644 --- a/lib/graphy/graph_api.rb +++ b/lib/graphy/graph_api.rb @@ -5,22 +5,22 @@ module Graphy module GraphAPI # Each implementation module must implement the following routines: - # * directed?() # Is the graph directed? + # * directed? # Is the graph directed? # * add_vertex!(v,l=nil) # Add a vertex to the graph and return the graph, l is an optional label. # * add_edge!(u,v=nil,l=nil) # Add an edge to the graph and return the graph. u can be an Arc or Edge # or u,v is an edge pair. The last parameter is an optional label. # * remove_vertex!(v) # Remove a vertex to the graph and return the graph. # * remove_edge!(u,v=nil) # Remove an edge from the graph and return the graph. - # * vertices() # Returns an array of of all vertices. - # * edges() # Returns an array of all edges. - # * edge_class() # Returns the class used to store edges. + # * vertices # Returns an array of of all vertices. + # * edges # Returns an array of all edges. + # * edge_class # Returns the class used to store edges. # # @raise if the API is not completely implemented def self.included(klass) - API_methods = [:directed?, :add_vertex!, :add_edge!, :remove_vertex!, :remove_edge!, :vertices, :edges, :edge_class] - ruby_18 { API_methods.each { |m| m.to_s } } + @api_methods ||= [:directed?, :add_vertex!, :add_edge!, :remove_vertex!, :remove_edge!, :vertices, :edges, :edge_class] + ruby_18 { @api_methods.each { |m| m.to_s } } - API_methods.each do |meth| + @api_methods.each do |meth| raise "Must implement #{meth}" unless klass.instance_methods.include?(meth) end diff --git a/lib/graphy/helpers.rb b/lib/graphy/helpers.rb new file mode 100644 index 0000000..f3fb786 --- /dev/null +++ b/lib/graphy/helpers.rb @@ -0,0 +1,13 @@ +module Graphy + module InitArray + # using my helper: + extends_host_with :ClassMethods + + module ClassMethods + def self.[](*a) + puts self + self.new.from_array(*a) + end + end + end +end diff --git a/lib/graphy/labels.rb b/lib/graphy/labels.rb index 602ea08..1c41a0d 100644 --- a/lib/graphy/labels.rb +++ b/lib/graphy/labels.rb @@ -1,13 +1,14 @@ module Graphy module Labels + # Return a label for an edge or vertex - def [](u) (u.kind_of? Graphy::Arc) ? edge_label(u) : vertex_label(u); end + def [](u) (u.is_a? Graphy::Arc) ? edge_label(u) : vertex_label(u); end # Set a label for an edge or vertex - def []= (u, value) (u.kind_of? Graphy::Arc) ? edge_label_set(u,value) : vertex_label_set(u, value); end + def []= (u, value) (u.is_a? Graphy::Arc) ? edge_label_set(u,value) : vertex_label_set(u, value); end # Delete a label entirely - def delete_label(u) (u.kind_of? Graphy::Arc) ? edge_label_delete(u) : vertex_label_delete(u); end + def delete_label(u) (u.is_a? Graphy::Arc) ? edge_label_delete(u) : vertex_label_delete(u); end # Get the label for an edge def vertex_label(v) vertex_label_dict[v]; end @@ -23,7 +24,7 @@ def edge_label(u,v=nil,n=nil) # Set the label for an edge def edge_label_set(u, v=nil, l=nil, n=nil) - u.kind_of?(Graphy::Arc) ? l = v : u = edge_convert(u,v,n) + u.is_a?(Graphy::Arc) ? l = v : u = edge_convert(u,v,n) edge_label_dict[u] = l; self end @@ -39,7 +40,7 @@ def edge_label_delete(u, v=nil, n=nil) # Delete a vertex label def vertex_label_delete(v) vertex_label_dict.delete(v); end - protected + protected def vertex_label_dict() @vertex_labels ||= {}; end def edge_label_dict() @edge_labels ||= {}; end @@ -52,7 +53,7 @@ def edge_label_dict() @edge_labels ||= {}; end # Note: This function will not work for Pseudo or Multi graphs at present. # FIXME: Remove u,v interface to fix Pseudo Multi graph problems. def cost(u,v=nil,weight=nil) - u.kind_of?(Arc) ? weight = v : u = edge_class[u,v] + u.is_a?(Arc) ? weight = v : u = edge_class[u,v] case weight when Proc weight.call(u) @@ -62,10 +63,10 @@ def cost(u,v=nil,weight=nil) self[u][weight] end end - + # An alias of cost for property retrieval in general alias property cost - + # A function to set properties specified by the user. def property_set(u,name,value) case name @@ -78,5 +79,5 @@ def property_set(u,name,value) end end - end -end + end # Labels +end # Graphy diff --git a/lib/graphy/maximum_flow.rb b/lib/graphy/maximum_flow.rb index 63d230e..fe6f081 100644 --- a/lib/graphy/maximum_flow.rb +++ b/lib/graphy/maximum_flow.rb @@ -1,6 +1,7 @@ module Graphy - class Network < Digraph + class Network + include Digraph attr_accessor :lower, :upper, :cost, :flow def residual(residual_capacity, cost_property, zero = 0) diff --git a/lib/graphy/undirected_graph.rb b/lib/graphy/undirected_graph.rb index 1119b2f..1c34eea 100644 --- a/lib/graphy/undirected_graph.rb +++ b/lib/graphy/undirected_graph.rb @@ -1,6 +1,7 @@ module Graphy - class UndirectedGraph < Graph + module UndirectedGraph + include Graph autoload :Algorithms, "graphy/undirected_graph/algorithms" @@ -13,7 +14,8 @@ def initialize(*params) # This is a Digraph that allows for parallel edges, but does not # allow loops - class UndirectedPseudoGraph < UndirectedGraph + module UndirectedPseudoGraph + include UndirectedGraph def initialize(*params) args = (params.pop if params.last.kind_of? Hash) || {} args[:parallel_edges] = true @@ -22,7 +24,8 @@ def initialize(*params) end # This is a Digraph that allows for parallel edges and loops - class UndirectedMultiGraph < UndirectedPseudoGraph + module UndirectedMultiGraph + UndirectedPseudoGraph def initialize(*params) args = (params.pop if params.last.kind_of? Hash) || {} args[:loops] = true diff --git a/lib/graphy/undirected_graph/algorithms.rb b/lib/graphy/undirected_graph/algorithms.rb index c687f48..9e3358e 100644 --- a/lib/graphy/undirected_graph/algorithms.rb +++ b/lib/graphy/undirected_graph/algorithms.rb @@ -1,7 +1,5 @@ module Graphy - - class UndirectedGraph - + module UndirectedGraph module Algorithms include Search @@ -88,7 +86,5 @@ def triangulated_chromatic_number end end # UndirectedGraphAlgorithms - - end - -end + end # UndirectedGraph +end # Graphy From a39cec905d17c0ef30312f67b205109554013d5c Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 15:23:24 +0200 Subject: [PATCH 11/44] modules are now builders, providing classes so as to keep the previous public API identical --- graphy.gemspec | 148 ++++++ lib/ext/ext.rb | 78 +++ lib/graphy.rb | 62 ++- lib/graphy/adjacency_graph.rb | 2 +- lib/graphy/arc.rb | 10 +- lib/graphy/chinese_postman.rb | 17 +- lib/graphy/classes/graph_classes.rb | 11 + lib/graphy/common.rb | 10 +- lib/graphy/directed_graph.rb | 75 +-- lib/graphy/directed_graph/algorithms.rb | 16 +- lib/graphy/directed_graph/distance.rb | 2 +- lib/graphy/dot.rb | 18 +- lib/graphy/edge.rb | 10 +- lib/graphy/graph.rb | 26 +- lib/graphy/helpers.rb | 13 - lib/graphy/maximum_flow.rb | 27 +- lib/graphy/search.rb | 547 +++++++++++----------- lib/graphy/strong_components.rb | 3 +- lib/graphy/undirected_graph.rb | 83 ++-- lib/graphy/undirected_graph/algorithms.rb | 24 +- 20 files changed, 721 insertions(+), 461 deletions(-) create mode 100644 graphy.gemspec create mode 100644 lib/ext/ext.rb create mode 100644 lib/graphy/classes/graph_classes.rb delete mode 100644 lib/graphy/helpers.rb diff --git a/graphy.gemspec b/graphy.gemspec new file mode 100644 index 0000000..3593c12 --- /dev/null +++ b/graphy.gemspec @@ -0,0 +1,148 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command +# -*- encoding: utf-8 -*- + +Gem::Specification.new do |s| + s.name = %q{graphy} + s.version = "0.5.2" + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Bruce Williams"] + s.date = %q{2010-03-28} + s.description = %q{A framework for graph data structures and algorithms. + +This library is based on GRATR and RGL. + +Graph algorithms currently provided are: + +* Topological Sort +* Strongly Connected Components +* Transitive Closure +* Rural Chinese Postman +* Biconnected +} + s.email = %q{bruce@codefluency.com} + s.extra_rdoc_files = [ + "LICENSE", + "README.markdown" + ] + s.files = [ + ".document", + ".gitignore", + "CREDITS.markdown", + "LICENSE", + "README.markdown", + "Rakefile", + "TODO.markdown", + "VERSION", + "examples/graph_self.rb", + "examples/module_graph.jpg", + "examples/module_graph.rb", + "examples/self_graph.jpg", + "examples/visualize.jpg", + "examples/visualize.rb", + "graphy.gemspec", + "lib/ext/ext.rb", + "lib/graphy.rb", + "lib/graphy/adjacency_graph.rb", + "lib/graphy/arc.rb", + "lib/graphy/arc_number.rb", + "lib/graphy/biconnected.rb", + "lib/graphy/chinese_postman.rb", + "lib/graphy/classes/graph_classes.rb", + "lib/graphy/common.rb", + "lib/graphy/comparability.rb", + "lib/graphy/directed_graph.rb", + "lib/graphy/directed_graph/algorithms.rb", + "lib/graphy/directed_graph/distance.rb", + "lib/graphy/dot.rb", + "lib/graphy/edge.rb", + "lib/graphy/graph.rb", + "lib/graphy/graph_api.rb", + "lib/graphy/labels.rb", + "lib/graphy/maximum_flow.rb", + "lib/graphy/ruby_compatibility.rb", + "lib/graphy/search.rb", + "lib/graphy/strong_components.rb", + "lib/graphy/undirected_graph.rb", + "lib/graphy/undirected_graph/algorithms.rb", + "spec/biconnected_spec.rb", + "spec/chinese_postman_spec.rb", + "spec/community_spec.rb", + "spec/complement_spec.rb", + "spec/digraph_distance_spec.rb", + "spec/digraph_spec.rb", + "spec/dot_spec.rb", + "spec/edge_spec.rb", + "spec/inspection_spec.rb", + "spec/multi_edge_spec.rb", + "spec/neighborhood_spec.rb", + "spec/properties_spec.rb", + "spec/search_spec.rb", + "spec/spec.opts", + "spec/spec_helper.rb", + "spec/strong_components_spec.rb", + "spec/triangulated_spec.rb", + "spec/undirected_graph_spec.rb", + "vendor/priority-queue/CHANGELOG", + "vendor/priority-queue/Makefile", + "vendor/priority-queue/README", + "vendor/priority-queue/benchmark/dijkstra.rb", + "vendor/priority-queue/compare_comments.rb", + "vendor/priority-queue/doc/c-vs-rb.png", + "vendor/priority-queue/doc/compare_big.gp", + "vendor/priority-queue/doc/compare_big.png", + "vendor/priority-queue/doc/compare_small.gp", + "vendor/priority-queue/doc/compare_small.png", + "vendor/priority-queue/doc/results.csv", + "vendor/priority-queue/ext/priority_queue/CPriorityQueue/extconf.rb", + "vendor/priority-queue/ext/priority_queue/CPriorityQueue/priority_queue.c", + "vendor/priority-queue/lib/priority_queue.rb", + "vendor/priority-queue/lib/priority_queue/c_priority_queue.rb", + "vendor/priority-queue/lib/priority_queue/poor_priority_queue.rb", + "vendor/priority-queue/lib/priority_queue/ruby_priority_queue.rb", + "vendor/priority-queue/priority_queue.so", + "vendor/priority-queue/setup.rb", + "vendor/priority-queue/test/priority_queue_test.rb", + "vendor/rdot.rb" + ] + s.homepage = %q{http://github.com/bruce/graphy} + s.rdoc_options = ["--charset=UTF-8"] + s.require_paths = ["lib"] + s.rubygems_version = %q{1.3.6} + s.summary = %q{A Graph Theory Ruby library} + s.test_files = [ + "spec/edge_spec.rb", + "spec/triangulated_spec.rb", + "spec/undirected_graph_spec.rb", + "spec/properties_spec.rb", + "spec/complement_spec.rb", + "spec/search_spec.rb", + "spec/digraph_spec.rb", + "spec/inspection_spec.rb", + "spec/community_spec.rb", + "spec/multi_edge_spec.rb", + "spec/neighborhood_spec.rb", + "spec/biconnected_spec.rb", + "spec/spec_helper.rb", + "spec/chinese_postman_spec.rb", + "spec/dot_spec.rb", + "spec/digraph_distance_spec.rb", + "spec/strong_components_spec.rb", + "examples/visualize.rb", + "examples/module_graph.rb", + "examples/graph_self.rb" + ] + + if s.respond_to? :specification_version then + current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION + s.specification_version = 3 + + if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then + else + end + else + end +end + diff --git a/lib/ext/ext.rb b/lib/ext/ext.rb new file mode 100644 index 0000000..ee32f07 --- /dev/null +++ b/lib/ext/ext.rb @@ -0,0 +1,78 @@ +class Object + # Get the singleton class of the object. + # Depending on the object which requested its singleton class, + # a `module_eval` or a `class_eval` will be performed. + # Object of special constant type (`Fixnum`, `NilClass`, `TrueClass`, + # `FalseClass` and `Symbol`) return `nil` as they do not have a + # singleton class. + # + # @return the singleton class + def singleton_class + if self.respond_to? :module_eval + self.module_eval("class << self; self; end") + elsif self.respond_to? :instance_eval + begin + self.instance_eval("class << self; self; end") + rescue TypeError + nil + end + end + end + + # Check wether the object is of the specified kind. + # If the receiver has a singleton class, will also perform + # the check on its singleton class' ancestors, so as to catch + # any included modules for object instances. + # + # Example: + # + # class A; include Digraph; end + # a.singleton_class.ancestors + # # => [Graphy::GraphAPI, Graphy::DirectedGraph::Algorithms, ... + # Graphy::Labels, Enumerable, Object, Graphy, Kernel, BasicObject] + # a.is_a? Graphy::Graph + # # => true + # + # @return [Boolean] + def is_a? klass + sc = self.singleton_class + if not sc.nil? + self.singleton_class.ancestors.include?(klass) || super + else + super + end + end +end + +class Module + # Helper which purpose is, given a class including a module, + # each methods defined within a module `ClassMethods` in the + # module as class methods to the class. + # + # Example: + # + # module A + # extends_host + # module ClassMethods + # def selfy; puts "class method for #{self}"; end + # end + # end + # + # class B; include A; end + # + # B.selfy + # # => class method for B + # + # @option *params [Symbol] (:ClassMethods) :with the name of the + # module to extend the receiver with + def extends_host(*params) + args = (params.pop if params.last.is_a? Hash) || {} + @_extension_module = args[:with] || :ClassMethods + + def included(base) + unless @_extension_module.nil? + base.extend(self.const_get(@_extension_module)) + end + end + end +end diff --git a/lib/graphy.rb b/lib/graphy.rb index 146c704..8cc61f3 100644 --- a/lib/graphy.rb +++ b/lib/graphy.rb @@ -34,42 +34,54 @@ module Graphy # ruby 1.8.x/1.9.x compatibility require 'graphy/ruby_compatibility' - require 'graphy/helpers' + # Graphy internals: graph builders and additionnal behaviors + autoload :AdjacencyGraphBuilder, 'graphy/adjacency_graph' + autoload :Arc, 'graphy/arc' + autoload :ArcNumber, 'graphy/arc_number' + autoload :Biconnected, 'graphy/biconnected' + autoload :ChinesePostman, 'graphy/chinese_postman' + autoload :Common, 'graphy/common' + autoload :Comparability, 'graphy/comparability' - # Graphy internals - autoload :AdjacencyGraph, 'graphy/adjacency_graph' - autoload :Arc, 'graphy/arc' - autoload :ArcNumber, 'graphy/arc_number' - autoload :Biconnected, 'graphy/biconnected' - autoload :ChinesePostman, 'graphy/chinese_postman' - autoload :Common, 'graphy/common' - autoload :Comparability, 'graphy/comparability' + autoload :DirectedGraphBuilder, 'graphy/directed_graph' + autoload :DigraphBuilder, 'graphy/directed_graph' + autoload :DirectedPseudoGraphBuilder, 'graphy/directed_graph' + autoload :DirectedMultiGraphBuilder, 'graphy/directed_graph' - autoload :DirectedGraph, 'graphy/directed_graph' - autoload :Digraph, 'graphy/directed_graph' - autoload :DirectedPseudoGraph, 'graphy/directed_graph' - autoload :DirectedMultiGraph, 'graphy/directed_graph' - - autoload :Dot, 'graphy/dot' - autoload :Edge, 'graphy/edge' - autoload :Graph, 'graphy/graph' - autoload :GraphAPI, 'graphy/graph_api' - autoload :Labels, 'graphy/labels' - autoload :MaximumFlow, 'graphy/maximum_flow' - autoload :Rdot, 'graphy/rdot' - autoload :Search, 'graphy/search' - autoload :StrongComponents, 'graphy/strong_components' - autoload :UndirectedGraph, 'graphy/undirected_graph' + autoload :Dot, 'graphy/dot' + autoload :Edge, 'graphy/edge' + autoload :GraphBuilder, 'graphy/graph' + autoload :GraphAPI, 'graphy/graph_api' + autoload :Labels, 'graphy/labels' + autoload :MaximumFlow, 'graphy/maximum_flow' + #autoload :Rdot, 'graphy/dot' + autoload :Search, 'graphy/search' + autoload :StrongComponents, 'graphy/strong_components' + + autoload :UndirectedGraphBuilder, 'graphy/undirected_graph' + autoload :UndirectedPseudoGraphBuilder, 'graphy/undirected_graph' + autoload :UndirectedMultiGraphBuilder, 'graphy/undirected_graph' + + # Graphy classes + autoload :AdjacencyGraph, 'graphy/classes/graph_classes' + autoload :DirectedGraph, 'graphy/classes/graph_classes' + autoload :Digraph, 'graphy/classes/graph_classes' + autoload :DirectedPseudoGraph, 'graphy/classes/graph_classes' + autoload :DirectedMultiGraph, 'graphy/classes/graph_classes' + autoload :UndirectedGraph, 'graphy/classes/graph_classes' + autoload :UndirectedPseudoGraph, 'graphy/classes/graph_classes' + autoload :UndirectedMultiGraph, 'graphy/classes/graph_classes' end # Vendored libraries require 'pathname' - path = Pathname.new(__FILE__) $LOAD_PATH.unshift(path + '../../vendor') $LOAD_PATH.unshift(path + '../../vendor/priority-queue/lib') require 'rdot' + require 'priority_queue/ruby_priority_queue' PriorityQueue = RubyPriorityQueue + diff --git a/lib/graphy/adjacency_graph.rb b/lib/graphy/adjacency_graph.rb index 509bc88..209130e 100644 --- a/lib/graphy/adjacency_graph.rb +++ b/lib/graphy/adjacency_graph.rb @@ -4,7 +4,7 @@ module Graphy # UndirectedGraph, PseudoGraph, DirectedPseudoGraph, MultiGraph and # DirectedPseudoGraph classes, through Graph, under the control of # the GraphAPI. - module AdjacencyGraph + module AdjacencyGraphBuilder class ArrayWithAdd < Array # :nodoc: alias add push diff --git a/lib/graphy/arc.rb b/lib/graphy/arc.rb index d866fb4..86f15c5 100644 --- a/lib/graphy/arc.rb +++ b/lib/graphy/arc.rb @@ -5,11 +5,11 @@ module Graphy # object can be a vertex of a graph. # # Arc's base is a Struct with a :source, a :target and a :label - Struct.new("ArcBase",:source, :target, :label) + Struct.new("ArcBase", :source, :target, :label) class Arc < Struct::ArcBase - def initialize(p_source,p_target,p_label=nil) + def initialize(p_source, p_target, p_label = nil) super(p_source, p_target, p_label) end @@ -42,10 +42,10 @@ def self.[](p_source, p_target, p_label=nil) def inspect() "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{label.inspect}]"; end - end + end # Arc class MultiArc < Arc include ArcNumber end - -end + +end # Graphy diff --git a/lib/graphy/chinese_postman.rb b/lib/graphy/chinese_postman.rb index e5cb866..ae0df19 100644 --- a/lib/graphy/chinese_postman.rb +++ b/lib/graphy/chinese_postman.rb @@ -1,5 +1,4 @@ module Graphy - module ChinesePostman # Returns the shortest walk that traverses all arcs at least @@ -13,8 +12,8 @@ def closed_chinese_postman_tour(start, weight=nil, zero=0) cp_euler_circuit(start, f, path) end - private - + private + def cp_euler_circuit(start, f, path) # :nodoc: circuit = [u=v=start] bridge_taken = Hash.new {|h,k| h[k] = Hash.new} @@ -32,7 +31,7 @@ def cp_euler_circuit(start, f, path) # :nodoc: u=v end; circuit end - + def cp_cancel_cycle(cost, path, f, start, zero) # :nodoc: u = start; k = nil begin @@ -46,7 +45,7 @@ def cp_cancel_cycle(cost, path, f, start, zero) # :nodoc: end until (u=v) != start true # This routine always returns true to make cp_improve easier end - + def cp_improve(f, positive, negative, cost, zero) # :nodoc: residual = self.class.new negative.each do |u| @@ -59,7 +58,7 @@ def cp_improve(f, positive, negative, cost, zero) # :nodoc: i = residual.vertices.detect {|v| r_cost[v][v] and r_cost[v][v] < zero} i ? cp_cancel_cycle(r_cost, r_path, f, i) : false end - + def cp_find_feasible(delta, positive, negative, zero) # :nodoc: f = Hash.new {|h,k| h[k] = Hash.new} negative.each do |i| @@ -70,7 +69,7 @@ def cp_find_feasible(delta, positive, negative, zero) # :nodoc: end end; f end - + def cp_valid_least_cost?(c, zero) # :nodoc: vertices.each do |i| vertices.each do |j| @@ -78,7 +77,7 @@ def cp_valid_least_cost?(c, zero) # :nodoc: end end; true end - + def cp_unbalanced(delta) # :nodoc: negative = []; positive = [] vertices.each do |v| @@ -89,4 +88,4 @@ def cp_unbalanced(delta) # :nodoc: end # Chinese Postman end # Graphy - + diff --git a/lib/graphy/classes/graph_classes.rb b/lib/graphy/classes/graph_classes.rb new file mode 100644 index 0000000..887c8d7 --- /dev/null +++ b/lib/graphy/classes/graph_classes.rb @@ -0,0 +1,11 @@ +module Graphy + class Graph; include GraphBuilder; end + class AdjacencyGraph; include AdjacencyGraphBuilder; end + class DirectedGraph; include DirectedGraphBuilder; end + class Digraph; include DigraphBuilder; end + class DirectedPseudoGraph; include DirectedPseudoGraphBuilder; end + class DirectedMultiGraph; include DirectedMultiGraphBuilder; end + class UndirectedGraph; include UndirectedGraphBuilder; end + class UndirectedPseudoGraph; include UndirectedPseudoGraphBuilder; end + class UndirectedMultiGraph; include UndirectedMultiraphBuilder; end +end diff --git a/lib/graphy/common.rb b/lib/graphy/common.rb index 5a7e3ff..d2f7dee 100644 --- a/lib/graphy/common.rb +++ b/lib/graphy/common.rb @@ -5,7 +5,7 @@ module Graphy # class and implemeting the minimum methods needed to # make it work. This is a good example to look # at for making one's own graph classes. - module Cycle + module CycleBuilder def initialize(n) @size = n; end @@ -30,14 +30,14 @@ def edge?(u,v = nil) def edges Array.new(@size) { |i| Graphy::Edge[i+1, (i+1) == @size ? 1 : i+2]} end - end # Cycle - + end # CycleBuilder + # This class defines a complete graph of size n. # This is easily done by using the base Graph # class and implemeting the minimum methods needed to # make it work. This is a good example to look # at for making one's own graph classes. - module Complete < Cycle + module CompleteBuilder < CycleBuilder def initialize(n) @size = n @edges = nil @@ -56,6 +56,6 @@ def edge?(u, v = nil) u, v = [u.source, v.target] if u.kind_of? Graphy::Arc vertex?(u) && vertex?(v) end + end # CompleteBuilder - end # Complete end # Graphy diff --git a/lib/graphy/directed_graph.rb b/lib/graphy/directed_graph.rb index 1b95a9b..a1b3a8e 100644 --- a/lib/graphy/directed_graph.rb +++ b/lib/graphy/directed_graph.rb @@ -1,61 +1,66 @@ module Graphy - module DirectedGraph - include Graph + module DirectedGraphBuilder + include GraphBuilder autoload :Algorithms, "graphy/directed_graph/algorithms" autoload :Distance, "graphy/directed_graph/distance" - #def self.included? base - ## FIXME: don't understand why but that's not triggered - ## by include Digraph so the [] class methods is never - ## added to the receiver :( - #puts "Digraph included" - #base.extend(ClassMethods) - #end - - ## using my helper: - #extends_host_with :ClassMethods - + # FIXME: DRY this snippet, I didn't find a clever way to + # to dit though + # TODO: well, extends_host_with do ... end would be cool, + # using Module.new.module_eval(&block) in the helper. + extends_host module ClassMethods def [](*a) - puts self self.new.from_array(*a) end end def initialize(*params) args = (params.pop if params.last.kind_of? Hash) || {} - args[:algorithmic_category] = DirectedGraph::Algorithms + args[:algorithmic_category] = DirectedGraphBuilder::Algorithms super *(params << args) end - end # DirectedGraph + end # DirectedGraphBuilder # DirectedGraph is just an alias for Digraph should one desire - Digraph = DirectedGraph + DigraphBuilder = DirectedGraphBuilder # This is a Digraph that allows for paral#lel edges, but does not - ## allow loops - #module DirectedPseudoGraph - #include DirectedGraph + # allow loops + module DirectedPseudoGraphBuilder + include DirectedGraphBuilder + extends_host + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end - #def initialize(*params) - #args = (params.pop if params.last.kind_of? Hash) || {} - #args[:parallel_edges] = true - #super *(params << args) - #end + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:parallel_edges] = true + super *(params << args) + end - #end # DirectedPseudoGraph + end # DirectedPseudoGraph - ## This is a Digraph that allows for parallel edges and loops - #module DirectedMultiGraph - #include DirectedPseudoGraph + # This is a Digraph that allows for parallel edges and loops + module DirectedMultiGraphBuilder + include DirectedPseudoGraphBuilder + extends_host + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end - #def initialize(*params) - #args = (params.pop if params.last.kind_of? Hash) || {} - #args[:loops] = true - #super *(params << args) - #end - #end # DirectedMultiGraph + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:loops] = true + super *(params << args) + end + end # DirectedMultiGraph end # Graphy diff --git a/lib/graphy/directed_graph/algorithms.rb b/lib/graphy/directed_graph/algorithms.rb index d75f172..d281324 100644 --- a/lib/graphy/directed_graph/algorithms.rb +++ b/lib/graphy/directed_graph/algorithms.rb @@ -9,9 +9,9 @@ module Graphy # DirectedPseudoGraph is a class that allows for parallel edges, and # DirectedMultiGraph is a class that allows for parallel edges and loops # as well. - module DirectedGraph + module DirectedGraphBuilder module Algorithms - + include Search include StrongComponents include Distance @@ -31,7 +31,7 @@ def directed? def edge_class @parallel_edges ? Graphy::MultiArc : Graphy::Arc end - + # Reverse all edges in a graph # # @return [DirectedGraph] a copy of the receiver for which the direction of edges has @@ -51,19 +51,19 @@ def oriented? re = e.map { |x| x.reverse} not e.any? { |x| re.include?(x)} end - + # Balanced is when the out edge count is equal to the in edge count # # @return [Boolean] def balanced?(v) out_degree(v) == in_degree(v) end - + # Returns out_degree(v) - in_degree(v) def delta(v) out_degree(v) - in_degree(v) end - + def community(node, direction) nodes, stack = {}, adjacent(node, :direction => direction) while n = stack.pop @@ -74,7 +74,7 @@ def community(node, direction) end nodes.values end - + def descendants(node) community(node, :out) end @@ -88,5 +88,5 @@ def family(node) end end # Algorithms - end # DirectedGraph + end # DirectedGraphBuilder end # Graphy diff --git a/lib/graphy/directed_graph/distance.rb b/lib/graphy/directed_graph/distance.rb index 97a7ba8..c142ae1 100644 --- a/lib/graphy/directed_graph/distance.rb +++ b/lib/graphy/directed_graph/distance.rb @@ -1,5 +1,5 @@ module Graphy - module DirectedGraph + module DirectedGraphBuilder module Distance # Shortest path from Jorgen Band-Jensen and Gregory Gutin, diff --git a/lib/graphy/dot.rb b/lib/graphy/dot.rb index 6cbf39a..a3cd78d 100644 --- a/lib/graphy/dot.rb +++ b/lib/graphy/dot.rb @@ -1,6 +1,10 @@ module Graphy - module Graph - + module Dot + + #FIXME: don't really understood where we stand with the dot generators. + # RDoc ships with a dot.rb which seems pretty efficient. + # Are these helpers still needed, and if not, how should we replace them? + # Return a DOT::DOTDigraph for directed graphs or a DOT::DOTSubgraph for an # undirected Graph. _params_ can contain any graph property specified in # rdot.rb. If an edge or vertex label is a kind of Hash then the keys @@ -29,7 +33,7 @@ def to_dot_graph (params = {}) end graph end - + # Output the dot format as a string def to_dot (params={}) to_dot_graph(params).to_s; end @@ -45,12 +49,12 @@ def dotty (params = {}, dotfile = 'graph.dot') def write_to_graphic_file (fmt='png', dotfile='graph') src = dotfile + '.dot' dot = dotfile + '.' + fmt - + File.open(src, 'w') {|f| f << self.to_dot << "\n"} - + system( "dot -T#{fmt} #{src} -o #{dot}" ) dot end - end # module Graph -end # module Graphy + end # Dot +end # module Graphy diff --git a/lib/graphy/edge.rb b/lib/graphy/edge.rb index 4952b3a..b1ad0bc 100644 --- a/lib/graphy/edge.rb +++ b/lib/graphy/edge.rb @@ -6,7 +6,7 @@ class Edge < Arc # Equality allows for the swapping of source and target def eql?(other) super or (self.class == other.class and target==other.source and source==other.target); end - + # Alias for eql? alias == eql? @@ -19,7 +19,7 @@ def <=>(rhs) [[source,target].max,[source,target].min] <=> [[rhs.source,rhs.target].max,[rhs.source,rhs.target].min] end - + # Edge[1,2].to_s == "(1=2 'label)" def to_s l = label ? " '#{label.to_s}'" : '' @@ -27,11 +27,11 @@ def to_s t = target.to_s "(#{[s,t].min}=#{[s,t].max}#{l})" end - + end - + class MultiEdge < Edge include ArcNumber end - + end diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb index 9991830..f0eec99 100644 --- a/lib/graphy/graph.rb +++ b/lib/graphy/graph.rb @@ -4,25 +4,21 @@ module Graphy # basic functions of a Graph class by using only functions in GraphAPI. # An actual implementation still needs to be done, as in Digraph or # UndirectedGraph. - module Graph + module GraphBuilder include Enumerable include Labels + include Dot - # usi#ng my helper: - #extends_host_with :ClassMethods - - #module ClassMethods - #def self.[](*a) - #puts self - #self.new.from_array(*a) - #end + #def self.[](*a) + #puts self + #self.new.from_array(*a) #end - include InitArray + # after the class->module transition, has been moved at implementation level, + # using a helper (extends_host) + # Create a graph. def initialize(*params) - puts "trapped" - puts self raise ArgumentError if params.any? do |p| !(p.is_a? Graphy::Graph or p.is_a? Array or p.is_a? Hash) end @@ -285,7 +281,8 @@ def degree(v) # Minimum in-degree def min_in_degree - to_a.map {|v| in_degree(v)}.min + return nil if to_a.empty? + to_a.map { |v| in_degree(v)}.min end # Minimum out-degree @@ -295,6 +292,8 @@ def min_out_degree # Minimum degree of all vertexes def min_degree + puts "---" + puts self [min_in_degree, min_out_degree].min end @@ -315,6 +314,7 @@ def max_degree # Regular def regular? + puts self min_degree == max_degree end diff --git a/lib/graphy/helpers.rb b/lib/graphy/helpers.rb deleted file mode 100644 index f3fb786..0000000 --- a/lib/graphy/helpers.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Graphy - module InitArray - # using my helper: - extends_host_with :ClassMethods - - module ClassMethods - def self.[](*a) - puts self - self.new.from_array(*a) - end - end - end -end diff --git a/lib/graphy/maximum_flow.rb b/lib/graphy/maximum_flow.rb index fe6f081..5afb7e2 100644 --- a/lib/graphy/maximum_flow.rb +++ b/lib/graphy/maximum_flow.rb @@ -1,7 +1,7 @@ module Graphy - class Network - include Digraph + include DigraphBuilder + attr_accessor :lower, :upper, :cost, :flow def residual(residual_capacity, cost_property, zero = 0) @@ -17,15 +17,15 @@ def residual(residual_capacity, cost_property, zero = 0) end r end - + def maximum_flow() eliminate_lower_bounds.maximum_flow_prime.restore_lower_bounds(self); end - - private: + + private def eliminate_lower_bounds no_lower_bounds = Digraph.new(self) if self.upper.kind_of? Proc then - no_lower_bounds.upper = Proc.new {|e| self.upper.call(e) - property(e,self.lower) } + no_lower_bounds.upper = Proc.new {|e| self.upper.call(e) - property(e,self.lower) } else no_lower_bounds.edges.each {|e| no_lower_bounds[e][self.upper] -= property(e,self.lower)} end @@ -33,19 +33,16 @@ def eliminate_lower_bounds end def restore_lower_bounds(src) - src.edges.each do {|e| (src.flow ? src[e][src.flow] : src[e]) = property(e,self.flow) + src.property(e,self.lower) } + src.edges.each { |e| (src.flow ? src[e][src.flow] : src[e]) = property(e,self.flow) + src.property(e,self.lower) } src end - + def maximum_flow_prime end - - end - - module Graph + end # Network + module GraphBuilder module MaximumFlow - # Maximum flow, it returns an array with the maximum flow and a hash of flow per edge # Currently a highly inefficient implementation, FIXME, This should use Goldberg and Tarjan's method. def maximum_flow(s, t, capacity = nil, zero = 0) @@ -69,10 +66,10 @@ def maximum_flow(s, t, capacity = nil, zero = 0) end total += amt end - + [total, flow] end end # MaximumFlow - end # Graph + end # GraphBuilder end # Graphy diff --git a/lib/graphy/search.rb b/lib/graphy/search.rb index 28dc086..fdbe708 100644 --- a/lib/graphy/search.rb +++ b/lib/graphy/search.rb @@ -64,7 +64,7 @@ def bfs(options={}, &block) graphy_search_helper(:shift, options, &block); end # See options for bfs method def dfs(options={}, &block) graphy_search_helper(:pop, options, &block); end - + # Routine to compute a spanning forest for the given search method # Returns two values, first is a hash of predecessors and second an array of root nodes def spanning_forest(start, routine) @@ -75,13 +75,13 @@ def spanning_forest(start, routine) send routine, :start => start, :tree_edge => te, :root_vertex => rv [predecessor, roots] end - + # Return the dfs spanning forest for the given start node, see spanning_forest def dfs_spanning_forest(start) spanning_forest(start, :dfs); end - + # Return the bfs spanning forest for the given start node, see spanning_forest def bfs_spanning_forest(start) spanning_forest(start, :bfs); end - + # Returns a hash of predecessors in a tree rooted at the start node. If this is a connected graph # then it will be a spanning tree and contain all vertices. An easier way to tell if it's a spanning tree is to # use a spanning_forest call and check if there is a single root node. @@ -93,19 +93,19 @@ def tree_from_vertex(start, routine) send routine, :start => start, :tree_edge => te, :root_vertex => rv predecessor end - + # Returns a hash of predecessors for the depth first search tree rooted at the given node def dfs_tree_from_vertex(start) tree_from_vertex(start, :dfs); end - + # Returns a hash of predecessors for the depth first search tree rooted at the given node def bfs_tree_from_vertex(start) tree_from_vertex(start, :bfs); end - + # An inner class used for greater efficiency in lexicograph_bfs # # Original desgn taken from Golumbic's, "Algorithmic Graph Theory and # Perfect Graphs" pg, 87-89 class LexicographicQueue - + # Called with the initial values (array) def initialize(values) @node = Struct.new(:back, :forward, :data) @@ -114,288 +114,287 @@ def initialize(values) @tail = @node.new(nil, nil, Array.new(values)) @tail.instance_eval { @hash = (@@cnt+=1) } values.each {|a| @set[a] = @tail} - end - - # Pop an entry with maximum lexical value from queue - def pop() - return nil unless @tail - value = @tail[:data].pop - @tail = @tail[:forward] while @tail and @tail[:data].size == 0 - @set.delete(value); value - end - - # Increase lexical value of given values (array) - def add_lexeme(values) - fix = {} - values.select {|v| @set[v]}.each do |w| - sw = @set[w] - if fix[sw] - s_prime = sw[:back] - else - s_prime = @node.new(sw[:back], sw, []) - s_prime.instance_eval { @hash = (@@cnt+=1) } - @tail = s_prime if @tail == sw - sw[:back][:forward] = s_prime if sw[:back] - sw[:back] = s_prime - fix[sw] = true - end - s_prime[:data] << w - sw[:data].delete(w) - @set[w] = s_prime - end - fix.keys.select {|n| n[:data].size == 0}.each do |e| - e[:forward][:back] = e[:back] if e[:forward] - e[:back][:forward] = e[:forward] if e[:back] - end - end - end - - # Lexicographic breadth-first search, the usual queue of vertices - # is replaced by a queue of unordered subsets of the vertices, - # which is sometimes refined but never reordered. - # - # Originally developed by Rose, Tarjan, and Leuker, "Algorithmic - # aspects of vertex elimination on graphs", SIAM J. Comput. 5, 266-283 - # MR53 #12077 - # - # Implementation taken from Golumbic's, "Algorithmic Graph Theory and - # Perfect Graphs" pg, 84-90 - def lexicograph_bfs(&block) - lex_q = Graphy::Graph::Search::LexicographicQueue.new(vertices) - result = [] - num_vertices.times do - v = lex_q.pop - result.unshift(v) - lex_q.add_lexeme(adjacent(v)) + + # Pop an entry with maximum lexical value from queue + def pop() + return nil unless @tail + value = @tail[:data].pop + @tail = @tail[:forward] while @tail and @tail[:data].size == 0 + @set.delete(value); value + end + + # Increase lexical value of given values (array) + def add_lexeme(values) + fix = {} + values.select {|v| @set[v]}.each do |w| + sw = @set[w] + if fix[sw] + s_prime = sw[:back] + else + s_prime = @node.new(sw[:back], sw, []) + s_prime.instance_eval { @hash = (@@cnt+=1) } + @tail = s_prime if @tail == sw + sw[:back][:forward] = s_prime if sw[:back] + sw[:back] = s_prime + fix[sw] = true + end + s_prime[:data] << w + sw[:data].delete(w) + @set[w] = s_prime + end + fix.keys.select {|n| n[:data].size == 0}.each do |e| + e[:forward][:back] = e[:back] if e[:forward] + e[:back][:forward] = e[:forward] if e[:back] end - result.each {|r| block.call(r)} if block - result + end + + end + + # Lexicographic breadth-first search, the usual queue of vertices + # is replaced by a queue of unordered subsets of the vertices, + # which is sometimes refined but never reordered. + # + # Originally developed by Rose, Tarjan, and Leuker, "Algorithmic + # aspects of vertex elimination on graphs", SIAM J. Comput. 5, 266-283 + # MR53 #12077 + # + # Implementation taken from Golumbic's, "Algorithmic Graph Theory and + # Perfect Graphs" pg, 84-90 + def lexicograph_bfs(&block) + lex_q = Graphy::Graph::Search::LexicographicQueue.new(vertices) + result = [] + num_vertices.times do + v = lex_q.pop + result.unshift(v) + lex_q.add_lexeme(adjacent(v)) end + result.each {|r| block.call(r)} if block + result + end - # A* Heuristic best first search - # - # start is the starting vertex for the search - # - # func is a Proc that when passed a vertex returns the heuristic - # weight of sending the path through that node. It must always - # be equal to or less than the true cost - # - # options are mostly callbacks passed in as a hash, the default block is - # :discover_vertex and weight is assumed to be the label for the Arc. - # The following options are valid, anything else is ignored. - # - # * :weight => can be a Proc, or anything else is accessed using the [] for the - # the label or it defaults to using - # the value stored in the label for the Arc. If it is a Proc it will - # pass the edge to the proc and use the resulting value. - # * :discover_vertex => Proc invoked when a vertex is first discovered - # and is added to the open list. - # * :examine_vertex => Proc invoked when a vertex is popped from the - # queue (i.e., it has the lowest cost on the open list). - # * :examine_edge => Proc invoked on each out-edge of a vertex - # immediately after it is examined. - # * :edge_relaxed => Proc invoked on edge (u,v) if d[u] + w(u,v) < d[v]. - # * :edge_not_relaxed=> Proc invoked if the edge is not relaxed (see above). - # * :black_target => Proc invoked when a vertex that is on the closed - # list is "rediscovered" via a more efficient path, and is re-added - # to the OPEN list. - # * :finish_vertex => Proc invoked on a vertex when it is added to the - # closed list, which happens after all of its out edges have been - # examined. - # - # Returns array of nodes in path, or calls block on all nodes, - # upon failure returns nil - # - # Can also be called like astar_examine_edge {|e| ... } or - # astar_edge_relaxed {|e| ... } for any of the callbacks - # - # The criteria for expanding a vertex on the open list is that it has the - # lowest f(v) = g(v) + h(v) value of all vertices on open. - # - # The time complexity of A* depends on the heuristic. It is exponential - # in the worst case, but is polynomial when the heuristic function h - # meets the following condition: |h(x) - h*(x)| < O(log h*(x)) where h* - # is the optimal heuristic, i.e. the exact cost to get from x to the goal. - # - # Also see: http://en.wikipedia.org/wiki/A-star_search_algorithm - # - def astar(start, goal, func, options, &block) - options.instance_eval "def handle_callback(sym,u) self[sym].call(u) if self[sym]; end" - - # Initialize - d = { start => 0 } - - color = {start => :gray} # Open is :gray, Closed is :black - parent = Hash.new {|k| parent[k] = k} - f = {start => func.call(start)} - queue = PriorityQueue.new.push(start,f[start]) - block.call(start) if block - - # Process queue - until queue.empty? - u,dummy = queue.delete_min - options.handle_callback(:examine_vertex, u) - - # Unravel solution if goal is reached. - if u == goal - solution = [goal] - while u != start - solution << parent[u]; u = parent[u] - end - return solution.reverse + # A* Heuristic best first search + # + # start is the starting vertex for the search + # + # func is a Proc that when passed a vertex returns the heuristic + # weight of sending the path through that node. It must always + # be equal to or less than the true cost + # + # options are mostly callbacks passed in as a hash, the default block is + # :discover_vertex and weight is assumed to be the label for the Arc. + # The following options are valid, anything else is ignored. + # + # * :weight => can be a Proc, or anything else is accessed using the [] for the + # the label or it defaults to using + # the value stored in the label for the Arc. If it is a Proc it will + # pass the edge to the proc and use the resulting value. + # * :discover_vertex => Proc invoked when a vertex is first discovered + # and is added to the open list. + # * :examine_vertex => Proc invoked when a vertex is popped from the + # queue (i.e., it has the lowest cost on the open list). + # * :examine_edge => Proc invoked on each out-edge of a vertex + # immediately after it is examined. + # * :edge_relaxed => Proc invoked on edge (u,v) if d[u] + w(u,v) < d[v]. + # * :edge_not_relaxed=> Proc invoked if the edge is not relaxed (see above). + # * :black_target => Proc invoked when a vertex that is on the closed + # list is "rediscovered" via a more efficient path, and is re-added + # to the OPEN list. + # * :finish_vertex => Proc invoked on a vertex when it is added to the + # closed list, which happens after all of its out edges have been + # examined. + # + # Returns array of nodes in path, or calls block on all nodes, + # upon failure returns nil + # + # Can also be called like astar_examine_edge {|e| ... } or + # astar_edge_relaxed {|e| ... } for any of the callbacks + # + # The criteria for expanding a vertex on the open list is that it has the + # lowest f(v) = g(v) + h(v) value of all vertices on open. + # + # The time complexity of A* depends on the heuristic. It is exponential + # in the worst case, but is polynomial when the heuristic function h + # meets the following condition: |h(x) - h*(x)| < O(log h*(x)) where h* + # is the optimal heuristic, i.e. the exact cost to get from x to the goal. + # + # Also see: http://en.wikipedia.org/wiki/A-star_search_algorithm + # + def astar(start, goal, func, options, &block) + options.instance_eval "def handle_callback(sym,u) self[sym].call(u) if self[sym]; end" + + # Initialize + d = { start => 0 } + + color = {start => :gray} # Open is :gray, Closed is :black + parent = Hash.new {|k| parent[k] = k} + f = {start => func.call(start)} + queue = PriorityQueue.new.push(start,f[start]) + block.call(start) if block + + # Process queue + until queue.empty? + u,dummy = queue.delete_min + options.handle_callback(:examine_vertex, u) + + # Unravel solution if goal is reached. + if u == goal + solution = [goal] + while u != start + solution << parent[u]; u = parent[u] end + return solution.reverse + end - adjacent(u, :type => :edges).each do |e| - v = e.source == u ? e.target : e.source - options.handle_callback(:examine_edge, e) - w = cost(e, options[:weight]) - raise ArgumentError unless w - if d[v].nil? or (w + d[u]) < d[v] - options.handle_callback(:edge_relaxed, e) - d[v] = w + d[u] - f[v] = d[v] + func.call(v) - parent[v] = u - unless color[v] == :gray - options.handle_callback(:black_target, v) if color[v] == :black - color[v] = :gray - options.handle_callback(:discover_vertex, v) - queue.push v, f[v] - block.call(v) if block - end - else - options.handle_callback(:edge_not_relaxed, e) + adjacent(u, :type => :edges).each do |e| + v = e.source == u ? e.target : e.source + options.handle_callback(:examine_edge, e) + w = cost(e, options[:weight]) + raise ArgumentError unless w + if d[v].nil? or (w + d[u]) < d[v] + options.handle_callback(:edge_relaxed, e) + d[v] = w + d[u] + f[v] = d[v] + func.call(v) + parent[v] = u + unless color[v] == :gray + options.handle_callback(:black_target, v) if color[v] == :black + color[v] = :gray + options.handle_callback(:discover_vertex, v) + queue.push v, f[v] + block.call(v) if block end - end # adjacent(u) - color[u] = :black - options.handle_callback(:finish_vertex,u) - end # queue.empty? - - nil # failure, on fall through - - end # astar - - # Best first has all the same options as astar with func set to h(v) = 0. - # There is an additional option zero which should be defined to zero - # for the operation '+' on the objects used in the computation of cost. - # The parameter zero defaults to 0. - def best_first(start, goal, options, zero=0, &block) - func = Proc.new {|v| zero} - astar(start, goal, func, options, &block) - end + else + options.handle_callback(:edge_not_relaxed, e) + end + end # adjacent(u) + color[u] = :black + options.handle_callback(:finish_vertex,u) + end # queue.empty? - alias_method :pre_search_method_missing, :method_missing # :nodoc: - def method_missing(sym,*args, &block) # :nodoc: - m1=/^dfs_(\w+)$/.match(sym.to_s) - dfs((args[0] || {}).merge({m1.captures[0].to_sym => block})) if m1 - m2=/^bfs_(\w+)$/.match(sym.to_s) - bfs((args[0] || {}).merge({m2.captures[0].to_sym => block})) if m2 - pre_search_method_missing(sym, *args, &block) unless m1 or m2 - end + nil # failure, on fall through + + end # astar - private - - def graphy_search_helper(op, options={}, &block) # :nodoc: - return nil if size == 0 - result = [] - # Create options hash that handles callbacks - options = {:enter_vertex => block, :start => to_a[0]}.merge(options) - options.instance_eval "def handle_vertex(sym,u) self[sym].call(u) if self[sym]; end" - options.instance_eval "def handle_edge(sym,e) self[sym].call(e) if self[sym]; end" - # Create waiting list that is a queue or stack depending on op specified. - # First entry is the start vertex. - waiting = [options[:start]] - waiting.instance_eval "def next() #{op.to_s}; end" - # Create color map with all set to unvisited except for start vertex - # will be set to waiting - color_map = vertices.inject({}) {|a,v| a[v] = :unvisited; a} - color_map.merge!(waiting[0] => :waiting) - options.handle_vertex(:start_vertex, waiting[0]) - options.handle_vertex(:root_vertex, waiting[0]) - # Perform the actual search until nothing is waiting + # Best first has all the same options as astar with func set to h(v) = 0. + # There is an additional option zero which should be defined to zero + # for the operation '+' on the objects used in the computation of cost. + # The parameter zero defaults to 0. + def best_first(start, goal, options, zero=0, &block) + func = Proc.new {|v| zero} + astar(start, goal, func, options, &block) + end + + alias_method :pre_search_method_missing, :method_missing # :nodoc: + def method_missing(sym,*args, &block) # :nodoc: + m1=/^dfs_(\w+)$/.match(sym.to_s) + dfs((args[0] || {}).merge({m1.captures[0].to_sym => block})) if m1 + m2=/^bfs_(\w+)$/.match(sym.to_s) + bfs((args[0] || {}).merge({m2.captures[0].to_sym => block})) if m2 + pre_search_method_missing(sym, *args, &block) unless m1 or m2 + end + + private + + def graphy_search_helper(op, options={}, &block) # :nodoc: + return nil if size == 0 + result = [] + # Create options hash that handles callbacks + options = {:enter_vertex => block, :start => to_a[0]}.merge(options) + options.instance_eval "def handle_vertex(sym,u) self[sym].call(u) if self[sym]; end" + options.instance_eval "def handle_edge(sym,e) self[sym].call(e) if self[sym]; end" + # Create waiting list that is a queue or stack depending on op specified. + # First entry is the start vertex. + waiting = [options[:start]] + waiting.instance_eval "def next() #{op.to_s}; end" + # Create color map with all set to unvisited except for start vertex + # will be set to waiting + color_map = vertices.inject({}) {|a,v| a[v] = :unvisited; a} + color_map.merge!(waiting[0] => :waiting) + options.handle_vertex(:start_vertex, waiting[0]) + options.handle_vertex(:root_vertex, waiting[0]) + # Perform the actual search until nothing is waiting + until waiting.empty? + # Loop till the search iterator exhausts the waiting list + visited_edges={} # This prevents retraversing edges in undirected graphs until waiting.empty? - # Loop till the search iterator exhausts the waiting list - visited_edges={} # This prevents retraversing edges in undirected graphs - until waiting.empty? - graphy_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop) - end - # Waiting list is exhausted, see if a new root vertex is available - u=color_map.detect {|key,value| value == :unvisited} - waiting.push(u[0]) if u - options.handle_vertex(:root_vertex, u[0]) if u - end; result - end + graphy_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop) + end + # Waiting list is exhausted, see if a new root vertex is available + u=color_map.detect {|key,value| value == :unvisited} + waiting.push(u[0]) if u + options.handle_vertex(:root_vertex, u[0]) if u + end; result + end - def graphy_search_iteration(options, waiting, color_map, visited_edges, result, recursive=false) # :nodoc: - # Get the next waiting vertex in the list - u = waiting.next - options.handle_vertex(:enter_vertex,u) - result << u - # Examine all adjacent outgoing edges, not previously traversed - adj_proc = options[:adjacent] || self.method(:adjacent).to_proc - adj_proc.call(u,:type => :edges, :direction => :out).reject {|w| visited_edges[w]}.each do |e| - e = e.reverse unless directed? or e.source == u # Preserves directionality where required - v = e.target - options.handle_edge(:examine_edge, e) - visited_edges[e]=true - case color_map[v] - # If it's unvisited it goes into the waiting list - when :unvisited - options.handle_edge(:tree_edge, e) - color_map[v] = :waiting - waiting.push(v) - # If it's recursive (i.e. dfs) then call self - graphy_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive - when :waiting - options.handle_edge(:back_edge, e) - else - options.handle_edge(:forward_edge, e) - end + def graphy_search_iteration(options, waiting, color_map, visited_edges, result, recursive=false) # :nodoc: + # Get the next waiting vertex in the list + u = waiting.next + options.handle_vertex(:enter_vertex,u) + result << u + # Examine all adjacent outgoing edges, not previously traversed + adj_proc = options[:adjacent] || self.method(:adjacent).to_proc + adj_proc.call(u,:type => :edges, :direction => :out).reject {|w| visited_edges[w]}.each do |e| + e = e.reverse unless directed? or e.source == u # Preserves directionality where required + v = e.target + options.handle_edge(:examine_edge, e) + visited_edges[e]=true + case color_map[v] + # If it's unvisited it goes into the waiting list + when :unvisited + options.handle_edge(:tree_edge, e) + color_map[v] = :waiting + waiting.push(v) + # If it's recursive (i.e. dfs) then call self + graphy_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive + when :waiting + options.handle_edge(:back_edge, e) + else + options.handle_edge(:forward_edge, e) end - # Finished with this vertex - options.handle_vertex(:exit_vertex, u) - color_map[u] = :visited - end - - public - # Topological Sort Iterator - # - # The topological sort algorithm creates a linear ordering of the vertices - # such that if edge (u,v) appears in the graph, then u comes before v in - # the ordering. The graph must be a directed acyclic graph (DAG). - # - # The iterator can also be applied to undirected graph or to a DG graph - # which contains a cycle. In this case, the Iterator does not reach all - # vertices. The implementation of acyclic? and cyclic? uses this fact. - # - # Can be called with a block as a standard Ruby iterator, or it can - # be used directly as it will return the result as an Array - def topsort(start = nil, &block) - result = [] - go = true - back = Proc.new {|e| go = false } - push = Proc.new {|v| result.unshift(v) if go} - start ||= vertices[0] - dfs({:exit_vertex => push, :back_edge => back, :start => start}) - result.each {|v| block.call(v)} if block; result - end - - # Does a top sort, but trudges forward if a cycle occurs. Use with caution. - def sort(start = nil, &block) - result = [] - push = Proc.new {|v| result.unshift(v)} - start ||= vertices[0] - dfs({:exit_vertex => push, :start => start}) - result.each {|v| block.call(v)} if block; result end + # Finished with this vertex + options.handle_vertex(:exit_vertex, u) + color_map[u] = :visited + end + + public + # Topological Sort Iterator + # + # The topological sort algorithm creates a linear ordering of the vertices + # such that if edge (u,v) appears in the graph, then u comes before v in + # the ordering. The graph must be a directed acyclic graph (DAG). + # + # The iterator can also be applied to undirected graph or to a DG graph + # which contains a cycle. In this case, the Iterator does not reach all + # vertices. The implementation of acyclic? and cyclic? uses this fact. + # + # Can be called with a block as a standard Ruby iterator, or it can + # be used directly as it will return the result as an Array + def topsort(start = nil, &block) + result = [] + go = true + back = Proc.new {|e| go = false } + push = Proc.new {|v| result.unshift(v) if go} + start ||= vertices[0] + dfs({:exit_vertex => push, :back_edge => back, :start => start}) + result.each {|v| block.call(v)} if block; result + end + + # Does a top sort, but trudges forward if a cycle occurs. Use with caution. + def sort(start = nil, &block) + result = [] + push = Proc.new {|v| result.unshift(v)} + start ||= vertices[0] + dfs({:exit_vertex => push, :start => start}) + result.each {|v| block.call(v)} if block; result + end - # Returns true if a graph contains no cycles, false otherwise - def acyclic?() topsort.size == size; end + # Returns true if a graph contains no cycles, false otherwise + def acyclic?() topsort.size == size; end - # Returns false if a graph contains no cycles, true otherwise - def cyclic?() not acyclic?; end + # Returns false if a graph contains no cycles, true otherwise + def cyclic?() not acyclic?; end - end # Search end # Graphy diff --git a/lib/graphy/strong_components.rb b/lib/graphy/strong_components.rb index 14e8a99..3a201e4 100644 --- a/lib/graphy/strong_components.rb +++ b/lib/graphy/strong_components.rb @@ -15,7 +15,6 @@ module StrongComponents # from each other. # def strong_components - dfs_num = 0 stack = []; result = []; root = {}; comp = {}; number = {} @@ -49,7 +48,7 @@ def strong_components dfs({:enter_vertex => enter, :exit_vertex => exit}); result end # strong_components - + # Returns a condensation graph of the strongly connected components # Each node is an array of nodes from the original graph def condensation diff --git a/lib/graphy/undirected_graph.rb b/lib/graphy/undirected_graph.rb index 1c34eea..c4f7688 100644 --- a/lib/graphy/undirected_graph.rb +++ b/lib/graphy/undirected_graph.rb @@ -1,36 +1,57 @@ module Graphy - module UndirectedGraph - include Graph + module UndirectedGraphBuilder autoload :Algorithms, "graphy/undirected_graph/algorithms" - - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:algorithmic_category] = UndirectedGraph::Algorithms - super *(params << args) - end - end - - # This is a Digraph that allows for parallel edges, but does not - # allow loops - module UndirectedPseudoGraph - include UndirectedGraph - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:parallel_edges] = true - super *(params << args) - end - end - - # This is a Digraph that allows for parallel edges and loops - module UndirectedMultiGraph - UndirectedPseudoGraph - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:loops] = true - super *(params << args) - end - end - + + include GraphBuilder + extends_host + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:algorithmic_category] = UndirectedGraphBuilder::Algorithms + super *(params << args) + end + end + + # This is a Digraph that allows for parallel edges, but does not + # allow loops + module UndirectedPseudoGraphBuilder + include UndirectedGraphBuilder + extends_host + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:parallel_edges] = true + super *(params << args) + end + end + + # This is a Digraph that allows for parallel edges and loops + module UndirectedMultiGraphBuilder + UndirectedPseudoGraphBuilder + extends_host + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:loops] = true + super *(params << args) + end + end + end # Graphy diff --git a/lib/graphy/undirected_graph/algorithms.rb b/lib/graphy/undirected_graph/algorithms.rb index 9e3358e..28fd339 100644 --- a/lib/graphy/undirected_graph/algorithms.rb +++ b/lib/graphy/undirected_graph/algorithms.rb @@ -1,17 +1,17 @@ module Graphy - module UndirectedGraph + module UndirectedGraphBuilder module Algorithms - + include Search include Biconnected include Comparability # UndirectedGraph is by definition undirected, always returns false def directed?() false; end - + # Redefine degree (default was sum) def degree(v) in_degree(v); end - + # A vertex of an undirected graph is balanced by definition def balanced?(v) true; end @@ -26,7 +26,7 @@ def remove_edge!(u, v=nil) super(u.reverse) unless u.source == u.target super(u) end - + # A triangulated graph is an undirected perfect graph that every cycle of length greater than # three possesses a chord. They have also been called chordal, rigid circuit, monotone transitive, # and perfect elimination graphs. @@ -46,28 +46,28 @@ def triangulated? end true end - + def chromatic_number return triangulated_chromatic_number if triangulated? raise NotImplementedError end - + # An interval graph can have its vertices into one-to-one # correspondence with a set of intervals F of a linearly ordered # set (like the real line) such that two vertices are connected # by an edge of G if and only if their corresponding intervals # have nonempty intersection. def interval?() triangulated? and complement.comparability?; end - + # A permutation diagram consists of n points on each of two parallel # lines and n straight line segments matchin the points. The intersection # graph of the line segments is called a permutation graph. def permutation?() comparability? and complement.comparability?; end - + # An undirected graph is defined to be split if there is a partition # V = S + K of its vertex set into a stable set S and a complete set K. def split?() triangulated? and complement.triangulated?; end - + private # Implementation taken from Golumbic's, "Algorithmic Graph Theory and # Perfect Graphs" pg. 99 @@ -84,7 +84,7 @@ def triangulated_chromatic_number end end; chi end - + end # UndirectedGraphAlgorithms - end # UndirectedGraph + end # UndirectedGraphBuilder end # Graphy From b8074f9de223822f8ad085412197c1b46015328a Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 15:25:24 +0200 Subject: [PATCH 12/44] reordering autoloads --- lib/graphy.rb | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/graphy.rb b/lib/graphy.rb index 8cc61f3..7dacc2f 100644 --- a/lib/graphy.rb +++ b/lib/graphy.rb @@ -29,12 +29,16 @@ require 'set' module Graphy - # ruby stdlib extensions - require 'ext/ext' - # ruby 1.8.x/1.9.x compatibility - require 'graphy/ruby_compatibility' - # Graphy internals: graph builders and additionnal behaviors + autoload :DirectedGraphBuilder, 'graphy/directed_graph' + autoload :DigraphBuilder, 'graphy/directed_graph' + autoload :DirectedPseudoGraphBuilder, 'graphy/directed_graph' + autoload :DirectedMultiGraphBuilder, 'graphy/directed_graph' + + autoload :UndirectedGraphBuilder, 'graphy/undirected_graph' + autoload :UndirectedPseudoGraphBuilder, 'graphy/undirected_graph' + autoload :UndirectedMultiGraphBuilder, 'graphy/undirected_graph' + autoload :AdjacencyGraphBuilder, 'graphy/adjacency_graph' autoload :Arc, 'graphy/arc' autoload :ArcNumber, 'graphy/arc_number' @@ -43,11 +47,6 @@ module Graphy autoload :Common, 'graphy/common' autoload :Comparability, 'graphy/comparability' - autoload :DirectedGraphBuilder, 'graphy/directed_graph' - autoload :DigraphBuilder, 'graphy/directed_graph' - autoload :DirectedPseudoGraphBuilder, 'graphy/directed_graph' - autoload :DirectedMultiGraphBuilder, 'graphy/directed_graph' - autoload :Dot, 'graphy/dot' autoload :Edge, 'graphy/edge' autoload :GraphBuilder, 'graphy/graph' @@ -58,10 +57,6 @@ module Graphy autoload :Search, 'graphy/search' autoload :StrongComponents, 'graphy/strong_components' - autoload :UndirectedGraphBuilder, 'graphy/undirected_graph' - autoload :UndirectedPseudoGraphBuilder, 'graphy/undirected_graph' - autoload :UndirectedMultiGraphBuilder, 'graphy/undirected_graph' - # Graphy classes autoload :AdjacencyGraph, 'graphy/classes/graph_classes' autoload :DirectedGraph, 'graphy/classes/graph_classes' @@ -71,6 +66,11 @@ module Graphy autoload :UndirectedGraph, 'graphy/classes/graph_classes' autoload :UndirectedPseudoGraph, 'graphy/classes/graph_classes' autoload :UndirectedMultiGraph, 'graphy/classes/graph_classes' + + # ruby stdlib extensions + require 'ext/ext' + # ruby 1.8.x/1.9.x compatibility + require 'graphy/ruby_compatibility' end # Vendored libraries From fa6fb89bfa6d51ba178f0ad9c51f69bb288aba38 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 15:38:54 +0200 Subject: [PATCH 13/44] everything in the same place --- lib/graphy.rb | 2 +- lib/{ext => graphy}/ext.rb | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/{ext => graphy}/ext.rb (100%) diff --git a/lib/graphy.rb b/lib/graphy.rb index 7dacc2f..18f45e1 100644 --- a/lib/graphy.rb +++ b/lib/graphy.rb @@ -68,7 +68,7 @@ module Graphy autoload :UndirectedMultiGraph, 'graphy/classes/graph_classes' # ruby stdlib extensions - require 'ext/ext' + require 'graphy/ext' # ruby 1.8.x/1.9.x compatibility require 'graphy/ruby_compatibility' end diff --git a/lib/ext/ext.rb b/lib/graphy/ext.rb similarity index 100% rename from lib/ext/ext.rb rename to lib/graphy/ext.rb From 7674d6cef00c8321c7b61a2a1f474da2d0315a5f Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 15:40:15 +0200 Subject: [PATCH 14/44] a typo --- graphy.gemspec | 2 +- lib/graphy/classes/graph_classes.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/graphy.gemspec b/graphy.gemspec index 3593c12..fc596fd 100644 --- a/graphy.gemspec +++ b/graphy.gemspec @@ -43,7 +43,6 @@ Graph algorithms currently provided are: "examples/visualize.jpg", "examples/visualize.rb", "graphy.gemspec", - "lib/ext/ext.rb", "lib/graphy.rb", "lib/graphy/adjacency_graph.rb", "lib/graphy/arc.rb", @@ -58,6 +57,7 @@ Graph algorithms currently provided are: "lib/graphy/directed_graph/distance.rb", "lib/graphy/dot.rb", "lib/graphy/edge.rb", + "lib/graphy/ext.rb", "lib/graphy/graph.rb", "lib/graphy/graph_api.rb", "lib/graphy/labels.rb", diff --git a/lib/graphy/classes/graph_classes.rb b/lib/graphy/classes/graph_classes.rb index 887c8d7..16c7ec3 100644 --- a/lib/graphy/classes/graph_classes.rb +++ b/lib/graphy/classes/graph_classes.rb @@ -7,5 +7,5 @@ class DirectedPseudoGraph; include DirectedPseudoGraphBuilder; end class DirectedMultiGraph; include DirectedMultiGraphBuilder; end class UndirectedGraph; include UndirectedGraphBuilder; end class UndirectedPseudoGraph; include UndirectedPseudoGraphBuilder; end - class UndirectedMultiGraph; include UndirectedMultiraphBuilder; end + class UndirectedMultiGraph; include UndirectedMultiGraphBuilder; end end From efdc80abc581a6e8bc4d30d7e41714f40e89dd5e Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 15:41:33 +0200 Subject: [PATCH 15/44] classes up and running, eventually --- lib/graphy/graph.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb index f0eec99..debcea0 100644 --- a/lib/graphy/graph.rb +++ b/lib/graphy/graph.rb @@ -26,8 +26,8 @@ def initialize(*params) class << self self end.module_eval do - include(args[:implementation] ? args[:implementation] : AdjacencyGraph) - include(args[:algorithmic_category] ? args[:algorithmic_category] : Digraph ) + include(args[:implementation] ? args[:implementation] : AdjacencyGraphBuilder) + include(args[:algorithmic_category] ? args[:algorithmic_category] : DigraphBuilder ) include GraphAPI end implementation_initialize(*params) From 7cf78ab2683b0e9cefe64443163a443e22d412a9 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 17:45:51 +0200 Subject: [PATCH 16/44] md extension --- CREDITS.markdown => CREDITS.md | 0 README.markdown => README.md | 7 ++---- Rakefile | 5 +++++ TODO.markdown => TODO.md | 0 lib/graphy.rb | 8 ++++--- lib/graphy/directed_graph.rb | 7 ++++++ lib/graphy/graph.rb | 40 +++++++++++++++++++++++++--------- 7 files changed, 49 insertions(+), 18 deletions(-) rename CREDITS.markdown => CREDITS.md (100%) rename README.markdown => README.md (96%) rename TODO.markdown => TODO.md (100%) diff --git a/CREDITS.markdown b/CREDITS.md similarity index 100% rename from CREDITS.markdown rename to CREDITS.md diff --git a/README.markdown b/README.md similarity index 96% rename from README.markdown rename to README.md index 1fcdbc6..f87c0b1 100644 --- a/README.markdown +++ b/README.md @@ -1,7 +1,4 @@ -Graphy: A Graph Theory Library for Ruby -======================================= - -**A framework for graph data structures and algorithms.** +# Graphy. A framework for graph theory, graph data structures and associated algorithms. Graph algorithms currently provided are: @@ -93,7 +90,7 @@ A few properties of the graph we just created: >> dg.edge?(4,2) => false >> dg.vertices - => [5, 6, 1, 2, 3, 4] + => [1, 2, 3, 4, 5, 6] Every object could be a vertex, even the class object `Object`: diff --git a/Rakefile b/Rakefile index 02da44c..4303dc2 100644 --- a/Rakefile +++ b/Rakefile @@ -53,3 +53,8 @@ end #task :spec => :check_dependencies #task :default => :spec + +YARD::Rake::YardocTask.new do |t| + t.files = ['lib/**/*.rb', 'README.markdown', 'TODO.markdown', 'CREDITS.markdown', 'LICENSE', 'VERSION'] + #t.options = ['--any', '--extra', '--opts'] # optional +end diff --git a/TODO.markdown b/TODO.md similarity index 100% rename from TODO.markdown rename to TODO.md diff --git a/lib/graphy.rb b/lib/graphy.rb index 18f45e1..3ce70d3 100644 --- a/lib/graphy.rb +++ b/lib/graphy.rb @@ -30,6 +30,11 @@ module Graphy # Graphy internals: graph builders and additionnal behaviors + autoload :GraphAPI, 'graphy/graph_api' + + autoload :GraphBuilder, 'graphy/graph' + autoload :AdjacencyGraphBuilder, 'graphy/adjacency_graph' + autoload :DirectedGraphBuilder, 'graphy/directed_graph' autoload :DigraphBuilder, 'graphy/directed_graph' autoload :DirectedPseudoGraphBuilder, 'graphy/directed_graph' @@ -39,7 +44,6 @@ module Graphy autoload :UndirectedPseudoGraphBuilder, 'graphy/undirected_graph' autoload :UndirectedMultiGraphBuilder, 'graphy/undirected_graph' - autoload :AdjacencyGraphBuilder, 'graphy/adjacency_graph' autoload :Arc, 'graphy/arc' autoload :ArcNumber, 'graphy/arc_number' autoload :Biconnected, 'graphy/biconnected' @@ -49,8 +53,6 @@ module Graphy autoload :Dot, 'graphy/dot' autoload :Edge, 'graphy/edge' - autoload :GraphBuilder, 'graphy/graph' - autoload :GraphAPI, 'graphy/graph_api' autoload :Labels, 'graphy/labels' autoload :MaximumFlow, 'graphy/maximum_flow' #autoload :Rdot, 'graphy/dot' diff --git a/lib/graphy/directed_graph.rb b/lib/graphy/directed_graph.rb index a1b3a8e..39cabe6 100644 --- a/lib/graphy/directed_graph.rb +++ b/lib/graphy/directed_graph.rb @@ -18,6 +18,13 @@ def [](*a) end def initialize(*params) + # FIXME/TODO: setting args to the hash or {} while getting rid + # on the previous parameters prevents from passing another + # graph to the initializer, so you cannot do things like: + # UndirectedGraph.new(Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6]) + # As args must be a hash, if we're to allow such syntax, + # we should provide a way to handle the graph as a hash + # member. args = (params.pop if params.last.kind_of? Hash) || {} args[:algorithmic_category] = DirectedGraphBuilder::Algorithms super *(params << args) diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb index debcea0..46f553f 100644 --- a/lib/graphy/graph.rb +++ b/lib/graphy/graph.rb @@ -16,11 +16,24 @@ module GraphBuilder #end # after the class->module transition, has been moved at implementation level, # using a helper (extends_host) + extends_host + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end # Create a graph. def initialize(*params) - raise ArgumentError if params.any? do |p| - !(p.is_a? Graphy::Graph or p.is_a? Array or p.is_a? Hash) + puts params.inspect + params.each {|p| puts "#{p.inspect} (#{p.class})"} + puts + raise ArgumentError if params.any? do |p| + # FIXME: checking wether it's a GraphBuilder (module) is not sufficient + # and the is_a? redefinition trick (instance_evaling) should be + # completed by a clever way to check the actual class of p. + # Maybe using ObjectSpace to get the available Graph classes? + !(p.is_a? Graphy::GraphBuilder or p.is_a? Array or p.is_a? Hash) end args = params.last || {} class << self @@ -292,8 +305,6 @@ def min_out_degree # Minimum degree of all vertexes def min_degree - puts "---" - puts self [min_in_degree, min_out_degree].min end @@ -314,7 +325,6 @@ def max_degree # Regular def regular? - puts self min_degree == max_degree end @@ -405,11 +415,21 @@ def induced_subgraph(v) end; end - #def inspect - #puts self.inspect - ##l = vertices.select { |v| self[v]}.map { |u| "vertex_label_set(#{u.inspect}, #{self[u].inspect})"}.join('.') - ##self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l && l != '' ? '.'+l : '') - #end + def inspect + # FIXME: broken, it's not updated. The issue's not with inspect, but it's worth mentionning here. + # Example: + # dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] + # dg.add_vertices! 1, 5, "yosh" + # # => Graphy::Digraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[6,4,nil]] + # dg.vertex?("yosh") + # # => true + # dg + # # =>Graphy::Digraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[6,4,nil]] + # the new vertex doesn't show up. + # Actually this version of inspect is far too verbose IMO :) + l = vertices.select { |v| self[v]}.map { |u| "vertex_label_set(#{u.inspect}, #{self[u].inspect})"}.join('.') + self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l && l != '' ? '.'+l : '') + end private From 0a6921a27ed68688880598271d85a4ba64950c21 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 28 Mar 2010 17:47:15 +0200 Subject: [PATCH 17/44] yardoc --- Rakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index 4303dc2..4c46789 100644 --- a/Rakefile +++ b/Rakefile @@ -55,6 +55,6 @@ end #task :default => :spec YARD::Rake::YardocTask.new do |t| - t.files = ['lib/**/*.rb', 'README.markdown', 'TODO.markdown', 'CREDITS.markdown', 'LICENSE', 'VERSION'] + t.files = ['lib/**/*.rb', 'README.md', 'TODO.md', 'CREDITS.md', 'LICENSE', 'VERSION'] #t.options = ['--any', '--extra', '--opts'] # optional end From 231ef897768b598afb9a1202b500bb2aeb5c9316 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Mon, 29 Mar 2010 12:05:20 +0200 Subject: [PATCH 18/44] some more documentation (backup commit) --- .document | 4 +- Rakefile | 1 + TODO.md | 18 +- graphy.gemspec | 10 +- lib/graphy/adjacency_graph.rb | 112 +++- lib/graphy/common.rb | 4 +- lib/graphy/directed_graph.rb | 15 +- lib/graphy/directed_graph/distance.rb | 152 ++--- lib/graphy/dot.rb | 21 +- lib/graphy/graph.rb | 250 +++++--- lib/graphy/graph_api.rb | 28 +- lib/graphy/maximum_flow.rb | 4 +- lib/graphy/search.rb | 812 +++++++++++++++----------- 13 files changed, 859 insertions(+), 572 deletions(-) diff --git a/.document b/.document index ecf3673..e57b319 100644 --- a/.document +++ b/.document @@ -1,5 +1,3 @@ -README.rdoc lib/**/*.rb -bin/* +spec/**/*.rb features/**/*.feature -LICENSE diff --git a/Rakefile b/Rakefile index 4c46789..918fc1b 100644 --- a/Rakefile +++ b/Rakefile @@ -1,5 +1,6 @@ require 'rubygems' require 'rake' +require 'yard' begin require 'jeweler' diff --git a/TODO.md b/TODO.md index 415ce47..fd2c74e 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,17 @@ -TODO -==== +# TODO -From GRATR ----------- +## From the current Graphy project -The following list were present in GRATR, and may (or may not) be -added to this library: +* code cleanup: syntax, typo +* YARD documentation +* hunt for buggy behaviors +* a solid helper to check graphy object type in replacement of the current `is_a?` hack +* have the `[]` class method propagate through nested module inclusions [see this topic](http://www.ruby-forum.com/topic/68638) +* [Incremental heuristic search algorithms](http://en.wikipedia.org/wiki/Incremental_heuristic_search)? + +## From GRATR + +The following list was present in GRATR, and items may (or may not) be added to this library: * Primal Dual for combinatorial optimization problems * Min-Max Flow diff --git a/graphy.gemspec b/graphy.gemspec index fc596fd..7a4b0e1 100644 --- a/graphy.gemspec +++ b/graphy.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Bruce Williams"] - s.date = %q{2010-03-28} + s.date = %q{2010-03-29} s.description = %q{A framework for graph data structures and algorithms. This library is based on GRATR and RGL. @@ -25,16 +25,16 @@ Graph algorithms currently provided are: s.email = %q{bruce@codefluency.com} s.extra_rdoc_files = [ "LICENSE", - "README.markdown" + "README.md" ] s.files = [ ".document", ".gitignore", - "CREDITS.markdown", + "CREDITS.md", "LICENSE", - "README.markdown", + "README.md", "Rakefile", - "TODO.markdown", + "TODO.md", "VERSION", "examples/graph_self.rb", "examples/module_graph.jpg", diff --git a/lib/graphy/adjacency_graph.rb b/lib/graphy/adjacency_graph.rb index 209130e..5c495e9 100644 --- a/lib/graphy/adjacency_graph.rb +++ b/lib/graphy/adjacency_graph.rb @@ -1,21 +1,30 @@ module Graphy - # This provides the basic routines needed to implement the Digraph, - # UndirectedGraph, PseudoGraph, DirectedPseudoGraph, MultiGraph and - # DirectedPseudoGraph classes, through Graph, under the control of - # the GraphAPI. + # This module provides the basic routines needed to implement the specialized builders: + # {DigraphBuilder}, {UndirectedGraphBuilder}, {DirectedPseudoGraphBuilder}, + # {UndirectedPseudoGraphBuilder}, {DirectedMultiGraphBuilder} and {UndirectedMultiGraphBuilder} + # modules, each of them streamlining {AdjacencyGraphBuilder}'s behavior. Those + # implementations rely on the {GraphBuilder}, under the control of the {GraphAPI}. module AdjacencyGraphBuilder - class ArrayWithAdd < Array # :nodoc: + # Useful `push` -> `add` aliasing for Array. + class ArrayWithAdd < Array alias add push end - # Initialization parameters can include an Array of edges to add, Graphs to - # copy (will merge if multiple) - # :parallel_edges denotes that duplicate edges are allowed - # :loops denotes that loops are allowed + # This method is called by the specialized implementations + # upon graph creation. + # + # Initialization parameters can include: + # + # * an array of edges to add + # * one or several graphs to copy (will be merged if multiple) + # * `:parallel_edges` denotes that duplicate edges are allowed + # * `:loops denotes` that loops are allowed + # + # @param *params [Hash] the initialization parameters def implementation_initialize(*params) - @vertex_dict = Hash.new + @vertex_dict = Hash.new clear_all_labels # FIXME: could definitely make use of the activesupport helper @@ -23,7 +32,7 @@ def implementation_initialize(*params) # to handle parameters args = (params.pop if params.last.is_a? Hash) || {} - # Basic configuration of adjacency + # Basic configuration of adjacency. @allow_loops = args[:loops] || false @parallel_edges = args[:parallel_edges] || false @edgelist_class = @parallel_edges ? ArrayWithAdd : Set @@ -32,7 +41,7 @@ def implementation_initialize(*params) @next_edge_number = 0 end - # Copy any given graph into this graph + # Copy any given graph into this graph. params.select { |p| p.is_a? Graphy::Graph }.each do |g| g.edges.each do |e| add_edge!(e) @@ -43,54 +52,83 @@ def implementation_initialize(*params) end end - # Add all array edges specified - params.select { |p| p.is_a? Array}.each do |a| - 0.step(a.size-1, 2) { |i| add_edge!(a[i], a[i+1])} + # Add all array edges specified. + params.select { |p| p.is_a? Array }.each do |a| + 0.step(a.size-1, 2) { |i| add_edge!(a[i], a[i+1]) } end end # Returns true if v is a vertex of this Graph - # (an "O(1)" implementation of vertex?) + # (an "O(1)" implementation of `vertex?`). + # + # @param [vertex] v + # @return [Boolean] def vertex?(v) @vertex_dict.has_key?(v) end - # Returns true if [u,v] or u is an Arc - # (an "O(1)" implementation of edge?) + # Returns true if [u,v] or u is an {Arc} + # (an "O(1)" implementation of `edge?`). + # + # @param [vertex] u + # @param [vertex] v (nil) + # @return [Boolean] def edge?(u, v = nil) u, v = u.source, u.target if u.is_a? Graphy::Arc vertex?(u) and @vertex_dict[u].include?(v) end - # Adds a vertex to the graph with an optional label + # Adds a vertex to the graph with an optional label. + # + # @param [vertex(Object)] vertex any kind of Object can act as a vertex + # @param [#to_s] label (nil) def add_vertex!(vertex, label = nil) @vertex_dict[vertex] ||= @edgelist_class.new self[vertex] = label if label self end - # Adds an edge to the graph - # Can be called in two basic ways, label is optional - # * add_edge!(Arc[source,target], "Label") - # * add_edge!(source,target, "Label") + # Adds an edge to the graph. + # + # Can be called in two basic ways, label is optional: + # @overload add_edge!(Arc[source,target], "Label") + # Using an explicit {Arc} object + # @param [Arc] arc + # @param [#to_s] label (nil) + # @return [AdjacencyGraph] `self` + # @overload add_edge!(source, target, "Label") + # Using vertices + # @param [vertex(Object)] u + # @param [vertex(Object)] v (nil) + # @param [#to_s] l (nil) + # @param [Integer] n (nil) numéro de l'{Arc} `(u,v)` (si `nil` et si `u` + # possède un {ArcNumber}, il sera utilisé) + # @return [AdjacencyGraph] `self` def add_edge!(u, v = nil, l = nil, n = nil) n = u.number if u.class.include? ArcNumber and n.nil? u, v, l = u.source, u.target, u.label if u.is_a? Graphy::Arc + return self if not @allow_loops and u == v + n = (@next_edge_number += 1) unless n if @parallel_edges add_vertex!(u) add_vertex!(v) @vertex_dict[u].add(v) (@edge_number[u] ||= @edgelist_class.new).add(n) if @parallel_edges + unless directed? @vertex_dict[v].add(u) (@edge_number[v] ||= @edgelist_class.new).add(n) if @parallel_edges end + self[n ? edge_class[u,v,n] : edge_class[u,v]] = l if l self end - # Removes a given vertex from the graph + # Removes a given vertex from the graph. + # + # @param [vertex] v + # @return [AdjacencyGraph] `self` def remove_vertex!(v) # FIXME This is broken for multi graphs @vertex_dict.delete(v) @@ -103,8 +141,16 @@ def remove_vertex!(v) self end - # Removes an edge from the graph, can be called with source and target or with - # and object of Graphy::Arc derivation + # Removes an edge from the graph. + # + # Can be called with both source and target as vertex, + # or with source and object of {Graphy::Arc} derivation. + # + # @param [vertex, Graphy::Arc] u if `u` is a {Graphy::Arc}, then `v` must be left out + # @param [vertex] v (nil) + # @return [AdjacencyGraph] `self` + # @raise [ArgumentError] if passed a {Graphy::Arc} while parallel edges are enabled + # @raise [ArgumentError] if parallel edges are enabled and the {ArcNumber} of `u` is zero def remove_edge!(u, v = nil) unless u.is_a? Graphy::Arc raise ArgumentError if @parallel_edges @@ -124,13 +170,17 @@ def remove_edge!(u, v = nil) self end - # Returns an array of vertices that the graph has + # Returns an array of vertices that the graph has. + # + # @return [Array] graph's vertices def vertices @vertex_dict.keys end - # Returns an array of edges, most likely of class Arc or Edge depending - # upon the type of graph + # Returns an array of edges, most likely of class {Arc} or {Edge} depending + # upon the type of graph. + # + # @return [Array] def edges @vertex_dict.keys.inject(Set.new) do |a,v| if @parallel_edges and @edge_number[v] @@ -147,7 +197,9 @@ def edges end.to_a end - # FIXME, EFFED UP + # FIXME, EFFED UP (but why?) + # + # @fixme def adjacent(x, options = {}) options[:direction] ||= :out if !x.is_a?(Graphy::Arc) and (options[:direction] == :out || !directed?) diff --git a/lib/graphy/common.rb b/lib/graphy/common.rb index d2f7dee..cacf6ea 100644 --- a/lib/graphy/common.rb +++ b/lib/graphy/common.rb @@ -37,7 +37,9 @@ def edges # class and implemeting the minimum methods needed to # make it work. This is a good example to look # at for making one's own graph classes. - module CompleteBuilder < CycleBuilder + module CompleteBuilder + include CycleBuilder + def initialize(n) @size = n @edges = nil diff --git a/lib/graphy/directed_graph.rb b/lib/graphy/directed_graph.rb index 39cabe6..9fbd2ae 100644 --- a/lib/graphy/directed_graph.rb +++ b/lib/graphy/directed_graph.rb @@ -1,5 +1,9 @@ module Graphy + # This implements a directed graph which does not allow parallel + # edges nor loops. That is, only one arc per nodes couple, + # and only one parent per node. Mimics the typical hierarchy + # structure. module DirectedGraphBuilder include GraphBuilder @@ -34,8 +38,8 @@ def initialize(*params) # DirectedGraph is just an alias for Digraph should one desire DigraphBuilder = DirectedGraphBuilder - # This is a Digraph that allows for paral#lel edges, but does not - # allow loops + # This is a Digraph that allows for parallel edges, but does not + # allow loops. module DirectedPseudoGraphBuilder include DirectedGraphBuilder extends_host @@ -50,10 +54,9 @@ def initialize(*params) args[:parallel_edges] = true super *(params << args) end + end # DirectedPseudoGraphBuilder - end # DirectedPseudoGraph - - # This is a Digraph that allows for parallel edges and loops + # This is a Digraph that allows for both parallel edges and loops. module DirectedMultiGraphBuilder include DirectedPseudoGraphBuilder extends_host @@ -68,6 +71,6 @@ def initialize(*params) args[:loops] = true super *(params << args) end - end # DirectedMultiGraph + end # DirectedMultiGraphBuilder end # Graphy diff --git a/lib/graphy/directed_graph/distance.rb b/lib/graphy/directed_graph/distance.rb index c142ae1..0eeff60 100644 --- a/lib/graphy/directed_graph/distance.rb +++ b/lib/graphy/directed_graph/distance.rb @@ -1,27 +1,31 @@ module Graphy module DirectedGraphBuilder + + # This module provides algorithms computing distance between + # vertices. module Distance - # Shortest path from Jorgen Band-Jensen and Gregory Gutin, - # _DIGRAPHS:_Theory,_Algorithms_and_Applications, pg 53-54 + # Shortest path computation. # - # Requires that the graph be acyclic. If the graph is not - # acyclic, then see dijkstras_algorithm or bellman_ford_moore - # for possible solutions. + # From: Jorgen Band-Jensen and Gregory Gutin, + # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 53-54. + # Complexity `O(n+m)`. # - # start is the starting vertex - # weight can be a Proc, or anything else is accessed using the [] for the - # the label or it defaults to using - # the value stored in the label for the Arc. If it is a Proc it will - # pass the edge to the proc and use the resulting value. - # zero is used for math system with a different definition of zero + # Requires the graph to be acyclic. If the graph is not acyclic, + # then see {Distance#dijkstras_algorithm} or {Distance#bellman_ford_moore} + # for possible solutions. # - # Returns a hash with the key being a vertex and the value being the - # distance. A missing vertex from the hash is an infinite distance + # @param [vertex] start the starting vertex + # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]` + # operator. If not a `Proc`, and if no label accessible through `[]`, it will + # default to using the value stored in the label for the {Arc}. If a `Proc`, it will + # pass the edge to the proc and use the resulting value. + # @param [Integer] zero used for math systems with a different definition of zero # - # Complexity O(n+m) + # @return [Hash] a hash with the key being a vertex and the value being the + # distance. A missing vertex from the hash is equivalent to an infinite distance. def shortest_path(start, weight = nil, zero = 0) - dist = {start => zero} + dist = { start => zero } path = {} topsort(start) do |vi| next if vi == start @@ -30,29 +34,32 @@ def shortest_path(start, weight = nil, zero = 0) end.min { |a,b| a[0] <=> b[0]} end; dist.keys.size == vertices.size ? [dist, path] : nil - end # shortest_path + end - # Algorithm from Jorgen Band-Jensen and Gregory Gutin, - # _DIGRAPHS:_Theory,_Algorithms_and_Applications, pg 53-54 - # - # Finds the distances from a given vertex s in a weighted digraph + # Finds the distance from a given vertex in a weighted digraph # to the rest of the vertices, provided all the weights of arcs - # are non-negative. If negative arcs exist in the graph, two - # basic options exist, 1) modify all weights to be positive by - # using an offset, or 2) use the bellman_ford_moore algorithm. - # Also if the graph is acyclic, use the shortest_path algorithm. + # are non-negative. # - # weight can be a Proc, or anything else is accessed using the [] for the - # the label or it defaults to using - # the value stored in the label for the Arc. If it is a Proc it will - # pass the edge to the proc and use the resulting value. - # - # zero is used for math system with a different definition of zero + # If negative arcs exist in the graph, two basic options exist: + # + # * modify all weights to be positive using an offset (temporary at least) + # * use the {Distance#bellman_ford_moore} algorithm. # - # Returns a hash with the key being a vertex and the value being the - # distance. A missing vertex from the hash is an infinite distance + # Also, if the graph is acyclic, use the {Distance#shortest_path algorithm}. # - # O(n*log(n) + m) complexity + # From: Jorgen Band-Jensen and Gregory Gutin, + # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 53-54. + # + # Complexity `O(n*log(n) + m)`. + # + # @param [vertex] s + # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]` + # operator. If not a `Proc`, and if no label accessible through `[]`, it will + # default to using the value stored in the label for the {Arc}. If a `Proc`, it will + # pass the edge to the proc and use the resulting value. + # @param [Integer] zero used for math systems with a different definition of zero + # @return [Hash] a hash with the key being a vertex and the value being the + # distance. A missing vertex from the hash is equivalent to an infinite distance. def dijkstras_algorithm(s, weight = nil, zero = 0) q = vertices; distance = { s => zero } path = {} @@ -60,7 +67,7 @@ def dijkstras_algorithm(s, weight = nil, zero = 0) v = (q & distance.keys).inject(nil) { |a,k| (!a.nil?) && (distance[a] < distance[k]) ? a : k} q.delete(v) (q & adjacent(v)).each do |u| - c = cost(v,u,weight) + c = cost(v, u, weight) if distance[u].nil? or distance[u] > (c + distance[v]) distance[u] = c + distance[v] path[u] = v @@ -68,28 +75,28 @@ def dijkstras_algorithm(s, weight = nil, zero = 0) end end [distance, path] - end # dijkstras_algorithm + end - # Algorithm from Jorgen Band-Jensen and Gregory Gutin, - # _DIGRAPHS:_Theory,_Algorithms_and_Applications, pg 56-58 - # - # Finds the distances from a given vertex s in a weighted digraph + # Finds the distances from a given vertex in a weighted digraph # to the rest of the vertices, provided the graph has no negative cycle. - # If no negative weights exist, then dijkstras_algorithm is more - # efficient in time and space. Also if the graph is acyclic, use the - # shortest_path algorithm. # - # weight can be a Proc, or anything else is accessed using the [] for the - # the label or it defaults to using - # the value stored in the label for the Arc. If it is a Proc it will - # pass the edge to the proc and use the resulting value. - # - # zero is used for math system with a different definition of zero + # If no negative weights exist, then {Distance#dijkstras_algorithm} is more + # efficient in time and space. Also, if the graph is acyclic, use the + # {Distance#shortest_path} algorithm. # - # Returns a hash with the key being a vertex and the value being the - # distance. A missing vertex from the hash is an infinite distance + # From: Jorgen Band-Jensen and Gregory Gutin, + # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 56-58.. # - # O(nm) complexity + # Complexity `O(nm)`. + # + # @param [vertex] s + # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]` + # operator. If not a `Proc`, and if no label accessible through `[]`, it will + # default to using the value stored in the label for the {Arc}. If a `Proc`, it will + # pass the edge to the proc and use the resulting value. + # @param [Integer] zero used for math systems with a different definition of zero + # @return [Hash] a hash with the key being a vertex and the value being the + # distance. A missing vertex from the hash is equivalent to an infinite distance. def bellman_ford_moore(start, weight = nil, zero = 0) distance = { start => zero } path = {} @@ -106,35 +113,32 @@ def bellman_ford_moore(start, weight = nil, zero = 0) end end [distance, path] - end # bellman_ford_moore + end - # This uses the Floyd-Warshall algorithm to efficiently find - # and record shortest paths at the same time as establishing + # Uses the Floyd-Warshall algorithm to efficiently find + # and record shortest paths while establishing at the same time # the costs for all vertices in a graph. - # See, S.Skiena, "The Algorithm Design Manual", - # Springer Verlag, 1998 for more details. - # - # Returns a pair of matrices and a hash of delta values. - # The matrices will be indexed by two vertices and are - # implemented as a Hash of Hashes. The first matrix is the cost, the second - # matrix is the shortest path spanning tree. The delta (difference of number - # of in edges and out edges) is indexed by vertex. - # - # weight specifies how an edge weight is determined, if it's a - # Proc the Arc is passed to it, if it's nil it will just use - # the value in the label for the Arc, otherwise the weight is - # determined by applying the [] operator to the value in the - # label for the Arc # - # zero defines the zero value in the math system used. Defaults - # of course, to 0. This allows for no assumptions to be made - # about the math system and fully functional duck typing. + # See S.Skiena, *The Algorithm Design Manual*, Springer Verlag, 1998 for more details. # # O(n^3) complexity in time. + # + # @param [Proc, nil] weight specifies how an edge weight is determined. + # If it's a `Proc`, the {Arc} is passed to it; if it's `nil`, it will just use + # the value in the label for the Arc; otherwise the weight is + # determined by applying the `[]` operator to the value in the + # label for the {Arc}. + # @param [Integer] zero defines the zero value in the math system used. + # This allows for no assumptions to be made about the math system and + # fully functional duck typing. + # @return [Array(matrice, matrice, Hash)] a pair of matrices and a hash of delta values. + # The matrices will be indexed by two vertices and are implemented as a Hash of Hashes. + # The first matrix is the cost, the second matrix is the shortest path spanning tree. + # The delta (difference of number of in-edges and out-edges) is indexed by vertex. def floyd_warshall(weight = nil, zero = 0) - c = Hash.new { |h,k| h[k] = Hash.new} - path = Hash.new { |h,k| h[k] = Hash.new} - delta = Hash.new { |h,k| h[k] = 0} + c = Hash.new { |h,k| h[k] = Hash.new } + path = Hash.new { |h,k| h[k] = Hash.new } + delta = Hash.new { |h,k| h[k] = 0 } edges.each do |e| delta[e.source] += 1 delta[e.target] -= 1 diff --git a/lib/graphy/dot.rb b/lib/graphy/dot.rb index a3cd78d..e013c5f 100644 --- a/lib/graphy/dot.rb +++ b/lib/graphy/dot.rb @@ -13,24 +13,31 @@ def to_dot_graph (params = {}) params['name'] ||= self.class.name.gsub(/:/,'_') fontsize = params['fontsize'] ? params['fontsize'] : '8' graph = (directed? ? DOT::DOTDigraph : DOT::DOTSubgraph).new(params) - edge_klass = directed? ? DOT::DOTDirectedArc : DOT::DOTArc + edge_klass = directed? ? DOT::DOTDirectedArc : DOT::DOTArc + vertices.each do |v| name = v.to_s - params = {'name' => '"'+name+'"', - 'fontsize' => fontsize, - 'label' => name} + if name.nil? + #name = v.class.to_s + ":" + v.__id__.to_s + name = v.name + end + params = { 'name' => '"'+ name +'"', + 'fontsize' => fontsize, + 'label' => name} v_label = vertex_label(v) params.merge!(v_label) if v_label and v_label.kind_of? Hash graph << DOT::DOTNode.new(params) end + edges.each do |e| - params = {'from' => '"'+ e.source.to_s + '"', - 'to' => '"'+ e.target.to_s + '"', - 'fontsize' => fontsize } + params = { 'from' => '"'+ e.source.to_s + '"', + 'to' => '"'+ e.target.to_s + '"', + 'fontsize' => fontsize } e_label = edge_label(e) params.merge!(e_label) if e_label and e_label.kind_of? Hash graph << edge_klass.new(params) end + graph end diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb index 46f553f..4c6666f 100644 --- a/lib/graphy/graph.rb +++ b/lib/graphy/graph.rb @@ -1,9 +1,13 @@ module Graphy - # Using the functions required by the GraphAPI, it implements all the - # basic functions of a Graph class by using only functions in GraphAPI. - # An actual implementation still needs to be done, as in Digraph or - # UndirectedGraph. + # Using the methods required by the {GraphAPI}, it implements all the + # *basic* functions of a {Graph} using *only* functions + # requested in {GraphAPI}. The process is under the control of the pattern + # {AdjacencyGraphBuilder}, unless a specific implementation is specified + # during initialization. + # + # An actual, complete implementation still needs to be done using this cheap result, + # hence {Digraph}, {UndirectedGraph} and their roomates. module GraphBuilder include Enumerable @@ -23,11 +27,12 @@ def [](*a) end end - # Create a graph. + # Create a generic graph. + # + # @param [Hash(Graphy::Graph, Array)] *params initialization parameters. + # See {AdjacencyGraphBuilder#implementation_initialize} for more details. + # @return [Graph] def initialize(*params) - puts params.inspect - params.each {|p| puts "#{p.inspect} (#{p.class})"} - puts raise ArgumentError if params.any? do |p| # FIXME: checking wether it's a GraphBuilder (module) is not sufficient # and the is_a? redefinition trick (instance_evaling) should be @@ -35,24 +40,34 @@ def initialize(*params) # Maybe using ObjectSpace to get the available Graph classes? !(p.is_a? Graphy::GraphBuilder or p.is_a? Array or p.is_a? Hash) end + args = params.last || {} + class << self self end.module_eval do + # These inclusions trigger some validations checks by the way. include(args[:implementation] ? args[:implementation] : AdjacencyGraphBuilder) include(args[:algorithmic_category] ? args[:algorithmic_category] : DigraphBuilder ) include GraphAPI end + implementation_initialize(*params) end - # Shortcut for creating a Graph + # Shortcut for creating a Graph. # - # Example: Graphy::Graph[1,2, 2,3, 2,4, 4,5].edges.to_a.to_s => - # "(1-2)(2-3)(2-4)(4-5)" + # Using an arry of implicit {Arc}, specifying the vertices: + # + # Graphy::Graph[1,2, 2,3, 2,4, 4,5].edges.to_a.to_s + # # => "(1-2)(2-3)(2-4)(4-5)" # - # Or as a Hash for specifying labels - # Graphy::Graph[ [:a,:b] => 3, [:b,:c] => 4 ] (Note: Do not use for Multi or Pseudo graphs) + # Using a Hash for specifying labels along the way: + # + # Graphy::Graph[ [:a,:b] => 3, [:b,:c] => 4 ] (note: do not use for Multi or Pseudo graphs) + # + # @param [Array, Hash] *a + # @return [Graph] def from_array(*a) if a.size == 1 and a[0].is_a? Hash # Convert to edge class @@ -75,50 +90,69 @@ def from_array(*a) self end - # Non destructive version of add_vertex!, returns modified copy of Graph + # Non destructive version of {AdjacencyGraphBuilder#add_vertex!} (works on a copy of the graph). + # + # @param [vertex] u + # @param [#to_s] l + # @return [Graph] a new graph with the supplementary vertex def add_vertex(v, l = nil) x = self.class.new(self) x.add_vertex!(v,l) end - # Non destructive version add_edge!, returns modified copy of Graph + # Non destructive version {AdjacencyGraphBuilder#add_edge!} (works on a copy of the graph). + # + # @param [vertex] u + # @param [vertex] v + # @param [#to_s] l + # @return [Graph] a new graph with the supplementary edge def add_edge(u, v = nil, l = nil) x = self.class.new(self) x.add_edge!(u,v,l) end alias add_arc add_edge - # Non destructive version of remove_vertex!, returns modified copy of Graph + # Non destructive version of {AdjacencyGraphBuilder#remove_vertex!} (works on a copy of the graph). + # + # @param [vertex] v + # @return [Graph] a new graph without the specified vertex def remove_vertex(v) x = self.class.new(self) x.remove_vertex!(v) end - # Non destructive version of remove_edge!, returns modified copy of Graph - def remove_edge(u,v = nil) + # Non destructive version {AdjacencyGraphBuilder#remove_edge!} (works on a copy of the graph). + # + # @param [vertex] u + # @param [vertex] v + # @return [Graph] a new graph without the specified edge + def remove_edge(u, v = nil) x = self.class.new(self) - x.remove_edge!(u,v) + x.remove_edge!(u, v) end alias remove_arc remove_edge - # Return Array of adjacent portions of the Graph - # x can either be a vertex an edge. - # options specifies parameters about the adjacency search: - # :type can be either :edges or :vertices (default). - # :direction can be :in, :out(default) or :all. + # Computes the adjacent portions of the Graph. + # + # The options specify the parameters about the adjacency search. + # Note: it is probably more efficently done in the implementation class. # - # Note: It is probably more efficently done in the implementation class. + # @param [vertex, Edge] x can either be a vertex an edge + # @option options [Symbol] :type (:vertices) can be either `:edges` or `:vertices` + # @option options [Symbol] :direction (:all) can be `:in`, `:out` or `:all` + # @return [Array] an array of the adjacent portions + # @fixme def adjacent(x, options={}) d = directed? ? (options[:direction] || :out) : :all - # Discharge the easy ones first + # Discharge the easy ones first. return [x.source] if x.is_a? Arc and options[:type] == :vertices and d == :in return [x.target] if x.is_a? Arc and options[:type] == :vertices and d == :out return [x.source, x.target] if x.is_a? Arc and options[:type] != :edges and d == :all - (options[:type] == :edges ? edges : to_a).select {|u| adjacent?(x,u,d)} + (options[:type] == :edges ? edges : to_a).select { |u| adjacent?(x,u,d) } end - #FIXME, THIS IS A HACK AROUND A SERIOUS PROBLEM + #FIXME: This is a hack around a serious problem alias graph_adjacent adjacent @@ -157,12 +191,14 @@ def add_edges(*a) def remove_vertices!(*a) a.each { |v| remove_vertex! v } end + alias delete_vertices! remove_vertices! # See remove_vertices! def remove_vertices(*a) x = self.class.new(self) x.remove_vertices(*a) end + alias delete_vertices remove_vertices # Remove all vertices edges by the Enumerable a from the graph by # calling remove_edge! @@ -170,6 +206,8 @@ def remove_edges!(*a) a.each { |e| remove_edges! e } end alias remove_arcs! remove_edges! + alias delete_edges! remove_edges! + alias delete_arcs! remove_edges! # See remove_edges def remove_edges(*a) @@ -177,6 +215,8 @@ def remove_edges(*a) x.remove_edges(*a) end alias remove_arcs remove_edges + alias delete_edges remove_edges + alias delete_arcs remove_edges # Execute given block for each vertex, provides for methods in Enumerable def each(&block) @@ -189,7 +229,8 @@ def each(&block) # made into an O(1) average complexity operation. def vertex?(v) vertices.include?(v) - end + end + alias has_vertex? vertex? # Returns true if u or (u,v) is an Arc of the graph. def edge?(*arg) @@ -235,24 +276,39 @@ def empty? def include?(x) x.is_a?(Graphy::Arc) ? edge?(x) : vertex?(x) end + alias has? include? - # Returns the neighboorhood of the given vertex (or Arc) - # This is equivalent to adjacent, but bases type on the type of object. - # direction can be :all, :in, or :out + # Returns the neighboorhood of the given vertex (or {Arc}). + # + # This is equivalent to {GraphBuilder#adjacent adjacent}, but bases type on the type of object. + # + # @param [vertex] x + # @param [Symbol] direction can be `:all`, `:in`, or `:out` def neighborhood(x, direction = :all) adjacent(x, :direction => direction, :type => ((x.is_a? Graphy::Arc) ? :edges : :vertices )) end - # Union of all neighborhoods of vertices (or edges) in the Enumerable x minus the contents of x - # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: Theory, Algorithms and Applications_, pg 4 + # Union of all neighborhoods of vertices (or edges) in the Enumerable x minus the contents of x. + # + # Definition taken from: Jorgen Bang-Jensen, Gregory Gutin, *Digraphs: Theory, Algorithms and Applications*, pg. 4 + # + # @param [vertex] x + # @param [Symbol] direction can be `:all`, `:in`, or `:out` def set_neighborhood(x, direction = :all) x.inject(Set.new) { |a,v| a.merge(neighborhood(v, idirection))}.reject { |v2| x.include?(v2) } end - # Union of all set_neighborhoods reachable in p edges - # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: Theory, Algorithms and Applications_, pg 46 + # Union of all {GraphBuilder#set_neighborhood set_neighborhoods} reachable + # among the specified edges. + # + # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, *Digraphs: + # Theory, Algorithms and Applications*, pg. 46 + # + # @param [vertex] w + # @param [Edges] p + # @param [Symbol] direction can be `:all`, `:in`, or `:out` def closed_pth_neighborhood(w, p, direction = :all) - if p <= 0 + if p <= 0 w elsif p == 1 (w + set_neighborhood(w, direction)).uniq @@ -262,86 +318,123 @@ def closed_pth_neighborhood(w, p, direction = :all) end end - # Returns the neighboorhoods reachable in p steps from every vertex (or edge) - # in the Enumerable x - # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: Theory, Algorithms and Applications_, pg 46 + # Returns the neighboorhoods reachable in a certain amount of steps from + # every vertex (or edge) in the specified Enumerable. + # + # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: + # Theory, Algorithms and Applications_, pg. 46 + # + # @param [Enumerable] x + # @param [Integer] p number of steps to perform + # @param [Symbol] direction can be `:all`, `:in`, or `:out` def open_pth_neighborhood(x, p, direction = :all) if p <= 0 x elsif p == 1 set_neighborhood(x,direction) else - set_neighborhood(open_pth_neighborhood(x, p-1, direction),direction) - closed_pth_neighborhood(x, p-1, direction) + set_neighborhood(open_pth_neighborhood(x, p-1, direction), direction) - + closed_pth_neighborhood(x, p-1, direction) end end # Returns the number of out-edges (for directed graphs) or the number of - # incident edges (for undirected graphs) of vertex _v_. + # incident edges (for undirected graphs) of the specified vertex. + # + # @param [vertex] v + # @return [Integer] number of matching edges def out_degree(v) adjacent(v, :direction => :out).size end # Returns the number of in-edges (for directed graphs) or the number of - # incident edges (for undirected graphs) of vertex _v_. + # incident edges (for undirected graphs) of the specified vertex + # + # @param [vertex] v + # @return [Integer] number of matching edges def in_degree(v) adjacent(v, :direction => :in).size end - # Returns the sum of the number in and out edges for a vertex + # Returns the sum of the number in and out edges for the specified vertex. + # + # @param [vertex] v + # @returm [Integer] degree def degree(v) in_degree(v) + out_degree(v) end - # Minimum in-degree + # Minimum in-degree of the graph. + # + # @return [Integer, nil] returns `nil` if the graph is empty def min_in_degree return nil if to_a.empty? - to_a.map { |v| in_degree(v)}.min + to_a.map { |v| in_degree(v) }.min end - # Minimum out-degree + # Minimum out-degree of the graph. + # + # @return [Integer, nil] returns `nil` if the graph is empty def min_out_degree + return nil if to_a.empty? to_a.map {|v| out_degree(v)}.min end - # Minimum degree of all vertexes + # Minimum degree of all vertexes of the graph. + # + # @return [Integer] `min` between {GraphBuilder#min_in_degree min_in_degree} + # and {GraphBuilder#min_out_degree max_out_degree} def min_degree [min_in_degree, min_out_degree].min end - # Maximum in-degree + # Maximum in-degree of the graph. + # + # @return [Integer, nil] returns `nil` if the graph is empty def max_in_degree + return nil if to_a.empty? vertices.map { |v| in_degree(v)}.max end - # Maximum out-degree + # Maximum out-degree of the graph. + # + # @return [Integer, nil] returns nil if the graph is empty def max_out_degree + return nil if to_a.empty? vertices.map { |v| out_degree(v)}.max end - # Minimum degree of all vertexes + # Maximum degree of all vertexes of the graph. + # + # @return [Integer] `max` between {GraphBuilder#max_in_degree max_in_degree} + # and {GraphBuilder#max_out_degree max_out_degree} def max_degree [max_in_degree, max_out_degree].max end - # Regular + # Is the graph regular, that is are its min degree and max degree equal? + # + # @return [Boolean] def regular? min_degree == max_degree end - # Returns the number of vertices. + # Number of vertices. + # + # @return [Integer] def size vertices.size end + alias num_vertices size + alias number_of_vertices size - # Synonym for size. - def num_vertices - vertices.size - end - - # Returns the number of edges. + # Number of edges. + # + # @return [Integer] def num_edges edges.size end + alias number_of_edges num_edges # Utility method to show a string representation of the edges of the graph. #def to_s @@ -356,13 +449,12 @@ def eql?(g) (vertices.sort == g.vertices.sort) and (g.edges.sort == edges.sort) end + alias == eql? - # Synonym for eql? - def ==(rhs) - eql?(rhs) - end - - # Merge another graph into this one + # Merges another graph into the receiver. + # + # @param [Graph] other the graph to merge in + # @return [Graph] `self` def merge(other) other.vertices.each { |v| add_vertex!(v) } other.edges.each { |e| add_edge!(e) } @@ -370,7 +462,10 @@ def merge(other) self end - # A synonym for merge, that doesn't modify the current graph + # A synonym for {GraphBuilder#merge merge}, but doesn't modify the current graph. + # + # @param [Graph, Arc] other + # @return [Graph] a new graph def +(other) result = self.class.new(self) case other @@ -383,9 +478,12 @@ def +(other) end end - # Remove all vertices in the specified right hand side graph + # Removes all vertices in the specified graph. + # + # @param [Graph, Arc] other + # @return [Graph] def -(other) - case other + case other when Graphy::Graph induced_subgraph(vertices - other.vertices) when Graphy::Arc @@ -395,12 +493,14 @@ def -(other) end end - # A synonym for add_edge! + # A synonym for {AdjacencyGraphBuilder#add_edge! add_edge!}. def <<(edge) add_edge!(edge) end - # Return the complement of the current graph + # Computes the complement of the current graph. + # + # @return [Graph] def complement vertices.inject(self.class.new) do |a,v| a.add_vertex!(v) @@ -408,11 +508,14 @@ def complement end end - # Given an array of vertices return the induced subgraph + # Given an array of vertices, computes the induced subgraph. + # + # @param [Array(vertex)] v + # @return [Graph] def induced_subgraph(v) edges.inject(self.class.new) do |a,e| - ( v.include?(e.source) and v.include?(e.target) ) ? (a << e) : a - end; + (v.include?(e.source) and v.include?(e.target)) ? (a << e) : a + end end def inspect @@ -433,6 +536,7 @@ def inspect private + # ? def edge_convert(*args) args[0].is_a?(Graphy::Arc) ? args[0] : edge_class[*args] end diff --git a/lib/graphy/graph_api.rb b/lib/graphy/graph_api.rb index b4d987b..435c075 100644 --- a/lib/graphy/graph_api.rb +++ b/lib/graphy/graph_api.rb @@ -1,20 +1,18 @@ module Graphy - - # This defines the minimum set of functions required to make a graph class that can - # use the algorithms defined by this library + # This module defines the minimum set of functions required to make a graph class that can + # use the algorithms defined by this library. + # + # Each implementation module must implement the following routines: + # + # * directed? # Is the graph directed? + # * add_vertex!(v,l=nil) # Add a vertex to the graph and return the graph. `l` is an optional label. + # * add_edge!(u,v=nil,l=nil) # Add an edge to the graph and return the graph. `u` can be an {Arc} or {Edge}, or `u,v` a {Edge} pair. The last parameter `l` is an optional label. + # * remove_vertex!(v) # Remove a vertex to the graph and return the graph. + # * remove_edge!(u,v=nil) # Remove an edge from the graph and return the graph. + # * vertices # Returns an array of of all vertices. + # * edges # Returns an array of all edges. + # * edge_class # Returns the class used to store edges. module GraphAPI - - # Each implementation module must implement the following routines: - # * directed? # Is the graph directed? - # * add_vertex!(v,l=nil) # Add a vertex to the graph and return the graph, l is an optional label. - # * add_edge!(u,v=nil,l=nil) # Add an edge to the graph and return the graph. u can be an Arc or Edge - # or u,v is an edge pair. The last parameter is an optional label. - # * remove_vertex!(v) # Remove a vertex to the graph and return the graph. - # * remove_edge!(u,v=nil) # Remove an edge from the graph and return the graph. - # * vertices # Returns an array of of all vertices. - # * edges # Returns an array of all edges. - # * edge_class # Returns the class used to store edges. - # # @raise if the API is not completely implemented def self.included(klass) @api_methods ||= [:directed?, :add_vertex!, :add_edge!, :remove_vertex!, :remove_edge!, :vertices, :edges, :edge_class] diff --git a/lib/graphy/maximum_flow.rb b/lib/graphy/maximum_flow.rb index 5afb7e2..ca0e094 100644 --- a/lib/graphy/maximum_flow.rb +++ b/lib/graphy/maximum_flow.rb @@ -33,7 +33,9 @@ def eliminate_lower_bounds end def restore_lower_bounds(src) - src.edges.each { |e| (src.flow ? src[e][src.flow] : src[e]) = property(e,self.flow) + src.property(e,self.lower) } + src.edges.each do |e| + (src.flow ? src[e][src.flow] : src[e]) = property(e, self.flow) + src.property(e, self.lower) + end src end diff --git a/lib/graphy/search.rb b/lib/graphy/search.rb index fdbe708..d05bc02 100644 --- a/lib/graphy/search.rb +++ b/lib/graphy/search.rb @@ -1,400 +1,510 @@ module Graphy + # **Search/traversal algorithms.** + # + # This module defines a collection of search/traversal algorithms, in a unified API. + # Read through the doc to get familiar with the calling pattern. + # + # Options are mostly callbacks passed in as a hash. The following are valid, + # anything else is ignored: + # + # * `:enter_vertex` => `Proc` Called upon entry of a vertex. + # * `:exit_vertex` => `Proc` Called upon exit of a vertex. + # * `:root_vertex` => `Proc` Called when a vertex is the root of a tree. + # * `:start_vertex` => `Proc` Called for the first vertex of the search. + # * `:examine_edge` => `Proc` Called when an edge is examined. + # * `:tree_edge` => `Proc` Called when the edge is a member of the tree. + # * `:back_edge` => `Proc` Called when the edge is a back edge. + # * `:forward_edge` => `Proc` Called when the edge is a forward edge. + # * `:adjacent` => `Proc` which, given a vertex, returns adjacent nodes, defaults to adjacent call of graph useful for changing the definition of adjacent in some algorithms. + # * `:start` => vertex Specifies the vertex to start search from. + # + # If a `&block` instead of an option hash is specified, it defines `:enter_vertex`. + # + # Each search algorithm returns the list of vertexes as reached by `enter_vertex`. + # This allows for calls like, `g.bfs.each { |v| ... }` + # + # Can also be called like `bfs_examine_edge { |e| ... }` or + # `dfs_back_edge { |e| ... }` for any of the callbacks. + # + # A full example usage is as follows: + # + # ev = Proc.new { |x| puts "Enter vertex #{x}" } + # xv = Proc.new { |x| puts "Exit vertex #{x}" } + # sv = Proc.new { |x| puts "Start vertex #{x}" } + # ee = Proc.new { |x| puts "Examine Arc #{x}" } + # te = Proc.new { |x| puts "Tree Arc #{x}" } + # be = Proc.new { |x| puts "Back Arc #{x}" } + # fe = Proc.new { |x| puts "Forward Arc #{x}" } + # Digraph[1,2, 2,3, 3,4].dfs({ + # :enter_vertex => ev, + # :exit_vertex => xv, + # :start_vertex => sv, + # :examine_edge => ee, + # :tree_edge => te, + # :back_edge => be, + # :forward_edge => fe }) + # + # Which outputs: + # + # Start vertex 1 + # Enter vertex 1 + # Examine Arc (1=2) + # Tree Arc (1=2) + # Enter vertex 2 + # Examine Arc (2=3) + # Tree Arc (2=3) + # Enter vertex 3 + # Examine Arc (3=4) + # Tree Arc (3=4) + # Enter vertex 4 + # Examine Arc (1=4) + # Back Arc (1=4) + # Exit vertex 4 + # Exit vertex 3 + # Exit vertex 2 + # Exit vertex 1 + # => [1, 2, 3, 4] module Search - # Options are mostly callbacks passed in as a hash. - # The following are valid, anything else is ignored - # :enter_vertex => Proc Called upon entry of a vertex - # :exit_vertex => Proc Called upon exit of a vertex - # :root_vertex => Proc Called when a vertex the a root of a tree - # :start_vertex => Proc Called for the first vertex of the search - # :examine_edge => Proc Called when an edge is examined - # :tree_edge => Proc Called when the edge is a member of the tree - # :back_edge => Proc Called when the edge is a back edge - # :forward_edge => Proc Called when the edge is a forward edge - # :adjacent => Proc that given a vertex returns adjacent nodes, defaults to adjacent call of graph useful for changing the definition of adjacent in some algorithms - # - # :start => Vertex Specifies the vertex to start search from - # - # If a &block is specified it defaults to :enter_vertex - # - # Returns the list of vertexes as reached by enter_vertex - # This allows for calls like, g.bfs.each {|v| ...} - # - # Can also be called like bfs_examine_edge {|e| ... } or - # dfs_back_edge {|e| ... } for any of the callbacks + # Performs a breadth-first search. # - # A full example usage is as follows: + # @param [Hash] options + def bfs(options = {}, &block) + graphy_search_helper(:shift, options, &block) + end + alias :bread_first_search :bfs + + # Performs a depth-first search. # - # ev = Proc.new {|x| puts "Enter Vertex #{x}"} - # xv = Proc.new {|x| puts "Exit Vertex #{x}"} - # sv = Proc.new {|x| puts "Start Vertex #{x}"} - # ee = Proc.new {|x| puts "Examine Arc #{x}"} - # te = Proc.new {|x| puts "Tree Arc #{x}"} - # be = Proc.new {|x| puts "Back Arc #{x}"} - # fe = Proc.new {|x| puts "Forward Arc #{x}"} - # Digraph[1,2,2,3,3,4].dfs({ - # :enter_vertex => ev, - # :exit_vertex => xv, - # :start_vertex => sv, - # :examine_edge => ee, - # :tree_edge => te, - # :back_edge => be, - # :forward_edge => fe }) - # - # Which outputs: + # @param [Hash] options + def dfs(options = {}, &block) + graphy_search_helper(:pop, options, &block) + end + alias :depth_first_search :dfs + + # Routine which computes a spanning forest for the given search method. + # Returns two values: a hash of predecessors and an array of root nodes. # - # Start Vertex 1 - # Enter Vertex 1 - # Examine Arc (1=2) - # Tree Arc (1=2) - # Enter Vertex 2 - # Examine Arc (2=3) - # Tree Arc (2=3) - # Enter Vertex 3 - # Examine Arc (3=4) - # Tree Arc (3=4) - # Enter Vertex 4 - # Examine Arc (1=4) - # Back Arc (1=4) - # Exit Vertex 4 - # Exit Vertex 3 - # Exit Vertex 2 - # Exit Vertex 1 - def bfs(options={}, &block) graphy_search_helper(:shift, options, &block); end - - # See options for bfs method - def dfs(options={}, &block) graphy_search_helper(:pop, options, &block); end - - # Routine to compute a spanning forest for the given search method - # Returns two values, first is a hash of predecessors and second an array of root nodes + # @param [vertex] start + # @param [Symbol] routine the search method (`:dfs`, `:bfs`) + # @return [Array] predecessors and root nodes def spanning_forest(start, routine) predecessor = {} roots = [] - te = Proc.new {|e| predecessor[e.target] = e.source} - rv = Proc.new {|v| roots << v} + te = Proc.new { |e| predecessor[e.target] = e.source } + rv = Proc.new { |v| roots << v } send routine, :start => start, :tree_edge => te, :root_vertex => rv [predecessor, roots] end - # Return the dfs spanning forest for the given start node, see spanning_forest - def dfs_spanning_forest(start) spanning_forest(start, :dfs); end + # Returns the dfs spanning forest for the given start node, see {Search#spanning_forest spanning_forest}. + # + # @param [vertex] start + # @return [Array] predecessors and root nodes + def dfs_spanning_forest(start) + spanning_forest(start, :dfs) + end - # Return the bfs spanning forest for the given start node, see spanning_forest - def bfs_spanning_forest(start) spanning_forest(start, :bfs); end + # Returns the bfs spanning forest for the given start node, see {Search#spanning_forest spanning_forest}. + # + # @param [vertex] start + # @return [Array] predecessors and root nodes + def bfs_spanning_forest(start) + spanning_forest(start, :bfs) + end - # Returns a hash of predecessors in a tree rooted at the start node. If this is a connected graph - # then it will be a spanning tree and contain all vertices. An easier way to tell if it's a spanning tree is to - # use a spanning_forest call and check if there is a single root node. + # Returns a hash of predecessors in a tree rooted at the start node. If this is a connected graph, + # then it will be a spanning tree containing all vertices. An easier way to tell if it's a + # spanning tree is to use a {Search#spanning_forest spanning_forest} call and check if there is a + # single root node. + # + # @param [vertex] start + # @param [Symbol] routine the search method (`:dfs`, `:bfs`) + # @return [Hash] predecessors vertices def tree_from_vertex(start, routine) - predecessor={} + predecessor = {} correct_tree = false - te = Proc.new {|e| predecessor[e.target] = e.source if correct_tree} - rv = Proc.new {|v| correct_tree = (v == start)} + te = Proc.new { |e| predecessor[e.target] = e.source if correct_tree } + rv = Proc.new { |v| correct_tree = (v == start) } send routine, :start => start, :tree_edge => te, :root_vertex => rv predecessor end - # Returns a hash of predecessors for the depth first search tree rooted at the given node - def dfs_tree_from_vertex(start) tree_from_vertex(start, :dfs); end + # Returns a hash of predecessors for the depth-first search tree rooted at the given node. + # + # @param [vertex] start + # @return [Hash] predecessors vertices + def dfs_tree_from_vertex(start) + tree_from_vertex(start, :dfs) + end - # Returns a hash of predecessors for the depth first search tree rooted at the given node - def bfs_tree_from_vertex(start) tree_from_vertex(start, :bfs); end + # Returns a hash of predecessors for the breadth-first search tree rooted at the given node. + # + # @param [Proc] start + # @return [Hash] predecessors vertices + def bfs_tree_from_vertex(start) + tree_from_vertex(start, :bfs) + end - # An inner class used for greater efficiency in lexicograph_bfs + # An inner class used for greater efficiency in {Search#lexicograph_bfs}. # - # Original desgn taken from Golumbic's, "Algorithmic Graph Theory and - # Perfect Graphs" pg, 87-89 + # Original design taken from Golumbic's, *Algorithmic Graph Theory and Perfect Graphs* pg. 87-89. class LexicographicQueue - - # Called with the initial values (array) + # Called with the initial values. + # + # @param [Array] initial vertices values def initialize(values) @node = Struct.new(:back, :forward, :data) - @node.class_eval { def hash() @hash; end; @@cnt=0 } + @node.class_eval do + def hash + @hash + end + @@cnt = 0 + end @set = {} @tail = @node.new(nil, nil, Array.new(values)) - @tail.instance_eval { @hash = (@@cnt+=1) } - values.each {|a| @set[a] = @tail} + @tail.instance_eval { @hash = (@@cnt += 1) } + values.each { |a| @set[a] = @tail } + end + + # Pops an entry with the maximum lexical value from the queue. + # + # @return [vertex] + def pop + return nil unless @tail + value = @tail[:data].pop + @tail = @tail[:forward] while @tail and @tail[:data].size == 0 + @set.delete(value) + value + end + + # Increase the lexical value of the given values. + # + # @param [Array] vertices values + def add_lexeme(values) + fix = {} + + values.select { |v| @set[v] }.each do |w| + sw = @set[w] + if fix[sw] + s_prime = sw[:back] + else + s_prime = @node.new(sw[:back], sw, []) + s_prime.instance_eval { @hash = (@@cnt += 1) } + @tail = s_prime if @tail == sw + sw[:back][:forward] = s_prime if sw[:back] + sw[:back] = s_prime + fix[sw] = true + end + + s_prime[:data] << w + sw[:data].delete(w) + @set[w] = s_prime + end + + fix.keys.select { |n| n[:data].size == 0 }.each do |e| + e[:forward][:back] = e[:back] if e[:forward] + e[:back][:forward] = e[:forward] if e[:back] + end + end + end - # Pop an entry with maximum lexical value from queue - def pop() - return nil unless @tail - value = @tail[:data].pop - @tail = @tail[:forward] while @tail and @tail[:data].size == 0 - @set.delete(value); value + # Lexicographic breadth-first search. + # + # The usual queue of vertices is replaced by a queue of *unordered subsets* + # of the vertices, which is sometimes refined but never reordered. + # + # Originally developed by Rose, Tarjan, and Leuker, *Algorithmic + # aspects of vertex elimination on graphs*, SIAM J. Comput. 5, 266-283 + # MR53 #12077 + # + # Implementation taken from Golumbic's, *Algorithmic Graph Theory and + # Perfect Graphs*, pg. 84-90. + # + # @return [vertex] + def lexicograph_bfs(&block) + lex_q = Graphy::Search::LexicographicQueue.new(vertices) + result = [] + num_vertices.times do + v = lex_q.pop + result.unshift(v) + lex_q.add_lexeme(adjacent(v)) + end + result.each { |r| block.call(r) } if block + result end - # Increase lexical value of given values (array) - def add_lexeme(values) - fix = {} - values.select {|v| @set[v]}.each do |w| - sw = @set[w] - if fix[sw] - s_prime = sw[:back] - else - s_prime = @node.new(sw[:back], sw, []) - s_prime.instance_eval { @hash = (@@cnt+=1) } - @tail = s_prime if @tail == sw - sw[:back][:forward] = s_prime if sw[:back] - sw[:back] = s_prime - fix[sw] = true + # A* Heuristic best first search. + # + # `start` is the starting vertex for the search. + # + # `func` is a `Proc` that when passed a vertex returns the heuristic + # weight of sending the path through that node. It must always + # be equal to or less than the true cost. + # + # `options` are mostly callbacks passed in as a hash, the default block is + # `:discover_vertex` and the weight is assumed to be the label for the {Arc}. + # The following options are valid, anything else is ignored: + # + # * `:weight` => can be a `Proc`, or anything else is accessed using the `[]` for the + # the label or it defaults to using + # the value stored in the label for the {Arc}. If it is a `Proc` it will + # pass the edge to the proc and use the resulting value. + # * `:discover_vertex` => `Proc` invoked when a vertex is first discovered + # and is added to the open list. + # * `:examine_vertex` => `Proc` invoked when a vertex is popped from the + # queue (i.e., it has the lowest cost on the open list). + # * `:examine_edge` => `Proc` invoked on each out-edge of a vertex + # immediately after it is examined. + # * `:edge_relaxed` => `Proc` invoked on edge `(u,v) if d[u] + w(u,v) < d[v]`. + # * `:edge_not_relaxed`=> `Proc` invoked if the edge is not relaxed (see above). + # * `:black_target` => `Proc` invoked when a vertex that is on the closed + # list is "rediscovered" via a more efficient path, and is re-added + # to the open list. + # * `:finish_vertex` => Proc invoked on a vertex when it is added to the + # closed list, which happens after all of its out edges have been + # examined. + # + # Can also be called like `astar_examine_edge {|e| ... }` or + # `astar_edge_relaxed {|e| ... }` for any of the callbacks. + # + # The criteria for expanding a vertex on the open list is that it has the + # lowest `f(v) = g(v) + h(v)` value of all vertices on open. + # + # The time complexity of A* depends on the heuristic. It is exponential + # in the worst case, but is polynomial when the heuristic function h + # meets the following condition: `|h(x) - h*(x)| < O(log h*(x))` where `h*` + # is the optimal heuristic, i.e. the exact cost to get from `x` to the `goal`. + # + # See also: [A* search algorithm](http://en.wikipedia.org/wiki/A*_search_algorithm) on Wikipedia. + # + # @param [vertex] start the starting vertex for the search + # @param [vertex] goal the vertex to reach + # @param [Proc] func heuristic weight computing process + # @param [Hash] options + # @return [Array(vertices), call, nil] an array of nodes in path, or calls block on all nodes, + # upon failure returns `nil` + def astar(start, goal, func, options, &block) + options.instance_eval "def handle_callback(sym,u) self[sym].call(u) if self[sym]; end" + + # Initialize. + d = { start => 0 } + color = { start => :gray } # Open is :gray, closed is :black. + parent = Hash.new { |k| parent[k] = k } + f = { start => func.call(start) } + queue = PriorityQueue.new.push(start, f[start]) + block.call(start) if block + + # Process queue. + until queue.empty? + u, dummy = queue.delete_min + options.handle_callback(:examine_vertex, u) + + # Unravel solution if the goal is reached. + if u == goal + solution = [goal] + while u != start + solution << parent[u]; u = parent[u] + end + return solution.reverse end - s_prime[:data] << w - sw[:data].delete(w) - @set[w] = s_prime - end - fix.keys.select {|n| n[:data].size == 0}.each do |e| - e[:forward][:back] = e[:back] if e[:forward] - e[:back][:forward] = e[:forward] if e[:back] - end - end - end + adjacent(u, :type => :edges).each do |e| + v = e.source == u ? e.target : e.source + options.handle_callback(:examine_edge, e) + w = cost(e, options[:weight]) + raise ArgumentError unless w + + if d[v].nil? or (w + d[u]) < d[v] + options.handle_callback(:edge_relaxed, e) + d[v] = w + d[u] + f[v] = d[v] + func.call(v) + parent[v] = u + + unless color[v] == :gray + options.handle_callback(:black_target, v) if color[v] == :black + color[v] = :gray + options.handle_callback(:discover_vertex, v) + queue.push v, f[v] + block.call(v) if block + end + else + options.handle_callback(:edge_not_relaxed, e) + end + end # adjacent(u) - # Lexicographic breadth-first search, the usual queue of vertices - # is replaced by a queue of unordered subsets of the vertices, - # which is sometimes refined but never reordered. - # - # Originally developed by Rose, Tarjan, and Leuker, "Algorithmic - # aspects of vertex elimination on graphs", SIAM J. Comput. 5, 266-283 - # MR53 #12077 - # - # Implementation taken from Golumbic's, "Algorithmic Graph Theory and - # Perfect Graphs" pg, 84-90 - def lexicograph_bfs(&block) - lex_q = Graphy::Graph::Search::LexicographicQueue.new(vertices) - result = [] - num_vertices.times do - v = lex_q.pop - result.unshift(v) - lex_q.add_lexeme(adjacent(v)) + color[u] = :black + options.handle_callback(:finish_vertex,u) + end # queue.empty? + + nil # failure, on fall through + end # astar + + # `best_first` has all the same options as {Search#astar astar}, with `func` set to `h(v) = 0`. + # There is an additional option, `zero`, which should be defined as the zero element + # for the `+` operation performed on the objects used in the computation of cost. + # + # @param [vertex] start the starting vertex for the search + # @param [vertex] goal the vertex to reach + # @param [Proc] func heuristic weight computing process + # @param [Hash] options + # @param [Integer] zero (0) + # @return [Array(vertices), call, nil] an array of nodes in path, or calls block on all nodes, + # upon failure returns `nil` + def best_first(start, goal, options, zero = 0, &block) + func = Proc.new { |v| zero } + astar(start, goal, func, options, &block) end - result.each {|r| block.call(r)} if block - result - end + # @private + alias_method :pre_search_method_missing, :method_missing + def method_missing(sym, *args, &block) + m1 = /^dfs_(\w+)$/.match(sym.to_s) + dfs((args[0] || {}).merge({ m1.captures[0].to_sym => block })) if m1 + m2 = /^bfs_(\w+)$/.match(sym.to_s) + bfs((args[0] || {}).merge({ m2.captures[0].to_sym => block })) if m2 + pre_search_method_missing(sym, *args, &block) unless m1 or m2 + end - # A* Heuristic best first search - # - # start is the starting vertex for the search - # - # func is a Proc that when passed a vertex returns the heuristic - # weight of sending the path through that node. It must always - # be equal to or less than the true cost - # - # options are mostly callbacks passed in as a hash, the default block is - # :discover_vertex and weight is assumed to be the label for the Arc. - # The following options are valid, anything else is ignored. - # - # * :weight => can be a Proc, or anything else is accessed using the [] for the - # the label or it defaults to using - # the value stored in the label for the Arc. If it is a Proc it will - # pass the edge to the proc and use the resulting value. - # * :discover_vertex => Proc invoked when a vertex is first discovered - # and is added to the open list. - # * :examine_vertex => Proc invoked when a vertex is popped from the - # queue (i.e., it has the lowest cost on the open list). - # * :examine_edge => Proc invoked on each out-edge of a vertex - # immediately after it is examined. - # * :edge_relaxed => Proc invoked on edge (u,v) if d[u] + w(u,v) < d[v]. - # * :edge_not_relaxed=> Proc invoked if the edge is not relaxed (see above). - # * :black_target => Proc invoked when a vertex that is on the closed - # list is "rediscovered" via a more efficient path, and is re-added - # to the OPEN list. - # * :finish_vertex => Proc invoked on a vertex when it is added to the - # closed list, which happens after all of its out edges have been - # examined. - # - # Returns array of nodes in path, or calls block on all nodes, - # upon failure returns nil - # - # Can also be called like astar_examine_edge {|e| ... } or - # astar_edge_relaxed {|e| ... } for any of the callbacks - # - # The criteria for expanding a vertex on the open list is that it has the - # lowest f(v) = g(v) + h(v) value of all vertices on open. - # - # The time complexity of A* depends on the heuristic. It is exponential - # in the worst case, but is polynomial when the heuristic function h - # meets the following condition: |h(x) - h*(x)| < O(log h*(x)) where h* - # is the optimal heuristic, i.e. the exact cost to get from x to the goal. - # - # Also see: http://en.wikipedia.org/wiki/A-star_search_algorithm - # - def astar(start, goal, func, options, &block) - options.instance_eval "def handle_callback(sym,u) self[sym].call(u) if self[sym]; end" - - # Initialize - d = { start => 0 } - - color = {start => :gray} # Open is :gray, Closed is :black - parent = Hash.new {|k| parent[k] = k} - f = {start => func.call(start)} - queue = PriorityQueue.new.push(start,f[start]) - block.call(start) if block - - # Process queue - until queue.empty? - u,dummy = queue.delete_min - options.handle_callback(:examine_vertex, u) - - # Unravel solution if goal is reached. - if u == goal - solution = [goal] - while u != start - solution << parent[u]; u = parent[u] + private + + # Performs the search using a specific algorithm and a set of options. + # + # @param [Symbol] op the algorithm to be used te perform the search + # @param [Hash] options + # @return [Object] result + def graphy_search_helper(op, options = {}, &block) + return nil if size == 0 + result = [] + + # Create the options hash handling callbacks. + options = {:enter_vertex => block, :start => to_a[0]}.merge(options) + options.instance_eval "def handle_vertex(sym,u) self[sym].call(u) if self[sym]; end" + options.instance_eval "def handle_edge(sym,e) self[sym].call(e) if self[sym]; end" + + # Create a waiting list, which is a queue or a stack, depending on the op specified. + # The first entry is the start vertex. + waiting = [options[:start]] + waiting.instance_eval "def next; #{op.to_s}; end" + + # Create a color map, all elements set to "unvisited" except for start vertex, + # which will be set to waiting. + color_map = vertices.inject({}) { |a,v| a[v] = :unvisited; a } + color_map.merge!(waiting[0] => :waiting) + options.handle_vertex(:start_vertex, waiting[0]) + options.handle_vertex(:root_vertex, waiting[0]) + + # Perform the actual search until nothing is "waiting". + until waiting.empty? + # Loop till the search iterator exhausts the waiting list. + visited_edges = {} # This prevents retraversing edges in undirected graphs. + until waiting.empty? + graphy_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop) end - return solution.reverse + # Waiting for the list to be exhausted, check if a new root vertex is available. + u = color_map.detect { |key,value| value == :unvisited } + waiting.push(u[0]) if u + options.handle_vertex(:root_vertex, u[0]) if u end - adjacent(u, :type => :edges).each do |e| - v = e.source == u ? e.target : e.source - options.handle_callback(:examine_edge, e) - w = cost(e, options[:weight]) - raise ArgumentError unless w - if d[v].nil? or (w + d[u]) < d[v] - options.handle_callback(:edge_relaxed, e) - d[v] = w + d[u] - f[v] = d[v] + func.call(v) - parent[v] = u - unless color[v] == :gray - options.handle_callback(:black_target, v) if color[v] == :black - color[v] = :gray - options.handle_callback(:discover_vertex, v) - queue.push v, f[v] - block.call(v) if block - end - else - options.handle_callback(:edge_not_relaxed, e) + result + end + + # Performs a search iteration (step). + # + # @private + def graphy_search_iteration(options, waiting, color_map, visited_edges, result, recursive = false) + # Fetch the next waiting vertex in the list. + #sleep + u = waiting.next + options.handle_vertex(:enter_vertex, u) + result << u + + # Examine all adjacent outgoing edges, but only those not previously traversed. + adj_proc = options[:adjacent] || self.method(:adjacent).to_proc + adj_proc.call(u, :type => :edges, :direction => :out).reject { |w| visited_edges[w] }.each do |e| + e = e.reverse unless directed? or e.source == u # Preserves directionality where required. + v = e.target + options.handle_edge(:examine_edge, e) + visited_edges[e] = true + + case color_map[v] + # If it's unvisited, it goes into the waiting list. + when :unvisited + options.handle_edge(:tree_edge, e) + color_map[v] = :waiting + waiting.push(v) + # If it's recursive (i.e. dfs), then call self. + graphy_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive + when :waiting + options.handle_edge(:back_edge, e) + else + options.handle_edge(:forward_edge, e) end - end # adjacent(u) - color[u] = :black - options.handle_callback(:finish_vertex,u) - end # queue.empty? - - nil # failure, on fall through - - end # astar - - # Best first has all the same options as astar with func set to h(v) = 0. - # There is an additional option zero which should be defined to zero - # for the operation '+' on the objects used in the computation of cost. - # The parameter zero defaults to 0. - def best_first(start, goal, options, zero=0, &block) - func = Proc.new {|v| zero} - astar(start, goal, func, options, &block) - end - - alias_method :pre_search_method_missing, :method_missing # :nodoc: - def method_missing(sym,*args, &block) # :nodoc: - m1=/^dfs_(\w+)$/.match(sym.to_s) - dfs((args[0] || {}).merge({m1.captures[0].to_sym => block})) if m1 - m2=/^bfs_(\w+)$/.match(sym.to_s) - bfs((args[0] || {}).merge({m2.captures[0].to_sym => block})) if m2 - pre_search_method_missing(sym, *args, &block) unless m1 or m2 - end - - private - - def graphy_search_helper(op, options={}, &block) # :nodoc: - return nil if size == 0 - result = [] - # Create options hash that handles callbacks - options = {:enter_vertex => block, :start => to_a[0]}.merge(options) - options.instance_eval "def handle_vertex(sym,u) self[sym].call(u) if self[sym]; end" - options.instance_eval "def handle_edge(sym,e) self[sym].call(e) if self[sym]; end" - # Create waiting list that is a queue or stack depending on op specified. - # First entry is the start vertex. - waiting = [options[:start]] - waiting.instance_eval "def next() #{op.to_s}; end" - # Create color map with all set to unvisited except for start vertex - # will be set to waiting - color_map = vertices.inject({}) {|a,v| a[v] = :unvisited; a} - color_map.merge!(waiting[0] => :waiting) - options.handle_vertex(:start_vertex, waiting[0]) - options.handle_vertex(:root_vertex, waiting[0]) - # Perform the actual search until nothing is waiting - until waiting.empty? - # Loop till the search iterator exhausts the waiting list - visited_edges={} # This prevents retraversing edges in undirected graphs - until waiting.empty? - graphy_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop) - end - # Waiting list is exhausted, see if a new root vertex is available - u=color_map.detect {|key,value| value == :unvisited} - waiting.push(u[0]) if u - options.handle_vertex(:root_vertex, u[0]) if u - end; result - end - - def graphy_search_iteration(options, waiting, color_map, visited_edges, result, recursive=false) # :nodoc: - # Get the next waiting vertex in the list - u = waiting.next - options.handle_vertex(:enter_vertex,u) - result << u - # Examine all adjacent outgoing edges, not previously traversed - adj_proc = options[:adjacent] || self.method(:adjacent).to_proc - adj_proc.call(u,:type => :edges, :direction => :out).reject {|w| visited_edges[w]}.each do |e| - e = e.reverse unless directed? or e.source == u # Preserves directionality where required - v = e.target - options.handle_edge(:examine_edge, e) - visited_edges[e]=true - case color_map[v] - # If it's unvisited it goes into the waiting list - when :unvisited - options.handle_edge(:tree_edge, e) - color_map[v] = :waiting - waiting.push(v) - # If it's recursive (i.e. dfs) then call self - graphy_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive - when :waiting - options.handle_edge(:back_edge, e) - else - options.handle_edge(:forward_edge, e) end + + # Done with this vertex! + options.handle_vertex(:exit_vertex, u) + color_map[u] = :visited end - # Finished with this vertex - options.handle_vertex(:exit_vertex, u) - color_map[u] = :visited - end - public - # Topological Sort Iterator - # - # The topological sort algorithm creates a linear ordering of the vertices - # such that if edge (u,v) appears in the graph, then u comes before v in - # the ordering. The graph must be a directed acyclic graph (DAG). - # - # The iterator can also be applied to undirected graph or to a DG graph - # which contains a cycle. In this case, the Iterator does not reach all - # vertices. The implementation of acyclic? and cyclic? uses this fact. - # - # Can be called with a block as a standard Ruby iterator, or it can - # be used directly as it will return the result as an Array - def topsort(start = nil, &block) - result = [] - go = true - back = Proc.new {|e| go = false } - push = Proc.new {|v| result.unshift(v) if go} - start ||= vertices[0] - dfs({:exit_vertex => push, :back_edge => back, :start => start}) - result.each {|v| block.call(v)} if block; result - end - - # Does a top sort, but trudges forward if a cycle occurs. Use with caution. - def sort(start = nil, &block) - result = [] - push = Proc.new {|v| result.unshift(v)} - start ||= vertices[0] - dfs({:exit_vertex => push, :start => start}) - result.each {|v| block.call(v)} if block; result - end - - # Returns true if a graph contains no cycles, false otherwise - def acyclic?() topsort.size == size; end - - # Returns false if a graph contains no cycles, true otherwise - def cyclic?() not acyclic?; end + public + + # Topological Sort Iterator. + # + # The topological sort algorithm creates a linear ordering of the vertices + # such that if edge (u,v) appears in the graph, then u comes before v in + # the ordering. The graph must be a directed acyclic graph (DAG). + # + # The iterator can also be applied to undirected graph or to a DG graph + # which contains a cycle. In this case, the Iterator does not reach all + # vertices. The implementation of acyclic? and cyclic? uses this fact. + # + # Can be called with a block as a standard ruby iterator, or can + # be used directly as it will return the result as an Array. + # + # @param [vertex] start (nil) the start vertex (nil will fallback on the first + # vertex inserted within the graph) + # @return [Array] a linear representation of the sorted graph + def topsort(start = nil, &block) + result = [] + go = true + back = Proc.new { |e| go = false } + push = Proc.new { |v| result.unshift(v) if go } + start ||= vertices[0] + dfs({ :exit_vertex => push, :back_edge => back, :start => start }) + result.each { |v| block.call(v) } if block + result + end + + # Does a top sort, but trudges forward if a cycle occurs. Use with caution. + # + # @param [vertex] start (nil) the start vertex (nil will fallback on the first + # vertex inserted within the graph) + # @return [Array] a linear representation of the sorted graph + def sort(start = nil, &block) + result = [] + push = Proc.new { |v| result.unshift(v) } + start ||= vertices[0] + dfs({ :exit_vertex => push, :start => start }) + result.each { |v| block.call(v) } if block + result + end + + # Returns true if a graph contains no cycles, false otherwise. + # + # @return [Boolean] + def acyclic? + topsort.size == size + end + + # Returns false if a graph contains no cycles, true otherwise. + # + # @return [Boolean] + def cyclic? + not acyclic? + end end # Search end # Graphy From 9e64333b931aa2971ccaf184e64fac14c5348b8c Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Tue, 30 Mar 2010 21:41:47 +0200 Subject: [PATCH 19/44] synched with evergreen --- graphy.gemspec | 2 +- lib/graphy.rb | 1 + lib/graphy/adjacency_graph.rb | 39 +++--- lib/graphy/arc.rb | 34 ++++-- lib/graphy/arc_number.rb | 49 +++++--- lib/graphy/classes/graph_classes.rb | 17 +++ lib/graphy/dot.rb | 63 ++++++---- lib/graphy/ext.rb | 7 +- lib/graphy/graph.rb | 178 ++++++++++++++++++++-------- lib/graphy/graph_api.rb | 2 +- lib/graphy/labels.rb | 98 +++++++++------ lib/graphy/search.rb | 6 +- spec/spec_helper.rb | 6 +- 13 files changed, 345 insertions(+), 157 deletions(-) diff --git a/graphy.gemspec b/graphy.gemspec index 7a4b0e1..c225347 100644 --- a/graphy.gemspec +++ b/graphy.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Bruce Williams"] - s.date = %q{2010-03-29} + s.date = %q{2010-03-30} s.description = %q{A framework for graph data structures and algorithms. This library is based on GRATR and RGL. diff --git a/lib/graphy.rb b/lib/graphy.rb index 3ce70d3..24f90b2 100644 --- a/lib/graphy.rb +++ b/lib/graphy.rb @@ -83,6 +83,7 @@ module Graphy $LOAD_PATH.unshift(path + '../../vendor/priority-queue/lib') require 'rdot' +require 'facets/hash' require 'priority_queue/ruby_priority_queue' PriorityQueue = RubyPriorityQueue diff --git a/lib/graphy/adjacency_graph.rb b/lib/graphy/adjacency_graph.rb index 5c495e9..febc7f7 100644 --- a/lib/graphy/adjacency_graph.rb +++ b/lib/graphy/adjacency_graph.rb @@ -7,7 +7,7 @@ module Graphy # implementations rely on the {GraphBuilder}, under the control of the {GraphAPI}. module AdjacencyGraphBuilder - # Useful `push` -> `add` aliasing for Array. + # Defines a useful `push` -> `add` alias for arrays. class ArrayWithAdd < Array alias add push end @@ -91,18 +91,17 @@ def add_vertex!(vertex, label = nil) # Adds an edge to the graph. # # Can be called in two basic ways, label is optional: - # @overload add_edge!(Arc[source,target], "Label") - # Using an explicit {Arc} object - # @param [Arc] arc - # @param [#to_s] label (nil) + # @overload add_edge!(arc) + # Using an explicit {Arc} + # @param [Arc] arc an {Arc}[source, target, label = nil] object # @return [AdjacencyGraph] `self` - # @overload add_edge!(source, target, "Label") - # Using vertices - # @param [vertex(Object)] u - # @param [vertex(Object)] v (nil) - # @param [#to_s] l (nil) - # @param [Integer] n (nil) numéro de l'{Arc} `(u,v)` (si `nil` et si `u` - # possède un {ArcNumber}, il sera utilisé) + # @overload add_edge!(source, target, label = nil) + # Using vertices to define an arc implicitly + # @param [vertex] u + # @param [vertex] v (nil) + # @param [Label] l (nil) + # @param [Integer] n (nil) {Arc arc} number of `(u, v)` (if `nil` and if `u` + # has an {ArcNumber}, then it will be used) # @return [AdjacencyGraph] `self` def add_edge!(u, v = nil, l = nil, n = nil) n = u.number if u.class.include? ArcNumber and n.nil? @@ -112,7 +111,7 @@ def add_edge!(u, v = nil, l = nil, n = nil) n = (@next_edge_number += 1) unless n if @parallel_edges add_vertex!(u) - add_vertex!(v) + add_vertex!(v) @vertex_dict[u].add(v) (@edge_number[u] ||= @edgelist_class.new).add(n) if @parallel_edges @@ -146,11 +145,15 @@ def remove_vertex!(v) # Can be called with both source and target as vertex, # or with source and object of {Graphy::Arc} derivation. # - # @param [vertex, Graphy::Arc] u if `u` is a {Graphy::Arc}, then `v` must be left out - # @param [vertex] v (nil) - # @return [AdjacencyGraph] `self` - # @raise [ArgumentError] if passed a {Graphy::Arc} while parallel edges are enabled - # @raise [ArgumentError] if parallel edges are enabled and the {ArcNumber} of `u` is zero + # @overload remove_edge!(a) + # @param [Graphy::Arc] a + # @return [AdjacencyGraph] `self` + # @raise [ArgumentError] if parallel edges are enabled + # @overload remove_edge!(u, v) + # @param [vertex] u + # @param [vertex] v + # @return [AdjacencyGraph] `self` + # @raise [ArgumentError] if parallel edges are enabled and the {ArcNumber} of `u` is zero def remove_edge!(u, v = nil) unless u.is_a? Graphy::Arc raise ArgumentError if @parallel_edges diff --git a/lib/graphy/arc.rb b/lib/graphy/arc.rb index 86f15c5..a40b999 100644 --- a/lib/graphy/arc.rb +++ b/lib/graphy/arc.rb @@ -13,17 +13,19 @@ def initialize(p_source, p_target, p_label = nil) super(p_source, p_target, p_label) end - # Ignore labels for equality - def eql?(other) self.class == other.class and target==other.target and source==other.source; end - - # Alias for eql? + # Ignore labels for equality. + def eql?(other) + self.class == other.class and target==other.target and source==other.source + end alias == eql? # Returns (v,u) if self == (u,v). def reverse() self.class.new(target, source, label); end - # Sort support - def <=>(rhs) [source,target] <=> [rhs.source,rhs.target]; end + # Sort support. + def <=>(rhs) + [source,target] <=> [rhs.source,rhs.target] + end # Arc.new[1,2].to_s => "(1-2 'label')" def to_s @@ -33,14 +35,26 @@ def to_s # Hash is defined in such a way that label is not # part of the hash value - def hash() source.hash ^ (target.hash+1); end + # FIXME: I had to get rid of that in order to make to_dot_graph + # work, but I can't figure it out (doesn't show up in the stack!) + #def hash + #puts "--- #{caller}" + ##puts source.inspect + ##puts target.inspect + #source.hash ^ (target.hash + 1) + #puts "---" + #end - # Shortcut constructor. Instead of Arc.new(1,2) one can use Arc[1,2] - def self.[](p_source, p_target, p_label=nil) + # Shortcut constructor. + # + # Instead of Arc.new(1,2) one can use Arc[1,2]. + def self.[](p_source, p_target, p_label = nil) new(p_source, p_target, p_label) end - def inspect() "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{label.inspect}]"; end + #def inspect + #"#{self.class.to_s}[#{source.inspect},#{target.inspect},#{label.inspect}]" + #end end # Arc diff --git a/lib/graphy/arc_number.rb b/lib/graphy/arc_number.rb index 287d167..c06151b 100644 --- a/lib/graphy/arc_number.rb +++ b/lib/graphy/arc_number.rb @@ -1,30 +1,49 @@ module Graphy - - # This module provides for internal numbering of edges for differentiating between mutliple edges + # This module handles internal numbering of edges in order to differente between mutliple edges. module ArcNumber + + # Used to differentiate between mutli-edges + attr_accessor :number - attr_accessor :number # Used to differentiate between mutli-edges - - def initialize(p_source,p_target,p_number,p_label=nil) + def initialize(p_source, p_target, p_number, p_label = nil) self.number = p_number super(p_source, p_target, p_label) end # Returns (v,u) if self == (u,v). - def reverse() self.class.new(target, source, number, label); end + def reverse + self.class.new(target, source, number, label) + end + + # Allow for hashing of self loops. + def hash + super ^ number.hash + end + + def to_s + super + "[#{number}]" + end + + def <=>(rhs) + (result = super(rhs)) == 0 ? number <=> rhs.number : result + end - # Allow for hashing of self loops - def hash() super ^ number.hash; end - def to_s() super + "[#{number}]"; end - def <=>(rhs) (result = super(rhs)) == 0 ? number <=> rhs.number : result; end - def inspect() "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{number.inspect},#{label.inspect}]"; end - def eql?(rhs) super(rhs) and (rhs.number.nil? or number.nil? or number == rhs.number); end - def ==(rhs) eql?(rhs); end + def inspect + "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{number.inspect},#{label.inspect}]" + end + + def eql?(rhs) + super(rhs) and (rhs.number.nil? or number.nil? or number == rhs.number) + end + + def ==(rhs) + eql?(rhs) + end # Shortcut constructor. Instead of Arc.new(1,2) one can use Arc[1,2] def self.included(cl) - - def cl.[](p_source, p_target, p_number=nil, p_label=nil) + # FIXME: lacks a cl.class_eval, no? + def cl.[](p_source, p_target, p_number = nil, p_label = nil) new(p_source, p_target, p_number, p_label) end end diff --git a/lib/graphy/classes/graph_classes.rb b/lib/graphy/classes/graph_classes.rb index 16c7ec3..3db4aec 100644 --- a/lib/graphy/classes/graph_classes.rb +++ b/lib/graphy/classes/graph_classes.rb @@ -1,11 +1,28 @@ module Graphy + # A generic {GraphBuilder Graph} class you can inherit from. class Graph; include GraphBuilder; end + + # A generic {AdjacencyGraphBuilder AdjacencyGraph} class you can inherit from. class AdjacencyGraph; include AdjacencyGraphBuilder; end + + # A generic {DirectedGraphBuilder DirectedGraph} class you can inherit from. class DirectedGraph; include DirectedGraphBuilder; end + + # A generic {DigraphBuilder Digraph} class you can inherit from. class Digraph; include DigraphBuilder; end + + # A generic {DirectedPseudoGraphBuilder DirectedPseudoGraph} class you can inherit from. class DirectedPseudoGraph; include DirectedPseudoGraphBuilder; end + + # A generic {DirectedMultiGraphBuilder DirectedMultiGraph} class you can inherit from. class DirectedMultiGraph; include DirectedMultiGraphBuilder; end + + # A generic {UndirectedGraphBuilder UndirectedGraph} class you can inherit from. class UndirectedGraph; include UndirectedGraphBuilder; end + + # A generic {UndirectedPseudoGraphBuilder UndirectedPseudoGraph} class you can inherit from. class UndirectedPseudoGraph; include UndirectedPseudoGraphBuilder; end + + # A generic {UndirectedMultiGraphBuilder UndirectedMultiGraph} class you can inherit from. class UndirectedMultiGraph; include UndirectedMultiGraphBuilder; end end diff --git a/lib/graphy/dot.rb b/lib/graphy/dot.rb index e013c5f..d752093 100644 --- a/lib/graphy/dot.rb +++ b/lib/graphy/dot.rb @@ -5,36 +5,56 @@ module Dot # RDoc ships with a dot.rb which seems pretty efficient. # Are these helpers still needed, and if not, how should we replace them? - # Return a DOT::DOTDigraph for directed graphs or a DOT::DOTSubgraph for an - # undirected Graph. _params_ can contain any graph property specified in - # rdot.rb. If an edge or vertex label is a kind of Hash then the keys - # which match +dot+ properties will be used as well. - def to_dot_graph (params = {}) - params['name'] ||= self.class.name.gsub(/:/,'_') - fontsize = params['fontsize'] ? params['fontsize'] : '8' - graph = (directed? ? DOT::DOTDigraph : DOT::DOTSubgraph).new(params) - edge_klass = directed? ? DOT::DOTDirectedArc : DOT::DOTArc + # Creates a DOT::DOTDigraph for directed graphs or a DOT::DOTSubgraph for + # undirected graphs. + # + # @param [Hash] params can contain any graph property specified in + # rdot.rb. If an edge or vertex label is a kind of Hash, then the keys + # which match dot properties will be used as well. + # @return [DOT::DOTDigraph, DOT::DOTSubgraph] + def to_dot_graph(params = {}) + params['name'] ||= self.class.name.gsub(/:/, '_') + fontsize = params['fontsize'] ? params['fontsize'] : '8' + graph = (directed? ? DOT::DOTDigraph : DOT::DOTSubgraph).new(params) + edge_klass = directed? ? DOT::DOTDirectedArc : DOT::DOTArc vertices.each do |v| - name = v.to_s - if name.nil? - #name = v.class.to_s + ":" + v.__id__.to_s - name = v.name - end + name = v.to_s || v.__id__.to_s + name = name.dup.gsub(/"/, "'") + params = { 'name' => '"'+ name +'"', 'fontsize' => fontsize, 'label' => name} + v_label = vertex_label(v) params.merge!(v_label) if v_label and v_label.kind_of? Hash + graph << DOT::DOTNode.new(params) end edges.each do |e| - params = { 'from' => '"'+ e.source.to_s + '"', - 'to' => '"'+ e.target.to_s + '"', + if e.source.to_s.nil? + source_label = e.source.__id__.to_s + else + source_label = e.source.to_s.dup + end + + if e.target.to_s.nil? + target_label = e.target.__id__.to_s + else + target_label = e.target.to_s.dup + end + + source_label.gsub!(/"/, "'") + target_label.gsub!(/"/, "'") + + params = { 'from' => '"'+ source_label + '"', + 'to' => '"'+ target_label + '"', 'fontsize' => fontsize } + e_label = edge_label(e) params.merge!(e_label) if e_label and e_label.kind_of? Hash + graph << edge_klass.new(params) end @@ -42,26 +62,29 @@ def to_dot_graph (params = {}) end # Output the dot format as a string - def to_dot (params={}) to_dot_graph(params).to_s; end + def to_dot(params = {}) + to_dot_graph(params).to_s + end # Call +dotty+ for the graph which is written to the file 'graph.dot' # in the # current directory. - def dotty (params = {}, dotfile = 'graph.dot') + def dotty(params = {}, dotfile = 'graph.dot') File.open(dotfile, 'w') {|f| f << to_dot(params) } system('dotty', dotfile) end # Use +dot+ to create a graphical representation of the graph. Returns the # filename of the graphics file. - def write_to_graphic_file (fmt='png', dotfile='graph') + def write_to_graphic_file(fmt = 'png', dotfile = 'graph') src = dotfile + '.dot' dot = dotfile + '.' + fmt File.open(src, 'w') {|f| f << self.to_dot << "\n"} - system( "dot -T#{fmt} #{src} -o #{dot}" ) + dot end + alias as_dot_graphic write_to_graphic_file end # Dot end # module Graphy diff --git a/lib/graphy/ext.rb b/lib/graphy/ext.rb index ee32f07..5694b14 100644 --- a/lib/graphy/ext.rb +++ b/lib/graphy/ext.rb @@ -33,6 +33,7 @@ def singleton_class # a.is_a? Graphy::Graph # # => true # + # @param [Class] klass # @return [Boolean] def is_a? klass sc = self.singleton_class @@ -46,8 +47,8 @@ def is_a? klass class Module # Helper which purpose is, given a class including a module, - # each methods defined within a module `ClassMethods` in the - # module as class methods to the class. + # to make each methods defined within a module's submodule `ClassMethods` + # available as class methods to the receiving class. # # Example: # @@ -63,7 +64,7 @@ class Module # B.selfy # # => class method for B # - # @option *params [Symbol] (:ClassMethods) :with the name of the + # @option *params [Symbol] :with (:ClassMethods) the name of the # module to extend the receiver with def extends_host(*params) args = (params.pop if params.last.is_a? Hash) || {} diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb index 4c6666f..d289a0c 100644 --- a/lib/graphy/graph.rb +++ b/lib/graphy/graph.rb @@ -156,29 +156,42 @@ def adjacent(x, options={}) alias graph_adjacent adjacent - # Add all objects in _a_ to the vertex set. + # Adds all specified vertices to the vertex set. + # + # @param [#each] *a an Enumerable vertices set + # @return [Graph] `self` def add_vertices!(*a) - a.each {|v| add_vertex! v} + a.each { |v| add_vertex! v } self end - # See add_vertices! + # Same as {GraphBuilder#add_vertices! add_vertices!} but works on copy of the receiver. + # + # @param [#each] *a + # @return [Graph] a modified copy of `self` def add_vertices(*a) x = self.class.new(self) x.add_vertices(*a) self end - # Add all edges in the _edges_ Enumerable to the edge set. Elements of the - # Enumerable can be both two-element arrays or instances of DirectedArc or - # UnDirectedArc. - def add_edges!(*args) - args.each { |edge| add_edge!(edge) } + # Adds all edges mentionned in the specified Enumerable to the edge set. + # + # Elements of the Enumerable can be either two-element arrays or instances of + # {Edge} or {Arc}. + # + # @param [#each] *a an Enumerable edges set + # @return [Graph] `self` + def add_edges!(*a) + a.each { |edge| add_edge!(edge) } self end alias add_arcs! add_edges! - # See add_edge! + # Same as {GraphBuilder#add_egdes! add_edges!} but works on a copy of the receiver. + # + # @param [#each] *a an Enumerable edges set + # @return [Graph] a modified copy of `self` def add_edges(*a) x = self.class.new(self) x.add_edges!(*a) @@ -186,22 +199,33 @@ def add_edges(*a) end alias add_arcs add_edges - # Remove all vertices specified by the Enumerable a from the graph by - # calling remove_vertex!. + # Removes all vertices mentionned in the specified Enumerable from the graph. + # + # The process relies on {GraphBuilder#remove_vertex! remove_vertex!}. + # + # @param [#each] *a an Enumerable vertices set + # @return [Graph] `self` def remove_vertices!(*a) a.each { |v| remove_vertex! v } end alias delete_vertices! remove_vertices! - # See remove_vertices! + # Same as {GraphBuilder#remove_vertices! remove_vertices!} but works on a copy of the receiver. + # + # @param [#each] *a a vertex Enumerable set + # @return [Graph] a modified copy of `self` def remove_vertices(*a) x = self.class.new(self) x.remove_vertices(*a) end alias delete_vertices remove_vertices - # Remove all vertices edges by the Enumerable a from the graph by - # calling remove_edge! + # Removes all edges mentionned in the specified Enumerable from the graph. + # + # The process relies on {GraphBuilder#remove_edges! remove_edges!}. + # + # @param [#each] *a an Enumerable edges set + # @return [Graph] `self` def remove_edges!(*a) a.each { |e| remove_edges! e } end @@ -209,42 +233,63 @@ def remove_edges!(*a) alias delete_edges! remove_edges! alias delete_arcs! remove_edges! - # See remove_edges + # Same as {GraphBuilder#remove_edges! remove_edges!} but works on a copy of the receiver. + # + # @param [#each] *a an Enumerable edges set + # @return [Graph] a modified copy of `self` def remove_edges(*a) x = self.class.new(self) - x.remove_edges(*a) + x.remove_edges!(*a) end alias remove_arcs remove_edges alias delete_edges remove_edges alias delete_arcs remove_edges - # Execute given block for each vertex, provides for methods in Enumerable + # Executes the given block for each vertex. It allows for mixing Enumerable in. def each(&block) vertices.each(&block) end - # Returns true if _v_ is a vertex of the graph. + # Returns true if the specified vertex belongs to the graph. + # # This is a default implementation that is of O(n) average complexity. # If a subclass uses a hash to store vertices, then this can be # made into an O(1) average complexity operation. + # + # @param [vertex] v + # @return [Boolean] def vertex?(v) vertices.include?(v) end alias has_vertex? vertex? - - # Returns true if u or (u,v) is an Arc of the graph. - def edge?(*arg) + # TODO: (has_)vertices? + + # Returns true if u or (u,v) is an {Edge edge} of the graph. + # + # @overload edge?(a) + # @param [Arc, Edge] a + # @return [Boolean] + # @overload edge?(u, v) + # @param [vertex] u + # @param [vertex] v + # @return [Boolean] + def edge?(*args) edges.include?(edge_convert(*args)) end alias arc? edge? + alias has_edge? edge? + alias has_arc? edge? # Tests two objects to see if they are adjacent. - # direction parameter specifies direction of adjacency, :in, :out, or :all(default) - # All denotes that if there is any adjacency, then it will return true. - # Note that the default is different than adjacent where one is primarily concerned with finding - # all adjacent objects in a graph to a given object. Here the concern is primarily on seeing - # if two objects touch. For vertexes, any edge between the two will usually do, but the direction - # can be specified if need be. + # + # Note that in this method, one is primarily concerned with finding + # all adjacent objects in a graph to a given object. The concern is primarily on seeing + # if two objects touch. For two vertexes, any edge between the two will usually do, but + # the direction can be specified if needed. + # + # @param [vertex] source + # @param [vertex] target + # @param [Symbol] direction (:all) constraint on the direction of adjacency; may be either `:in`, `:out` or `:all` def adjacent?(source, target, direction = :all) if source.is_a? Graphy::Arc raise NoArcError unless edge? source @@ -267,23 +312,56 @@ def adjacent?(source, target, direction = :all) end end - # Returns true if the graph has no vertex, i.e. num_vertices == 0. + # Is the graph connected? + # + # A graph is called connected if every pair of distinct vertices in the graph + # can be connected through some path. The exact definition depends on whether + # the graph is directed or not, hence this method should overriden in specific + # implementations. + # + # This methods implements a lazy routine using the internal vertices hash. + # If you ever want to check connectivity state using a bfs/dfs algorithm, use + # the `:algo => :bfs` or `:dfs` option. + # + # @return [Boolean] `true` if the graph is connected, `false` otherwise + def connected?(options = {}) + options = options.reverse_merge! :algo => :bfs + if options[:algo] == (:bfs || :dfs) + num_nodes = 0 + send(options[:algo]) { |n| num_nodes += 1 } + return num_nodes == @vertex_dict.size + else + !@vertex_dict.collect { |v| degree(v) > 0 }.any? { |check| check == false } + end + end + # TODO: do it! + # TODO: for directed graphs, add weakly_connected? and strongly_connected? (aliased as strong?) + # TODO: in the context of vertices/Arc, add connected_vertices? and disconnected_vertices? + # TODO: maybe implement some routine which would compute cuts and connectivity? tricky though, + # but would be useful (k_connected?(k)) + + # Returns true if the graph has no vertex. + # + # @return [Boolean] def empty? vertices.size.zero? end - # Returns true if the given object is a vertex or Arc in the Graph. + # Returns true if the given object is a vertex or an {Arc arc} of the graph. + # + # @param [vertex, Arc] x def include?(x) x.is_a?(Graphy::Arc) ? edge?(x) : vertex?(x) end alias has? include? - # Returns the neighboorhood of the given vertex (or {Arc}). + # Returns the neighborhood of the given vertex or {Arc arc}. # - # This is equivalent to {GraphBuilder#adjacent adjacent}, but bases type on the type of object. + # This is equivalent to {GraphBuilder#adjacent adjacent}, but the type is based on the + # type of the specified object. # - # @param [vertex] x - # @param [Symbol] direction can be `:all`, `:in`, or `:out` + # @param [vertex, Arc] x + # @param [Symbol] direction (:all) can be either `:all`, `:in` or `:out` def neighborhood(x, direction = :all) adjacent(x, :direction => direction, :type => ((x.is_a? Graphy::Arc) ? :edges : :vertices )) end @@ -293,7 +371,7 @@ def neighborhood(x, direction = :all) # Definition taken from: Jorgen Bang-Jensen, Gregory Gutin, *Digraphs: Theory, Algorithms and Applications*, pg. 4 # # @param [vertex] x - # @param [Symbol] direction can be `:all`, `:in`, or `:out` + # @param [Symbol] direction can be either `:all`, `:in` or `:out` def set_neighborhood(x, direction = :all) x.inject(Set.new) { |a,v| a.merge(neighborhood(v, idirection))}.reject { |v2| x.include?(v2) } end @@ -359,7 +437,7 @@ def in_degree(v) # Returns the sum of the number in and out edges for the specified vertex. # # @param [vertex] v - # @returm [Integer] degree + # @return [Integer] degree def degree(v) in_degree(v) + out_degree(v) end @@ -518,21 +596,21 @@ def induced_subgraph(v) end end - def inspect - # FIXME: broken, it's not updated. The issue's not with inspect, but it's worth mentionning here. - # Example: - # dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] - # dg.add_vertices! 1, 5, "yosh" - # # => Graphy::Digraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[6,4,nil]] - # dg.vertex?("yosh") - # # => true - # dg - # # =>Graphy::Digraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[6,4,nil]] - # the new vertex doesn't show up. - # Actually this version of inspect is far too verbose IMO :) - l = vertices.select { |v| self[v]}.map { |u| "vertex_label_set(#{u.inspect}, #{self[u].inspect})"}.join('.') - self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l && l != '' ? '.'+l : '') - end + #def inspect + ## FIXME: broken, it's not updated. The issue's not with inspect, but it's worth mentionning here. + ## Example: + ## dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] + ## dg.add_vertices! 1, 5, "yosh" + ## # => Graphy::Digraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[6,4,nil]] + ## dg.vertex?("yosh") + ## # => true + ## dg + ## # =>Graphy::Digraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[6,4,nil]] + ## the new vertex doesn't show up. + ## Actually this version of inspect is far too verbose IMO :) + #l = vertices.select { |v| self[v]}.map { |u| "vertex_label_set(#{u.inspect}, #{self[u].inspect})"}.join('.') + #self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l && l != '' ? '.'+l : '') + #end private diff --git a/lib/graphy/graph_api.rb b/lib/graphy/graph_api.rb index 435c075..877b6d1 100644 --- a/lib/graphy/graph_api.rb +++ b/lib/graphy/graph_api.rb @@ -1,5 +1,5 @@ module Graphy - # This module defines the minimum set of functions required to make a graph class that can + # This module defines the minimum set of functions required to make a graph that can # use the algorithms defined by this library. # # Each implementation module must implement the following routines: diff --git a/lib/graphy/labels.rb b/lib/graphy/labels.rb index 1c41a0d..5b5e581 100644 --- a/lib/graphy/labels.rb +++ b/lib/graphy/labels.rb @@ -1,58 +1,90 @@ module Graphy + # This module add support for labels. + # + # The graph labeling process consist in assigning labels, traditionally represented + # by integers, to the edges or vertices, or both, of a graph. Graphy recommands you + # abide by this rule and do use integers as labels. + # + # Some algorithms can make use of labeling (sea {Graphy::Search} for instance). module Labels - # Return a label for an edge or vertex - def [](u) (u.is_a? Graphy::Arc) ? edge_label(u) : vertex_label(u); end + # Return a label for an edge or vertex. + def [](u) + (u.is_a? Graphy::Arc) ? edge_label(u) : vertex_label(u) + end - # Set a label for an edge or vertex - def []= (u, value) (u.is_a? Graphy::Arc) ? edge_label_set(u,value) : vertex_label_set(u, value); end + # Set a label for an edge or vertex. + def []=(u, value) + (u.is_a? Graphy::Arc) ? edge_label_set(u, value) : vertex_label_set(u, value) + end - # Delete a label entirely - def delete_label(u) (u.is_a? Graphy::Arc) ? edge_label_delete(u) : vertex_label_delete(u); end + # Delete a label entirely. + def delete_label(u) + (u.is_a? Graphy::Arc) ? edge_label_delete(u) : vertex_label_delete(u) + end - # Get the label for an edge - def vertex_label(v) vertex_label_dict[v]; end + # Get the label for an edge. + def vertex_label(v) + vertex_label_dict[v] + end - # Set the label for an edge - def vertex_label_set(v, l) vertex_label_dict[v] = l; self; end + # Set the label for an edge. + def vertex_label_set(v, l) + vertex_label_dict[v] = l + self + end - # Get the label for an edge - def edge_label(u,v=nil,n=nil) + # Get the label for an edge. + def edge_label(u, v = nil, n = nil) u = edge_convert(u,v,n) edge_label_dict[u] end - # Set the label for an edge - def edge_label_set(u, v=nil, l=nil, n=nil) - u.is_a?(Graphy::Arc) ? l = v : u = edge_convert(u,v,n) - edge_label_dict[u] = l; self + # Set the label for an edge. + def edge_label_set(u, v = nil, l = nil, n = nil) + u.is_a?(Graphy::Arc) ? l = v : u = edge_convert(u, v, n) + edge_label_dict[u] = l + self end - # Delete all graph labels - def clear_all_labels() @vertex_labels = {}; @edge_labels = {}; end + # Delete all graph labels. + def clear_all_labels + @vertex_labels = {} + @edge_labels = {} + end - # Delete an edge label - def edge_label_delete(u, v=nil, n=nil) - u = edge_convert(u,v,n) + # Delete an edge label. + def edge_label_delete(u, v = nil, n = nil) + u = edge_convert(u, v, n) edge_label_dict.delete(u) end - # Delete a vertex label - def vertex_label_delete(v) vertex_label_dict.delete(v); end + # Delete a vertex label. + def vertex_label_delete(v) + vertex_label_dict.delete(v) + end protected - def vertex_label_dict() @vertex_labels ||= {}; end - def edge_label_dict() @edge_labels ||= {}; end + def vertex_label_dict + @vertex_labels ||= {} + end + + def edge_label_dict + @edge_labels ||= {} + end - # A generic cost function. It either calls the weight function with and edge - # constructed from the two nodes, or calls the [] operator of the label - # when given a value. If no weight value is specified, the label itself is - # treated as the cost value. + # A generic cost function. + # + # It either calls the `weight` function with an edge constructed from the + # two specified nodes, or calls the `[]` operator of the label when given + # a single value. + # + # If no weight value is specified, the label itself is treated as the cost value. # # Note: This function will not work for Pseudo or Multi graphs at present. # FIXME: Remove u,v interface to fix Pseudo Multi graph problems. - def cost(u,v=nil,weight=nil) + def cost(u, v = nil, weight = nil) u.is_a?(Arc) ? weight = v : u = edge_class[u,v] case weight when Proc @@ -63,12 +95,10 @@ def cost(u,v=nil,weight=nil) self[u][weight] end end - - # An alias of cost for property retrieval in general - alias property cost + alias property cost # makes sense for property retrieval in general # A function to set properties specified by the user. - def property_set(u,name,value) + def property_set(u, name, value) case name when Proc name.call(value) diff --git a/lib/graphy/search.rb b/lib/graphy/search.rb index d05bc02..993db25 100644 --- a/lib/graphy/search.rb +++ b/lib/graphy/search.rb @@ -305,7 +305,8 @@ def astar(start, goal, func, options, &block) if u == goal solution = [goal] while u != start - solution << parent[u]; u = parent[u] + solution << parent[u] + u = parent[u] end return solution.reverse end @@ -314,6 +315,7 @@ def astar(start, goal, func, options, &block) v = e.source == u ? e.target : e.source options.handle_callback(:examine_edge, e) w = cost(e, options[:weight]) + puts w.inspect raise ArgumentError unless w if d[v].nil? or (w + d[u]) < d[v] @@ -335,7 +337,7 @@ def astar(start, goal, func, options, &block) end # adjacent(u) color[u] = :black - options.handle_callback(:finish_vertex,u) + options.handle_callback(:finish_vertex, u) end # queue.empty? nil # failure, on fall through diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 698c6fb..ab7c989 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,7 +8,7 @@ module AncestryHelper - # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles + # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles # Golumbic, 1980, Academic Press, page 38, Figure 2.6 def assign_dfsnumber_ancestry(graph, dfsnumber, father, start) i = 0 @@ -19,7 +19,7 @@ def assign_dfsnumber_ancestry(graph, dfsnumber, father, start) graph.dfs({:enter_vertex => ev, :tree_edge => te, :start => start}) end - # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles + # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles # Golumbic, 1980, Academic Press, page 40, Figure 2.7 def assign_bfsnumber_ancestry(graph, bfsnum, level, father, start) i = 0 @@ -34,7 +34,7 @@ def assign_bfsnumber_ancestry(graph, bfsnum, level, father, start) end - # Is v an ancestor of u + # Is v an ancestor of u? def ancestor?(father, u, v) i = 1 while v From e08b0feb7d218ce0a86ecd585ffa0b895218def0 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Wed, 31 Mar 2010 12:38:06 +0200 Subject: [PATCH 20/44] added support.rb as a placeholder for generic helpers --- graphy.gemspec | 2 +- lib/graphy/adjacency_graph.rb | 6 +++--- lib/graphy/graph.rb | 21 ++++++++++----------- lib/graphy/search.rb | 1 - lib/graphy/support/support.rb | 9 +++++++++ lib/graphy/undirected_graph.rb | 4 ++-- 6 files changed, 25 insertions(+), 18 deletions(-) create mode 100644 lib/graphy/support/support.rb diff --git a/graphy.gemspec b/graphy.gemspec index c225347..fff856f 100644 --- a/graphy.gemspec +++ b/graphy.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Bruce Williams"] - s.date = %q{2010-03-30} + s.date = %q{2010-03-31} s.description = %q{A framework for graph data structures and algorithms. This library is based on GRATR and RGL. diff --git a/lib/graphy/adjacency_graph.rb b/lib/graphy/adjacency_graph.rb index febc7f7..2f98f7a 100644 --- a/lib/graphy/adjacency_graph.rb +++ b/lib/graphy/adjacency_graph.rb @@ -42,7 +42,7 @@ def implementation_initialize(*params) end # Copy any given graph into this graph. - params.select { |p| p.is_a? Graphy::Graph }.each do |g| + params.select { |p| p.is_a? Graphy::GraphBuilder }.each do |g| g.edges.each do |e| add_edge!(e) edge_label_set(e, edge_label(e)) if edge_label(e) @@ -209,8 +209,8 @@ def adjacent(x, options = {}) if options[:type] == :edges i = -1 @parallel_edges ? - @vertex_dict[x].map { |v| e=edge_class[x, v, @edge_number[x][i+=1]]; e.label = self[e]; e} : - @vertex_dict[x].map { |v| e=edge_class[x, v]; e.label = self[e]; e} + @vertex_dict[x].map { |v| e = edge_class[x, v, @edge_number[x][i+=1]]; e.label = self[e]; e } : + @vertex_dict[x].map { |v| e = edge_class[x, v]; e.label = self[e]; e } else @vertex_dict[x].to_a end diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb index d289a0c..944b85a 100644 --- a/lib/graphy/graph.rb +++ b/lib/graphy/graph.rb @@ -27,7 +27,7 @@ def [](*a) end end - # Create a generic graph. + # Creates a generic graph. # # @param [Hash(Graphy::Graph, Array)] *params initialization parameters. # See {AdjacencyGraphBuilder#implementation_initialize} for more details. @@ -47,9 +47,9 @@ class << self self end.module_eval do # These inclusions trigger some validations checks by the way. - include(args[:implementation] ? args[:implementation] : AdjacencyGraphBuilder) - include(args[:algorithmic_category] ? args[:algorithmic_category] : DigraphBuilder ) - include GraphAPI + include(args[:implementation] ? args[:implementation] : Graphy::AdjacencyGraphBuilder) + include(args[:algorithmic_category] ? args[:algorithmic_category] : Graphy::DigraphBuilder ) + include Graphy::GraphAPI end implementation_initialize(*params) @@ -92,23 +92,23 @@ def from_array(*a) # Non destructive version of {AdjacencyGraphBuilder#add_vertex!} (works on a copy of the graph). # - # @param [vertex] u - # @param [#to_s] l + # @param [vertex] v + # @param [Label] l # @return [Graph] a new graph with the supplementary vertex def add_vertex(v, l = nil) x = self.class.new(self) - x.add_vertex!(v,l) + x.add_vertex!(v, l) end # Non destructive version {AdjacencyGraphBuilder#add_edge!} (works on a copy of the graph). # # @param [vertex] u # @param [vertex] v - # @param [#to_s] l + # @param [Label] l # @return [Graph] a new graph with the supplementary edge def add_edge(u, v = nil, l = nil) x = self.class.new(self) - x.add_edge!(u,v,l) + x.add_edge!(u, v, l) end alias add_arc add_edge @@ -268,11 +268,10 @@ def vertex?(v) # # @overload edge?(a) # @param [Arc, Edge] a - # @return [Boolean] # @overload edge?(u, v) # @param [vertex] u # @param [vertex] v - # @return [Boolean] + # @return [Boolean] def edge?(*args) edges.include?(edge_convert(*args)) end diff --git a/lib/graphy/search.rb b/lib/graphy/search.rb index 993db25..f2b746b 100644 --- a/lib/graphy/search.rb +++ b/lib/graphy/search.rb @@ -315,7 +315,6 @@ def astar(start, goal, func, options, &block) v = e.source == u ? e.target : e.source options.handle_callback(:examine_edge, e) w = cost(e, options[:weight]) - puts w.inspect raise ArgumentError unless w if d[v].nil? or (w + d[u]) < d[v] diff --git a/lib/graphy/support/support.rb b/lib/graphy/support/support.rb new file mode 100644 index 0000000..61dbfe5 --- /dev/null +++ b/lib/graphy/support/support.rb @@ -0,0 +1,9 @@ +module Graphy + # Errors + # TODO FIXME: must review all raise lines and streamline things + + # Base error class for the library. + class GraphyError < StandardError; end + + class NoArcError < GraphyError; end +end diff --git a/lib/graphy/undirected_graph.rb b/lib/graphy/undirected_graph.rb index c4f7688..f5de9b1 100644 --- a/lib/graphy/undirected_graph.rb +++ b/lib/graphy/undirected_graph.rb @@ -4,7 +4,7 @@ module UndirectedGraphBuilder autoload :Algorithms, "graphy/undirected_graph/algorithms" - include GraphBuilder + include Graphy::GraphBuilder extends_host module ClassMethods def [](*a) @@ -14,7 +14,7 @@ def [](*a) def initialize(*params) args = (params.pop if params.last.kind_of? Hash) || {} - args[:algorithmic_category] = UndirectedGraphBuilder::Algorithms + args[:algorithmic_category] = Graphy::UndirectedGraphBuilder::Algorithms super *(params << args) end end From c0b7a5cca13d3af3b22fdfa9f55d525597a69b9e Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Wed, 31 Mar 2010 13:42:47 +0200 Subject: [PATCH 21/44] minor tweaks --- graphy.gemspec | 1 + lib/graphy/graph.rb | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/graphy.gemspec b/graphy.gemspec index fff856f..fae4bb9 100644 --- a/graphy.gemspec +++ b/graphy.gemspec @@ -65,6 +65,7 @@ Graph algorithms currently provided are: "lib/graphy/ruby_compatibility.rb", "lib/graphy/search.rb", "lib/graphy/strong_components.rb", + "lib/graphy/support/support.rb", "lib/graphy/undirected_graph.rb", "lib/graphy/undirected_graph/algorithms.rb", "spec/biconnected_spec.rb", diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb index 944b85a..0c0f05a 100644 --- a/lib/graphy/graph.rb +++ b/lib/graphy/graph.rb @@ -227,7 +227,7 @@ def remove_vertices(*a) # @param [#each] *a an Enumerable edges set # @return [Graph] `self` def remove_edges!(*a) - a.each { |e| remove_edges! e } + a.each { |e| remove_edge! e } end alias remove_arcs! remove_edges! alias delete_edges! remove_edges! @@ -505,6 +505,14 @@ def size alias num_vertices size alias number_of_vertices size + # Number of vertices. + # + # @return [Integer] + def num_vertices + vertices.size + end + alias number_of_vertices num_vertices + # Number of edges. # # @return [Integer] From 7d1a71acb77ea0594b8dbb9ea7adea81cb5bb247 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Wed, 31 Mar 2010 14:42:00 +0200 Subject: [PATCH 22/44] fixed a bug when merging graph upon creation. --- lib/graphy/adjacency_graph.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/graphy/adjacency_graph.rb b/lib/graphy/adjacency_graph.rb index 2f98f7a..3311192 100644 --- a/lib/graphy/adjacency_graph.rb +++ b/lib/graphy/adjacency_graph.rb @@ -31,6 +31,7 @@ def implementation_initialize(*params) # extract_options! and facets' reverse_merge! technique # to handle parameters args = (params.pop if params.last.is_a? Hash) || {} + puts args.inspect # Basic configuration of adjacency. @allow_loops = args[:loops] || false @@ -48,6 +49,7 @@ def implementation_initialize(*params) edge_label_set(e, edge_label(e)) if edge_label(e) end g.vertices.each do |v| + add_vertex!(v) vertex_label_set(v, vertex_label(v)) if vertex_label(v) end end From 3e50a556642f56264e37750e93941c94c4aaa035 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 4 Apr 2010 00:04:21 +0200 Subject: [PATCH 23/44] synched with evergreen, a few fixes --- lib/graphy/adjacency_graph.rb | 1 - lib/graphy/graph.rb | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/graphy/adjacency_graph.rb b/lib/graphy/adjacency_graph.rb index 3311192..a041da3 100644 --- a/lib/graphy/adjacency_graph.rb +++ b/lib/graphy/adjacency_graph.rb @@ -31,7 +31,6 @@ def implementation_initialize(*params) # extract_options! and facets' reverse_merge! technique # to handle parameters args = (params.pop if params.last.is_a? Hash) || {} - puts args.inspect # Basic configuration of adjacency. @allow_loops = args[:loops] || false diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb index 0c0f05a..b1e3f8f 100644 --- a/lib/graphy/graph.rb +++ b/lib/graphy/graph.rb @@ -142,7 +142,7 @@ def remove_edge(u, v = nil) # @option options [Symbol] :direction (:all) can be `:in`, `:out` or `:all` # @return [Array] an array of the adjacent portions # @fixme - def adjacent(x, options={}) + def adjacent(x, options = {}) d = directed? ? (options[:direction] || :out) : :all # Discharge the easy ones first. @@ -171,7 +171,7 @@ def add_vertices!(*a) # @return [Graph] a modified copy of `self` def add_vertices(*a) x = self.class.new(self) - x.add_vertices(*a) + x.add_vertices!(*a) self end @@ -343,6 +343,7 @@ def connected?(options = {}) # # @return [Boolean] def empty? + puts "yan" vertices.size.zero? end From 21a672be150be272db23e7c8a84a57bb379439be Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 4 Apr 2010 01:35:01 +0200 Subject: [PATCH 24/44] fix for DOT generation of undirected graphs --- graphy.gemspec | 2 +- lib/graphy/dot.rb | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/graphy.gemspec b/graphy.gemspec index fae4bb9..961bdfa 100644 --- a/graphy.gemspec +++ b/graphy.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Bruce Williams"] - s.date = %q{2010-03-31} + s.date = %q{2010-04-04} s.description = %q{A framework for graph data structures and algorithms. This library is based on GRATR and RGL. diff --git a/lib/graphy/dot.rb b/lib/graphy/dot.rb index d752093..c70c24f 100644 --- a/lib/graphy/dot.rb +++ b/lib/graphy/dot.rb @@ -73,13 +73,17 @@ def dotty(params = {}, dotfile = 'graph.dot') system('dotty', dotfile) end - # Use +dot+ to create a graphical representation of the graph. Returns the + # Use +dot+ to create a graphical representation of the graph. Returns the # filename of the graphics file. def write_to_graphic_file(fmt = 'png', dotfile = 'graph') src = dotfile + '.dot' dot = dotfile + '.' + fmt - File.open(src, 'w') {|f| f << self.to_dot << "\n"} + # DOT::DOTSubgraph creates subgraphs, but that's broken. + buffer = self.to_dot + buffer.gsub!(/^subgraph/, "graph") + + File.open(src, 'w') {|f| f << buffer << "\n"} system( "dot -T#{fmt} #{src} -o #{dot}" ) dot From 88fbdfe39566814d788dcae2e8f5f993a4993c06 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Tue, 13 Apr 2010 11:34:44 +0200 Subject: [PATCH 25/44] cosmetic spacing fix --- lib/graphy/arc.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/graphy/arc.rb b/lib/graphy/arc.rb index a40b999..8403bd6 100644 --- a/lib/graphy/arc.rb +++ b/lib/graphy/arc.rb @@ -15,7 +15,7 @@ def initialize(p_source, p_target, p_label = nil) # Ignore labels for equality. def eql?(other) - self.class == other.class and target==other.target and source==other.source + self.class == other.class and target == other.target and source == other.source end alias == eql? @@ -24,7 +24,7 @@ def reverse() self.class.new(target, source, label); end # Sort support. def <=>(rhs) - [source,target] <=> [rhs.source,rhs.target] + [source, target] <=> [rhs.source, rhs.target] end # Arc.new[1,2].to_s => "(1-2 'label')" From 1a4c329c884bc3e0d41b391eef9d308ee0740781 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Wed, 6 Jul 2011 17:17:40 +0200 Subject: [PATCH 26/44] overall cleanup after one year --- .gitignore | 31 +++------- .rvmrc | 1 + Gemfile | 3 + Gemfile.lock | 28 +++++++++ Rakefile | 64 +++++-------------- graphy.gemspec | 140 +++--------------------------------------- lib/graphy.rb | 10 +-- lib/graphy/version.rb | 6 ++ spec/spec_helper.rb | 21 ++++--- 9 files changed, 88 insertions(+), 216 deletions(-) create mode 100644 .rvmrc create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 lib/graphy/version.rb diff --git a/.gitignore b/.gitignore index c1e0daf..efeb224 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,10 @@ -## MAC OS -.DS_Store - -## TEXTMATE -*.tmproj -tmtags - -## EMACS -*~ -\#* -.\#* - -## VIM -*.swp - -## PROJECT::GENERAL -coverage -rdoc -pkg - -## PROJECT::SPECIFIC +.bundle/ +log/*.log +pkg/ +spec/dummy/db/*.sqlite3 +spec/dummy/log/*.log +spec/dummy/tmp/ +tmp* +*.gem +.gitignore +TODO.md diff --git a/.rvmrc b/.rvmrc new file mode 100644 index 0000000..c4cbdb3 --- /dev/null +++ b/.rvmrc @@ -0,0 +1 @@ +rvm 1.9.2@graphy --create diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..c80ee36 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "http://rubygems.org" + +gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..6d3c33b --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,28 @@ +PATH + remote: . + specs: + graphy (0.5.3) + facets + +GEM + remote: http://rubygems.org/ + specs: + diff-lcs (1.1.2) + facets (2.9.1) + rspec (2.6.0) + rspec-core (~> 2.6.0) + rspec-expectations (~> 2.6.0) + rspec-mocks (~> 2.6.0) + rspec-core (2.6.4) + rspec-expectations (2.6.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.6.0) + yard (0.7.2) + +PLATFORMS + ruby + +DEPENDENCIES + graphy! + rspec + yard diff --git a/Rakefile b/Rakefile index 918fc1b..52ded35 100644 --- a/Rakefile +++ b/Rakefile @@ -1,61 +1,25 @@ +# encoding: UTF-8 require 'rubygems' -require 'rake' -require 'yard' - begin - require 'jeweler' - Jeweler::Tasks.new do |gem| - gem.name = "graphy" - gem.summary = "A Graph Theory Ruby library" - gem.description =<<-EOD -A framework for graph data structures and algorithms. - -This library is based on GRATR and RGL. - -Graph algorithms currently provided are: - -* Topological Sort -* Strongly Connected Components -* Transitive Closure -* Rural Chinese Postman -* Biconnected - EOD - gem.email = "bruce@codefluency.com" - gem.homepage = "http://github.com/bruce/graphy" - gem.authors = ["Bruce Williams"] - end - Jeweler::GemcutterTasks.new + require 'bundler/setup' rescue LoadError - puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' end +require 'rake' require 'rake/rdoctask' -Rake::RDocTask.new do |rdoc| - version = File.exist?('VERSION') ? File.read('VERSION') : "" - rdoc.rdoc_dir = 'rdoc' - rdoc.title = "graphy #{version}" - rdoc.rdoc_files.include('README*') - rdoc.rdoc_files.include('lib/**/*.rb') -end - -#require 'spec/rake/spectask' -#Spec::Rake::SpecTask.new(:spec) do |spec| - #spec.libs << 'lib' << 'spec' - #spec.spec_files = FileList['spec/**/*_spec.rb'] -#end +require 'rspec/core' +require 'rspec/core/rake_task' -#Spec::Rake::SpecTask.new(:rcov) do |spec| - #spec.libs << 'lib' << 'spec' - #spec.pattern = 'spec/**/*_spec.rb' - #spec.rcov = true -#end +RSpec::Core::RakeTask.new(:spec) -#task :spec => :check_dependencies +task :default => :spec -#task :default => :spec - -YARD::Rake::YardocTask.new do |t| - t.files = ['lib/**/*.rb', 'README.md', 'TODO.md', 'CREDITS.md', 'LICENSE', 'VERSION'] - #t.options = ['--any', '--extra', '--opts'] # optional +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'Graphy' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') end diff --git a/graphy.gemspec b/graphy.gemspec index 961bdfa..e9c6ae8 100644 --- a/graphy.gemspec +++ b/graphy.gemspec @@ -2,148 +2,26 @@ # DO NOT EDIT THIS FILE DIRECTLY # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command # -*- encoding: utf-8 -*- +require './lib/graphy/version' Gem::Specification.new do |s| s.name = %q{graphy} - s.version = "0.5.2" - - s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= - s.authors = ["Bruce Williams"] - s.date = %q{2010-04-04} - s.description = %q{A framework for graph data structures and algorithms. - -This library is based on GRATR and RGL. + s.version = Graphy::VERSION + s.authors = ["Bruce Williams", "Jean-Denis Vauguet "] + s.summary = "A framework for graph data structures and algorithms." + s.description = %q{This library is based on GRATR and RGL. Graph algorithms currently provided are: * Topological Sort -* Strongly Connected Components +* Strongly Connected Components * Transitive Closure * Rural Chinese Postman * Biconnected } s.email = %q{bruce@codefluency.com} - s.extra_rdoc_files = [ - "LICENSE", - "README.md" - ] - s.files = [ - ".document", - ".gitignore", - "CREDITS.md", - "LICENSE", - "README.md", - "Rakefile", - "TODO.md", - "VERSION", - "examples/graph_self.rb", - "examples/module_graph.jpg", - "examples/module_graph.rb", - "examples/self_graph.jpg", - "examples/visualize.jpg", - "examples/visualize.rb", - "graphy.gemspec", - "lib/graphy.rb", - "lib/graphy/adjacency_graph.rb", - "lib/graphy/arc.rb", - "lib/graphy/arc_number.rb", - "lib/graphy/biconnected.rb", - "lib/graphy/chinese_postman.rb", - "lib/graphy/classes/graph_classes.rb", - "lib/graphy/common.rb", - "lib/graphy/comparability.rb", - "lib/graphy/directed_graph.rb", - "lib/graphy/directed_graph/algorithms.rb", - "lib/graphy/directed_graph/distance.rb", - "lib/graphy/dot.rb", - "lib/graphy/edge.rb", - "lib/graphy/ext.rb", - "lib/graphy/graph.rb", - "lib/graphy/graph_api.rb", - "lib/graphy/labels.rb", - "lib/graphy/maximum_flow.rb", - "lib/graphy/ruby_compatibility.rb", - "lib/graphy/search.rb", - "lib/graphy/strong_components.rb", - "lib/graphy/support/support.rb", - "lib/graphy/undirected_graph.rb", - "lib/graphy/undirected_graph/algorithms.rb", - "spec/biconnected_spec.rb", - "spec/chinese_postman_spec.rb", - "spec/community_spec.rb", - "spec/complement_spec.rb", - "spec/digraph_distance_spec.rb", - "spec/digraph_spec.rb", - "spec/dot_spec.rb", - "spec/edge_spec.rb", - "spec/inspection_spec.rb", - "spec/multi_edge_spec.rb", - "spec/neighborhood_spec.rb", - "spec/properties_spec.rb", - "spec/search_spec.rb", - "spec/spec.opts", - "spec/spec_helper.rb", - "spec/strong_components_spec.rb", - "spec/triangulated_spec.rb", - "spec/undirected_graph_spec.rb", - "vendor/priority-queue/CHANGELOG", - "vendor/priority-queue/Makefile", - "vendor/priority-queue/README", - "vendor/priority-queue/benchmark/dijkstra.rb", - "vendor/priority-queue/compare_comments.rb", - "vendor/priority-queue/doc/c-vs-rb.png", - "vendor/priority-queue/doc/compare_big.gp", - "vendor/priority-queue/doc/compare_big.png", - "vendor/priority-queue/doc/compare_small.gp", - "vendor/priority-queue/doc/compare_small.png", - "vendor/priority-queue/doc/results.csv", - "vendor/priority-queue/ext/priority_queue/CPriorityQueue/extconf.rb", - "vendor/priority-queue/ext/priority_queue/CPriorityQueue/priority_queue.c", - "vendor/priority-queue/lib/priority_queue.rb", - "vendor/priority-queue/lib/priority_queue/c_priority_queue.rb", - "vendor/priority-queue/lib/priority_queue/poor_priority_queue.rb", - "vendor/priority-queue/lib/priority_queue/ruby_priority_queue.rb", - "vendor/priority-queue/priority_queue.so", - "vendor/priority-queue/setup.rb", - "vendor/priority-queue/test/priority_queue_test.rb", - "vendor/rdot.rb" - ] - s.homepage = %q{http://github.com/bruce/graphy} - s.rdoc_options = ["--charset=UTF-8"] - s.require_paths = ["lib"] - s.rubygems_version = %q{1.3.6} - s.summary = %q{A Graph Theory Ruby library} - s.test_files = [ - "spec/edge_spec.rb", - "spec/triangulated_spec.rb", - "spec/undirected_graph_spec.rb", - "spec/properties_spec.rb", - "spec/complement_spec.rb", - "spec/search_spec.rb", - "spec/digraph_spec.rb", - "spec/inspection_spec.rb", - "spec/community_spec.rb", - "spec/multi_edge_spec.rb", - "spec/neighborhood_spec.rb", - "spec/biconnected_spec.rb", - "spec/spec_helper.rb", - "spec/chinese_postman_spec.rb", - "spec/dot_spec.rb", - "spec/digraph_distance_spec.rb", - "spec/strong_components_spec.rb", - "examples/visualize.rb", - "examples/module_graph.rb", - "examples/graph_self.rb" - ] - - if s.respond_to? :specification_version then - current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION - s.specification_version = 3 - - if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then - else - end - else - end + s.add_dependency "facets" + s.add_development_dependency "rspec" + s.add_development_dependency "yard" end diff --git a/lib/graphy.rb b/lib/graphy.rb index 24f90b2..00a5eda 100644 --- a/lib/graphy.rb +++ b/lib/graphy.rb @@ -1,10 +1,10 @@ #-- # Copyright (c) 2006 Shawn Patrick Garbett # Copyright (c) 2002,2004,2005 by Horst Duchene -# +# # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: -# +# # * Redistributions of source code must retain the above copyright notice(s), # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, @@ -13,7 +13,7 @@ # * Neither the name of the Shawn Garbett nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. -# +# # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -50,7 +50,7 @@ module Graphy autoload :ChinesePostman, 'graphy/chinese_postman' autoload :Common, 'graphy/common' autoload :Comparability, 'graphy/comparability' - + autoload :Dot, 'graphy/dot' autoload :Edge, 'graphy/edge' autoload :Labels, 'graphy/labels' @@ -79,7 +79,7 @@ module Graphy require 'pathname' path = Pathname.new(__FILE__) -$LOAD_PATH.unshift(path + '../../vendor') +$LOAD_PATH.unshift(path + '../../vendor') # http://ruby.brian-amberg.de/priority-queue/ $LOAD_PATH.unshift(path + '../../vendor/priority-queue/lib') require 'rdot' diff --git a/lib/graphy/version.rb b/lib/graphy/version.rb new file mode 100644 index 0000000..74b10e9 --- /dev/null +++ b/lib/graphy/version.rb @@ -0,0 +1,6 @@ +module Graphy + MAJOR = 0 + MINOR = 5 + PATCH = 3 + VERSION = [MAJOR, MINOR, PATCH].join('.') +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ab7c989..21a2c22 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,13 +1,9 @@ -$LOAD_PATH.unshift(File.dirname(__FILE__)) -$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require 'graphy' -require 'spec' -require 'spec/autorun' +require File.expand_path("../../lib/graphy.rb", __FILE__) +require 'graphy' include Graphy module AncestryHelper - # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles # Golumbic, 1980, Academic Press, page 38, Figure 2.6 def assign_dfsnumber_ancestry(graph, dfsnumber, father, start) @@ -20,7 +16,7 @@ def assign_dfsnumber_ancestry(graph, dfsnumber, father, start) end # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles - # Golumbic, 1980, Academic Press, page 40, Figure 2.7 + # Golumbic, 1980, Academic Press, page 40, Figure 2.7 def assign_bfsnumber_ancestry(graph, bfsnum, level, father, start) i = 0 bfsnum.clear @@ -29,7 +25,7 @@ def assign_bfsnumber_ancestry(graph, bfsnum, level, father, start) rt = Proc.new {|v| level[v] = 0 } ev = Proc.new {|v| bfsnum[v]=(i+=1);level[v]=(level[father[v]]+1) if father[v]} te = Proc.new {|e| father[e.target] = e.source } - graph.dfs({:enter_vertex => ev, :tree_edge => te, + graph.dfs({:enter_vertex => ev, :tree_edge => te, :root_vertex => rt, :start => start}) end @@ -51,6 +47,13 @@ def related?(father,u,v) end -Spec::Runner.configure do |config| +RSpec.configure do |config| + # Remove this line if you don't want RSpec's should and should_not + # methods or matchers + require 'rspec/expectations' + config.include RSpec::Matchers config.include AncestryHelper + + # == Mock Framework + config.mock_with :rspec end From ef4a8151c17f40f5c4e2f6be46958d67eff1ab60 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Wed, 6 Jul 2011 18:05:44 +0200 Subject: [PATCH 27/44] fixed most of the failing specs; only search algo related remains --- lib/graphy/arc.rb | 36 +++++----- lib/graphy/classes/graph_classes.rb | 18 ++--- lib/graphy/directed_graph.rb | 24 ++++--- lib/graphy/directed_graph/algorithms.rb | 31 +++++---- lib/graphy/edge.rb | 15 ++--- lib/graphy/ext.rb | 2 +- lib/graphy/graph.rb | 65 +++++++++--------- lib/graphy/search.rb | 73 ++++++++++---------- lib/graphy/undirected_graph.rb | 21 +++--- spec/edge_spec.rb | 35 +++++----- spec/inspection_spec.rb | 28 ++++---- spec/neighborhood_spec.rb | 8 +-- spec/search_spec.rb | 90 ++++++++++++------------- 13 files changed, 216 insertions(+), 230 deletions(-) diff --git a/lib/graphy/arc.rb b/lib/graphy/arc.rb index 8403bd6..e5d6345 100644 --- a/lib/graphy/arc.rb +++ b/lib/graphy/arc.rb @@ -1,14 +1,12 @@ module Graphy - # Arc includes classes for representing egdes of directed and # undirected graphs. There is no need for a Vertex class, because any ruby # object can be a vertex of a graph. # - # Arc's base is a Struct with a :source, a :target and a :label + # Arc's base is a Struct with a :source, a :target and a :label. Struct.new("ArcBase", :source, :target, :label) class Arc < Struct::ArcBase - def initialize(p_source, p_target, p_label = nil) super(p_source, p_target, p_label) end @@ -20,7 +18,9 @@ def eql?(other) alias == eql? # Returns (v,u) if self == (u,v). - def reverse() self.class.new(target, source, label); end + def reverse() + self.class.new(target, source, label) + end # Sort support. def <=>(rhs) @@ -32,18 +32,14 @@ def to_s l = label ? " '#{label.to_s}'" : '' "(#{source}-#{target}#{l})" end - + # Hash is defined in such a way that label is not # part of the hash value # FIXME: I had to get rid of that in order to make to_dot_graph # work, but I can't figure it out (doesn't show up in the stack!) - #def hash - #puts "--- #{caller}" - ##puts source.inspect - ##puts target.inspect - #source.hash ^ (target.hash + 1) - #puts "---" - #end + def hash + source.hash ^ (target.hash + 1) + end # Shortcut constructor. # @@ -51,15 +47,13 @@ def to_s def self.[](p_source, p_target, p_label = nil) new(p_source, p_target, p_label) end - - #def inspect - #"#{self.class.to_s}[#{source.inspect},#{target.inspect},#{label.inspect}]" - #end - - end # Arc - + + def inspect + "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{label.inspect}]" + end + end + class MultiArc < Arc include ArcNumber end - -end # Graphy +end diff --git a/lib/graphy/classes/graph_classes.rb b/lib/graphy/classes/graph_classes.rb index 3db4aec..9eae7a0 100644 --- a/lib/graphy/classes/graph_classes.rb +++ b/lib/graphy/classes/graph_classes.rb @@ -1,28 +1,28 @@ module Graphy # A generic {GraphBuilder Graph} class you can inherit from. - class Graph; include GraphBuilder; end + class Graph; include GraphBuilder; end # A generic {AdjacencyGraphBuilder AdjacencyGraph} class you can inherit from. - class AdjacencyGraph; include AdjacencyGraphBuilder; end + class AdjacencyGraph < Graph; include AdjacencyGraphBuilder; end # A generic {DirectedGraphBuilder DirectedGraph} class you can inherit from. - class DirectedGraph; include DirectedGraphBuilder; end + class DirectedGraph < Graph; include DirectedGraphBuilder; end # A generic {DigraphBuilder Digraph} class you can inherit from. - class Digraph; include DigraphBuilder; end + class Digraph < Graph; include DigraphBuilder; end # A generic {DirectedPseudoGraphBuilder DirectedPseudoGraph} class you can inherit from. - class DirectedPseudoGraph; include DirectedPseudoGraphBuilder; end + class DirectedPseudoGraph < Graph; include DirectedPseudoGraphBuilder; end # A generic {DirectedMultiGraphBuilder DirectedMultiGraph} class you can inherit from. - class DirectedMultiGraph; include DirectedMultiGraphBuilder; end + class DirectedMultiGraph < Graph; include DirectedMultiGraphBuilder; end # A generic {UndirectedGraphBuilder UndirectedGraph} class you can inherit from. - class UndirectedGraph; include UndirectedGraphBuilder; end + class UndirectedGraph < Graph; include UndirectedGraphBuilder; end # A generic {UndirectedPseudoGraphBuilder UndirectedPseudoGraph} class you can inherit from. - class UndirectedPseudoGraph; include UndirectedPseudoGraphBuilder; end + class UndirectedPseudoGraph < Graph; include UndirectedPseudoGraphBuilder; end # A generic {UndirectedMultiGraphBuilder UndirectedMultiGraph} class you can inherit from. - class UndirectedMultiGraph; include UndirectedMultiGraphBuilder; end + class UndirectedMultiGraph < Graph; include UndirectedMultiGraphBuilder; end end diff --git a/lib/graphy/directed_graph.rb b/lib/graphy/directed_graph.rb index 9fbd2ae..a069a1d 100644 --- a/lib/graphy/directed_graph.rb +++ b/lib/graphy/directed_graph.rb @@ -6,21 +6,22 @@ module Graphy # structure. module DirectedGraphBuilder include GraphBuilder - + autoload :Algorithms, "graphy/directed_graph/algorithms" - autoload :Distance, "graphy/directed_graph/distance" + autoload :Distance, "graphy/directed_graph/distance" # FIXME: DRY this snippet, I didn't find a clever way to # to dit though # TODO: well, extends_host_with do ... end would be cool, # using Module.new.module_eval(&block) in the helper. extends_host + module ClassMethods def [](*a) self.new.from_array(*a) end end - + def initialize(*params) # FIXME/TODO: setting args to the hash or {} while getting rid # on the previous parameters prevents from passing another @@ -30,11 +31,11 @@ def initialize(*params) # we should provide a way to handle the graph as a hash # member. args = (params.pop if params.last.kind_of? Hash) || {} - args[:algorithmic_category] = DirectedGraphBuilder::Algorithms + args[:algorithmic_category] = DirectedGraphBuilder::Algorithms super *(params << args) end - end # DirectedGraphBuilder - + end + # DirectedGraph is just an alias for Digraph should one desire DigraphBuilder = DirectedGraphBuilder @@ -43,23 +44,25 @@ def initialize(*params) module DirectedPseudoGraphBuilder include DirectedGraphBuilder extends_host + module ClassMethods def [](*a) self.new.from_array(*a) end end - + def initialize(*params) args = (params.pop if params.last.kind_of? Hash) || {} args[:parallel_edges] = true super *(params << args) end - end # DirectedPseudoGraphBuilder + end # This is a Digraph that allows for both parallel edges and loops. module DirectedMultiGraphBuilder include DirectedPseudoGraphBuilder extends_host + module ClassMethods def [](*a) self.new.from_array(*a) @@ -71,6 +74,5 @@ def initialize(*params) args[:loops] = true super *(params << args) end - end # DirectedMultiGraphBuilder - -end # Graphy + end +end diff --git a/lib/graphy/directed_graph/algorithms.rb b/lib/graphy/directed_graph/algorithms.rb index d281324..dc6664d 100644 --- a/lib/graphy/directed_graph/algorithms.rb +++ b/lib/graphy/directed_graph/algorithms.rb @@ -1,9 +1,8 @@ module Graphy - # Digraph is a directed graph which is a finite set of vertices # and a finite set of edges connecting vertices. It cannot contain parallel # edges going from the same source vertex to the same target. It also - # cannot contain loops, i.e. edges that go have the same vertex for source + # cannot contain loops, i.e. edges that go have the same vertex for source # and target. # # DirectedPseudoGraph is a class that allows for parallel edges, and @@ -11,31 +10,33 @@ module Graphy # as well. module DirectedGraphBuilder module Algorithms - include Search include StrongComponents include Distance include ChinesePostman - # A directed graph is directed by definition + # A directed graph is directed by definition. # # @return [Boolean] always true + # def directed? true end - # A digraph uses the Arc class for edges + # A digraph uses the Arc class for edges. # # @return [Graphy::MultiArc, Graphy::Arc] `Graphy::MultiArc` if the graph allows for parallel edges, # `Graphy::Arc` otherwise. + # def edge_class @parallel_edges ? Graphy::MultiArc : Graphy::Arc end - # Reverse all edges in a graph + # Reverse all edges in a graph. # # @return [DirectedGraph] a copy of the receiver for which the direction of edges has # been inverted. + # def reversal result = self.class.new edges.inject(result) { |a,e| a << e.reverse} @@ -43,23 +44,26 @@ def reversal result end - # Check whether the Graph is oriented or not. + # Check whether the graph is oriented or not. # # @return [Boolean] + # def oriented? e = edges re = e.map { |x| x.reverse} not e.any? { |x| re.include?(x)} end - # Balanced is when the out edge count is equal to the in edge count + # Balanced is the state when the out edges count is equal to the in edges count. # # @return [Boolean] + # def balanced?(v) out_degree(v) == in_degree(v) end - # Returns out_degree(v) - in_degree(v) + # Returns out_degree(v) - in_degree(v). + # def delta(v) out_degree(v) - in_degree(v) end @@ -85,8 +89,7 @@ def ancestors(node) def family(node) community(node, :all) - end - - end # Algorithms - end # DirectedGraphBuilder -end # Graphy + end + end + end +end diff --git a/lib/graphy/edge.rb b/lib/graphy/edge.rb index b1ad0bc..72d001f 100644 --- a/lib/graphy/edge.rb +++ b/lib/graphy/edge.rb @@ -1,23 +1,23 @@ module Graphy - # An undirected edge is simply an undirected pair (source, target) used in # undirected graphs. Edge[u,v] == Edge[v,u] class Edge < Arc # Equality allows for the swapping of source and target - def eql?(other) super or (self.class == other.class and target==other.source and source==other.target); end - - # Alias for eql? + def eql?(other) + super or (self.class == other.class and target == other.source and source == other.target) + end alias == eql? # Hash is defined such that source and target can be reversed and the # hash value will be the same - def hash() source.hash ^ target.hash; end + def hash + source.hash ^ target.hash + end # Sort support def <=>(rhs) - [[source,target].max,[source,target].min] <=> - [[rhs.source,rhs.target].max,[rhs.source,rhs.target].min] + [[source,target].max, [source,target].min] <=> [[rhs.source,rhs.target].max, [rhs.source,rhs.target].min] end # Edge[1,2].to_s == "(1=2 'label)" @@ -33,5 +33,4 @@ def to_s class MultiEdge < Edge include ArcNumber end - end diff --git a/lib/graphy/ext.rb b/lib/graphy/ext.rb index 5694b14..b218ac7 100644 --- a/lib/graphy/ext.rb +++ b/lib/graphy/ext.rb @@ -28,7 +28,7 @@ def singleton_class # # class A; include Digraph; end # a.singleton_class.ancestors - # # => [Graphy::GraphAPI, Graphy::DirectedGraph::Algorithms, ... + # # => [Graphy::GraphAPI, Graphy::DirectedGraph::Algorithms, ... # Graphy::Labels, Enumerable, Object, Graphy, Kernel, BasicObject] # a.is_a? Graphy::Graph # # => true diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb index b1e3f8f..8cb25a9 100644 --- a/lib/graphy/graph.rb +++ b/lib/graphy/graph.rb @@ -1,5 +1,4 @@ module Graphy - # Using the methods required by the {GraphAPI}, it implements all the # *basic* functions of a {Graph} using *only* functions # requested in {GraphAPI}. The process is under the control of the pattern @@ -9,7 +8,6 @@ module Graphy # An actual, complete implementation still needs to be done using this cheap result, # hence {Digraph}, {UndirectedGraph} and their roomates. module GraphBuilder - include Enumerable include Labels include Dot @@ -53,7 +51,7 @@ class << self end implementation_initialize(*params) - end + end # Shortcut for creating a Graph. # @@ -61,7 +59,7 @@ class << self # # Graphy::Graph[1,2, 2,3, 2,4, 4,5].edges.to_a.to_s # # => "(1-2)(2-3)(2-4)(4-5)" - # + # # Using a Hash for specifying labels along the way: # # Graphy::Graph[ [:a,:b] => 3, [:b,:c] => 4 ] (note: do not use for Multi or Pseudo graphs) @@ -77,12 +75,12 @@ def from_array(*a) add_edge!(edge_class[k[0],k[1],nil,v]) else add_edge!(edge_class[k[0],k[1],v]) - end + end end #FIXME, edge class shouldn't be assume here!!! elsif a[0].is_a? Graphy::Arc a.each{ |e| add_edge!(e); self[e] = e.label} - elsif a.size % 2 == 0 + elsif a.size % 2 == 0 0.step(a.size-1, 2) {|i| add_edge!(a[i], a[i+1])} else raise ArgumentError @@ -110,7 +108,7 @@ def add_edge(u, v = nil, l = nil) x = self.class.new(self) x.add_edge!(u, v, l) end - alias add_arc add_edge + alias add_arc add_edge # Non destructive version of {AdjacencyGraphBuilder#remove_vertex!} (works on a copy of the graph). # @@ -130,7 +128,7 @@ def remove_edge(u, v = nil) x = self.class.new(self) x.remove_edge!(u, v) end - alias remove_arc remove_edge + alias remove_arc remove_edge # Computes the adjacent portions of the Graph. # @@ -155,7 +153,6 @@ def adjacent(x, options = {}) #FIXME: This is a hack around a serious problem alias graph_adjacent adjacent - # Adds all specified vertices to the vertex set. # # @param [#each] *a an Enumerable vertices set @@ -186,7 +183,7 @@ def add_edges!(*a) a.each { |edge| add_edge!(edge) } self end - alias add_arcs! add_edges! + alias add_arcs! add_edges! # Same as {GraphBuilder#add_egdes! add_edges!} but works on a copy of the receiver. # @@ -274,7 +271,7 @@ def vertex?(v) # @return [Boolean] def edge?(*args) edges.include?(edge_convert(*args)) - end + end alias arc? edge? alias has_edge? edge? alias has_arc? edge? @@ -343,12 +340,11 @@ def connected?(options = {}) # # @return [Boolean] def empty? - puts "yan" vertices.size.zero? end # Returns true if the given object is a vertex or an {Arc arc} of the graph. - # + # # @param [vertex, Arc] x def include?(x) x.is_a?(Graphy::Arc) ? edge?(x) : vertex?(x) @@ -359,11 +355,11 @@ def include?(x) # # This is equivalent to {GraphBuilder#adjacent adjacent}, but the type is based on the # type of the specified object. - # + # # @param [vertex, Arc] x # @param [Symbol] direction (:all) can be either `:all`, `:in` or `:out` def neighborhood(x, direction = :all) - adjacent(x, :direction => direction, :type => ((x.is_a? Graphy::Arc) ? :edges : :vertices )) + adjacent(x, :direction => direction, :type => ((x.is_a? Graphy::Arc) ? :edges : :vertices )) end # Union of all neighborhoods of vertices (or edges) in the Enumerable x minus the contents of x. @@ -373,8 +369,8 @@ def neighborhood(x, direction = :all) # @param [vertex] x # @param [Symbol] direction can be either `:all`, `:in` or `:out` def set_neighborhood(x, direction = :all) - x.inject(Set.new) { |a,v| a.merge(neighborhood(v, idirection))}.reject { |v2| x.include?(v2) } - end + x.inject(Set.new) { |a,v| a.merge(neighborhood(v, direction))}.reject { |v2| x.include?(v2) } + end # Union of all {GraphBuilder#set_neighborhood set_neighborhoods} reachable # among the specified edges. @@ -387,7 +383,7 @@ def set_neighborhood(x, direction = :all) # @param [Symbol] direction can be `:all`, `:in`, or `:out` def closed_pth_neighborhood(w, p, direction = :all) if p <= 0 - w + w elsif p == 1 (w + set_neighborhood(w, direction)).uniq else @@ -410,10 +406,10 @@ def open_pth_neighborhood(x, p, direction = :all) x elsif p == 1 set_neighborhood(x,direction) - else + else set_neighborhood(open_pth_neighborhood(x, p-1, direction), direction) - closed_pth_neighborhood(x, p-1, direction) - end + end end # Returns the number of out-edges (for directed graphs) or the number of @@ -451,7 +447,7 @@ def min_in_degree end # Minimum out-degree of the graph. - # + # # @return [Integer, nil] returns `nil` if the graph is empty def min_out_degree return nil if to_a.empty? @@ -531,9 +527,9 @@ def num_edges def eql?(g) return false unless g.is_a? Graphy::Graph - (g.directed? == self.directed?) and + (directed? == g.directed?) and (vertices.sort == g.vertices.sort) and - (g.edges.sort == edges.sort) + (edges.sort == g.edges.sort) end alias == eql? @@ -544,8 +540,8 @@ def eql?(g) def merge(other) other.vertices.each { |v| add_vertex!(v) } other.edges.each { |e| add_edge!(e) } - other.edges.each { |e| add_edge!(e.reverse) } if directed? and !other.directed? - self + other.edges.each { |e| add_edge!(e.reverse) } if directed? and !other.directed? + self end # A synonym for {GraphBuilder#merge merge}, but doesn't modify the current graph. @@ -567,7 +563,7 @@ def +(other) # Removes all vertices in the specified graph. # # @param [Graph, Arc] other - # @return [Graph] + # @return [Graph] def -(other) case other when Graphy::Graph @@ -599,12 +595,12 @@ def complement # @param [Array(vertex)] v # @return [Graph] def induced_subgraph(v) - edges.inject(self.class.new) do |a,e| + edges.inject(self.class.new) do |a,e| (v.include?(e.source) and v.include?(e.target)) ? (a << e) : a end end - #def inspect + def inspect ## FIXME: broken, it's not updated. The issue's not with inspect, but it's worth mentionning here. ## Example: ## dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] @@ -613,12 +609,12 @@ def induced_subgraph(v) ## dg.vertex?("yosh") ## # => true ## dg - ## # =>Graphy::Digraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[6,4,nil]] + ## # =>Graphy::Digraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[6,4,nil]] ## the new vertex doesn't show up. ## Actually this version of inspect is far too verbose IMO :) - #l = vertices.select { |v| self[v]}.map { |u| "vertex_label_set(#{u.inspect}, #{self[u].inspect})"}.join('.') - #self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l && l != '' ? '.'+l : '') - #end + l = vertices.select { |v| self[v]}.map { |u| "vertex_label_set(#{u.inspect}, #{self[u].inspect})"}.join('.') + self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l && l != '' ? '.'+l : '') + end private @@ -626,6 +622,5 @@ def induced_subgraph(v) def edge_convert(*args) args[0].is_a?(Graphy::Arc) ? args[0] : edge_class[*args] end - - end # Graph -end # Graphy + end +end diff --git a/lib/graphy/search.rb b/lib/graphy/search.rb index f2b746b..56a7374 100644 --- a/lib/graphy/search.rb +++ b/lib/graphy/search.rb @@ -35,7 +35,7 @@ module Graphy # te = Proc.new { |x| puts "Tree Arc #{x}" } # be = Proc.new { |x| puts "Back Arc #{x}" } # fe = Proc.new { |x| puts "Forward Arc #{x}" } - # Digraph[1,2, 2,3, 3,4].dfs({ + # Digraph[1,2, 2,3, 3,4].dfs({ # :enter_vertex => ev, # :exit_vertex => xv, # :start_vertex => sv, @@ -43,7 +43,7 @@ module Graphy # :tree_edge => te, # :back_edge => be, # :forward_edge => fe }) - # + # # Which outputs: # # Start vertex 1 @@ -127,7 +127,7 @@ def tree_from_vertex(start, routine) te = Proc.new { |e| predecessor[e.target] = e.source if correct_tree } rv = Proc.new { |v| correct_tree = (v == start) } send routine, :start => start, :tree_edge => te, :root_vertex => rv - predecessor + predecessor end # Returns a hash of predecessors for the depth-first search tree rooted at the given node. @@ -164,12 +164,12 @@ def hash @set = {} @tail = @node.new(nil, nil, Array.new(values)) @tail.instance_eval { @hash = (@@cnt += 1) } - values.each { |a| @set[a] = @tail } + values.each { |a| @set[a] = @tail } end # Pops an entry with the maximum lexical value from the queue. # - # @return [vertex] + # @return [vertex] def pop return nil unless @tail value = @tail[:data].pop @@ -179,7 +179,7 @@ def pop end # Increase the lexical value of the given values. - # + # # @param [Array] vertices values def add_lexeme(values) fix = {} @@ -188,7 +188,7 @@ def add_lexeme(values) sw = @set[w] if fix[sw] s_prime = sw[:back] - else + else s_prime = @node.new(sw[:back], sw, []) s_prime.instance_eval { @hash = (@@cnt += 1) } @tail = s_prime if @tail == sw @@ -206,7 +206,7 @@ def add_lexeme(values) e[:forward][:back] = e[:back] if e[:forward] e[:back][:forward] = e[:forward] if e[:back] end - end + end end @@ -214,7 +214,7 @@ def add_lexeme(values) # # The usual queue of vertices is replaced by a queue of *unordered subsets* # of the vertices, which is sometimes refined but never reordered. - # + # # Originally developed by Rose, Tarjan, and Leuker, *Algorithmic # aspects of vertex elimination on graphs*, SIAM J. Comput. 5, 266-283 # MR53 #12077 @@ -226,30 +226,30 @@ def add_lexeme(values) def lexicograph_bfs(&block) lex_q = Graphy::Search::LexicographicQueue.new(vertices) result = [] - num_vertices.times do + num_vertices.times do v = lex_q.pop result.unshift(v) - lex_q.add_lexeme(adjacent(v)) + lex_q.add_lexeme(adjacent(v)) end result.each { |r| block.call(r) } if block result end # A* Heuristic best first search. - # + # # `start` is the starting vertex for the search. # - # `func` is a `Proc` that when passed a vertex returns the heuristic + # `func` is a `Proc` that when passed a vertex returns the heuristic # weight of sending the path through that node. It must always # be equal to or less than the true cost. - # - # `options` are mostly callbacks passed in as a hash, the default block is + # + # `options` are mostly callbacks passed in as a hash, the default block is # `:discover_vertex` and the weight is assumed to be the label for the {Arc}. # The following options are valid, anything else is ignored: # # * `:weight` => can be a `Proc`, or anything else is accessed using the `[]` for the # the label or it defaults to using - # the value stored in the label for the {Arc}. If it is a `Proc` it will + # the value stored in the label for the {Arc}. If it is a `Proc` it will # pass the edge to the proc and use the resulting value. # * `:discover_vertex` => `Proc` invoked when a vertex is first discovered # and is added to the open list. @@ -259,12 +259,12 @@ def lexicograph_bfs(&block) # immediately after it is examined. # * `:edge_relaxed` => `Proc` invoked on edge `(u,v) if d[u] + w(u,v) < d[v]`. # * `:edge_not_relaxed`=> `Proc` invoked if the edge is not relaxed (see above). - # * `:black_target` => `Proc` invoked when a vertex that is on the closed + # * `:black_target` => `Proc` invoked when a vertex that is on the closed # list is "rediscovered" via a more efficient path, and is re-added # to the open list. - # * `:finish_vertex` => Proc invoked on a vertex when it is added to the + # * `:finish_vertex` => Proc invoked on a vertex when it is added to the # closed list, which happens after all of its out edges have been - # examined. + # examined. # # Can also be called like `astar_examine_edge {|e| ... }` or # `astar_edge_relaxed {|e| ... }` for any of the callbacks. @@ -272,9 +272,9 @@ def lexicograph_bfs(&block) # The criteria for expanding a vertex on the open list is that it has the # lowest `f(v) = g(v) + h(v)` value of all vertices on open. # - # The time complexity of A* depends on the heuristic. It is exponential + # The time complexity of A* depends on the heuristic. It is exponential # in the worst case, but is polynomial when the heuristic function h - # meets the following condition: `|h(x) - h*(x)| < O(log h*(x))` where `h*` + # meets the following condition: `|h(x) - h*(x)| < O(log h*(x))` where `h*` # is the optimal heuristic, i.e. the exact cost to get from `x` to the `goal`. # # See also: [A* search algorithm](http://en.wikipedia.org/wiki/A*_search_algorithm) on Wikipedia. @@ -317,7 +317,7 @@ def astar(start, goal, func, options, &block) w = cost(e, options[:weight]) raise ArgumentError unless w - if d[v].nil? or (w + d[u]) < d[v] + if d[v].nil? or (w + d[u]) < d[v] options.handle_callback(:edge_relaxed, e) d[v] = w + d[u] f[v] = d[v] + func.call(v) @@ -325,9 +325,9 @@ def astar(start, goal, func, options, &block) unless color[v] == :gray options.handle_callback(:black_target, v) if color[v] == :black - color[v] = :gray + color[v] = :gray options.handle_callback(:discover_vertex, v) - queue.push v, f[v] + queue.push v, f[v] block.call(v) if block end else @@ -354,12 +354,12 @@ def astar(start, goal, func, options, &block) # @return [Array(vertices), call, nil] an array of nodes in path, or calls block on all nodes, # upon failure returns `nil` def best_first(start, goal, options, zero = 0, &block) - func = Proc.new { |v| zero } + func = Proc.new { |v| zero } astar(start, goal, func, options, &block) end # @private - alias_method :pre_search_method_missing, :method_missing + alias_method :pre_search_method_missing, :method_missing def method_missing(sym, *args, &block) m1 = /^dfs_(\w+)$/.match(sym.to_s) dfs((args[0] || {}).merge({ m1.captures[0].to_sym => block })) if m1 @@ -401,7 +401,7 @@ def graphy_search_helper(op, options = {}, &block) # Loop till the search iterator exhausts the waiting list. visited_edges = {} # This prevents retraversing edges in undirected graphs. until waiting.empty? - graphy_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop) + graphy_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop) end # Waiting for the list to be exhausted, check if a new root vertex is available. u = color_map.detect { |key,value| value == :unvisited } @@ -418,7 +418,7 @@ def graphy_search_helper(op, options = {}, &block) def graphy_search_iteration(options, waiting, color_map, visited_edges, result, recursive = false) # Fetch the next waiting vertex in the list. #sleep - u = waiting.next + u = waiting.next options.handle_vertex(:enter_vertex, u) result << u @@ -432,15 +432,15 @@ def graphy_search_iteration(options, waiting, color_map, visited_edges, result, case color_map[v] # If it's unvisited, it goes into the waiting list. - when :unvisited + when :unvisited options.handle_edge(:tree_edge, e) color_map[v] = :waiting - waiting.push(v) + waiting.push(v) # If it's recursive (i.e. dfs), then call self. graphy_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive - when :waiting + when :waiting options.handle_edge(:back_edge, e) - else + else options.handle_edge(:forward_edge, e) end end @@ -470,8 +470,8 @@ def graphy_search_iteration(options, waiting, color_map, visited_edges, result, # @return [Array] a linear representation of the sorted graph def topsort(start = nil, &block) result = [] - go = true - back = Proc.new { |e| go = false } + go = true + back = Proc.new { |e| go = false } push = Proc.new { |v| result.unshift(v) if go } start ||= vertices[0] dfs({ :exit_vertex => push, :back_edge => back, :start => start }) @@ -506,6 +506,5 @@ def acyclic? def cyclic? not acyclic? end - - end # Search -end # Graphy + end +end diff --git a/lib/graphy/undirected_graph.rb b/lib/graphy/undirected_graph.rb index f5de9b1..7bf0d97 100644 --- a/lib/graphy/undirected_graph.rb +++ b/lib/graphy/undirected_graph.rb @@ -1,11 +1,10 @@ module Graphy - module UndirectedGraphBuilder - autoload :Algorithms, "graphy/undirected_graph/algorithms" include Graphy::GraphBuilder extends_host + module ClassMethods def [](*a) self.new.from_array(*a) @@ -14,16 +13,16 @@ def [](*a) def initialize(*params) args = (params.pop if params.last.kind_of? Hash) || {} - args[:algorithmic_category] = Graphy::UndirectedGraphBuilder::Algorithms + args[:algorithmic_category] = Graphy::UndirectedGraphBuilder::Algorithms super *(params << args) end end - # This is a Digraph that allows for parallel edges, but does not - # allow loops + # This is a Digraph that allows for parallel edges, but does not allow loops. module UndirectedPseudoGraphBuilder include UndirectedGraphBuilder extends_host + module ClassMethods def [](*a) self.new.from_array(*a) @@ -34,13 +33,14 @@ def initialize(*params) args = (params.pop if params.last.kind_of? Hash) || {} args[:parallel_edges] = true super *(params << args) - end + end end - # This is a Digraph that allows for parallel edges and loops + # This is a Digraph that allows for parallel edges and loops. module UndirectedMultiGraphBuilder - UndirectedPseudoGraphBuilder + include UndirectedPseudoGraphBuilder extends_host + module ClassMethods def [](*a) self.new.from_array(*a) @@ -51,7 +51,6 @@ def initialize(*params) args = (params.pop if params.last.kind_of? Hash) || {} args[:loops] = true super *(params << args) - end + end end - -end # Graphy +end diff --git a/spec/edge_spec.rb b/spec/edge_spec.rb index e4b030f..74aabde 100644 --- a/spec/edge_spec.rb +++ b/spec/edge_spec.rb @@ -1,13 +1,12 @@ -require File.join(File.dirname(__FILE__), 'spec_helper') - -describe "Arc" do # :nodoc: +require 'spec_helper' +describe Arc do before do @e = Arc.new(1,2,'boo') @u = Edge.new(1,2,'hoo') end - describe "edge_new" do + describe "#edge_new" do it do proc { Arc.new }.should raise_error(ArgumentError) proc { Arc.new(1) }.should raise_error(ArgumentError) @@ -16,7 +15,7 @@ end end - describe "edge_getters" do + describe "#edge_getters" do it do @e.source.should == 1 @e.target.should == 2 @@ -25,14 +24,14 @@ @e[0].should == 1 @e[1].should == 2 @e[2].should == 'boo' - + @e[-3].should == 1 @e[-2].should == 2 @e[-1].should == 'boo' - proc { @e[-4] }.should raise_error(IndexError) - proc { @e[3] }.should raise_error(IndexError) - + proc { @e[-4] }.should raise_error(IndexError) + proc { @e[3] }.should raise_error(IndexError) + @e['source'].should == 1 @e['target'].should == 2 @e['label'].should == 'boo' @@ -43,7 +42,7 @@ end end - describe "edge_setters" do + describe "#edge_setters" do it do @e.source = 23 @e.target = 42 @@ -82,7 +81,7 @@ end end - describe "edge_simple_methods" do + describe "#edge_simple_methods" do it do @e.to_a.should == [1,2,'boo'] @e.to_s.should == "(1-2 'boo')" @@ -99,14 +98,14 @@ end end - describe "edge_sort" do + describe "#edge_sort" do it do x = [ Arc.new(2,3), Arc.new(1,3), Arc.new(1,2), Arc.new(2,1) ].sort x.should == [Arc.new(1,2), Arc.new(1,3), Arc.new(2,1), Arc.new(2,3)] end end - describe "undirected_edge_new" do + describe "#undirected_edge_new" do it do proc { Edge.new }.should raise_error(ArgumentError) proc { Edge.new(1) }.should raise_error(ArgumentError) @@ -115,7 +114,7 @@ end end - describe "undirected_edge_getters" do + describe "#undirected_edge_getters" do it do @u.source.should == 1 @u.target.should == 2 @@ -124,7 +123,7 @@ end end - describe "undirected_edge_methods" do + describe "#undirected_edge_methods" do it do @u.label = nil @u.to_s.should == "(1=2)" @@ -138,14 +137,14 @@ end end - describe "undirected_edge_sort" do + describe "#undirected_edge_sort" do it do x = [Edge.new(12, 1), Edge.new(2,11)].sort x.should == [Edge.new(2,11), Edge.new(1,12)] end end - - describe "hash" do + + describe "#hash" do it do Arc[1,2,:c].should == Arc[1,2,:b] Arc[1,2,:c].hash.should == Arc[1,2,:b].hash diff --git a/spec/inspection_spec.rb b/spec/inspection_spec.rb index 2a36ee4..642b176 100644 --- a/spec/inspection_spec.rb +++ b/spec/inspection_spec.rb @@ -1,21 +1,21 @@ -require File.join(File.dirname(__FILE__), 'spec_helper') +require 'spec_helper' describe "Inspection" do # :nodoc: - before do - @dg = DirectedMultiGraph[ - [0,0,1] => 1, - [1,2,2] => 2, - [1,3,3] => 4, - [1,4,4] => nil, - [4,1,5] => 8, - [1,2,6] => 16, - [3,3,7] => 32, - [3,3,8] => 64 ] + @dg = DirectedMultiGraph[ + [0,0,1] => 1, + [1,2,2] => 2, + [1,3,3] => 4, + [1,4,4] => nil, + [4,1,5] => 8, + [1,2,6] => 16, + [3,3,7] => 32, + [3,3,8] => 64 + ] @dg[3] = 128 @dg[0] = 256 end - + describe "inspection_without_labels" do it do @dg = Digraph[1,2,3,4,5,6] @@ -23,7 +23,7 @@ reflect.should == @dg end end - + describe "inspection_with_labels" do it do inspect = @dg.inspect @@ -34,7 +34,5 @@ reflect.edges.inject(0) { |a,e| a += (reflect[e] || 0)}.should == 127 reflect.vertices.inject(0) {|a,v| a += (reflect[v] || 0)}.should == 384 end - end - end diff --git a/spec/neighborhood_spec.rb b/spec/neighborhood_spec.rb index 61f61df..56f767d 100644 --- a/spec/neighborhood_spec.rb +++ b/spec/neighborhood_spec.rb @@ -1,7 +1,6 @@ require File.join(File.dirname(__FILE__), 'spec_helper') -describe "Neighborhood" do # :nodoc: - +describe "Neighborhood" do before do @d = Digraph[:a,:b, :a,:f, :b,:g, @@ -12,7 +11,7 @@ :g,:a, :g,:e] @w = [:a,:b] end - + describe "open_out_neighborhood" do it do @d.set_neighborhood([:a], :in).should == [:g] @@ -24,7 +23,7 @@ ([:c] - @d.open_pth_neighborhood(@w, 4, :out)).should == [] end end - + describe "closed_out_neighborhood" do it do (@w - @d.closed_pth_neighborhood(@w, 0, :out)).should == [] @@ -34,5 +33,4 @@ ([:a,:b,:c,:d,:e,:f,:g] - @d.closed_pth_neighborhood(@w, 4, :out)).should == [] end end - end diff --git a/spec/search_spec.rb b/spec/search_spec.rb index 30a83f3..10d0d78 100644 --- a/spec/search_spec.rb +++ b/spec/search_spec.rb @@ -9,14 +9,14 @@ end # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles - # Golumbic, 1980, Academic Press, page 39, Propery (D1) and (D2) of + # Golumbic, 1980, Academic Press, page 39, Propery (D1) and (D2) of # depth first search describe "dfs_properties" do it do dfs = {} father = {} @directed.each do |vertex| - assign_dfsnumber_ancestry(@directed, dfs, father, vertex) + assign_dfsnumber_ancestry(@directed, dfs, father, vertex) # Property (D1) father.keys.each {|v| dfs[father[v]].should be < dfs[v] } # Property (D2) @@ -30,15 +30,15 @@ end # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles - # Golumbic, 1980, Academic Press, page 40, Propery (B1), (B2) and (B3) of + # Golumbic, 1980, Academic Press, page 40, Propery (B1), (B2) and (B3) of # breadth first search describe "bfs_properties" do it do - level = {} + level = {} father = {} bfs = {} @directed.each do |vertex| - assign_bfsnumber_ancestry(@directed, bfs, level, father, vertex) + assign_bfsnumber_ancestry(@directed, bfs, level, father, vertex) # Property (B1) father.keys.each do |v| bfs[father[v]].should be < bfs[v] @@ -107,7 +107,7 @@ # Heuristic from "Artificial Intelligence: A Modern Approach" by Stuart # Russell ande Peter Norvig, Prentice-Hall 2nd Edition, pg 95 - straight_line_to_Bucharest = + straight_line_to_Bucharest = { 'Arad' => 366, 'Bucharest' => 0, @@ -142,57 +142,57 @@ fv = Proc.new {|v| list << "fv #{v}" } er = Proc.new {|e| list << "er #{e}" } enr = Proc.new {|e| list << "enr #{e}" } - + options = { :discover_vertex => dv, :examine_vertex => ev, :black_target => bt, :finish_vertex => fv, :edge_relaxed => er, :edge_not_relaxed => enr } - + result = romania.astar('Arad', 'Bucharest', h, options) result.should == ["Arad", "Sibiu", "Rimnicu Vilcea", "Pitesti", "Bucharest"] # This isn't the greatest test since the exact ordering is not # not specified by the algorithm. If someone has a better idea, please fix list.should == ["ev Arad", - "er (Arad=Sibiu '99')", - "dv Sibiu", - "er (Arad=Timisoara '138')", - "dv Timisoara", - "er (Arad=Zerind '75')", - "dv Zerind", - "fv Arad", - "ev Sibiu", - "er (Rimnicu Vilcea=Sibiu '80')", - "dv Rimnicu Vilcea", - "er (Fagaras=Sibiu '99')", - "dv Fagaras", - "er (Oradea=Sibiu '151')", - "dv Oradea", - "enr (Arad=Sibiu '99')", - "fv Sibiu", - "ev Rimnicu Vilcea", - "enr (Rimnicu Vilcea=Sibiu '80')", - "er (Craiova=Rimnicu Vilcea '146')", - "dv Craiova", - "er (Pitesti=Rimnicu Vilcea '97')", - "dv Pitesti", - "fv Rimnicu Vilcea", - "ev Fagaras", - "enr (Fagaras=Sibiu '99')", - "er (Bucharest=Fagaras '211')", - "dv Bucharest", - "fv Fagaras", - "ev Pitesti", - "enr (Pitesti=Rimnicu Vilcea '97')", - "er (Bucharest=Pitesti '101')", - "enr (Craiova=Pitesti '138')", - "fv Pitesti", - "ev Bucharest"] + "er (Arad=Sibiu '99')", + "dv Sibiu", + "er (Arad=Timisoara '138')", + "dv Timisoara", + "er (Arad=Zerind '75')", + "dv Zerind", + "fv Arad", + "ev Sibiu", + "er (Rimnicu Vilcea=Sibiu '80')", + "dv Rimnicu Vilcea", + "er (Fagaras=Sibiu '99')", + "dv Fagaras", + "er (Oradea=Sibiu '151')", + "dv Oradea", + "enr (Arad=Sibiu '99')", + "fv Sibiu", + "ev Rimnicu Vilcea", + "enr (Rimnicu Vilcea=Sibiu '80')", + "er (Craiova=Rimnicu Vilcea '146')", + "dv Craiova", + "er (Pitesti=Rimnicu Vilcea '97')", + "dv Pitesti", + "fv Rimnicu Vilcea", + "ev Fagaras", + "enr (Fagaras=Sibiu '99')", + "er (Bucharest=Fagaras '211')", + "dv Bucharest", + "fv Fagaras", + "ev Pitesti", + "enr (Pitesti=Rimnicu Vilcea '97')", + "er (Bucharest=Pitesti '101')", + "enr (Craiova=Pitesti '138')", + "fv Pitesti", + "ev Bucharest"] end end - + describe "bfs_spanning_forest" do it do predecessor, roots = @tree.bfs_spanning_forest(1) @@ -203,7 +203,7 @@ roots.sort.should == [1,3,5,6,23] end end - + describe "dfs_spanning_forest" do it do predecessor, roots = @tree.dfs_spanning_forest(1) @@ -214,7 +214,7 @@ roots.sort.should == [1,3,5,6,23] end end - + describe "tree_from_vertex" do it do @tree.bfs_tree_from_vertex(1).should == {5=>2, 6=>2, 7=>6, 2=>1, 3=>1, 4=>1} From e4ff3c58e66515ea5205fcb49a7614ed2a0d61e0 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Wed, 6 Jul 2011 18:15:13 +0200 Subject: [PATCH 28/44] README and LICENSE updates --- LICENSE | 4 +++- README.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 09adea7..75eef1f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,10 +1,12 @@ +Copyright (c) 2010 Jean-Denis Vauguet + Copyright (c) 2009 Bruce Williams Copyright (c) 2007,2006 Shawn Patrick Garbett Copyright (c) 2002,2004,2005 by Horst Duchene -Copyright (c) 2000,2001 Jeremy Siek, Indiana University (jsiek@osl.iu.edu) +Copyright (c) 2000,2001 Jeremy Siek, Indiana University (jsiek@osl.iu.edu) All rights reserved. diff --git a/README.md b/README.md index f87c0b1..d3bc441 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ See CHANGELOG.markdown ## License -See LICENSE +[MIT License](http://en.wikipedia.org/wiki/MIT_License). See the LICENSE file. [1]: http://gratr.rubyforge.org [2]: http://rgl.rubyforge.org From 28228890ffdaab2cb3170015c0b32a0ff9f7c2da Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Wed, 6 Jul 2011 19:43:43 +0200 Subject: [PATCH 29/44] renamed project Plexus --- .rvmrc | 2 +- README.md | 64 ++++++++++---------- Rakefile | 2 +- TODO.md | 2 +- lib/graphy.rb | 72 +++++++++++------------ lib/graphy/adjacency_graph.rb | 18 +++--- lib/graphy/arc.rb | 2 +- lib/graphy/arc_number.rb | 4 +- lib/graphy/biconnected.rb | 4 +- lib/graphy/chinese_postman.rb | 4 +- lib/graphy/classes/graph_classes.rb | 2 +- lib/graphy/common.rb | 12 ++-- lib/graphy/comparability.rb | 20 +++---- lib/graphy/directed_graph.rb | 6 +- lib/graphy/directed_graph/algorithms.rb | 8 +-- lib/graphy/directed_graph/distance.rb | 4 +- lib/graphy/dot.rb | 4 +- lib/graphy/edge.rb | 2 +- lib/graphy/ext.rb | 6 +- lib/graphy/graph.rb | 46 +++++++-------- lib/graphy/graph_api.rb | 4 +- lib/graphy/labels.rb | 16 ++--- lib/graphy/maximum_flow.rb | 4 +- lib/graphy/search.rb | 16 ++--- lib/graphy/strong_components.rb | 8 +-- lib/graphy/support/support.rb | 6 +- lib/graphy/undirected_graph.rb | 8 +-- lib/graphy/undirected_graph/algorithms.rb | 8 +-- lib/graphy/version.rb | 2 +- plexus.gemspec | 24 ++++++++ spec/properties_spec.rb | 2 +- spec/spec_helper.rb | 6 +- spec/triangulated_spec.rb | 2 +- 33 files changed, 207 insertions(+), 183 deletions(-) create mode 100644 plexus.gemspec diff --git a/.rvmrc b/.rvmrc index c4cbdb3..b0c5757 100644 --- a/.rvmrc +++ b/.rvmrc @@ -1 +1 @@ -rvm 1.9.2@graphy --create +rvm 1.9.2@plexus --create diff --git a/README.md b/README.md index d3bc441..2a6778e 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ -# Graphy. A framework for graph theory, graph data structures and associated algorithms. +# Plexus (aka. plexus). A framework for graph theory, graph data structures and associated algorithms. Graph algorithms currently provided are: * Topological Sort -* Strongly Connected Components +* Strongly Connected Components * Transitive Closure * Rural Chinese Postman * Biconnected These are based on more general algorithm patterns: -* Breadth First Search -* Depth First Search +* Breadth First Search +* Depth First Search * A* Search * Floyd-Warshall * Best First Search @@ -22,7 +22,7 @@ These are based on more general algorithm patterns: ### Arcs -There are two vertices bound classes, `Graphy::Arc` and `Graphy::Edge`. The +There are two vertices bound classes, `Plexus::Arc` and `Plexus::Edge`. The former defines directional edges, the latter undirected edges. ### Vertices @@ -34,30 +34,30 @@ Vertices can be any `Object`. There are a number of different graph types, each of which provide different features and constraints: -`Graphy::Digraph` and its alias `Graphy::DirectedGraph`: +`Plexus::Digraph` and its alias `Plexus::DirectedGraph`: * Single directed edges (arcs) between vertices * Loops are forbidden -`Graphy::DirectedPseudoGraph`: +`Plexus::DirectedPseudoGraph`: * Multiple directed edges (arcs) between vertices * Loops are forbidden -`Graphy::DirectedMultiGraph`: +`Plexus::DirectedMultiGraph`: * Multiple directed edges (arcs) between vertices * Loops on vertices -`Graphy::UndirectedGraph`, `Graphy::UndirectedPseudoGraph`, and +`Plexus::UndirectedGraph`, `Plexus::UndirectedPseudoGraph`, and `Graph::UndirectedMultiGraph` are similar but all edges are undirected. ### Data Structures -In order to modelize data structures, make use of the `Graphy::AdjacencyGraph` +In order to modelize data structures, make use of the `Plexus::AdjacencyGraph` module which provides a generalized adjacency list and an edge list adaptor. -The `Graphy::Digraph` class is the general purpose "swiss army knife" of graph +The `Plexus::Digraph` class is the general purpose "swiss army knife" of graph classes, most of the other classes are just modifications to this class. It is optimized for efficient access to just the out-edges, fast vertex insertion and removal at the cost of extra space overhead, etc. @@ -67,17 +67,17 @@ insertion and removal at the cost of extra space overhead, etc. Using IRB, first require the library: require 'rubygems' # only if you are using ruby 1.8.x - require 'graphy' + require 'plexus' If you'd like to include all the classes in the current scope (so you -don't have to prefix with `Graphy::`), just: +don't have to prefix with `Plexus::`), just: - include Graphy + include Plexus Let's play with the library a bit in IRB: >> dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] - => Graphy::Digraph[[2, 3], [1, 6], [2, 4], [4, 5], [1, 2], [6, 4]] + => Plexus::Digraph[[2, 3], [1, 6], [2, 4], [4, 5], [1, 2], [6, 4]] A few properties of the graph we just created: @@ -98,56 +98,56 @@ Every object could be a vertex, even the class object `Object`: => false >> UndirectedGraph.new(dg).edges.sort.to_s - => "[Graphy::Edge[1,2,nil], Graphy::Edge[2,3,nil], Graphy::Edge[2,4,nil], - Graphy::Edge[4,5,nil], Graphy::Edge[1,6,nil], Graphy::Edge[6,4,nil]]" + => "[Plexus::Edge[1,2,nil], Plexus::Edge[2,3,nil], Plexus::Edge[2,4,nil], + Plexus::Edge[4,5,nil], Plexus::Edge[1,6,nil], Plexus::Edge[6,4,nil]]" Add inverse edge `(4-2)` to directed graph: >> dg.add_edge!(4,2) - => Graphy::DirectedGraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], - Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[4,2,nil], - Graphy::Arc[6,4,nil]] + => Plexus::DirectedGraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], + Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[4,2,nil], + Plexus::Arc[6,4,nil]] `(4-2) == (2-4)` in the undirected graph (4-2 doesn't show up): >> UndirectedGraph.new(dg).edges.sort.to_s - => "[Graphy::Edge[1,2,nil], Graphy::Edge[2,3,nil], Graphy::Edge[2,4,nil], - Graphy::Edge[4,5,nil], Graphy::Edge[1,6,nil], Graphy::Edge[6,4,nil]]" + => "[Plexus::Edge[1,2,nil], Plexus::Edge[2,3,nil], Plexus::Edge[2,4,nil], + Plexus::Edge[4,5,nil], Plexus::Edge[1,6,nil], Plexus::Edge[6,4,nil]]" `(4-2) != (2-4)` in directed graphs (both show up): >> dg.edges.sort.to_s - => "[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], - Graphy::Arc[2,4,nil], Graphy::Arc[4,2,nil], Graphy::Arc[4,5,nil], - Graphy::Arc[6,4,nil]]" + => "[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], + Plexus::Arc[2,4,nil], Plexus::Arc[4,2,nil], Plexus::Arc[4,5,nil], + Plexus::Arc[6,4,nil]]" >> dg.remove_edge! 4,2 - => Graphy::DirectedGraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], - Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[6,4,nil]] + => Plexus::DirectedGraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], + Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[6,4,nil]] Topological sorting is realized with an iterator: - >> dg.topsort + >> dg.topsort => [1, 6, 2, 4, 5, 3] >> y = 0; dg.topsort { |v| y += v }; y => 21 You can use DOT to visualize the graph: - >> require 'graphy/dot' + >> require 'plexus/dot' >> dg.write_to_graphic_file('jpg','visualize') Here's an example showing the module inheritance hierarchy: >> module_graph = Digraph.new >> ObjectSpace.each_object(Module) do |m| - >> m.ancestors.each {|a| module_graph.add_edge!(m,a) if m != a} + >> m.ancestors.each {|a| module_graph.add_edge!(m,a) if m != a} >> end - >> gv = module_graph.vertices.select {|v| v.to_s.match(/Graphy/) } + >> gv = module_graph.vertices.select {|v| v.to_s.match(/Plexus/) } >> module_graph.induced_subgraph(gv).write_to_graphic_file('jpg','module_graph') Look for more in the examples directory. - + ## History This library is based on [GRATR][1] by Shawn Garbett (itself a fork of diff --git a/Rakefile b/Rakefile index 52ded35..6024f67 100644 --- a/Rakefile +++ b/Rakefile @@ -18,7 +18,7 @@ task :default => :spec Rake::RDocTask.new(:rdoc) do |rdoc| rdoc.rdoc_dir = 'rdoc' - rdoc.title = 'Graphy' + rdoc.title = 'Plexus' rdoc.options << '--line-numbers' << '--inline-source' rdoc.rdoc_files.include('README.rdoc') rdoc.rdoc_files.include('lib/**/*.rb') diff --git a/TODO.md b/TODO.md index fd2c74e..3b63a3d 100644 --- a/TODO.md +++ b/TODO.md @@ -14,7 +14,7 @@ The following list was present in GRATR, and items may (or may not) be added to this library: * Primal Dual for combinatorial optimization problems -* Min-Max Flow +* Min-Max Flow * Near optimal traveling salesman problem * Orientation of undirected graphs * Undirected graphs from ActiveRecord diff --git a/lib/graphy.rb b/lib/graphy.rb index 00a5eda..aa03264 100644 --- a/lib/graphy.rb +++ b/lib/graphy.rb @@ -28,51 +28,51 @@ require 'set' -module Graphy - # Graphy internals: graph builders and additionnal behaviors - autoload :GraphAPI, 'graphy/graph_api' +module Plexus + # Plexus internals: graph builders and additionnal behaviors + autoload :GraphAPI, 'plexus/graph_api' - autoload :GraphBuilder, 'graphy/graph' - autoload :AdjacencyGraphBuilder, 'graphy/adjacency_graph' + autoload :GraphBuilder, 'plexus/graph' + autoload :AdjacencyGraphBuilder, 'plexus/adjacency_graph' - autoload :DirectedGraphBuilder, 'graphy/directed_graph' - autoload :DigraphBuilder, 'graphy/directed_graph' - autoload :DirectedPseudoGraphBuilder, 'graphy/directed_graph' - autoload :DirectedMultiGraphBuilder, 'graphy/directed_graph' + autoload :DirectedGraphBuilder, 'plexus/directed_graph' + autoload :DigraphBuilder, 'plexus/directed_graph' + autoload :DirectedPseudoGraphBuilder, 'plexus/directed_graph' + autoload :DirectedMultiGraphBuilder, 'plexus/directed_graph' - autoload :UndirectedGraphBuilder, 'graphy/undirected_graph' - autoload :UndirectedPseudoGraphBuilder, 'graphy/undirected_graph' - autoload :UndirectedMultiGraphBuilder, 'graphy/undirected_graph' + autoload :UndirectedGraphBuilder, 'plexus/undirected_graph' + autoload :UndirectedPseudoGraphBuilder, 'plexus/undirected_graph' + autoload :UndirectedMultiGraphBuilder, 'plexus/undirected_graph' - autoload :Arc, 'graphy/arc' - autoload :ArcNumber, 'graphy/arc_number' - autoload :Biconnected, 'graphy/biconnected' - autoload :ChinesePostman, 'graphy/chinese_postman' - autoload :Common, 'graphy/common' - autoload :Comparability, 'graphy/comparability' + autoload :Arc, 'plexus/arc' + autoload :ArcNumber, 'plexus/arc_number' + autoload :Biconnected, 'plexus/biconnected' + autoload :ChinesePostman, 'plexus/chinese_postman' + autoload :Common, 'plexus/common' + autoload :Comparability, 'plexus/comparability' - autoload :Dot, 'graphy/dot' - autoload :Edge, 'graphy/edge' - autoload :Labels, 'graphy/labels' - autoload :MaximumFlow, 'graphy/maximum_flow' - #autoload :Rdot, 'graphy/dot' - autoload :Search, 'graphy/search' - autoload :StrongComponents, 'graphy/strong_components' + autoload :Dot, 'plexus/dot' + autoload :Edge, 'plexus/edge' + autoload :Labels, 'plexus/labels' + autoload :MaximumFlow, 'plexus/maximum_flow' + #autoload :Rdot, 'plexus/dot' + autoload :Search, 'plexus/search' + autoload :StrongComponents, 'plexus/strong_components' - # Graphy classes - autoload :AdjacencyGraph, 'graphy/classes/graph_classes' - autoload :DirectedGraph, 'graphy/classes/graph_classes' - autoload :Digraph, 'graphy/classes/graph_classes' - autoload :DirectedPseudoGraph, 'graphy/classes/graph_classes' - autoload :DirectedMultiGraph, 'graphy/classes/graph_classes' - autoload :UndirectedGraph, 'graphy/classes/graph_classes' - autoload :UndirectedPseudoGraph, 'graphy/classes/graph_classes' - autoload :UndirectedMultiGraph, 'graphy/classes/graph_classes' + # Plexus classes + autoload :AdjacencyGraph, 'plexus/classes/graph_classes' + autoload :DirectedGraph, 'plexus/classes/graph_classes' + autoload :Digraph, 'plexus/classes/graph_classes' + autoload :DirectedPseudoGraph, 'plexus/classes/graph_classes' + autoload :DirectedMultiGraph, 'plexus/classes/graph_classes' + autoload :UndirectedGraph, 'plexus/classes/graph_classes' + autoload :UndirectedPseudoGraph, 'plexus/classes/graph_classes' + autoload :UndirectedMultiGraph, 'plexus/classes/graph_classes' # ruby stdlib extensions - require 'graphy/ext' + require 'plexus/ext' # ruby 1.8.x/1.9.x compatibility - require 'graphy/ruby_compatibility' + require 'plexus/ruby_compatibility' end # Vendored libraries diff --git a/lib/graphy/adjacency_graph.rb b/lib/graphy/adjacency_graph.rb index a041da3..e7edc49 100644 --- a/lib/graphy/adjacency_graph.rb +++ b/lib/graphy/adjacency_graph.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus # This module provides the basic routines needed to implement the specialized builders: # {DigraphBuilder}, {UndirectedGraphBuilder}, {DirectedPseudoGraphBuilder}, @@ -42,7 +42,7 @@ def implementation_initialize(*params) end # Copy any given graph into this graph. - params.select { |p| p.is_a? Graphy::GraphBuilder }.each do |g| + params.select { |p| p.is_a? Plexus::GraphBuilder }.each do |g| g.edges.each do |e| add_edge!(e) edge_label_set(e, edge_label(e)) if edge_label(e) @@ -75,7 +75,7 @@ def vertex?(v) # @param [vertex] v (nil) # @return [Boolean] def edge?(u, v = nil) - u, v = u.source, u.target if u.is_a? Graphy::Arc + u, v = u.source, u.target if u.is_a? Plexus::Arc vertex?(u) and @vertex_dict[u].include?(v) end @@ -106,7 +106,7 @@ def add_vertex!(vertex, label = nil) # @return [AdjacencyGraph] `self` def add_edge!(u, v = nil, l = nil, n = nil) n = u.number if u.class.include? ArcNumber and n.nil? - u, v, l = u.source, u.target, u.label if u.is_a? Graphy::Arc + u, v, l = u.source, u.target, u.label if u.is_a? Plexus::Arc return self if not @allow_loops and u == v @@ -144,10 +144,10 @@ def remove_vertex!(v) # Removes an edge from the graph. # # Can be called with both source and target as vertex, - # or with source and object of {Graphy::Arc} derivation. + # or with source and object of {Plexus::Arc} derivation. # # @overload remove_edge!(a) - # @param [Graphy::Arc] a + # @param [Plexus::Arc] a # @return [AdjacencyGraph] `self` # @raise [ArgumentError] if parallel edges are enabled # @overload remove_edge!(u, v) @@ -156,7 +156,7 @@ def remove_vertex!(v) # @return [AdjacencyGraph] `self` # @raise [ArgumentError] if parallel edges are enabled and the {ArcNumber} of `u` is zero def remove_edge!(u, v = nil) - unless u.is_a? Graphy::Arc + unless u.is_a? Plexus::Arc raise ArgumentError if @parallel_edges u = edge_class[u,v] end @@ -206,7 +206,7 @@ def edges # @fixme def adjacent(x, options = {}) options[:direction] ||= :out - if !x.is_a?(Graphy::Arc) and (options[:direction] == :out || !directed?) + if !x.is_a?(Plexus::Arc) and (options[:direction] == :out || !directed?) if options[:type] == :edges i = -1 @parallel_edges ? @@ -221,4 +221,4 @@ def adjacent(x, options = {}) end end # Adjacency Graph -end # Graphy +end # Plexus diff --git a/lib/graphy/arc.rb b/lib/graphy/arc.rb index e5d6345..d18d86f 100644 --- a/lib/graphy/arc.rb +++ b/lib/graphy/arc.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus # Arc includes classes for representing egdes of directed and # undirected graphs. There is no need for a Vertex class, because any ruby # object can be a vertex of a graph. diff --git a/lib/graphy/arc_number.rb b/lib/graphy/arc_number.rb index c06151b..17fb329 100644 --- a/lib/graphy/arc_number.rb +++ b/lib/graphy/arc_number.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus # This module handles internal numbering of edges in order to differente between mutliple edges. module ArcNumber @@ -49,4 +49,4 @@ def cl.[](p_source, p_target, p_number = nil, p_label = nil) end end # ArcNumber -end # Graphy +end # Plexus diff --git a/lib/graphy/biconnected.rb b/lib/graphy/biconnected.rb index c7d0f12..278e88e 100644 --- a/lib/graphy/biconnected.rb +++ b/lib/graphy/biconnected.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus # Biconnected is a module for adding the biconnected algorithm to # UndirectedGraphs @@ -81,4 +81,4 @@ def biconnected end # biconnected end # Biconnected -end # Graphy +end # Plexus diff --git a/lib/graphy/chinese_postman.rb b/lib/graphy/chinese_postman.rb index ae0df19..ce34d10 100644 --- a/lib/graphy/chinese_postman.rb +++ b/lib/graphy/chinese_postman.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus module ChinesePostman # Returns the shortest walk that traverses all arcs at least @@ -87,5 +87,5 @@ def cp_unbalanced(delta) # :nodoc: end end # Chinese Postman -end # Graphy +end # Plexus diff --git a/lib/graphy/classes/graph_classes.rb b/lib/graphy/classes/graph_classes.rb index 9eae7a0..fc0218c 100644 --- a/lib/graphy/classes/graph_classes.rb +++ b/lib/graphy/classes/graph_classes.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus # A generic {GraphBuilder Graph} class you can inherit from. class Graph; include GraphBuilder; end diff --git a/lib/graphy/common.rb b/lib/graphy/common.rb index cacf6ea..7ede4c6 100644 --- a/lib/graphy/common.rb +++ b/lib/graphy/common.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus # This class defines a cycle graph of size n. # This is easily done by using the base Graph @@ -23,12 +23,12 @@ def vertex?(v) end def edge?(u,v = nil) - u, v = [u.source, v.target] if u.is_a? Graphy::Arc + u, v = [u.source, v.target] if u.is_a? Plexus::Arc vertex?(u) && vertex?(v) && ((v-u == 1) or (u == @size && v = 1)) end def edges - Array.new(@size) { |i| Graphy::Edge[i+1, (i+1) == @size ? 1 : i+2]} + Array.new(@size) { |i| Plexus::Edge[i+1, (i+1) == @size ? 1 : i+2]} end end # CycleBuilder @@ -49,15 +49,15 @@ def edges return @edges if @edges # cache edges @edges = [] @size.times do |u| - @size.times { |v| @edges << Graphy::Edge[u+1, v+1]} + @size.times { |v| @edges << Plexus::Edge[u+1, v+1]} end @edges end def edge?(u, v = nil) - u, v = [u.source, v.target] if u.kind_of? Graphy::Arc + u, v = [u.source, v.target] if u.kind_of? Plexus::Arc vertex?(u) && vertex?(v) end end # CompleteBuilder -end # Graphy +end # Plexus diff --git a/lib/graphy/comparability.rb b/lib/graphy/comparability.rb index 5f8b9ec..41a038e 100644 --- a/lib/graphy/comparability.rb +++ b/lib/graphy/comparability.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus module Comparability # A comparability graph is an UndirectedGraph that has a transitive @@ -19,7 +19,7 @@ def gamma_decomposition if classification[e].nil? k += 1 classification[e] = k; classification[e.reverse] = -k - comparability &&= graphy_comparability_explore(e, k, classification) + comparability &&= plexus_comparability_explore(e, k, classification) end end; [classification, comparability] end @@ -34,12 +34,12 @@ def transitive_orientation(digraph_class=Digraph) # Taken from Figure 5.10, on pg. 130 of Martin Golumbic's, _Algorithmic_Graph_ # _Theory_and_Perfect_Graphs. - def graphy_comparability_explore(edge, k, classification, space='') - ret = graphy_comparability_explore_inner(edge, k, classification, :forward, space) - graphy_comparability_explore_inner(edge.reverse, k, classification, :backward, space) && ret + def plexus_comparability_explore(edge, k, classification, space='') + ret = plexus_comparability_explore_inner(edge, k, classification, :forward, space) + plexus_comparability_explore_inner(edge.reverse, k, classification, :backward, space) && ret end - def graphy_comparability_explore_inner(edge, k, classification, direction,space) + def plexus_comparability_explore_inner(edge, k, classification, direction,space) comparability = true adj_target = adjacent(edge[1]) adjacent(edge[0]).select do |mt| @@ -50,14 +50,14 @@ def graphy_comparability_explore_inner(edge, k, classification, direction,space) if classification[e].nil? classification[e] = k classification[e.reverse] = -k - comparability = graphy_comparability_explore(e, k, classification, ' '+space) && comparability + comparability = plexus_comparability_explore(e, k, classification, ' '+space) && comparability elsif classification[e] == -k classification[e] = k - graphy_comparability_explore(e, k, classification, ' '+space) + plexus_comparability_explore(e, k, classification, ' '+space) comparability = false end end; comparability - end # graphy_comparability_explore_inner + end # plexus_comparability_explore_inner end # Comparability -end # Graphy +end # Plexus diff --git a/lib/graphy/directed_graph.rb b/lib/graphy/directed_graph.rb index a069a1d..1664b33 100644 --- a/lib/graphy/directed_graph.rb +++ b/lib/graphy/directed_graph.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus # This implements a directed graph which does not allow parallel # edges nor loops. That is, only one arc per nodes couple, @@ -7,8 +7,8 @@ module Graphy module DirectedGraphBuilder include GraphBuilder - autoload :Algorithms, "graphy/directed_graph/algorithms" - autoload :Distance, "graphy/directed_graph/distance" + autoload :Algorithms, "plexus/directed_graph/algorithms" + autoload :Distance, "plexus/directed_graph/distance" # FIXME: DRY this snippet, I didn't find a clever way to # to dit though diff --git a/lib/graphy/directed_graph/algorithms.rb b/lib/graphy/directed_graph/algorithms.rb index dc6664d..4a6b156 100644 --- a/lib/graphy/directed_graph/algorithms.rb +++ b/lib/graphy/directed_graph/algorithms.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus # Digraph is a directed graph which is a finite set of vertices # and a finite set of edges connecting vertices. It cannot contain parallel # edges going from the same source vertex to the same target. It also @@ -25,11 +25,11 @@ def directed? # A digraph uses the Arc class for edges. # - # @return [Graphy::MultiArc, Graphy::Arc] `Graphy::MultiArc` if the graph allows for parallel edges, - # `Graphy::Arc` otherwise. + # @return [Plexus::MultiArc, Plexus::Arc] `Plexus::MultiArc` if the graph allows for parallel edges, + # `Plexus::Arc` otherwise. # def edge_class - @parallel_edges ? Graphy::MultiArc : Graphy::Arc + @parallel_edges ? Plexus::MultiArc : Plexus::Arc end # Reverse all edges in a graph. diff --git a/lib/graphy/directed_graph/distance.rb b/lib/graphy/directed_graph/distance.rb index 0eeff60..73b9f28 100644 --- a/lib/graphy/directed_graph/distance.rb +++ b/lib/graphy/directed_graph/distance.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus module DirectedGraphBuilder # This module provides algorithms computing distance between @@ -164,4 +164,4 @@ def floyd_warshall(weight = nil, zero = 0) end # Distance end # DirectedGraph -end # Graphy +end # Plexus diff --git a/lib/graphy/dot.rb b/lib/graphy/dot.rb index c70c24f..28770d8 100644 --- a/lib/graphy/dot.rb +++ b/lib/graphy/dot.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus module Dot #FIXME: don't really understood where we stand with the dot generators. @@ -91,4 +91,4 @@ def write_to_graphic_file(fmt = 'png', dotfile = 'graph') alias as_dot_graphic write_to_graphic_file end # Dot -end # module Graphy +end # module Plexus diff --git a/lib/graphy/edge.rb b/lib/graphy/edge.rb index 72d001f..600de87 100644 --- a/lib/graphy/edge.rb +++ b/lib/graphy/edge.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus # An undirected edge is simply an undirected pair (source, target) used in # undirected graphs. Edge[u,v] == Edge[v,u] class Edge < Arc diff --git a/lib/graphy/ext.rb b/lib/graphy/ext.rb index b218ac7..b29b150 100644 --- a/lib/graphy/ext.rb +++ b/lib/graphy/ext.rb @@ -28,9 +28,9 @@ def singleton_class # # class A; include Digraph; end # a.singleton_class.ancestors - # # => [Graphy::GraphAPI, Graphy::DirectedGraph::Algorithms, ... - # Graphy::Labels, Enumerable, Object, Graphy, Kernel, BasicObject] - # a.is_a? Graphy::Graph + # # => [Plexus::GraphAPI, Plexus::DirectedGraph::Algorithms, ... + # Plexus::Labels, Enumerable, Object, Plexus, Kernel, BasicObject] + # a.is_a? Plexus::Graph # # => true # # @param [Class] klass diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb index 8cb25a9..517f285 100644 --- a/lib/graphy/graph.rb +++ b/lib/graphy/graph.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus # Using the methods required by the {GraphAPI}, it implements all the # *basic* functions of a {Graph} using *only* functions # requested in {GraphAPI}. The process is under the control of the pattern @@ -27,7 +27,7 @@ def [](*a) # Creates a generic graph. # - # @param [Hash(Graphy::Graph, Array)] *params initialization parameters. + # @param [Hash(Plexus::Graph, Array)] *params initialization parameters. # See {AdjacencyGraphBuilder#implementation_initialize} for more details. # @return [Graph] def initialize(*params) @@ -36,7 +36,7 @@ def initialize(*params) # and the is_a? redefinition trick (instance_evaling) should be # completed by a clever way to check the actual class of p. # Maybe using ObjectSpace to get the available Graph classes? - !(p.is_a? Graphy::GraphBuilder or p.is_a? Array or p.is_a? Hash) + !(p.is_a? Plexus::GraphBuilder or p.is_a? Array or p.is_a? Hash) end args = params.last || {} @@ -45,9 +45,9 @@ class << self self end.module_eval do # These inclusions trigger some validations checks by the way. - include(args[:implementation] ? args[:implementation] : Graphy::AdjacencyGraphBuilder) - include(args[:algorithmic_category] ? args[:algorithmic_category] : Graphy::DigraphBuilder ) - include Graphy::GraphAPI + include(args[:implementation] ? args[:implementation] : Plexus::AdjacencyGraphBuilder) + include(args[:algorithmic_category] ? args[:algorithmic_category] : Plexus::DigraphBuilder ) + include Plexus::GraphAPI end implementation_initialize(*params) @@ -57,12 +57,12 @@ class << self # # Using an arry of implicit {Arc}, specifying the vertices: # - # Graphy::Graph[1,2, 2,3, 2,4, 4,5].edges.to_a.to_s + # Plexus::Graph[1,2, 2,3, 2,4, 4,5].edges.to_a.to_s # # => "(1-2)(2-3)(2-4)(4-5)" # # Using a Hash for specifying labels along the way: # - # Graphy::Graph[ [:a,:b] => 3, [:b,:c] => 4 ] (note: do not use for Multi or Pseudo graphs) + # Plexus::Graph[ [:a,:b] => 3, [:b,:c] => 4 ] (note: do not use for Multi or Pseudo graphs) # # @param [Array, Hash] *a # @return [Graph] @@ -71,14 +71,14 @@ def from_array(*a) # Convert to edge class a[0].each do |k,v| #FIXME, edge class shouldn't be assume here!!! - if edge_class.include? Graphy::ArcNumber + if edge_class.include? Plexus::ArcNumber add_edge!(edge_class[k[0],k[1],nil,v]) else add_edge!(edge_class[k[0],k[1],v]) end end #FIXME, edge class shouldn't be assume here!!! - elsif a[0].is_a? Graphy::Arc + elsif a[0].is_a? Plexus::Arc a.each{ |e| add_edge!(e); self[e] = e.label} elsif a.size % 2 == 0 0.step(a.size-1, 2) {|i| add_edge!(a[i], a[i+1])} @@ -287,9 +287,9 @@ def edge?(*args) # @param [vertex] target # @param [Symbol] direction (:all) constraint on the direction of adjacency; may be either `:in`, `:out` or `:all` def adjacent?(source, target, direction = :all) - if source.is_a? Graphy::Arc + if source.is_a? Plexus::Arc raise NoArcError unless edge? source - if target.is_a? Graphy::Arc + if target.is_a? Plexus::Arc raise NoArcError unless edge? target (direction != :out and source.source == target.target) or (direction != :in and source.target == target.source) else @@ -298,7 +298,7 @@ def adjacent?(source, target, direction = :all) end else raise NoVertexError unless vertex? source - if target.is_a? Graphy::Arc + if target.is_a? Plexus::Arc raise NoArcError unless edge? target (direction != :out and source == target.target) or (direction != :in and source == target.source) else @@ -347,7 +347,7 @@ def empty? # # @param [vertex, Arc] x def include?(x) - x.is_a?(Graphy::Arc) ? edge?(x) : vertex?(x) + x.is_a?(Plexus::Arc) ? edge?(x) : vertex?(x) end alias has? include? @@ -359,7 +359,7 @@ def include?(x) # @param [vertex, Arc] x # @param [Symbol] direction (:all) can be either `:all`, `:in` or `:out` def neighborhood(x, direction = :all) - adjacent(x, :direction => direction, :type => ((x.is_a? Graphy::Arc) ? :edges : :vertices )) + adjacent(x, :direction => direction, :type => ((x.is_a? Plexus::Arc) ? :edges : :vertices )) end # Union of all neighborhoods of vertices (or edges) in the Enumerable x minus the contents of x. @@ -525,7 +525,7 @@ def num_edges # Equality is defined to be same set of edges and directed? def eql?(g) - return false unless g.is_a? Graphy::Graph + return false unless g.is_a? Plexus::Graph (directed? == g.directed?) and (vertices.sort == g.vertices.sort) and @@ -551,9 +551,9 @@ def merge(other) def +(other) result = self.class.new(self) case other - when Graphy::Graph + when Plexus::Graph result.merge(other) - when Graphy::Arc + when Plexus::Arc result.add_edge!(other) else result.add_vertex!(other) @@ -566,9 +566,9 @@ def +(other) # @return [Graph] def -(other) case other - when Graphy::Graph + when Plexus::Graph induced_subgraph(vertices - other.vertices) - when Graphy::Arc + when Plexus::Arc self.class.new(self).remove_edge!(other) else self.class.new(self).remove_vertex!(other) @@ -605,11 +605,11 @@ def inspect ## Example: ## dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] ## dg.add_vertices! 1, 5, "yosh" - ## # => Graphy::Digraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[6,4,nil]] + ## # => Plexus::Digraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[6,4,nil]] ## dg.vertex?("yosh") ## # => true ## dg - ## # =>Graphy::Digraph[Graphy::Arc[1,2,nil], Graphy::Arc[1,6,nil], Graphy::Arc[2,3,nil], Graphy::Arc[2,4,nil], Graphy::Arc[4,5,nil], Graphy::Arc[6,4,nil]] + ## # =>Plexus::Digraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[6,4,nil]] ## the new vertex doesn't show up. ## Actually this version of inspect is far too verbose IMO :) l = vertices.select { |v| self[v]}.map { |u| "vertex_label_set(#{u.inspect}, #{self[u].inspect})"}.join('.') @@ -620,7 +620,7 @@ def inspect # ? def edge_convert(*args) - args[0].is_a?(Graphy::Arc) ? args[0] : edge_class[*args] + args[0].is_a?(Plexus::Arc) ? args[0] : edge_class[*args] end end end diff --git a/lib/graphy/graph_api.rb b/lib/graphy/graph_api.rb index 877b6d1..bebc192 100644 --- a/lib/graphy/graph_api.rb +++ b/lib/graphy/graph_api.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus # This module defines the minimum set of functions required to make a graph that can # use the algorithms defined by this library. # @@ -32,4 +32,4 @@ def self.included(klass) end end # GraphAPI -end # Graphy +end # Plexus diff --git a/lib/graphy/labels.rb b/lib/graphy/labels.rb index 5b5e581..6d47521 100644 --- a/lib/graphy/labels.rb +++ b/lib/graphy/labels.rb @@ -1,26 +1,26 @@ -module Graphy +module Plexus # This module add support for labels. # # The graph labeling process consist in assigning labels, traditionally represented - # by integers, to the edges or vertices, or both, of a graph. Graphy recommands you + # by integers, to the edges or vertices, or both, of a graph. Plexus recommands you # abide by this rule and do use integers as labels. # - # Some algorithms can make use of labeling (sea {Graphy::Search} for instance). + # Some algorithms can make use of labeling (sea {Plexus::Search} for instance). module Labels # Return a label for an edge or vertex. def [](u) - (u.is_a? Graphy::Arc) ? edge_label(u) : vertex_label(u) + (u.is_a? Plexus::Arc) ? edge_label(u) : vertex_label(u) end # Set a label for an edge or vertex. def []=(u, value) - (u.is_a? Graphy::Arc) ? edge_label_set(u, value) : vertex_label_set(u, value) + (u.is_a? Plexus::Arc) ? edge_label_set(u, value) : vertex_label_set(u, value) end # Delete a label entirely. def delete_label(u) - (u.is_a? Graphy::Arc) ? edge_label_delete(u) : vertex_label_delete(u) + (u.is_a? Plexus::Arc) ? edge_label_delete(u) : vertex_label_delete(u) end # Get the label for an edge. @@ -42,7 +42,7 @@ def edge_label(u, v = nil, n = nil) # Set the label for an edge. def edge_label_set(u, v = nil, l = nil, n = nil) - u.is_a?(Graphy::Arc) ? l = v : u = edge_convert(u, v, n) + u.is_a?(Plexus::Arc) ? l = v : u = edge_convert(u, v, n) edge_label_dict[u] = l self end @@ -110,4 +110,4 @@ def property_set(u, name, value) end end # Labels -end # Graphy +end # Plexus diff --git a/lib/graphy/maximum_flow.rb b/lib/graphy/maximum_flow.rb index ca0e094..242fdbb 100644 --- a/lib/graphy/maximum_flow.rb +++ b/lib/graphy/maximum_flow.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus class Network include DigraphBuilder @@ -74,4 +74,4 @@ def maximum_flow(s, t, capacity = nil, zero = 0) end # MaximumFlow end # GraphBuilder -end # Graphy +end # Plexus diff --git a/lib/graphy/search.rb b/lib/graphy/search.rb index 56a7374..bfcde4f 100644 --- a/lib/graphy/search.rb +++ b/lib/graphy/search.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus # **Search/traversal algorithms.** # # This module defines a collection of search/traversal algorithms, in a unified API. @@ -70,7 +70,7 @@ module Search # # @param [Hash] options def bfs(options = {}, &block) - graphy_search_helper(:shift, options, &block) + plexus_search_helper(:shift, options, &block) end alias :bread_first_search :bfs @@ -78,7 +78,7 @@ def bfs(options = {}, &block) # # @param [Hash] options def dfs(options = {}, &block) - graphy_search_helper(:pop, options, &block) + plexus_search_helper(:pop, options, &block) end alias :depth_first_search :dfs @@ -224,7 +224,7 @@ def add_lexeme(values) # # @return [vertex] def lexicograph_bfs(&block) - lex_q = Graphy::Search::LexicographicQueue.new(vertices) + lex_q = Plexus::Search::LexicographicQueue.new(vertices) result = [] num_vertices.times do v = lex_q.pop @@ -375,7 +375,7 @@ def method_missing(sym, *args, &block) # @param [Symbol] op the algorithm to be used te perform the search # @param [Hash] options # @return [Object] result - def graphy_search_helper(op, options = {}, &block) + def plexus_search_helper(op, options = {}, &block) return nil if size == 0 result = [] @@ -401,7 +401,7 @@ def graphy_search_helper(op, options = {}, &block) # Loop till the search iterator exhausts the waiting list. visited_edges = {} # This prevents retraversing edges in undirected graphs. until waiting.empty? - graphy_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop) + plexus_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop) end # Waiting for the list to be exhausted, check if a new root vertex is available. u = color_map.detect { |key,value| value == :unvisited } @@ -415,7 +415,7 @@ def graphy_search_helper(op, options = {}, &block) # Performs a search iteration (step). # # @private - def graphy_search_iteration(options, waiting, color_map, visited_edges, result, recursive = false) + def plexus_search_iteration(options, waiting, color_map, visited_edges, result, recursive = false) # Fetch the next waiting vertex in the list. #sleep u = waiting.next @@ -437,7 +437,7 @@ def graphy_search_iteration(options, waiting, color_map, visited_edges, result, color_map[v] = :waiting waiting.push(v) # If it's recursive (i.e. dfs), then call self. - graphy_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive + plexus_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive when :waiting options.handle_edge(:back_edge, e) else diff --git a/lib/graphy/strong_components.rb b/lib/graphy/strong_components.rb index 3a201e4..24f1fa7 100644 --- a/lib/graphy/strong_components.rb +++ b/lib/graphy/strong_components.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus module StrongComponents # strong_components computes the strongly connected components # of a graph using Tarjan's algorithm based on DFS. See: Robert E. Tarjan @@ -68,7 +68,7 @@ def condensation # Compute transitive closure of a graph. That is any node that is reachable # along a path is added as a directed edge. def transitive_closure! - cgtc = condensation.graphy_inner_transitive_closure! + cgtc = condensation.plexus_inner_transitive_closure! cgtc.each do |cgv| cgtc.adjacent(cgv).each do |adj| cgv.each do |u| @@ -82,7 +82,7 @@ def transitive_closure! # is not changed. def transitive_closure() self.class.new(self).transitive_closure!; end - def graphy_inner_transitive_closure! # :nodoc: + def plexus_inner_transitive_closure! # :nodoc: sort.reverse.each do |u| adjacent(u).each do |v| adjacent(v).each {|w| add_edge!(u,w) unless edge?(u,w)} @@ -90,4 +90,4 @@ def graphy_inner_transitive_closure! # :nodoc: end; self end end # StrongComponents -end # Graphy +end # Plexus diff --git a/lib/graphy/support/support.rb b/lib/graphy/support/support.rb index 61dbfe5..24005d6 100644 --- a/lib/graphy/support/support.rb +++ b/lib/graphy/support/support.rb @@ -1,9 +1,9 @@ -module Graphy +module Plexus # Errors # TODO FIXME: must review all raise lines and streamline things # Base error class for the library. - class GraphyError < StandardError; end + class PlexusError < StandardError; end - class NoArcError < GraphyError; end + class NoArcError < PlexusError; end end diff --git a/lib/graphy/undirected_graph.rb b/lib/graphy/undirected_graph.rb index 7bf0d97..cfbbf02 100644 --- a/lib/graphy/undirected_graph.rb +++ b/lib/graphy/undirected_graph.rb @@ -1,8 +1,8 @@ -module Graphy +module Plexus module UndirectedGraphBuilder - autoload :Algorithms, "graphy/undirected_graph/algorithms" + autoload :Algorithms, "plexus/undirected_graph/algorithms" - include Graphy::GraphBuilder + include Plexus::GraphBuilder extends_host module ClassMethods @@ -13,7 +13,7 @@ def [](*a) def initialize(*params) args = (params.pop if params.last.kind_of? Hash) || {} - args[:algorithmic_category] = Graphy::UndirectedGraphBuilder::Algorithms + args[:algorithmic_category] = Plexus::UndirectedGraphBuilder::Algorithms super *(params << args) end end diff --git a/lib/graphy/undirected_graph/algorithms.rb b/lib/graphy/undirected_graph/algorithms.rb index 28fd339..27655f8 100644 --- a/lib/graphy/undirected_graph/algorithms.rb +++ b/lib/graphy/undirected_graph/algorithms.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus module UndirectedGraphBuilder module Algorithms @@ -16,10 +16,10 @@ def degree(v) in_degree(v); end def balanced?(v) true; end # UndirectedGraph uses Edge for the edge class. - def edge_class() @parallel_edges ? Graphy::MultiEdge : Graphy::Edge; end + def edge_class() @parallel_edges ? Plexus::MultiEdge : Plexus::Edge; end def remove_edge!(u, v=nil) - unless u.kind_of? Graphy::Arc + unless u.kind_of? Plexus::Arc raise ArgumentError if @parallel_edges u = edge_class[u,v] end @@ -87,4 +87,4 @@ def triangulated_chromatic_number end # UndirectedGraphAlgorithms end # UndirectedGraphBuilder -end # Graphy +end # Plexus diff --git a/lib/graphy/version.rb b/lib/graphy/version.rb index 74b10e9..f1de255 100644 --- a/lib/graphy/version.rb +++ b/lib/graphy/version.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus MAJOR = 0 MINOR = 5 PATCH = 3 diff --git a/plexus.gemspec b/plexus.gemspec new file mode 100644 index 0000000..e4bc8c2 --- /dev/null +++ b/plexus.gemspec @@ -0,0 +1,24 @@ +# -*- encoding: utf-8 -*- +require './lib/plexus/version' + +Gem::Specification.new do |s| + s.name = %q{plexus} + s.version = Plexus::VERSION + s.authors = ["Bruce Williams", "Jean-Denis Vauguet "] + s.summary = "A framework for graph data structures and algorithms." + s.description = %q{This library is based on GRATR and RGL. + +Graph algorithms currently provided are: + +* Topological Sort +* Strongly Connected Components +* Transitive Closure +* Rural Chinese Postman +* Biconnected +} + s.email = %q{bruce@codefluency.com} + s.add_dependency "facets" + s.add_development_dependency "rspec" + s.add_development_dependency "yard" +end + diff --git a/spec/properties_spec.rb b/spec/properties_spec.rb index 70fda57..0294e3a 100644 --- a/spec/properties_spec.rb +++ b/spec/properties_spec.rb @@ -1,6 +1,6 @@ require File.join(File.dirname(__FILE__), 'spec_helper') -require 'graphy/dot' +require 'plexus/dot' # This test runs the classes from Appendix F in # _Algorithmic_Graph_Theory_and_Perfect_Graphs, diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 21a2c22..f12a8da 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,7 @@ -require File.expand_path("../../lib/graphy.rb", __FILE__) +require File.expand_path("../../lib/plexus.rb", __FILE__) -require 'graphy' -include Graphy +require 'plexus' +include Plexus module AncestryHelper # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles diff --git a/spec/triangulated_spec.rb b/spec/triangulated_spec.rb index b8834f1..3c2c281 100644 --- a/spec/triangulated_spec.rb +++ b/spec/triangulated_spec.rb @@ -100,7 +100,7 @@ describe "lexicographic_queue" do it do - q = Graphy::Search::LexicographicQueue.new([1,2,3,4,5,6,7,8,9]) + q = Plexus::Search::LexicographicQueue.new([1,2,3,4,5,6,7,8,9]) q.pop.should == 9 q.add_lexeme([3,4,5,6,7,8]) q.pop.should == 8 From b52263251a90522f76e64b82ffceca2fce826d0f Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Wed, 6 Jul 2011 19:46:48 +0200 Subject: [PATCH 30/44] renamed project Plexus, bis --- Gemfile.lock | 4 +- lib/plexus.rb | 90 ++++ lib/plexus/adjacency_graph.rb | 224 ++++++++ lib/plexus/arc.rb | 59 ++ lib/plexus/arc_number.rb | 52 ++ lib/plexus/biconnected.rb | 84 +++ lib/plexus/chinese_postman.rb | 91 ++++ lib/plexus/classes/graph_classes.rb | 28 + lib/plexus/common.rb | 63 +++ lib/plexus/comparability.rb | 63 +++ lib/plexus/directed_graph.rb | 78 +++ lib/plexus/directed_graph/algorithms.rb | 95 ++++ lib/plexus/directed_graph/distance.rb | 167 ++++++ lib/plexus/dot.rb | 94 ++++ lib/plexus/edge.rb | 36 ++ lib/plexus/ext.rb | 79 +++ lib/plexus/graph.rb | 626 ++++++++++++++++++++++ lib/plexus/graph_api.rb | 35 ++ lib/plexus/labels.rb | 113 ++++ lib/plexus/maximum_flow.rb | 77 +++ lib/plexus/ruby_compatibility.rb | 17 + lib/plexus/search.rb | 510 ++++++++++++++++++ lib/plexus/strong_components.rb | 93 ++++ lib/plexus/support/support.rb | 9 + lib/plexus/undirected_graph.rb | 56 ++ lib/plexus/undirected_graph/algorithms.rb | 90 ++++ lib/plexus/version.rb | 6 + 27 files changed, 2937 insertions(+), 2 deletions(-) create mode 100644 lib/plexus.rb create mode 100644 lib/plexus/adjacency_graph.rb create mode 100644 lib/plexus/arc.rb create mode 100644 lib/plexus/arc_number.rb create mode 100644 lib/plexus/biconnected.rb create mode 100644 lib/plexus/chinese_postman.rb create mode 100644 lib/plexus/classes/graph_classes.rb create mode 100644 lib/plexus/common.rb create mode 100644 lib/plexus/comparability.rb create mode 100644 lib/plexus/directed_graph.rb create mode 100644 lib/plexus/directed_graph/algorithms.rb create mode 100644 lib/plexus/directed_graph/distance.rb create mode 100644 lib/plexus/dot.rb create mode 100644 lib/plexus/edge.rb create mode 100644 lib/plexus/ext.rb create mode 100644 lib/plexus/graph.rb create mode 100644 lib/plexus/graph_api.rb create mode 100644 lib/plexus/labels.rb create mode 100644 lib/plexus/maximum_flow.rb create mode 100644 lib/plexus/ruby_compatibility.rb create mode 100644 lib/plexus/search.rb create mode 100644 lib/plexus/strong_components.rb create mode 100644 lib/plexus/support/support.rb create mode 100644 lib/plexus/undirected_graph.rb create mode 100644 lib/plexus/undirected_graph/algorithms.rb create mode 100644 lib/plexus/version.rb diff --git a/Gemfile.lock b/Gemfile.lock index 6d3c33b..1812604 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - graphy (0.5.3) + plexus (0.5.3) facets GEM @@ -23,6 +23,6 @@ PLATFORMS ruby DEPENDENCIES - graphy! + plexus! rspec yard diff --git a/lib/plexus.rb b/lib/plexus.rb new file mode 100644 index 0000000..aa03264 --- /dev/null +++ b/lib/plexus.rb @@ -0,0 +1,90 @@ +#-- +# Copyright (c) 2006 Shawn Patrick Garbett +# Copyright (c) 2002,2004,2005 by Horst Duchene +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice(s), +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the Shawn Garbett nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#++ + +require 'set' + +module Plexus + # Plexus internals: graph builders and additionnal behaviors + autoload :GraphAPI, 'plexus/graph_api' + + autoload :GraphBuilder, 'plexus/graph' + autoload :AdjacencyGraphBuilder, 'plexus/adjacency_graph' + + autoload :DirectedGraphBuilder, 'plexus/directed_graph' + autoload :DigraphBuilder, 'plexus/directed_graph' + autoload :DirectedPseudoGraphBuilder, 'plexus/directed_graph' + autoload :DirectedMultiGraphBuilder, 'plexus/directed_graph' + + autoload :UndirectedGraphBuilder, 'plexus/undirected_graph' + autoload :UndirectedPseudoGraphBuilder, 'plexus/undirected_graph' + autoload :UndirectedMultiGraphBuilder, 'plexus/undirected_graph' + + autoload :Arc, 'plexus/arc' + autoload :ArcNumber, 'plexus/arc_number' + autoload :Biconnected, 'plexus/biconnected' + autoload :ChinesePostman, 'plexus/chinese_postman' + autoload :Common, 'plexus/common' + autoload :Comparability, 'plexus/comparability' + + autoload :Dot, 'plexus/dot' + autoload :Edge, 'plexus/edge' + autoload :Labels, 'plexus/labels' + autoload :MaximumFlow, 'plexus/maximum_flow' + #autoload :Rdot, 'plexus/dot' + autoload :Search, 'plexus/search' + autoload :StrongComponents, 'plexus/strong_components' + + # Plexus classes + autoload :AdjacencyGraph, 'plexus/classes/graph_classes' + autoload :DirectedGraph, 'plexus/classes/graph_classes' + autoload :Digraph, 'plexus/classes/graph_classes' + autoload :DirectedPseudoGraph, 'plexus/classes/graph_classes' + autoload :DirectedMultiGraph, 'plexus/classes/graph_classes' + autoload :UndirectedGraph, 'plexus/classes/graph_classes' + autoload :UndirectedPseudoGraph, 'plexus/classes/graph_classes' + autoload :UndirectedMultiGraph, 'plexus/classes/graph_classes' + + # ruby stdlib extensions + require 'plexus/ext' + # ruby 1.8.x/1.9.x compatibility + require 'plexus/ruby_compatibility' +end + +# Vendored libraries + +require 'pathname' +path = Pathname.new(__FILE__) +$LOAD_PATH.unshift(path + '../../vendor') # http://ruby.brian-amberg.de/priority-queue/ +$LOAD_PATH.unshift(path + '../../vendor/priority-queue/lib') + +require 'rdot' +require 'facets/hash' + +require 'priority_queue/ruby_priority_queue' +PriorityQueue = RubyPriorityQueue + diff --git a/lib/plexus/adjacency_graph.rb b/lib/plexus/adjacency_graph.rb new file mode 100644 index 0000000..e7edc49 --- /dev/null +++ b/lib/plexus/adjacency_graph.rb @@ -0,0 +1,224 @@ +module Plexus + + # This module provides the basic routines needed to implement the specialized builders: + # {DigraphBuilder}, {UndirectedGraphBuilder}, {DirectedPseudoGraphBuilder}, + # {UndirectedPseudoGraphBuilder}, {DirectedMultiGraphBuilder} and {UndirectedMultiGraphBuilder} + # modules, each of them streamlining {AdjacencyGraphBuilder}'s behavior. Those + # implementations rely on the {GraphBuilder}, under the control of the {GraphAPI}. + module AdjacencyGraphBuilder + + # Defines a useful `push` -> `add` alias for arrays. + class ArrayWithAdd < Array + alias add push + end + + # This method is called by the specialized implementations + # upon graph creation. + # + # Initialization parameters can include: + # + # * an array of edges to add + # * one or several graphs to copy (will be merged if multiple) + # * `:parallel_edges` denotes that duplicate edges are allowed + # * `:loops denotes` that loops are allowed + # + # @param *params [Hash] the initialization parameters + def implementation_initialize(*params) + @vertex_dict = Hash.new + clear_all_labels + + # FIXME: could definitely make use of the activesupport helper + # extract_options! and facets' reverse_merge! technique + # to handle parameters + args = (params.pop if params.last.is_a? Hash) || {} + + # Basic configuration of adjacency. + @allow_loops = args[:loops] || false + @parallel_edges = args[:parallel_edges] || false + @edgelist_class = @parallel_edges ? ArrayWithAdd : Set + if @parallel_edges + @edge_number = Hash.new + @next_edge_number = 0 + end + + # Copy any given graph into this graph. + params.select { |p| p.is_a? Plexus::GraphBuilder }.each do |g| + g.edges.each do |e| + add_edge!(e) + edge_label_set(e, edge_label(e)) if edge_label(e) + end + g.vertices.each do |v| + add_vertex!(v) + vertex_label_set(v, vertex_label(v)) if vertex_label(v) + end + end + + # Add all array edges specified. + params.select { |p| p.is_a? Array }.each do |a| + 0.step(a.size-1, 2) { |i| add_edge!(a[i], a[i+1]) } + end + end + + # Returns true if v is a vertex of this Graph + # (an "O(1)" implementation of `vertex?`). + # + # @param [vertex] v + # @return [Boolean] + def vertex?(v) + @vertex_dict.has_key?(v) + end + + # Returns true if [u,v] or u is an {Arc} + # (an "O(1)" implementation of `edge?`). + # + # @param [vertex] u + # @param [vertex] v (nil) + # @return [Boolean] + def edge?(u, v = nil) + u, v = u.source, u.target if u.is_a? Plexus::Arc + vertex?(u) and @vertex_dict[u].include?(v) + end + + # Adds a vertex to the graph with an optional label. + # + # @param [vertex(Object)] vertex any kind of Object can act as a vertex + # @param [#to_s] label (nil) + def add_vertex!(vertex, label = nil) + @vertex_dict[vertex] ||= @edgelist_class.new + self[vertex] = label if label + self + end + + # Adds an edge to the graph. + # + # Can be called in two basic ways, label is optional: + # @overload add_edge!(arc) + # Using an explicit {Arc} + # @param [Arc] arc an {Arc}[source, target, label = nil] object + # @return [AdjacencyGraph] `self` + # @overload add_edge!(source, target, label = nil) + # Using vertices to define an arc implicitly + # @param [vertex] u + # @param [vertex] v (nil) + # @param [Label] l (nil) + # @param [Integer] n (nil) {Arc arc} number of `(u, v)` (if `nil` and if `u` + # has an {ArcNumber}, then it will be used) + # @return [AdjacencyGraph] `self` + def add_edge!(u, v = nil, l = nil, n = nil) + n = u.number if u.class.include? ArcNumber and n.nil? + u, v, l = u.source, u.target, u.label if u.is_a? Plexus::Arc + + return self if not @allow_loops and u == v + + n = (@next_edge_number += 1) unless n if @parallel_edges + add_vertex!(u) + add_vertex!(v) + @vertex_dict[u].add(v) + (@edge_number[u] ||= @edgelist_class.new).add(n) if @parallel_edges + + unless directed? + @vertex_dict[v].add(u) + (@edge_number[v] ||= @edgelist_class.new).add(n) if @parallel_edges + end + + self[n ? edge_class[u,v,n] : edge_class[u,v]] = l if l + self + end + + # Removes a given vertex from the graph. + # + # @param [vertex] v + # @return [AdjacencyGraph] `self` + def remove_vertex!(v) + # FIXME This is broken for multi graphs + @vertex_dict.delete(v) + @vertex_dict.each_value { |adjList| adjList.delete(v) } + @vertex_dict.keys.each do |u| + delete_label(edge_class[u,v]) + delete_label(edge_class[v,u]) + end + delete_label(v) + self + end + + # Removes an edge from the graph. + # + # Can be called with both source and target as vertex, + # or with source and object of {Plexus::Arc} derivation. + # + # @overload remove_edge!(a) + # @param [Plexus::Arc] a + # @return [AdjacencyGraph] `self` + # @raise [ArgumentError] if parallel edges are enabled + # @overload remove_edge!(u, v) + # @param [vertex] u + # @param [vertex] v + # @return [AdjacencyGraph] `self` + # @raise [ArgumentError] if parallel edges are enabled and the {ArcNumber} of `u` is zero + def remove_edge!(u, v = nil) + unless u.is_a? Plexus::Arc + raise ArgumentError if @parallel_edges + u = edge_class[u,v] + end + raise ArgumentError if @parallel_edges and (u.number || 0) == 0 + return self unless @vertex_dict[u.source] # It doesn't exist + delete_label(u) # Get rid of label + if @parallel_edges + index = @edge_number[u.source].index(u.number) + raise NoArcError unless index + @vertex_dict[u.source].delete_at(index) + @edge_number[u.source].delete_at(index) + else + @vertex_dict[u.source].delete(u.target) + end + self + end + + # Returns an array of vertices that the graph has. + # + # @return [Array] graph's vertices + def vertices + @vertex_dict.keys + end + + # Returns an array of edges, most likely of class {Arc} or {Edge} depending + # upon the type of graph. + # + # @return [Array] + def edges + @vertex_dict.keys.inject(Set.new) do |a,v| + if @parallel_edges and @edge_number[v] + @vertex_dict[v].zip(@edge_number[v]).each do |w| + s, t, n = v, w[0], w[1] + a.add(edge_class[s, t, n, edge_label(s, t, n)]) + end + else + @vertex_dict[v].each do |w| + a.add(edge_class[v, w, edge_label(v, w)]) + end + end + a + end.to_a + end + + # FIXME, EFFED UP (but why?) + # + # @fixme + def adjacent(x, options = {}) + options[:direction] ||= :out + if !x.is_a?(Plexus::Arc) and (options[:direction] == :out || !directed?) + if options[:type] == :edges + i = -1 + @parallel_edges ? + @vertex_dict[x].map { |v| e = edge_class[x, v, @edge_number[x][i+=1]]; e.label = self[e]; e } : + @vertex_dict[x].map { |v| e = edge_class[x, v]; e.label = self[e]; e } + else + @vertex_dict[x].to_a + end + else + graph_adjacent(x,options) + end + end + + end # Adjacency Graph +end # Plexus diff --git a/lib/plexus/arc.rb b/lib/plexus/arc.rb new file mode 100644 index 0000000..d18d86f --- /dev/null +++ b/lib/plexus/arc.rb @@ -0,0 +1,59 @@ +module Plexus + # Arc includes classes for representing egdes of directed and + # undirected graphs. There is no need for a Vertex class, because any ruby + # object can be a vertex of a graph. + # + # Arc's base is a Struct with a :source, a :target and a :label. + Struct.new("ArcBase", :source, :target, :label) + + class Arc < Struct::ArcBase + def initialize(p_source, p_target, p_label = nil) + super(p_source, p_target, p_label) + end + + # Ignore labels for equality. + def eql?(other) + self.class == other.class and target == other.target and source == other.source + end + alias == eql? + + # Returns (v,u) if self == (u,v). + def reverse() + self.class.new(target, source, label) + end + + # Sort support. + def <=>(rhs) + [source, target] <=> [rhs.source, rhs.target] + end + + # Arc.new[1,2].to_s => "(1-2 'label')" + def to_s + l = label ? " '#{label.to_s}'" : '' + "(#{source}-#{target}#{l})" + end + + # Hash is defined in such a way that label is not + # part of the hash value + # FIXME: I had to get rid of that in order to make to_dot_graph + # work, but I can't figure it out (doesn't show up in the stack!) + def hash + source.hash ^ (target.hash + 1) + end + + # Shortcut constructor. + # + # Instead of Arc.new(1,2) one can use Arc[1,2]. + def self.[](p_source, p_target, p_label = nil) + new(p_source, p_target, p_label) + end + + def inspect + "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{label.inspect}]" + end + end + + class MultiArc < Arc + include ArcNumber + end +end diff --git a/lib/plexus/arc_number.rb b/lib/plexus/arc_number.rb new file mode 100644 index 0000000..17fb329 --- /dev/null +++ b/lib/plexus/arc_number.rb @@ -0,0 +1,52 @@ +module Plexus + # This module handles internal numbering of edges in order to differente between mutliple edges. + module ArcNumber + + # Used to differentiate between mutli-edges + attr_accessor :number + + def initialize(p_source, p_target, p_number, p_label = nil) + self.number = p_number + super(p_source, p_target, p_label) + end + + # Returns (v,u) if self == (u,v). + def reverse + self.class.new(target, source, number, label) + end + + # Allow for hashing of self loops. + def hash + super ^ number.hash + end + + def to_s + super + "[#{number}]" + end + + def <=>(rhs) + (result = super(rhs)) == 0 ? number <=> rhs.number : result + end + + def inspect + "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{number.inspect},#{label.inspect}]" + end + + def eql?(rhs) + super(rhs) and (rhs.number.nil? or number.nil? or number == rhs.number) + end + + def ==(rhs) + eql?(rhs) + end + + # Shortcut constructor. Instead of Arc.new(1,2) one can use Arc[1,2] + def self.included(cl) + # FIXME: lacks a cl.class_eval, no? + def cl.[](p_source, p_target, p_number = nil, p_label = nil) + new(p_source, p_target, p_number, p_label) + end + end + + end # ArcNumber +end # Plexus diff --git a/lib/plexus/biconnected.rb b/lib/plexus/biconnected.rb new file mode 100644 index 0000000..278e88e --- /dev/null +++ b/lib/plexus/biconnected.rb @@ -0,0 +1,84 @@ +module Plexus + + # Biconnected is a module for adding the biconnected algorithm to + # UndirectedGraphs + module Biconnected + + # biconnected computes the biconnected subgraphs + # of a graph using Tarjan's algorithm based on DFS. See: Robert E. Tarjan + # _Depth_First_Search_and_Linear_Graph_Algorithms_. SIAM Journal on + # Computing, 1(2):146-160, 1972 + # + # The output of the algorithm is a pair, the first value is an + # array of biconnected subgraphs. The second is the set of + # articulation vertices. + # + # A connected graph is biconnected if the removal of any single vertex + # (and all edges incident on that vertex) cannot disconnect the graph. + # More generally, the biconnected components of a graph are the maximal + # subsets of vertices such that the removal of a vertex from a particular + # component will not disconnect the component. Unlike connected components, + # vertices may belong to multiple biconnected components: those vertices + # that belong to more than one biconnected component are called articulation + # points or, equivalently, cut vertices. Articulation points are vertices + # whose removal would increase the number of connected components in the graph. + # Thus, a graph without articulation points is biconnected. + def biconnected + dfs_num = 0 + number = {}; predecessor = {}; low_point = {} + stack = []; result = []; articulation= [] + + root_vertex = Proc.new {|v| predecessor[v]=v } + enter_vertex = Proc.new {|u| number[u]=low_point[u]=(dfs_num+=1) } + tree_edge = Proc.new do |e| + stack.push(e) + predecessor[e.target] = e.source + end + back_edge = Proc.new do |e| + if e.target != predecessor[e.source] + stack.push(e) + low_point[e.source] = [low_point[e.source], number[e.target]].min + end + end + exit_vertex = Proc.new do |u| + parent = predecessor[u] + is_articulation_point = false + if number[parent] > number[u] + parent = predecessor[parent] + is_articulation_point = true + end + if parent == u + is_articulation_point = false if (number[u] + 1) == number[predecessor[u]] + else + low_point[parent] = [low_point[parent], low_point[u]].min + if low_point[u] >= number[parent] + if number[parent] > number[predecessor[parent]] + predecessor[u] = predecessor[parent] + predecessor[parent] = u + end + result << (component = self.class.new) + while number[stack[-1].source] >= number[u] + component.add_edge!(stack.pop) + end + component.add_edge!(stack.pop) + if stack.empty? + predecessor[u] = parent + predecessor[parent] = u + end + end + end + articulation << u if is_articulation_point + end + + # Execute depth first search + dfs({:root_vertex => root_vertex, + :enter_vertex => enter_vertex, + :tree_edge => tree_edge, + :back_edge => back_edge, + :exit_vertex => exit_vertex}) + + [result, articulation] + end # biconnected + + end # Biconnected +end # Plexus diff --git a/lib/plexus/chinese_postman.rb b/lib/plexus/chinese_postman.rb new file mode 100644 index 0000000..ce34d10 --- /dev/null +++ b/lib/plexus/chinese_postman.rb @@ -0,0 +1,91 @@ +module Plexus + module ChinesePostman + + # Returns the shortest walk that traverses all arcs at least + # once, returning to the specified start node. + def closed_chinese_postman_tour(start, weight=nil, zero=0) + cost, path, delta = floyd_warshall(weight, zero) + return nil unless cp_valid_least_cost? cost, zero + positive, negative = cp_unbalanced(delta) + f = cp_find_feasible(delta, positive, negative, zero) + while cp_improve(f, positive, negative, cost, zero); end + cp_euler_circuit(start, f, path) + end + + private + + def cp_euler_circuit(start, f, path) # :nodoc: + circuit = [u=v=start] + bridge_taken = Hash.new {|h,k| h[k] = Hash.new} + until v.nil? + if v=f[u].keys.detect {|k| f[u][k] > 0} + f[u][v] -= 1 + circuit << (u = path[u][v]) while u != v + else + unless bridge_taken[u][bridge = path[u][start]] + v = vertices.detect {|v1| v1 != bridge && edge?(u,v1) && !bridge_taken[u][v1]} || bridge + bridge_taken[u][v] = true + circuit << v + end + end + u=v + end; circuit + end + + def cp_cancel_cycle(cost, path, f, start, zero) # :nodoc: + u = start; k = nil + begin + v = path[u][start] + k = f[v][u] if cost[u][v] < zero and (k.nil? || k > f[v][u]) + end until (u=v) != start + u = start + begin + v = path[u][start] + cost[u][v] < zero ? f[v][u] -= k : f[u][v] += k + end until (u=v) != start + true # This routine always returns true to make cp_improve easier + end + + def cp_improve(f, positive, negative, cost, zero) # :nodoc: + residual = self.class.new + negative.each do |u| + positive.each do |v| + residual.add_edge!(u,v,cost[u][v]) + residual.add_edge!(v,u,-cost[u][v]) if f[u][v] != 0 + end + end + r_cost, r_path, r_delta = residual.floyd_warshall(nil, zero) + i = residual.vertices.detect {|v| r_cost[v][v] and r_cost[v][v] < zero} + i ? cp_cancel_cycle(r_cost, r_path, f, i) : false + end + + def cp_find_feasible(delta, positive, negative, zero) # :nodoc: + f = Hash.new {|h,k| h[k] = Hash.new} + negative.each do |i| + positive.each do |j| + f[i][j] = -delta[i] < delta[j] ? -delta[i] : delta[j] + delta[i] += f[i][j] + delta[j] -= f[i][j] + end + end; f + end + + def cp_valid_least_cost?(c, zero) # :nodoc: + vertices.each do |i| + vertices.each do |j| + return false unless c[i][j] and c[i][j] >= zero + end + end; true + end + + def cp_unbalanced(delta) # :nodoc: + negative = []; positive = [] + vertices.each do |v| + negative << v if delta[v] < 0 + positive << v if delta[v] > 0 + end; [positive, negative] + end + + end # Chinese Postman +end # Plexus + diff --git a/lib/plexus/classes/graph_classes.rb b/lib/plexus/classes/graph_classes.rb new file mode 100644 index 0000000..fc0218c --- /dev/null +++ b/lib/plexus/classes/graph_classes.rb @@ -0,0 +1,28 @@ +module Plexus + # A generic {GraphBuilder Graph} class you can inherit from. + class Graph; include GraphBuilder; end + + # A generic {AdjacencyGraphBuilder AdjacencyGraph} class you can inherit from. + class AdjacencyGraph < Graph; include AdjacencyGraphBuilder; end + + # A generic {DirectedGraphBuilder DirectedGraph} class you can inherit from. + class DirectedGraph < Graph; include DirectedGraphBuilder; end + + # A generic {DigraphBuilder Digraph} class you can inherit from. + class Digraph < Graph; include DigraphBuilder; end + + # A generic {DirectedPseudoGraphBuilder DirectedPseudoGraph} class you can inherit from. + class DirectedPseudoGraph < Graph; include DirectedPseudoGraphBuilder; end + + # A generic {DirectedMultiGraphBuilder DirectedMultiGraph} class you can inherit from. + class DirectedMultiGraph < Graph; include DirectedMultiGraphBuilder; end + + # A generic {UndirectedGraphBuilder UndirectedGraph} class you can inherit from. + class UndirectedGraph < Graph; include UndirectedGraphBuilder; end + + # A generic {UndirectedPseudoGraphBuilder UndirectedPseudoGraph} class you can inherit from. + class UndirectedPseudoGraph < Graph; include UndirectedPseudoGraphBuilder; end + + # A generic {UndirectedMultiGraphBuilder UndirectedMultiGraph} class you can inherit from. + class UndirectedMultiGraph < Graph; include UndirectedMultiGraphBuilder; end +end diff --git a/lib/plexus/common.rb b/lib/plexus/common.rb new file mode 100644 index 0000000..7ede4c6 --- /dev/null +++ b/lib/plexus/common.rb @@ -0,0 +1,63 @@ +module Plexus + + # This class defines a cycle graph of size n. + # This is easily done by using the base Graph + # class and implemeting the minimum methods needed to + # make it work. This is a good example to look + # at for making one's own graph classes. + module CycleBuilder + def initialize(n) + @size = n; + end + + def directed? + false + end + + def vertices + (1..@size).to_a + end + + def vertex?(v) + v > 0 and v <= @size + end + + def edge?(u,v = nil) + u, v = [u.source, v.target] if u.is_a? Plexus::Arc + vertex?(u) && vertex?(v) && ((v-u == 1) or (u == @size && v = 1)) + end + + def edges + Array.new(@size) { |i| Plexus::Edge[i+1, (i+1) == @size ? 1 : i+2]} + end + end # CycleBuilder + + # This class defines a complete graph of size n. + # This is easily done by using the base Graph + # class and implemeting the minimum methods needed to + # make it work. This is a good example to look + # at for making one's own graph classes. + module CompleteBuilder + include CycleBuilder + + def initialize(n) + @size = n + @edges = nil + end + + def edges + return @edges if @edges # cache edges + @edges = [] + @size.times do |u| + @size.times { |v| @edges << Plexus::Edge[u+1, v+1]} + end + @edges + end + + def edge?(u, v = nil) + u, v = [u.source, v.target] if u.kind_of? Plexus::Arc + vertex?(u) && vertex?(v) + end + end # CompleteBuilder + +end # Plexus diff --git a/lib/plexus/comparability.rb b/lib/plexus/comparability.rb new file mode 100644 index 0000000..41a038e --- /dev/null +++ b/lib/plexus/comparability.rb @@ -0,0 +1,63 @@ +module Plexus + module Comparability + + # A comparability graph is an UndirectedGraph that has a transitive + # orientation. This returns a boolean that says if this graph + # is a comparability graph. + def comparability?() gamma_decomposition[1]; end + + # Returns an array with two values, the first being a hash of edges + # with a number containing their class assignment, the second valud + # is a boolean which states whether or not the graph is a + # comparability graph + # + # Complexity in time O(d*|E|) where d is the maximum degree of a vertex + # Complexity in space O(|V|+|E|) + def gamma_decomposition + k = 0; comparability=true; classification={} + edges.map {|edge| [edge.source,edge.target]}.each do |e| + if classification[e].nil? + k += 1 + classification[e] = k; classification[e.reverse] = -k + comparability &&= plexus_comparability_explore(e, k, classification) + end + end; [classification, comparability] + end + + # Returns one of the possible transitive orientations of + # the UndirectedGraph as a Digraph + def transitive_orientation(digraph_class=Digraph) + raise NotImplementError + end + + private + + # Taken from Figure 5.10, on pg. 130 of Martin Golumbic's, _Algorithmic_Graph_ + # _Theory_and_Perfect_Graphs. + def plexus_comparability_explore(edge, k, classification, space='') + ret = plexus_comparability_explore_inner(edge, k, classification, :forward, space) + plexus_comparability_explore_inner(edge.reverse, k, classification, :backward, space) && ret + end + + def plexus_comparability_explore_inner(edge, k, classification, direction,space) + comparability = true + adj_target = adjacent(edge[1]) + adjacent(edge[0]).select do |mt| + (classification[[edge[1],mt]] || k).abs < k or + not adj_target.any? {|adj_t| adj_t == mt} + end.each do |m| + e = (direction == :forward) ? [edge[0], m] : [m,edge[0]] + if classification[e].nil? + classification[e] = k + classification[e.reverse] = -k + comparability = plexus_comparability_explore(e, k, classification, ' '+space) && comparability + elsif classification[e] == -k + classification[e] = k + plexus_comparability_explore(e, k, classification, ' '+space) + comparability = false + end + end; comparability + end # plexus_comparability_explore_inner + + end # Comparability +end # Plexus diff --git a/lib/plexus/directed_graph.rb b/lib/plexus/directed_graph.rb new file mode 100644 index 0000000..1664b33 --- /dev/null +++ b/lib/plexus/directed_graph.rb @@ -0,0 +1,78 @@ +module Plexus + + # This implements a directed graph which does not allow parallel + # edges nor loops. That is, only one arc per nodes couple, + # and only one parent per node. Mimics the typical hierarchy + # structure. + module DirectedGraphBuilder + include GraphBuilder + + autoload :Algorithms, "plexus/directed_graph/algorithms" + autoload :Distance, "plexus/directed_graph/distance" + + # FIXME: DRY this snippet, I didn't find a clever way to + # to dit though + # TODO: well, extends_host_with do ... end would be cool, + # using Module.new.module_eval(&block) in the helper. + extends_host + + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + # FIXME/TODO: setting args to the hash or {} while getting rid + # on the previous parameters prevents from passing another + # graph to the initializer, so you cannot do things like: + # UndirectedGraph.new(Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6]) + # As args must be a hash, if we're to allow such syntax, + # we should provide a way to handle the graph as a hash + # member. + args = (params.pop if params.last.kind_of? Hash) || {} + args[:algorithmic_category] = DirectedGraphBuilder::Algorithms + super *(params << args) + end + end + + # DirectedGraph is just an alias for Digraph should one desire + DigraphBuilder = DirectedGraphBuilder + + # This is a Digraph that allows for parallel edges, but does not + # allow loops. + module DirectedPseudoGraphBuilder + include DirectedGraphBuilder + extends_host + + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:parallel_edges] = true + super *(params << args) + end + end + + # This is a Digraph that allows for both parallel edges and loops. + module DirectedMultiGraphBuilder + include DirectedPseudoGraphBuilder + extends_host + + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:loops] = true + super *(params << args) + end + end +end diff --git a/lib/plexus/directed_graph/algorithms.rb b/lib/plexus/directed_graph/algorithms.rb new file mode 100644 index 0000000..4a6b156 --- /dev/null +++ b/lib/plexus/directed_graph/algorithms.rb @@ -0,0 +1,95 @@ +module Plexus + # Digraph is a directed graph which is a finite set of vertices + # and a finite set of edges connecting vertices. It cannot contain parallel + # edges going from the same source vertex to the same target. It also + # cannot contain loops, i.e. edges that go have the same vertex for source + # and target. + # + # DirectedPseudoGraph is a class that allows for parallel edges, and + # DirectedMultiGraph is a class that allows for parallel edges and loops + # as well. + module DirectedGraphBuilder + module Algorithms + include Search + include StrongComponents + include Distance + include ChinesePostman + + # A directed graph is directed by definition. + # + # @return [Boolean] always true + # + def directed? + true + end + + # A digraph uses the Arc class for edges. + # + # @return [Plexus::MultiArc, Plexus::Arc] `Plexus::MultiArc` if the graph allows for parallel edges, + # `Plexus::Arc` otherwise. + # + def edge_class + @parallel_edges ? Plexus::MultiArc : Plexus::Arc + end + + # Reverse all edges in a graph. + # + # @return [DirectedGraph] a copy of the receiver for which the direction of edges has + # been inverted. + # + def reversal + result = self.class.new + edges.inject(result) { |a,e| a << e.reverse} + vertices.each { |v| result.add_vertex!(v) unless result.vertex?(v) } + result + end + + # Check whether the graph is oriented or not. + # + # @return [Boolean] + # + def oriented? + e = edges + re = e.map { |x| x.reverse} + not e.any? { |x| re.include?(x)} + end + + # Balanced is the state when the out edges count is equal to the in edges count. + # + # @return [Boolean] + # + def balanced?(v) + out_degree(v) == in_degree(v) + end + + # Returns out_degree(v) - in_degree(v). + # + def delta(v) + out_degree(v) - in_degree(v) + end + + def community(node, direction) + nodes, stack = {}, adjacent(node, :direction => direction) + while n = stack.pop + unless nodes[n.object_id] || node == n + nodes[n.object_id] = n + stack += adjacent(n, :direction => direction) + end + end + nodes.values + end + + def descendants(node) + community(node, :out) + end + + def ancestors(node) + community(node, :in) + end + + def family(node) + community(node, :all) + end + end + end +end diff --git a/lib/plexus/directed_graph/distance.rb b/lib/plexus/directed_graph/distance.rb new file mode 100644 index 0000000..73b9f28 --- /dev/null +++ b/lib/plexus/directed_graph/distance.rb @@ -0,0 +1,167 @@ +module Plexus + module DirectedGraphBuilder + + # This module provides algorithms computing distance between + # vertices. + module Distance + + # Shortest path computation. + # + # From: Jorgen Band-Jensen and Gregory Gutin, + # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 53-54. + # Complexity `O(n+m)`. + # + # Requires the graph to be acyclic. If the graph is not acyclic, + # then see {Distance#dijkstras_algorithm} or {Distance#bellman_ford_moore} + # for possible solutions. + # + # @param [vertex] start the starting vertex + # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]` + # operator. If not a `Proc`, and if no label accessible through `[]`, it will + # default to using the value stored in the label for the {Arc}. If a `Proc`, it will + # pass the edge to the proc and use the resulting value. + # @param [Integer] zero used for math systems with a different definition of zero + # + # @return [Hash] a hash with the key being a vertex and the value being the + # distance. A missing vertex from the hash is equivalent to an infinite distance. + def shortest_path(start, weight = nil, zero = 0) + dist = { start => zero } + path = {} + topsort(start) do |vi| + next if vi == start + dist[vi], path[vi] = adjacent(vi, :direction => :in).map do |vj| + [dist[vj] + cost(vj,vi,weight), vj] + end.min { |a,b| a[0] <=> b[0]} + end; + dist.keys.size == vertices.size ? [dist, path] : nil + end + + # Finds the distance from a given vertex in a weighted digraph + # to the rest of the vertices, provided all the weights of arcs + # are non-negative. + # + # If negative arcs exist in the graph, two basic options exist: + # + # * modify all weights to be positive using an offset (temporary at least) + # * use the {Distance#bellman_ford_moore} algorithm. + # + # Also, if the graph is acyclic, use the {Distance#shortest_path algorithm}. + # + # From: Jorgen Band-Jensen and Gregory Gutin, + # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 53-54. + # + # Complexity `O(n*log(n) + m)`. + # + # @param [vertex] s + # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]` + # operator. If not a `Proc`, and if no label accessible through `[]`, it will + # default to using the value stored in the label for the {Arc}. If a `Proc`, it will + # pass the edge to the proc and use the resulting value. + # @param [Integer] zero used for math systems with a different definition of zero + # @return [Hash] a hash with the key being a vertex and the value being the + # distance. A missing vertex from the hash is equivalent to an infinite distance. + def dijkstras_algorithm(s, weight = nil, zero = 0) + q = vertices; distance = { s => zero } + path = {} + while not q.empty? + v = (q & distance.keys).inject(nil) { |a,k| (!a.nil?) && (distance[a] < distance[k]) ? a : k} + q.delete(v) + (q & adjacent(v)).each do |u| + c = cost(v, u, weight) + if distance[u].nil? or distance[u] > (c + distance[v]) + distance[u] = c + distance[v] + path[u] = v + end + end + end + [distance, path] + end + + # Finds the distances from a given vertex in a weighted digraph + # to the rest of the vertices, provided the graph has no negative cycle. + # + # If no negative weights exist, then {Distance#dijkstras_algorithm} is more + # efficient in time and space. Also, if the graph is acyclic, use the + # {Distance#shortest_path} algorithm. + # + # From: Jorgen Band-Jensen and Gregory Gutin, + # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 56-58.. + # + # Complexity `O(nm)`. + # + # @param [vertex] s + # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]` + # operator. If not a `Proc`, and if no label accessible through `[]`, it will + # default to using the value stored in the label for the {Arc}. If a `Proc`, it will + # pass the edge to the proc and use the resulting value. + # @param [Integer] zero used for math systems with a different definition of zero + # @return [Hash] a hash with the key being a vertex and the value being the + # distance. A missing vertex from the hash is equivalent to an infinite distance. + def bellman_ford_moore(start, weight = nil, zero = 0) + distance = { start => zero } + path = {} + 2.upto(vertices.size) do + edges.each do |e| + u, v = e[0], e[1] + unless distance[u].nil? + c = cost(u, v, weight) + distance[u] + if distance[v].nil? or c < distance[v] + distance[v] = c + path[v] = u + end + end + end + end + [distance, path] + end + + # Uses the Floyd-Warshall algorithm to efficiently find + # and record shortest paths while establishing at the same time + # the costs for all vertices in a graph. + # + # See S.Skiena, *The Algorithm Design Manual*, Springer Verlag, 1998 for more details. + # + # O(n^3) complexity in time. + # + # @param [Proc, nil] weight specifies how an edge weight is determined. + # If it's a `Proc`, the {Arc} is passed to it; if it's `nil`, it will just use + # the value in the label for the Arc; otherwise the weight is + # determined by applying the `[]` operator to the value in the + # label for the {Arc}. + # @param [Integer] zero defines the zero value in the math system used. + # This allows for no assumptions to be made about the math system and + # fully functional duck typing. + # @return [Array(matrice, matrice, Hash)] a pair of matrices and a hash of delta values. + # The matrices will be indexed by two vertices and are implemented as a Hash of Hashes. + # The first matrix is the cost, the second matrix is the shortest path spanning tree. + # The delta (difference of number of in-edges and out-edges) is indexed by vertex. + def floyd_warshall(weight = nil, zero = 0) + c = Hash.new { |h,k| h[k] = Hash.new } + path = Hash.new { |h,k| h[k] = Hash.new } + delta = Hash.new { |h,k| h[k] = 0 } + edges.each do |e| + delta[e.source] += 1 + delta[e.target] -= 1 + path[e.source][e.target] = e.target + c[e.source][e.target] = cost(e, weight) + end + vertices.each do |k| + vertices.each do |i| + if c[i][k] + vertices.each do |j| + if c[k][j] && + (c[i][j].nil? or c[i][j] > (c[i][k] + c[k][j])) + path[i][j] = path[i][k] + c[i][j] = c[i][k] + c[k][j] + return nil if i == j and c[i][j] < zero + end + end + end + end + end + [c, path, delta] + end + + end # Distance + end # DirectedGraph +end # Plexus diff --git a/lib/plexus/dot.rb b/lib/plexus/dot.rb new file mode 100644 index 0000000..28770d8 --- /dev/null +++ b/lib/plexus/dot.rb @@ -0,0 +1,94 @@ +module Plexus + module Dot + + #FIXME: don't really understood where we stand with the dot generators. + # RDoc ships with a dot.rb which seems pretty efficient. + # Are these helpers still needed, and if not, how should we replace them? + + # Creates a DOT::DOTDigraph for directed graphs or a DOT::DOTSubgraph for + # undirected graphs. + # + # @param [Hash] params can contain any graph property specified in + # rdot.rb. If an edge or vertex label is a kind of Hash, then the keys + # which match dot properties will be used as well. + # @return [DOT::DOTDigraph, DOT::DOTSubgraph] + def to_dot_graph(params = {}) + params['name'] ||= self.class.name.gsub(/:/, '_') + fontsize = params['fontsize'] ? params['fontsize'] : '8' + graph = (directed? ? DOT::DOTDigraph : DOT::DOTSubgraph).new(params) + edge_klass = directed? ? DOT::DOTDirectedArc : DOT::DOTArc + + vertices.each do |v| + name = v.to_s || v.__id__.to_s + name = name.dup.gsub(/"/, "'") + + params = { 'name' => '"'+ name +'"', + 'fontsize' => fontsize, + 'label' => name} + + v_label = vertex_label(v) + params.merge!(v_label) if v_label and v_label.kind_of? Hash + + graph << DOT::DOTNode.new(params) + end + + edges.each do |e| + if e.source.to_s.nil? + source_label = e.source.__id__.to_s + else + source_label = e.source.to_s.dup + end + + if e.target.to_s.nil? + target_label = e.target.__id__.to_s + else + target_label = e.target.to_s.dup + end + + source_label.gsub!(/"/, "'") + target_label.gsub!(/"/, "'") + + params = { 'from' => '"'+ source_label + '"', + 'to' => '"'+ target_label + '"', + 'fontsize' => fontsize } + + e_label = edge_label(e) + params.merge!(e_label) if e_label and e_label.kind_of? Hash + + graph << edge_klass.new(params) + end + + graph + end + + # Output the dot format as a string + def to_dot(params = {}) + to_dot_graph(params).to_s + end + + # Call +dotty+ for the graph which is written to the file 'graph.dot' + # in the # current directory. + def dotty(params = {}, dotfile = 'graph.dot') + File.open(dotfile, 'w') {|f| f << to_dot(params) } + system('dotty', dotfile) + end + + # Use +dot+ to create a graphical representation of the graph. Returns the + # filename of the graphics file. + def write_to_graphic_file(fmt = 'png', dotfile = 'graph') + src = dotfile + '.dot' + dot = dotfile + '.' + fmt + + # DOT::DOTSubgraph creates subgraphs, but that's broken. + buffer = self.to_dot + buffer.gsub!(/^subgraph/, "graph") + + File.open(src, 'w') {|f| f << buffer << "\n"} + system( "dot -T#{fmt} #{src} -o #{dot}" ) + + dot + end + alias as_dot_graphic write_to_graphic_file + + end # Dot +end # module Plexus diff --git a/lib/plexus/edge.rb b/lib/plexus/edge.rb new file mode 100644 index 0000000..600de87 --- /dev/null +++ b/lib/plexus/edge.rb @@ -0,0 +1,36 @@ +module Plexus + # An undirected edge is simply an undirected pair (source, target) used in + # undirected graphs. Edge[u,v] == Edge[v,u] + class Edge < Arc + + # Equality allows for the swapping of source and target + def eql?(other) + super or (self.class == other.class and target == other.source and source == other.target) + end + alias == eql? + + # Hash is defined such that source and target can be reversed and the + # hash value will be the same + def hash + source.hash ^ target.hash + end + + # Sort support + def <=>(rhs) + [[source,target].max, [source,target].min] <=> [[rhs.source,rhs.target].max, [rhs.source,rhs.target].min] + end + + # Edge[1,2].to_s == "(1=2 'label)" + def to_s + l = label ? " '#{label.to_s}'" : '' + s = source.to_s + t = target.to_s + "(#{[s,t].min}=#{[s,t].max}#{l})" + end + + end + + class MultiEdge < Edge + include ArcNumber + end +end diff --git a/lib/plexus/ext.rb b/lib/plexus/ext.rb new file mode 100644 index 0000000..b29b150 --- /dev/null +++ b/lib/plexus/ext.rb @@ -0,0 +1,79 @@ +class Object + # Get the singleton class of the object. + # Depending on the object which requested its singleton class, + # a `module_eval` or a `class_eval` will be performed. + # Object of special constant type (`Fixnum`, `NilClass`, `TrueClass`, + # `FalseClass` and `Symbol`) return `nil` as they do not have a + # singleton class. + # + # @return the singleton class + def singleton_class + if self.respond_to? :module_eval + self.module_eval("class << self; self; end") + elsif self.respond_to? :instance_eval + begin + self.instance_eval("class << self; self; end") + rescue TypeError + nil + end + end + end + + # Check wether the object is of the specified kind. + # If the receiver has a singleton class, will also perform + # the check on its singleton class' ancestors, so as to catch + # any included modules for object instances. + # + # Example: + # + # class A; include Digraph; end + # a.singleton_class.ancestors + # # => [Plexus::GraphAPI, Plexus::DirectedGraph::Algorithms, ... + # Plexus::Labels, Enumerable, Object, Plexus, Kernel, BasicObject] + # a.is_a? Plexus::Graph + # # => true + # + # @param [Class] klass + # @return [Boolean] + def is_a? klass + sc = self.singleton_class + if not sc.nil? + self.singleton_class.ancestors.include?(klass) || super + else + super + end + end +end + +class Module + # Helper which purpose is, given a class including a module, + # to make each methods defined within a module's submodule `ClassMethods` + # available as class methods to the receiving class. + # + # Example: + # + # module A + # extends_host + # module ClassMethods + # def selfy; puts "class method for #{self}"; end + # end + # end + # + # class B; include A; end + # + # B.selfy + # # => class method for B + # + # @option *params [Symbol] :with (:ClassMethods) the name of the + # module to extend the receiver with + def extends_host(*params) + args = (params.pop if params.last.is_a? Hash) || {} + @_extension_module = args[:with] || :ClassMethods + + def included(base) + unless @_extension_module.nil? + base.extend(self.const_get(@_extension_module)) + end + end + end +end diff --git a/lib/plexus/graph.rb b/lib/plexus/graph.rb new file mode 100644 index 0000000..517f285 --- /dev/null +++ b/lib/plexus/graph.rb @@ -0,0 +1,626 @@ +module Plexus + # Using the methods required by the {GraphAPI}, it implements all the + # *basic* functions of a {Graph} using *only* functions + # requested in {GraphAPI}. The process is under the control of the pattern + # {AdjacencyGraphBuilder}, unless a specific implementation is specified + # during initialization. + # + # An actual, complete implementation still needs to be done using this cheap result, + # hence {Digraph}, {UndirectedGraph} and their roomates. + module GraphBuilder + include Enumerable + include Labels + include Dot + + #def self.[](*a) + #puts self + #self.new.from_array(*a) + #end + # after the class->module transition, has been moved at implementation level, + # using a helper (extends_host) + extends_host + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + # Creates a generic graph. + # + # @param [Hash(Plexus::Graph, Array)] *params initialization parameters. + # See {AdjacencyGraphBuilder#implementation_initialize} for more details. + # @return [Graph] + def initialize(*params) + raise ArgumentError if params.any? do |p| + # FIXME: checking wether it's a GraphBuilder (module) is not sufficient + # and the is_a? redefinition trick (instance_evaling) should be + # completed by a clever way to check the actual class of p. + # Maybe using ObjectSpace to get the available Graph classes? + !(p.is_a? Plexus::GraphBuilder or p.is_a? Array or p.is_a? Hash) + end + + args = params.last || {} + + class << self + self + end.module_eval do + # These inclusions trigger some validations checks by the way. + include(args[:implementation] ? args[:implementation] : Plexus::AdjacencyGraphBuilder) + include(args[:algorithmic_category] ? args[:algorithmic_category] : Plexus::DigraphBuilder ) + include Plexus::GraphAPI + end + + implementation_initialize(*params) + end + + # Shortcut for creating a Graph. + # + # Using an arry of implicit {Arc}, specifying the vertices: + # + # Plexus::Graph[1,2, 2,3, 2,4, 4,5].edges.to_a.to_s + # # => "(1-2)(2-3)(2-4)(4-5)" + # + # Using a Hash for specifying labels along the way: + # + # Plexus::Graph[ [:a,:b] => 3, [:b,:c] => 4 ] (note: do not use for Multi or Pseudo graphs) + # + # @param [Array, Hash] *a + # @return [Graph] + def from_array(*a) + if a.size == 1 and a[0].is_a? Hash + # Convert to edge class + a[0].each do |k,v| + #FIXME, edge class shouldn't be assume here!!! + if edge_class.include? Plexus::ArcNumber + add_edge!(edge_class[k[0],k[1],nil,v]) + else + add_edge!(edge_class[k[0],k[1],v]) + end + end + #FIXME, edge class shouldn't be assume here!!! + elsif a[0].is_a? Plexus::Arc + a.each{ |e| add_edge!(e); self[e] = e.label} + elsif a.size % 2 == 0 + 0.step(a.size-1, 2) {|i| add_edge!(a[i], a[i+1])} + else + raise ArgumentError + end + self + end + + # Non destructive version of {AdjacencyGraphBuilder#add_vertex!} (works on a copy of the graph). + # + # @param [vertex] v + # @param [Label] l + # @return [Graph] a new graph with the supplementary vertex + def add_vertex(v, l = nil) + x = self.class.new(self) + x.add_vertex!(v, l) + end + + # Non destructive version {AdjacencyGraphBuilder#add_edge!} (works on a copy of the graph). + # + # @param [vertex] u + # @param [vertex] v + # @param [Label] l + # @return [Graph] a new graph with the supplementary edge + def add_edge(u, v = nil, l = nil) + x = self.class.new(self) + x.add_edge!(u, v, l) + end + alias add_arc add_edge + + # Non destructive version of {AdjacencyGraphBuilder#remove_vertex!} (works on a copy of the graph). + # + # @param [vertex] v + # @return [Graph] a new graph without the specified vertex + def remove_vertex(v) + x = self.class.new(self) + x.remove_vertex!(v) + end + + # Non destructive version {AdjacencyGraphBuilder#remove_edge!} (works on a copy of the graph). + # + # @param [vertex] u + # @param [vertex] v + # @return [Graph] a new graph without the specified edge + def remove_edge(u, v = nil) + x = self.class.new(self) + x.remove_edge!(u, v) + end + alias remove_arc remove_edge + + # Computes the adjacent portions of the Graph. + # + # The options specify the parameters about the adjacency search. + # Note: it is probably more efficently done in the implementation class. + # + # @param [vertex, Edge] x can either be a vertex an edge + # @option options [Symbol] :type (:vertices) can be either `:edges` or `:vertices` + # @option options [Symbol] :direction (:all) can be `:in`, `:out` or `:all` + # @return [Array] an array of the adjacent portions + # @fixme + def adjacent(x, options = {}) + d = directed? ? (options[:direction] || :out) : :all + + # Discharge the easy ones first. + return [x.source] if x.is_a? Arc and options[:type] == :vertices and d == :in + return [x.target] if x.is_a? Arc and options[:type] == :vertices and d == :out + return [x.source, x.target] if x.is_a? Arc and options[:type] != :edges and d == :all + + (options[:type] == :edges ? edges : to_a).select { |u| adjacent?(x,u,d) } + end + #FIXME: This is a hack around a serious problem + alias graph_adjacent adjacent + + # Adds all specified vertices to the vertex set. + # + # @param [#each] *a an Enumerable vertices set + # @return [Graph] `self` + def add_vertices!(*a) + a.each { |v| add_vertex! v } + self + end + + # Same as {GraphBuilder#add_vertices! add_vertices!} but works on copy of the receiver. + # + # @param [#each] *a + # @return [Graph] a modified copy of `self` + def add_vertices(*a) + x = self.class.new(self) + x.add_vertices!(*a) + self + end + + # Adds all edges mentionned in the specified Enumerable to the edge set. + # + # Elements of the Enumerable can be either two-element arrays or instances of + # {Edge} or {Arc}. + # + # @param [#each] *a an Enumerable edges set + # @return [Graph] `self` + def add_edges!(*a) + a.each { |edge| add_edge!(edge) } + self + end + alias add_arcs! add_edges! + + # Same as {GraphBuilder#add_egdes! add_edges!} but works on a copy of the receiver. + # + # @param [#each] *a an Enumerable edges set + # @return [Graph] a modified copy of `self` + def add_edges(*a) + x = self.class.new(self) + x.add_edges!(*a) + self + end + alias add_arcs add_edges + + # Removes all vertices mentionned in the specified Enumerable from the graph. + # + # The process relies on {GraphBuilder#remove_vertex! remove_vertex!}. + # + # @param [#each] *a an Enumerable vertices set + # @return [Graph] `self` + def remove_vertices!(*a) + a.each { |v| remove_vertex! v } + end + alias delete_vertices! remove_vertices! + + # Same as {GraphBuilder#remove_vertices! remove_vertices!} but works on a copy of the receiver. + # + # @param [#each] *a a vertex Enumerable set + # @return [Graph] a modified copy of `self` + def remove_vertices(*a) + x = self.class.new(self) + x.remove_vertices(*a) + end + alias delete_vertices remove_vertices + + # Removes all edges mentionned in the specified Enumerable from the graph. + # + # The process relies on {GraphBuilder#remove_edges! remove_edges!}. + # + # @param [#each] *a an Enumerable edges set + # @return [Graph] `self` + def remove_edges!(*a) + a.each { |e| remove_edge! e } + end + alias remove_arcs! remove_edges! + alias delete_edges! remove_edges! + alias delete_arcs! remove_edges! + + # Same as {GraphBuilder#remove_edges! remove_edges!} but works on a copy of the receiver. + # + # @param [#each] *a an Enumerable edges set + # @return [Graph] a modified copy of `self` + def remove_edges(*a) + x = self.class.new(self) + x.remove_edges!(*a) + end + alias remove_arcs remove_edges + alias delete_edges remove_edges + alias delete_arcs remove_edges + + # Executes the given block for each vertex. It allows for mixing Enumerable in. + def each(&block) + vertices.each(&block) + end + + # Returns true if the specified vertex belongs to the graph. + # + # This is a default implementation that is of O(n) average complexity. + # If a subclass uses a hash to store vertices, then this can be + # made into an O(1) average complexity operation. + # + # @param [vertex] v + # @return [Boolean] + def vertex?(v) + vertices.include?(v) + end + alias has_vertex? vertex? + # TODO: (has_)vertices? + + # Returns true if u or (u,v) is an {Edge edge} of the graph. + # + # @overload edge?(a) + # @param [Arc, Edge] a + # @overload edge?(u, v) + # @param [vertex] u + # @param [vertex] v + # @return [Boolean] + def edge?(*args) + edges.include?(edge_convert(*args)) + end + alias arc? edge? + alias has_edge? edge? + alias has_arc? edge? + + # Tests two objects to see if they are adjacent. + # + # Note that in this method, one is primarily concerned with finding + # all adjacent objects in a graph to a given object. The concern is primarily on seeing + # if two objects touch. For two vertexes, any edge between the two will usually do, but + # the direction can be specified if needed. + # + # @param [vertex] source + # @param [vertex] target + # @param [Symbol] direction (:all) constraint on the direction of adjacency; may be either `:in`, `:out` or `:all` + def adjacent?(source, target, direction = :all) + if source.is_a? Plexus::Arc + raise NoArcError unless edge? source + if target.is_a? Plexus::Arc + raise NoArcError unless edge? target + (direction != :out and source.source == target.target) or (direction != :in and source.target == target.source) + else + raise NoVertexError unless vertex? target + (direction != :out and source.source == target) or (direction != :in and source.target == target) + end + else + raise NoVertexError unless vertex? source + if target.is_a? Plexus::Arc + raise NoArcError unless edge? target + (direction != :out and source == target.target) or (direction != :in and source == target.source) + else + raise NoVertexError unless vertex? target + (direction != :out and edge?(target,source)) or (direction != :in and edge?(source,target)) + end + end + end + + # Is the graph connected? + # + # A graph is called connected if every pair of distinct vertices in the graph + # can be connected through some path. The exact definition depends on whether + # the graph is directed or not, hence this method should overriden in specific + # implementations. + # + # This methods implements a lazy routine using the internal vertices hash. + # If you ever want to check connectivity state using a bfs/dfs algorithm, use + # the `:algo => :bfs` or `:dfs` option. + # + # @return [Boolean] `true` if the graph is connected, `false` otherwise + def connected?(options = {}) + options = options.reverse_merge! :algo => :bfs + if options[:algo] == (:bfs || :dfs) + num_nodes = 0 + send(options[:algo]) { |n| num_nodes += 1 } + return num_nodes == @vertex_dict.size + else + !@vertex_dict.collect { |v| degree(v) > 0 }.any? { |check| check == false } + end + end + # TODO: do it! + # TODO: for directed graphs, add weakly_connected? and strongly_connected? (aliased as strong?) + # TODO: in the context of vertices/Arc, add connected_vertices? and disconnected_vertices? + # TODO: maybe implement some routine which would compute cuts and connectivity? tricky though, + # but would be useful (k_connected?(k)) + + # Returns true if the graph has no vertex. + # + # @return [Boolean] + def empty? + vertices.size.zero? + end + + # Returns true if the given object is a vertex or an {Arc arc} of the graph. + # + # @param [vertex, Arc] x + def include?(x) + x.is_a?(Plexus::Arc) ? edge?(x) : vertex?(x) + end + alias has? include? + + # Returns the neighborhood of the given vertex or {Arc arc}. + # + # This is equivalent to {GraphBuilder#adjacent adjacent}, but the type is based on the + # type of the specified object. + # + # @param [vertex, Arc] x + # @param [Symbol] direction (:all) can be either `:all`, `:in` or `:out` + def neighborhood(x, direction = :all) + adjacent(x, :direction => direction, :type => ((x.is_a? Plexus::Arc) ? :edges : :vertices )) + end + + # Union of all neighborhoods of vertices (or edges) in the Enumerable x minus the contents of x. + # + # Definition taken from: Jorgen Bang-Jensen, Gregory Gutin, *Digraphs: Theory, Algorithms and Applications*, pg. 4 + # + # @param [vertex] x + # @param [Symbol] direction can be either `:all`, `:in` or `:out` + def set_neighborhood(x, direction = :all) + x.inject(Set.new) { |a,v| a.merge(neighborhood(v, direction))}.reject { |v2| x.include?(v2) } + end + + # Union of all {GraphBuilder#set_neighborhood set_neighborhoods} reachable + # among the specified edges. + # + # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, *Digraphs: + # Theory, Algorithms and Applications*, pg. 46 + # + # @param [vertex] w + # @param [Edges] p + # @param [Symbol] direction can be `:all`, `:in`, or `:out` + def closed_pth_neighborhood(w, p, direction = :all) + if p <= 0 + w + elsif p == 1 + (w + set_neighborhood(w, direction)).uniq + else + n = set_neighborhood(w, direction) + (w + n + closed_pth_neighborhood(n, p-1, direction)).uniq + end + end + + # Returns the neighboorhoods reachable in a certain amount of steps from + # every vertex (or edge) in the specified Enumerable. + # + # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: + # Theory, Algorithms and Applications_, pg. 46 + # + # @param [Enumerable] x + # @param [Integer] p number of steps to perform + # @param [Symbol] direction can be `:all`, `:in`, or `:out` + def open_pth_neighborhood(x, p, direction = :all) + if p <= 0 + x + elsif p == 1 + set_neighborhood(x,direction) + else + set_neighborhood(open_pth_neighborhood(x, p-1, direction), direction) - + closed_pth_neighborhood(x, p-1, direction) + end + end + + # Returns the number of out-edges (for directed graphs) or the number of + # incident edges (for undirected graphs) of the specified vertex. + # + # @param [vertex] v + # @return [Integer] number of matching edges + def out_degree(v) + adjacent(v, :direction => :out).size + end + + # Returns the number of in-edges (for directed graphs) or the number of + # incident edges (for undirected graphs) of the specified vertex + # + # @param [vertex] v + # @return [Integer] number of matching edges + def in_degree(v) + adjacent(v, :direction => :in).size + end + + # Returns the sum of the number in and out edges for the specified vertex. + # + # @param [vertex] v + # @return [Integer] degree + def degree(v) + in_degree(v) + out_degree(v) + end + + # Minimum in-degree of the graph. + # + # @return [Integer, nil] returns `nil` if the graph is empty + def min_in_degree + return nil if to_a.empty? + to_a.map { |v| in_degree(v) }.min + end + + # Minimum out-degree of the graph. + # + # @return [Integer, nil] returns `nil` if the graph is empty + def min_out_degree + return nil if to_a.empty? + to_a.map {|v| out_degree(v)}.min + end + + # Minimum degree of all vertexes of the graph. + # + # @return [Integer] `min` between {GraphBuilder#min_in_degree min_in_degree} + # and {GraphBuilder#min_out_degree max_out_degree} + def min_degree + [min_in_degree, min_out_degree].min + end + + # Maximum in-degree of the graph. + # + # @return [Integer, nil] returns `nil` if the graph is empty + def max_in_degree + return nil if to_a.empty? + vertices.map { |v| in_degree(v)}.max + end + + # Maximum out-degree of the graph. + # + # @return [Integer, nil] returns nil if the graph is empty + def max_out_degree + return nil if to_a.empty? + vertices.map { |v| out_degree(v)}.max + end + + # Maximum degree of all vertexes of the graph. + # + # @return [Integer] `max` between {GraphBuilder#max_in_degree max_in_degree} + # and {GraphBuilder#max_out_degree max_out_degree} + def max_degree + [max_in_degree, max_out_degree].max + end + + # Is the graph regular, that is are its min degree and max degree equal? + # + # @return [Boolean] + def regular? + min_degree == max_degree + end + + # Number of vertices. + # + # @return [Integer] + def size + vertices.size + end + alias num_vertices size + alias number_of_vertices size + + # Number of vertices. + # + # @return [Integer] + def num_vertices + vertices.size + end + alias number_of_vertices num_vertices + + # Number of edges. + # + # @return [Integer] + def num_edges + edges.size + end + alias number_of_edges num_edges + + # Utility method to show a string representation of the edges of the graph. + #def to_s + #edges.to_s + #end + + # Equality is defined to be same set of edges and directed? + def eql?(g) + return false unless g.is_a? Plexus::Graph + + (directed? == g.directed?) and + (vertices.sort == g.vertices.sort) and + (edges.sort == g.edges.sort) + end + alias == eql? + + # Merges another graph into the receiver. + # + # @param [Graph] other the graph to merge in + # @return [Graph] `self` + def merge(other) + other.vertices.each { |v| add_vertex!(v) } + other.edges.each { |e| add_edge!(e) } + other.edges.each { |e| add_edge!(e.reverse) } if directed? and !other.directed? + self + end + + # A synonym for {GraphBuilder#merge merge}, but doesn't modify the current graph. + # + # @param [Graph, Arc] other + # @return [Graph] a new graph + def +(other) + result = self.class.new(self) + case other + when Plexus::Graph + result.merge(other) + when Plexus::Arc + result.add_edge!(other) + else + result.add_vertex!(other) + end + end + + # Removes all vertices in the specified graph. + # + # @param [Graph, Arc] other + # @return [Graph] + def -(other) + case other + when Plexus::Graph + induced_subgraph(vertices - other.vertices) + when Plexus::Arc + self.class.new(self).remove_edge!(other) + else + self.class.new(self).remove_vertex!(other) + end + end + + # A synonym for {AdjacencyGraphBuilder#add_edge! add_edge!}. + def <<(edge) + add_edge!(edge) + end + + # Computes the complement of the current graph. + # + # @return [Graph] + def complement + vertices.inject(self.class.new) do |a,v| + a.add_vertex!(v) + vertices.each { |v2| a.add_edge!(v, v2) unless edge?(v, v2) }; a + end + end + + # Given an array of vertices, computes the induced subgraph. + # + # @param [Array(vertex)] v + # @return [Graph] + def induced_subgraph(v) + edges.inject(self.class.new) do |a,e| + (v.include?(e.source) and v.include?(e.target)) ? (a << e) : a + end + end + + def inspect + ## FIXME: broken, it's not updated. The issue's not with inspect, but it's worth mentionning here. + ## Example: + ## dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] + ## dg.add_vertices! 1, 5, "yosh" + ## # => Plexus::Digraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[6,4,nil]] + ## dg.vertex?("yosh") + ## # => true + ## dg + ## # =>Plexus::Digraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[6,4,nil]] + ## the new vertex doesn't show up. + ## Actually this version of inspect is far too verbose IMO :) + l = vertices.select { |v| self[v]}.map { |u| "vertex_label_set(#{u.inspect}, #{self[u].inspect})"}.join('.') + self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l && l != '' ? '.'+l : '') + end + + private + + # ? + def edge_convert(*args) + args[0].is_a?(Plexus::Arc) ? args[0] : edge_class[*args] + end + end +end diff --git a/lib/plexus/graph_api.rb b/lib/plexus/graph_api.rb new file mode 100644 index 0000000..bebc192 --- /dev/null +++ b/lib/plexus/graph_api.rb @@ -0,0 +1,35 @@ +module Plexus + # This module defines the minimum set of functions required to make a graph that can + # use the algorithms defined by this library. + # + # Each implementation module must implement the following routines: + # + # * directed? # Is the graph directed? + # * add_vertex!(v,l=nil) # Add a vertex to the graph and return the graph. `l` is an optional label. + # * add_edge!(u,v=nil,l=nil) # Add an edge to the graph and return the graph. `u` can be an {Arc} or {Edge}, or `u,v` a {Edge} pair. The last parameter `l` is an optional label. + # * remove_vertex!(v) # Remove a vertex to the graph and return the graph. + # * remove_edge!(u,v=nil) # Remove an edge from the graph and return the graph. + # * vertices # Returns an array of of all vertices. + # * edges # Returns an array of all edges. + # * edge_class # Returns the class used to store edges. + module GraphAPI + # @raise if the API is not completely implemented + def self.included(klass) + @api_methods ||= [:directed?, :add_vertex!, :add_edge!, :remove_vertex!, :remove_edge!, :vertices, :edges, :edge_class] + ruby_18 { @api_methods.each { |m| m.to_s } } + + @api_methods.each do |meth| + raise "Must implement #{meth}" unless klass.instance_methods.include?(meth) + end + + klass.class_eval do + # Is this right? + alias remove_arc! remove_edge! + alias add_arc! add_edge! + alias arcs edges + alias arc_class edge_class + end + end + + end # GraphAPI +end # Plexus diff --git a/lib/plexus/labels.rb b/lib/plexus/labels.rb new file mode 100644 index 0000000..6d47521 --- /dev/null +++ b/lib/plexus/labels.rb @@ -0,0 +1,113 @@ +module Plexus + # This module add support for labels. + # + # The graph labeling process consist in assigning labels, traditionally represented + # by integers, to the edges or vertices, or both, of a graph. Plexus recommands you + # abide by this rule and do use integers as labels. + # + # Some algorithms can make use of labeling (sea {Plexus::Search} for instance). + module Labels + + # Return a label for an edge or vertex. + def [](u) + (u.is_a? Plexus::Arc) ? edge_label(u) : vertex_label(u) + end + + # Set a label for an edge or vertex. + def []=(u, value) + (u.is_a? Plexus::Arc) ? edge_label_set(u, value) : vertex_label_set(u, value) + end + + # Delete a label entirely. + def delete_label(u) + (u.is_a? Plexus::Arc) ? edge_label_delete(u) : vertex_label_delete(u) + end + + # Get the label for an edge. + def vertex_label(v) + vertex_label_dict[v] + end + + # Set the label for an edge. + def vertex_label_set(v, l) + vertex_label_dict[v] = l + self + end + + # Get the label for an edge. + def edge_label(u, v = nil, n = nil) + u = edge_convert(u,v,n) + edge_label_dict[u] + end + + # Set the label for an edge. + def edge_label_set(u, v = nil, l = nil, n = nil) + u.is_a?(Plexus::Arc) ? l = v : u = edge_convert(u, v, n) + edge_label_dict[u] = l + self + end + + # Delete all graph labels. + def clear_all_labels + @vertex_labels = {} + @edge_labels = {} + end + + # Delete an edge label. + def edge_label_delete(u, v = nil, n = nil) + u = edge_convert(u, v, n) + edge_label_dict.delete(u) + end + + # Delete a vertex label. + def vertex_label_delete(v) + vertex_label_dict.delete(v) + end + + protected + + def vertex_label_dict + @vertex_labels ||= {} + end + + def edge_label_dict + @edge_labels ||= {} + end + + # A generic cost function. + # + # It either calls the `weight` function with an edge constructed from the + # two specified nodes, or calls the `[]` operator of the label when given + # a single value. + # + # If no weight value is specified, the label itself is treated as the cost value. + # + # Note: This function will not work for Pseudo or Multi graphs at present. + # FIXME: Remove u,v interface to fix Pseudo Multi graph problems. + def cost(u, v = nil, weight = nil) + u.is_a?(Arc) ? weight = v : u = edge_class[u,v] + case weight + when Proc + weight.call(u) + when nil + self[u] + else + self[u][weight] + end + end + alias property cost # makes sense for property retrieval in general + + # A function to set properties specified by the user. + def property_set(u, name, value) + case name + when Proc + name.call(value) + when nil + self[u] = value + else + self[u][name] = value + end + end + + end # Labels +end # Plexus diff --git a/lib/plexus/maximum_flow.rb b/lib/plexus/maximum_flow.rb new file mode 100644 index 0000000..242fdbb --- /dev/null +++ b/lib/plexus/maximum_flow.rb @@ -0,0 +1,77 @@ +module Plexus + class Network + include DigraphBuilder + + attr_accessor :lower, :upper, :cost, :flow + + def residual(residual_capacity, cost_property, zero = 0) + r = Digraph.new + edges.each do |e1| + [e1,e1.reverse].each do |e| + rij = property(e,self.upper) - property(e,self.flow) if edge? e + rij += property(e.reverse,self.flow) - property(e.reverse,self.lower) if edge? e.reverse + r.add_edge!(e) if rij > zero + r.property_set(e,residual_capacity, rij) + r.property_set(e,cost, cost(e,cost_property)) + end + end + r + end + + def maximum_flow() eliminate_lower_bounds.maximum_flow_prime.restore_lower_bounds(self); end + + private + + def eliminate_lower_bounds + no_lower_bounds = Digraph.new(self) + if self.upper.kind_of? Proc then + no_lower_bounds.upper = Proc.new {|e| self.upper.call(e) - property(e,self.lower) } + else + no_lower_bounds.edges.each {|e| no_lower_bounds[e][self.upper] -= property(e,self.lower)} + end + no_lower_bounds + end + + def restore_lower_bounds(src) + src.edges.each do |e| + (src.flow ? src[e][src.flow] : src[e]) = property(e, self.flow) + src.property(e, self.lower) + end + src + end + + def maximum_flow_prime + end + end # Network + + module GraphBuilder + module MaximumFlow + # Maximum flow, it returns an array with the maximum flow and a hash of flow per edge + # Currently a highly inefficient implementation, FIXME, This should use Goldberg and Tarjan's method. + def maximum_flow(s, t, capacity = nil, zero = 0) + flow = Hash.new(zero) + available = Hash.new(zero) + total = zero + edges.each {|e| available[e] = cost(e,capacity)} + adj_positive = Proc.new do |u| + adjacent(u).select {|r| available[edge_class[u,r]] > zero} + end + while (tree = bfs_tree_from_vertex(start))[t] + route = [t] + while route[-1] != s + route << tree[route[route[-1]]] + raise ArgumentError, "No route from #{s} to #{t} possible" + end; route.reverse + amt = route.map {|e| available[e]}.min + route.each do |e| + flow[e] += amt + available[e] -= amt + end + total += amt + end + + [total, flow] + end + + end # MaximumFlow + end # GraphBuilder +end # Plexus diff --git a/lib/plexus/ruby_compatibility.rb b/lib/plexus/ruby_compatibility.rb new file mode 100644 index 0000000..853f06d --- /dev/null +++ b/lib/plexus/ruby_compatibility.rb @@ -0,0 +1,17 @@ +if RUBY_VERSION < "1.9" + def ruby_18 + yield + end + + def ruby_19 + false + end +else + def ruby_18 + false + end + + def ruby_19 + yield + end +end diff --git a/lib/plexus/search.rb b/lib/plexus/search.rb new file mode 100644 index 0000000..bfcde4f --- /dev/null +++ b/lib/plexus/search.rb @@ -0,0 +1,510 @@ +module Plexus + # **Search/traversal algorithms.** + # + # This module defines a collection of search/traversal algorithms, in a unified API. + # Read through the doc to get familiar with the calling pattern. + # + # Options are mostly callbacks passed in as a hash. The following are valid, + # anything else is ignored: + # + # * `:enter_vertex` => `Proc` Called upon entry of a vertex. + # * `:exit_vertex` => `Proc` Called upon exit of a vertex. + # * `:root_vertex` => `Proc` Called when a vertex is the root of a tree. + # * `:start_vertex` => `Proc` Called for the first vertex of the search. + # * `:examine_edge` => `Proc` Called when an edge is examined. + # * `:tree_edge` => `Proc` Called when the edge is a member of the tree. + # * `:back_edge` => `Proc` Called when the edge is a back edge. + # * `:forward_edge` => `Proc` Called when the edge is a forward edge. + # * `:adjacent` => `Proc` which, given a vertex, returns adjacent nodes, defaults to adjacent call of graph useful for changing the definition of adjacent in some algorithms. + # * `:start` => vertex Specifies the vertex to start search from. + # + # If a `&block` instead of an option hash is specified, it defines `:enter_vertex`. + # + # Each search algorithm returns the list of vertexes as reached by `enter_vertex`. + # This allows for calls like, `g.bfs.each { |v| ... }` + # + # Can also be called like `bfs_examine_edge { |e| ... }` or + # `dfs_back_edge { |e| ... }` for any of the callbacks. + # + # A full example usage is as follows: + # + # ev = Proc.new { |x| puts "Enter vertex #{x}" } + # xv = Proc.new { |x| puts "Exit vertex #{x}" } + # sv = Proc.new { |x| puts "Start vertex #{x}" } + # ee = Proc.new { |x| puts "Examine Arc #{x}" } + # te = Proc.new { |x| puts "Tree Arc #{x}" } + # be = Proc.new { |x| puts "Back Arc #{x}" } + # fe = Proc.new { |x| puts "Forward Arc #{x}" } + # Digraph[1,2, 2,3, 3,4].dfs({ + # :enter_vertex => ev, + # :exit_vertex => xv, + # :start_vertex => sv, + # :examine_edge => ee, + # :tree_edge => te, + # :back_edge => be, + # :forward_edge => fe }) + # + # Which outputs: + # + # Start vertex 1 + # Enter vertex 1 + # Examine Arc (1=2) + # Tree Arc (1=2) + # Enter vertex 2 + # Examine Arc (2=3) + # Tree Arc (2=3) + # Enter vertex 3 + # Examine Arc (3=4) + # Tree Arc (3=4) + # Enter vertex 4 + # Examine Arc (1=4) + # Back Arc (1=4) + # Exit vertex 4 + # Exit vertex 3 + # Exit vertex 2 + # Exit vertex 1 + # => [1, 2, 3, 4] + module Search + + # Performs a breadth-first search. + # + # @param [Hash] options + def bfs(options = {}, &block) + plexus_search_helper(:shift, options, &block) + end + alias :bread_first_search :bfs + + # Performs a depth-first search. + # + # @param [Hash] options + def dfs(options = {}, &block) + plexus_search_helper(:pop, options, &block) + end + alias :depth_first_search :dfs + + # Routine which computes a spanning forest for the given search method. + # Returns two values: a hash of predecessors and an array of root nodes. + # + # @param [vertex] start + # @param [Symbol] routine the search method (`:dfs`, `:bfs`) + # @return [Array] predecessors and root nodes + def spanning_forest(start, routine) + predecessor = {} + roots = [] + te = Proc.new { |e| predecessor[e.target] = e.source } + rv = Proc.new { |v| roots << v } + send routine, :start => start, :tree_edge => te, :root_vertex => rv + [predecessor, roots] + end + + # Returns the dfs spanning forest for the given start node, see {Search#spanning_forest spanning_forest}. + # + # @param [vertex] start + # @return [Array] predecessors and root nodes + def dfs_spanning_forest(start) + spanning_forest(start, :dfs) + end + + # Returns the bfs spanning forest for the given start node, see {Search#spanning_forest spanning_forest}. + # + # @param [vertex] start + # @return [Array] predecessors and root nodes + def bfs_spanning_forest(start) + spanning_forest(start, :bfs) + end + + # Returns a hash of predecessors in a tree rooted at the start node. If this is a connected graph, + # then it will be a spanning tree containing all vertices. An easier way to tell if it's a + # spanning tree is to use a {Search#spanning_forest spanning_forest} call and check if there is a + # single root node. + # + # @param [vertex] start + # @param [Symbol] routine the search method (`:dfs`, `:bfs`) + # @return [Hash] predecessors vertices + def tree_from_vertex(start, routine) + predecessor = {} + correct_tree = false + te = Proc.new { |e| predecessor[e.target] = e.source if correct_tree } + rv = Proc.new { |v| correct_tree = (v == start) } + send routine, :start => start, :tree_edge => te, :root_vertex => rv + predecessor + end + + # Returns a hash of predecessors for the depth-first search tree rooted at the given node. + # + # @param [vertex] start + # @return [Hash] predecessors vertices + def dfs_tree_from_vertex(start) + tree_from_vertex(start, :dfs) + end + + # Returns a hash of predecessors for the breadth-first search tree rooted at the given node. + # + # @param [Proc] start + # @return [Hash] predecessors vertices + def bfs_tree_from_vertex(start) + tree_from_vertex(start, :bfs) + end + + # An inner class used for greater efficiency in {Search#lexicograph_bfs}. + # + # Original design taken from Golumbic's, *Algorithmic Graph Theory and Perfect Graphs* pg. 87-89. + class LexicographicQueue + # Called with the initial values. + # + # @param [Array] initial vertices values + def initialize(values) + @node = Struct.new(:back, :forward, :data) + @node.class_eval do + def hash + @hash + end + @@cnt = 0 + end + @set = {} + @tail = @node.new(nil, nil, Array.new(values)) + @tail.instance_eval { @hash = (@@cnt += 1) } + values.each { |a| @set[a] = @tail } + end + + # Pops an entry with the maximum lexical value from the queue. + # + # @return [vertex] + def pop + return nil unless @tail + value = @tail[:data].pop + @tail = @tail[:forward] while @tail and @tail[:data].size == 0 + @set.delete(value) + value + end + + # Increase the lexical value of the given values. + # + # @param [Array] vertices values + def add_lexeme(values) + fix = {} + + values.select { |v| @set[v] }.each do |w| + sw = @set[w] + if fix[sw] + s_prime = sw[:back] + else + s_prime = @node.new(sw[:back], sw, []) + s_prime.instance_eval { @hash = (@@cnt += 1) } + @tail = s_prime if @tail == sw + sw[:back][:forward] = s_prime if sw[:back] + sw[:back] = s_prime + fix[sw] = true + end + + s_prime[:data] << w + sw[:data].delete(w) + @set[w] = s_prime + end + + fix.keys.select { |n| n[:data].size == 0 }.each do |e| + e[:forward][:back] = e[:back] if e[:forward] + e[:back][:forward] = e[:forward] if e[:back] + end + end + + end + + # Lexicographic breadth-first search. + # + # The usual queue of vertices is replaced by a queue of *unordered subsets* + # of the vertices, which is sometimes refined but never reordered. + # + # Originally developed by Rose, Tarjan, and Leuker, *Algorithmic + # aspects of vertex elimination on graphs*, SIAM J. Comput. 5, 266-283 + # MR53 #12077 + # + # Implementation taken from Golumbic's, *Algorithmic Graph Theory and + # Perfect Graphs*, pg. 84-90. + # + # @return [vertex] + def lexicograph_bfs(&block) + lex_q = Plexus::Search::LexicographicQueue.new(vertices) + result = [] + num_vertices.times do + v = lex_q.pop + result.unshift(v) + lex_q.add_lexeme(adjacent(v)) + end + result.each { |r| block.call(r) } if block + result + end + + # A* Heuristic best first search. + # + # `start` is the starting vertex for the search. + # + # `func` is a `Proc` that when passed a vertex returns the heuristic + # weight of sending the path through that node. It must always + # be equal to or less than the true cost. + # + # `options` are mostly callbacks passed in as a hash, the default block is + # `:discover_vertex` and the weight is assumed to be the label for the {Arc}. + # The following options are valid, anything else is ignored: + # + # * `:weight` => can be a `Proc`, or anything else is accessed using the `[]` for the + # the label or it defaults to using + # the value stored in the label for the {Arc}. If it is a `Proc` it will + # pass the edge to the proc and use the resulting value. + # * `:discover_vertex` => `Proc` invoked when a vertex is first discovered + # and is added to the open list. + # * `:examine_vertex` => `Proc` invoked when a vertex is popped from the + # queue (i.e., it has the lowest cost on the open list). + # * `:examine_edge` => `Proc` invoked on each out-edge of a vertex + # immediately after it is examined. + # * `:edge_relaxed` => `Proc` invoked on edge `(u,v) if d[u] + w(u,v) < d[v]`. + # * `:edge_not_relaxed`=> `Proc` invoked if the edge is not relaxed (see above). + # * `:black_target` => `Proc` invoked when a vertex that is on the closed + # list is "rediscovered" via a more efficient path, and is re-added + # to the open list. + # * `:finish_vertex` => Proc invoked on a vertex when it is added to the + # closed list, which happens after all of its out edges have been + # examined. + # + # Can also be called like `astar_examine_edge {|e| ... }` or + # `astar_edge_relaxed {|e| ... }` for any of the callbacks. + # + # The criteria for expanding a vertex on the open list is that it has the + # lowest `f(v) = g(v) + h(v)` value of all vertices on open. + # + # The time complexity of A* depends on the heuristic. It is exponential + # in the worst case, but is polynomial when the heuristic function h + # meets the following condition: `|h(x) - h*(x)| < O(log h*(x))` where `h*` + # is the optimal heuristic, i.e. the exact cost to get from `x` to the `goal`. + # + # See also: [A* search algorithm](http://en.wikipedia.org/wiki/A*_search_algorithm) on Wikipedia. + # + # @param [vertex] start the starting vertex for the search + # @param [vertex] goal the vertex to reach + # @param [Proc] func heuristic weight computing process + # @param [Hash] options + # @return [Array(vertices), call, nil] an array of nodes in path, or calls block on all nodes, + # upon failure returns `nil` + def astar(start, goal, func, options, &block) + options.instance_eval "def handle_callback(sym,u) self[sym].call(u) if self[sym]; end" + + # Initialize. + d = { start => 0 } + color = { start => :gray } # Open is :gray, closed is :black. + parent = Hash.new { |k| parent[k] = k } + f = { start => func.call(start) } + queue = PriorityQueue.new.push(start, f[start]) + block.call(start) if block + + # Process queue. + until queue.empty? + u, dummy = queue.delete_min + options.handle_callback(:examine_vertex, u) + + # Unravel solution if the goal is reached. + if u == goal + solution = [goal] + while u != start + solution << parent[u] + u = parent[u] + end + return solution.reverse + end + + adjacent(u, :type => :edges).each do |e| + v = e.source == u ? e.target : e.source + options.handle_callback(:examine_edge, e) + w = cost(e, options[:weight]) + raise ArgumentError unless w + + if d[v].nil? or (w + d[u]) < d[v] + options.handle_callback(:edge_relaxed, e) + d[v] = w + d[u] + f[v] = d[v] + func.call(v) + parent[v] = u + + unless color[v] == :gray + options.handle_callback(:black_target, v) if color[v] == :black + color[v] = :gray + options.handle_callback(:discover_vertex, v) + queue.push v, f[v] + block.call(v) if block + end + else + options.handle_callback(:edge_not_relaxed, e) + end + end # adjacent(u) + + color[u] = :black + options.handle_callback(:finish_vertex, u) + end # queue.empty? + + nil # failure, on fall through + end # astar + + # `best_first` has all the same options as {Search#astar astar}, with `func` set to `h(v) = 0`. + # There is an additional option, `zero`, which should be defined as the zero element + # for the `+` operation performed on the objects used in the computation of cost. + # + # @param [vertex] start the starting vertex for the search + # @param [vertex] goal the vertex to reach + # @param [Proc] func heuristic weight computing process + # @param [Hash] options + # @param [Integer] zero (0) + # @return [Array(vertices), call, nil] an array of nodes in path, or calls block on all nodes, + # upon failure returns `nil` + def best_first(start, goal, options, zero = 0, &block) + func = Proc.new { |v| zero } + astar(start, goal, func, options, &block) + end + + # @private + alias_method :pre_search_method_missing, :method_missing + def method_missing(sym, *args, &block) + m1 = /^dfs_(\w+)$/.match(sym.to_s) + dfs((args[0] || {}).merge({ m1.captures[0].to_sym => block })) if m1 + m2 = /^bfs_(\w+)$/.match(sym.to_s) + bfs((args[0] || {}).merge({ m2.captures[0].to_sym => block })) if m2 + pre_search_method_missing(sym, *args, &block) unless m1 or m2 + end + + private + + # Performs the search using a specific algorithm and a set of options. + # + # @param [Symbol] op the algorithm to be used te perform the search + # @param [Hash] options + # @return [Object] result + def plexus_search_helper(op, options = {}, &block) + return nil if size == 0 + result = [] + + # Create the options hash handling callbacks. + options = {:enter_vertex => block, :start => to_a[0]}.merge(options) + options.instance_eval "def handle_vertex(sym,u) self[sym].call(u) if self[sym]; end" + options.instance_eval "def handle_edge(sym,e) self[sym].call(e) if self[sym]; end" + + # Create a waiting list, which is a queue or a stack, depending on the op specified. + # The first entry is the start vertex. + waiting = [options[:start]] + waiting.instance_eval "def next; #{op.to_s}; end" + + # Create a color map, all elements set to "unvisited" except for start vertex, + # which will be set to waiting. + color_map = vertices.inject({}) { |a,v| a[v] = :unvisited; a } + color_map.merge!(waiting[0] => :waiting) + options.handle_vertex(:start_vertex, waiting[0]) + options.handle_vertex(:root_vertex, waiting[0]) + + # Perform the actual search until nothing is "waiting". + until waiting.empty? + # Loop till the search iterator exhausts the waiting list. + visited_edges = {} # This prevents retraversing edges in undirected graphs. + until waiting.empty? + plexus_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop) + end + # Waiting for the list to be exhausted, check if a new root vertex is available. + u = color_map.detect { |key,value| value == :unvisited } + waiting.push(u[0]) if u + options.handle_vertex(:root_vertex, u[0]) if u + end + + result + end + + # Performs a search iteration (step). + # + # @private + def plexus_search_iteration(options, waiting, color_map, visited_edges, result, recursive = false) + # Fetch the next waiting vertex in the list. + #sleep + u = waiting.next + options.handle_vertex(:enter_vertex, u) + result << u + + # Examine all adjacent outgoing edges, but only those not previously traversed. + adj_proc = options[:adjacent] || self.method(:adjacent).to_proc + adj_proc.call(u, :type => :edges, :direction => :out).reject { |w| visited_edges[w] }.each do |e| + e = e.reverse unless directed? or e.source == u # Preserves directionality where required. + v = e.target + options.handle_edge(:examine_edge, e) + visited_edges[e] = true + + case color_map[v] + # If it's unvisited, it goes into the waiting list. + when :unvisited + options.handle_edge(:tree_edge, e) + color_map[v] = :waiting + waiting.push(v) + # If it's recursive (i.e. dfs), then call self. + plexus_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive + when :waiting + options.handle_edge(:back_edge, e) + else + options.handle_edge(:forward_edge, e) + end + end + + # Done with this vertex! + options.handle_vertex(:exit_vertex, u) + color_map[u] = :visited + end + + public + + # Topological Sort Iterator. + # + # The topological sort algorithm creates a linear ordering of the vertices + # such that if edge (u,v) appears in the graph, then u comes before v in + # the ordering. The graph must be a directed acyclic graph (DAG). + # + # The iterator can also be applied to undirected graph or to a DG graph + # which contains a cycle. In this case, the Iterator does not reach all + # vertices. The implementation of acyclic? and cyclic? uses this fact. + # + # Can be called with a block as a standard ruby iterator, or can + # be used directly as it will return the result as an Array. + # + # @param [vertex] start (nil) the start vertex (nil will fallback on the first + # vertex inserted within the graph) + # @return [Array] a linear representation of the sorted graph + def topsort(start = nil, &block) + result = [] + go = true + back = Proc.new { |e| go = false } + push = Proc.new { |v| result.unshift(v) if go } + start ||= vertices[0] + dfs({ :exit_vertex => push, :back_edge => back, :start => start }) + result.each { |v| block.call(v) } if block + result + end + + # Does a top sort, but trudges forward if a cycle occurs. Use with caution. + # + # @param [vertex] start (nil) the start vertex (nil will fallback on the first + # vertex inserted within the graph) + # @return [Array] a linear representation of the sorted graph + def sort(start = nil, &block) + result = [] + push = Proc.new { |v| result.unshift(v) } + start ||= vertices[0] + dfs({ :exit_vertex => push, :start => start }) + result.each { |v| block.call(v) } if block + result + end + + # Returns true if a graph contains no cycles, false otherwise. + # + # @return [Boolean] + def acyclic? + topsort.size == size + end + + # Returns false if a graph contains no cycles, true otherwise. + # + # @return [Boolean] + def cyclic? + not acyclic? + end + end +end diff --git a/lib/plexus/strong_components.rb b/lib/plexus/strong_components.rb new file mode 100644 index 0000000..24f1fa7 --- /dev/null +++ b/lib/plexus/strong_components.rb @@ -0,0 +1,93 @@ +module Plexus + module StrongComponents + # strong_components computes the strongly connected components + # of a graph using Tarjan's algorithm based on DFS. See: Robert E. Tarjan + # _Depth_First_Search_and_Linear_Graph_Algorithms_. SIAM Journal on + # Computing, 1(2):146-160, 1972 + # + # The output of the algorithm is an array of components where is + # component is an array of vertices + # + # A strongly connected component of a directed graph G=(V,E) is a maximal + # set of vertices U which is in V such that for every pair of + # vertices u and v in U, we have both a path from u to v + # and path from v to u. That is to say that u and v are reachable + # from each other. + # + def strong_components + dfs_num = 0 + stack = []; result = []; root = {}; comp = {}; number = {} + + # Enter vertex callback + enter = Proc.new do |v| + root[v] = v + comp[v] = :new + number[v] = (dfs_num += 1) + stack.push(v) + end + + # Exit vertex callback + exit = Proc.new do |v| + adjacent(v).each do |w| + if comp[w] == :new + root[v] = (number[root[v]] < number[root[w]] ? root[v] : root[w]) + end + end + if root[v] == v + component = [] + begin + w = stack.pop + comp[w] = :assigned + component << w + end until w == v + result << component + end + end + + # Execute depth first search + dfs({:enter_vertex => enter, :exit_vertex => exit}); result + + end # strong_components + + # Returns a condensation graph of the strongly connected components + # Each node is an array of nodes from the original graph + def condensation + sc = strong_components + cg = DirectedMultiGraph.new + map = sc.inject({}) do |a,c| + c.each {|v| a[v] = c }; a + end + sc.each do |c| + c.each do |v| + adjacent(v).each {|v1| cg.add_edge!(c, map[v1]) unless cg.edge?(c, map[v1]) } + end + end; + cg + end + + # Compute transitive closure of a graph. That is any node that is reachable + # along a path is added as a directed edge. + def transitive_closure! + cgtc = condensation.plexus_inner_transitive_closure! + cgtc.each do |cgv| + cgtc.adjacent(cgv).each do |adj| + cgv.each do |u| + adj.each {|v| add_edge!(u,v)} + end + end + end; self + end + + # This returns the transitive closure of a graph. The original graph + # is not changed. + def transitive_closure() self.class.new(self).transitive_closure!; end + + def plexus_inner_transitive_closure! # :nodoc: + sort.reverse.each do |u| + adjacent(u).each do |v| + adjacent(v).each {|w| add_edge!(u,w) unless edge?(u,w)} + end + end; self + end + end # StrongComponents +end # Plexus diff --git a/lib/plexus/support/support.rb b/lib/plexus/support/support.rb new file mode 100644 index 0000000..24005d6 --- /dev/null +++ b/lib/plexus/support/support.rb @@ -0,0 +1,9 @@ +module Plexus + # Errors + # TODO FIXME: must review all raise lines and streamline things + + # Base error class for the library. + class PlexusError < StandardError; end + + class NoArcError < PlexusError; end +end diff --git a/lib/plexus/undirected_graph.rb b/lib/plexus/undirected_graph.rb new file mode 100644 index 0000000..cfbbf02 --- /dev/null +++ b/lib/plexus/undirected_graph.rb @@ -0,0 +1,56 @@ +module Plexus + module UndirectedGraphBuilder + autoload :Algorithms, "plexus/undirected_graph/algorithms" + + include Plexus::GraphBuilder + extends_host + + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:algorithmic_category] = Plexus::UndirectedGraphBuilder::Algorithms + super *(params << args) + end + end + + # This is a Digraph that allows for parallel edges, but does not allow loops. + module UndirectedPseudoGraphBuilder + include UndirectedGraphBuilder + extends_host + + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:parallel_edges] = true + super *(params << args) + end + end + + # This is a Digraph that allows for parallel edges and loops. + module UndirectedMultiGraphBuilder + include UndirectedPseudoGraphBuilder + extends_host + + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:loops] = true + super *(params << args) + end + end +end diff --git a/lib/plexus/undirected_graph/algorithms.rb b/lib/plexus/undirected_graph/algorithms.rb new file mode 100644 index 0000000..27655f8 --- /dev/null +++ b/lib/plexus/undirected_graph/algorithms.rb @@ -0,0 +1,90 @@ +module Plexus + module UndirectedGraphBuilder + module Algorithms + + include Search + include Biconnected + include Comparability + + # UndirectedGraph is by definition undirected, always returns false + def directed?() false; end + + # Redefine degree (default was sum) + def degree(v) in_degree(v); end + + # A vertex of an undirected graph is balanced by definition + def balanced?(v) true; end + + # UndirectedGraph uses Edge for the edge class. + def edge_class() @parallel_edges ? Plexus::MultiEdge : Plexus::Edge; end + + def remove_edge!(u, v=nil) + unless u.kind_of? Plexus::Arc + raise ArgumentError if @parallel_edges + u = edge_class[u,v] + end + super(u.reverse) unless u.source == u.target + super(u) + end + + # A triangulated graph is an undirected perfect graph that every cycle of length greater than + # three possesses a chord. They have also been called chordal, rigid circuit, monotone transitive, + # and perfect elimination graphs. + # + # Implementation taken from Golumbic's, "Algorithmic Graph Theory and + # Perfect Graphs" pg. 90 + def triangulated? + a = Hash.new {|h,k| h[k]=Set.new}; sigma=lexicograph_bfs + inv_sigma = sigma.inject({}) {|acc,val| acc[val] = sigma.index(val); acc} + sigma[0..-2].each do |v| + x = adjacent(v).select {|w| inv_sigma[v] < inv_sigma[w] } + unless x.empty? + u = sigma[x.map {|y| inv_sigma[y]}.min] + a[u].merge(x - [u]) + end + return false unless a[v].all? {|z| adjacent?(v,z)} + end + true + end + + def chromatic_number + return triangulated_chromatic_number if triangulated? + raise NotImplementedError + end + + # An interval graph can have its vertices into one-to-one + # correspondence with a set of intervals F of a linearly ordered + # set (like the real line) such that two vertices are connected + # by an edge of G if and only if their corresponding intervals + # have nonempty intersection. + def interval?() triangulated? and complement.comparability?; end + + # A permutation diagram consists of n points on each of two parallel + # lines and n straight line segments matchin the points. The intersection + # graph of the line segments is called a permutation graph. + def permutation?() comparability? and complement.comparability?; end + + # An undirected graph is defined to be split if there is a partition + # V = S + K of its vertex set into a stable set S and a complete set K. + def split?() triangulated? and complement.triangulated?; end + + private + # Implementation taken from Golumbic's, "Algorithmic Graph Theory and + # Perfect Graphs" pg. 99 + def triangulated_chromatic_number + chi = 1; s= Hash.new {|h,k| h[k]=0} + sigma=lexicograph_bfs + inv_sigma = sigma.inject({}) {|acc,val| acc[val] = sigma.index(val); acc} + sigma.each do |v| + x = adjacent(v).select {|w| inv_sigma[v] < inv_sigma[w] } + unless x.empty? + u = sigma[x.map {|y| inv_sigma[y]}.min] + s[u] = [s[u], x.size-1].max + chi = [chi, x.size+1].max if s[v] < x.size + end + end; chi + end + + end # UndirectedGraphAlgorithms + end # UndirectedGraphBuilder +end # Plexus diff --git a/lib/plexus/version.rb b/lib/plexus/version.rb new file mode 100644 index 0000000..f1de255 --- /dev/null +++ b/lib/plexus/version.rb @@ -0,0 +1,6 @@ +module Plexus + MAJOR = 0 + MINOR = 5 + PATCH = 3 + VERSION = [MAJOR, MINOR, PATCH].join('.') +end From 5536fe8715be168cabdf3990ec91b255d6c9470b Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Wed, 6 Jul 2011 19:47:21 +0200 Subject: [PATCH 31/44] renamed project Plexus, ter --- graphy.gemspec | 27 - lib/graphy.rb | 90 ---- lib/graphy/adjacency_graph.rb | 224 -------- lib/graphy/arc.rb | 59 -- lib/graphy/arc_number.rb | 52 -- lib/graphy/biconnected.rb | 84 --- lib/graphy/chinese_postman.rb | 91 ---- lib/graphy/classes/graph_classes.rb | 28 - lib/graphy/common.rb | 63 --- lib/graphy/comparability.rb | 63 --- lib/graphy/directed_graph.rb | 78 --- lib/graphy/directed_graph/algorithms.rb | 95 ---- lib/graphy/directed_graph/distance.rb | 167 ------ lib/graphy/dot.rb | 94 ---- lib/graphy/edge.rb | 36 -- lib/graphy/ext.rb | 79 --- lib/graphy/graph.rb | 626 ---------------------- lib/graphy/graph_api.rb | 35 -- lib/graphy/labels.rb | 113 ---- lib/graphy/maximum_flow.rb | 77 --- lib/graphy/ruby_compatibility.rb | 17 - lib/graphy/search.rb | 510 ------------------ lib/graphy/strong_components.rb | 93 ---- lib/graphy/support/support.rb | 9 - lib/graphy/undirected_graph.rb | 56 -- lib/graphy/undirected_graph/algorithms.rb | 90 ---- lib/graphy/version.rb | 6 - 27 files changed, 2962 deletions(-) delete mode 100644 graphy.gemspec delete mode 100644 lib/graphy.rb delete mode 100644 lib/graphy/adjacency_graph.rb delete mode 100644 lib/graphy/arc.rb delete mode 100644 lib/graphy/arc_number.rb delete mode 100644 lib/graphy/biconnected.rb delete mode 100644 lib/graphy/chinese_postman.rb delete mode 100644 lib/graphy/classes/graph_classes.rb delete mode 100644 lib/graphy/common.rb delete mode 100644 lib/graphy/comparability.rb delete mode 100644 lib/graphy/directed_graph.rb delete mode 100644 lib/graphy/directed_graph/algorithms.rb delete mode 100644 lib/graphy/directed_graph/distance.rb delete mode 100644 lib/graphy/dot.rb delete mode 100644 lib/graphy/edge.rb delete mode 100644 lib/graphy/ext.rb delete mode 100644 lib/graphy/graph.rb delete mode 100644 lib/graphy/graph_api.rb delete mode 100644 lib/graphy/labels.rb delete mode 100644 lib/graphy/maximum_flow.rb delete mode 100644 lib/graphy/ruby_compatibility.rb delete mode 100644 lib/graphy/search.rb delete mode 100644 lib/graphy/strong_components.rb delete mode 100644 lib/graphy/support/support.rb delete mode 100644 lib/graphy/undirected_graph.rb delete mode 100644 lib/graphy/undirected_graph/algorithms.rb delete mode 100644 lib/graphy/version.rb diff --git a/graphy.gemspec b/graphy.gemspec deleted file mode 100644 index e9c6ae8..0000000 --- a/graphy.gemspec +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by jeweler -# DO NOT EDIT THIS FILE DIRECTLY -# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command -# -*- encoding: utf-8 -*- -require './lib/graphy/version' - -Gem::Specification.new do |s| - s.name = %q{graphy} - s.version = Graphy::VERSION - s.authors = ["Bruce Williams", "Jean-Denis Vauguet "] - s.summary = "A framework for graph data structures and algorithms." - s.description = %q{This library is based on GRATR and RGL. - -Graph algorithms currently provided are: - -* Topological Sort -* Strongly Connected Components -* Transitive Closure -* Rural Chinese Postman -* Biconnected -} - s.email = %q{bruce@codefluency.com} - s.add_dependency "facets" - s.add_development_dependency "rspec" - s.add_development_dependency "yard" -end - diff --git a/lib/graphy.rb b/lib/graphy.rb deleted file mode 100644 index aa03264..0000000 --- a/lib/graphy.rb +++ /dev/null @@ -1,90 +0,0 @@ -#-- -# Copyright (c) 2006 Shawn Patrick Garbett -# Copyright (c) 2002,2004,2005 by Horst Duchene -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice(s), -# this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# * Neither the name of the Shawn Garbett nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#++ - -require 'set' - -module Plexus - # Plexus internals: graph builders and additionnal behaviors - autoload :GraphAPI, 'plexus/graph_api' - - autoload :GraphBuilder, 'plexus/graph' - autoload :AdjacencyGraphBuilder, 'plexus/adjacency_graph' - - autoload :DirectedGraphBuilder, 'plexus/directed_graph' - autoload :DigraphBuilder, 'plexus/directed_graph' - autoload :DirectedPseudoGraphBuilder, 'plexus/directed_graph' - autoload :DirectedMultiGraphBuilder, 'plexus/directed_graph' - - autoload :UndirectedGraphBuilder, 'plexus/undirected_graph' - autoload :UndirectedPseudoGraphBuilder, 'plexus/undirected_graph' - autoload :UndirectedMultiGraphBuilder, 'plexus/undirected_graph' - - autoload :Arc, 'plexus/arc' - autoload :ArcNumber, 'plexus/arc_number' - autoload :Biconnected, 'plexus/biconnected' - autoload :ChinesePostman, 'plexus/chinese_postman' - autoload :Common, 'plexus/common' - autoload :Comparability, 'plexus/comparability' - - autoload :Dot, 'plexus/dot' - autoload :Edge, 'plexus/edge' - autoload :Labels, 'plexus/labels' - autoload :MaximumFlow, 'plexus/maximum_flow' - #autoload :Rdot, 'plexus/dot' - autoload :Search, 'plexus/search' - autoload :StrongComponents, 'plexus/strong_components' - - # Plexus classes - autoload :AdjacencyGraph, 'plexus/classes/graph_classes' - autoload :DirectedGraph, 'plexus/classes/graph_classes' - autoload :Digraph, 'plexus/classes/graph_classes' - autoload :DirectedPseudoGraph, 'plexus/classes/graph_classes' - autoload :DirectedMultiGraph, 'plexus/classes/graph_classes' - autoload :UndirectedGraph, 'plexus/classes/graph_classes' - autoload :UndirectedPseudoGraph, 'plexus/classes/graph_classes' - autoload :UndirectedMultiGraph, 'plexus/classes/graph_classes' - - # ruby stdlib extensions - require 'plexus/ext' - # ruby 1.8.x/1.9.x compatibility - require 'plexus/ruby_compatibility' -end - -# Vendored libraries - -require 'pathname' -path = Pathname.new(__FILE__) -$LOAD_PATH.unshift(path + '../../vendor') # http://ruby.brian-amberg.de/priority-queue/ -$LOAD_PATH.unshift(path + '../../vendor/priority-queue/lib') - -require 'rdot' -require 'facets/hash' - -require 'priority_queue/ruby_priority_queue' -PriorityQueue = RubyPriorityQueue - diff --git a/lib/graphy/adjacency_graph.rb b/lib/graphy/adjacency_graph.rb deleted file mode 100644 index e7edc49..0000000 --- a/lib/graphy/adjacency_graph.rb +++ /dev/null @@ -1,224 +0,0 @@ -module Plexus - - # This module provides the basic routines needed to implement the specialized builders: - # {DigraphBuilder}, {UndirectedGraphBuilder}, {DirectedPseudoGraphBuilder}, - # {UndirectedPseudoGraphBuilder}, {DirectedMultiGraphBuilder} and {UndirectedMultiGraphBuilder} - # modules, each of them streamlining {AdjacencyGraphBuilder}'s behavior. Those - # implementations rely on the {GraphBuilder}, under the control of the {GraphAPI}. - module AdjacencyGraphBuilder - - # Defines a useful `push` -> `add` alias for arrays. - class ArrayWithAdd < Array - alias add push - end - - # This method is called by the specialized implementations - # upon graph creation. - # - # Initialization parameters can include: - # - # * an array of edges to add - # * one or several graphs to copy (will be merged if multiple) - # * `:parallel_edges` denotes that duplicate edges are allowed - # * `:loops denotes` that loops are allowed - # - # @param *params [Hash] the initialization parameters - def implementation_initialize(*params) - @vertex_dict = Hash.new - clear_all_labels - - # FIXME: could definitely make use of the activesupport helper - # extract_options! and facets' reverse_merge! technique - # to handle parameters - args = (params.pop if params.last.is_a? Hash) || {} - - # Basic configuration of adjacency. - @allow_loops = args[:loops] || false - @parallel_edges = args[:parallel_edges] || false - @edgelist_class = @parallel_edges ? ArrayWithAdd : Set - if @parallel_edges - @edge_number = Hash.new - @next_edge_number = 0 - end - - # Copy any given graph into this graph. - params.select { |p| p.is_a? Plexus::GraphBuilder }.each do |g| - g.edges.each do |e| - add_edge!(e) - edge_label_set(e, edge_label(e)) if edge_label(e) - end - g.vertices.each do |v| - add_vertex!(v) - vertex_label_set(v, vertex_label(v)) if vertex_label(v) - end - end - - # Add all array edges specified. - params.select { |p| p.is_a? Array }.each do |a| - 0.step(a.size-1, 2) { |i| add_edge!(a[i], a[i+1]) } - end - end - - # Returns true if v is a vertex of this Graph - # (an "O(1)" implementation of `vertex?`). - # - # @param [vertex] v - # @return [Boolean] - def vertex?(v) - @vertex_dict.has_key?(v) - end - - # Returns true if [u,v] or u is an {Arc} - # (an "O(1)" implementation of `edge?`). - # - # @param [vertex] u - # @param [vertex] v (nil) - # @return [Boolean] - def edge?(u, v = nil) - u, v = u.source, u.target if u.is_a? Plexus::Arc - vertex?(u) and @vertex_dict[u].include?(v) - end - - # Adds a vertex to the graph with an optional label. - # - # @param [vertex(Object)] vertex any kind of Object can act as a vertex - # @param [#to_s] label (nil) - def add_vertex!(vertex, label = nil) - @vertex_dict[vertex] ||= @edgelist_class.new - self[vertex] = label if label - self - end - - # Adds an edge to the graph. - # - # Can be called in two basic ways, label is optional: - # @overload add_edge!(arc) - # Using an explicit {Arc} - # @param [Arc] arc an {Arc}[source, target, label = nil] object - # @return [AdjacencyGraph] `self` - # @overload add_edge!(source, target, label = nil) - # Using vertices to define an arc implicitly - # @param [vertex] u - # @param [vertex] v (nil) - # @param [Label] l (nil) - # @param [Integer] n (nil) {Arc arc} number of `(u, v)` (if `nil` and if `u` - # has an {ArcNumber}, then it will be used) - # @return [AdjacencyGraph] `self` - def add_edge!(u, v = nil, l = nil, n = nil) - n = u.number if u.class.include? ArcNumber and n.nil? - u, v, l = u.source, u.target, u.label if u.is_a? Plexus::Arc - - return self if not @allow_loops and u == v - - n = (@next_edge_number += 1) unless n if @parallel_edges - add_vertex!(u) - add_vertex!(v) - @vertex_dict[u].add(v) - (@edge_number[u] ||= @edgelist_class.new).add(n) if @parallel_edges - - unless directed? - @vertex_dict[v].add(u) - (@edge_number[v] ||= @edgelist_class.new).add(n) if @parallel_edges - end - - self[n ? edge_class[u,v,n] : edge_class[u,v]] = l if l - self - end - - # Removes a given vertex from the graph. - # - # @param [vertex] v - # @return [AdjacencyGraph] `self` - def remove_vertex!(v) - # FIXME This is broken for multi graphs - @vertex_dict.delete(v) - @vertex_dict.each_value { |adjList| adjList.delete(v) } - @vertex_dict.keys.each do |u| - delete_label(edge_class[u,v]) - delete_label(edge_class[v,u]) - end - delete_label(v) - self - end - - # Removes an edge from the graph. - # - # Can be called with both source and target as vertex, - # or with source and object of {Plexus::Arc} derivation. - # - # @overload remove_edge!(a) - # @param [Plexus::Arc] a - # @return [AdjacencyGraph] `self` - # @raise [ArgumentError] if parallel edges are enabled - # @overload remove_edge!(u, v) - # @param [vertex] u - # @param [vertex] v - # @return [AdjacencyGraph] `self` - # @raise [ArgumentError] if parallel edges are enabled and the {ArcNumber} of `u` is zero - def remove_edge!(u, v = nil) - unless u.is_a? Plexus::Arc - raise ArgumentError if @parallel_edges - u = edge_class[u,v] - end - raise ArgumentError if @parallel_edges and (u.number || 0) == 0 - return self unless @vertex_dict[u.source] # It doesn't exist - delete_label(u) # Get rid of label - if @parallel_edges - index = @edge_number[u.source].index(u.number) - raise NoArcError unless index - @vertex_dict[u.source].delete_at(index) - @edge_number[u.source].delete_at(index) - else - @vertex_dict[u.source].delete(u.target) - end - self - end - - # Returns an array of vertices that the graph has. - # - # @return [Array] graph's vertices - def vertices - @vertex_dict.keys - end - - # Returns an array of edges, most likely of class {Arc} or {Edge} depending - # upon the type of graph. - # - # @return [Array] - def edges - @vertex_dict.keys.inject(Set.new) do |a,v| - if @parallel_edges and @edge_number[v] - @vertex_dict[v].zip(@edge_number[v]).each do |w| - s, t, n = v, w[0], w[1] - a.add(edge_class[s, t, n, edge_label(s, t, n)]) - end - else - @vertex_dict[v].each do |w| - a.add(edge_class[v, w, edge_label(v, w)]) - end - end - a - end.to_a - end - - # FIXME, EFFED UP (but why?) - # - # @fixme - def adjacent(x, options = {}) - options[:direction] ||= :out - if !x.is_a?(Plexus::Arc) and (options[:direction] == :out || !directed?) - if options[:type] == :edges - i = -1 - @parallel_edges ? - @vertex_dict[x].map { |v| e = edge_class[x, v, @edge_number[x][i+=1]]; e.label = self[e]; e } : - @vertex_dict[x].map { |v| e = edge_class[x, v]; e.label = self[e]; e } - else - @vertex_dict[x].to_a - end - else - graph_adjacent(x,options) - end - end - - end # Adjacency Graph -end # Plexus diff --git a/lib/graphy/arc.rb b/lib/graphy/arc.rb deleted file mode 100644 index d18d86f..0000000 --- a/lib/graphy/arc.rb +++ /dev/null @@ -1,59 +0,0 @@ -module Plexus - # Arc includes classes for representing egdes of directed and - # undirected graphs. There is no need for a Vertex class, because any ruby - # object can be a vertex of a graph. - # - # Arc's base is a Struct with a :source, a :target and a :label. - Struct.new("ArcBase", :source, :target, :label) - - class Arc < Struct::ArcBase - def initialize(p_source, p_target, p_label = nil) - super(p_source, p_target, p_label) - end - - # Ignore labels for equality. - def eql?(other) - self.class == other.class and target == other.target and source == other.source - end - alias == eql? - - # Returns (v,u) if self == (u,v). - def reverse() - self.class.new(target, source, label) - end - - # Sort support. - def <=>(rhs) - [source, target] <=> [rhs.source, rhs.target] - end - - # Arc.new[1,2].to_s => "(1-2 'label')" - def to_s - l = label ? " '#{label.to_s}'" : '' - "(#{source}-#{target}#{l})" - end - - # Hash is defined in such a way that label is not - # part of the hash value - # FIXME: I had to get rid of that in order to make to_dot_graph - # work, but I can't figure it out (doesn't show up in the stack!) - def hash - source.hash ^ (target.hash + 1) - end - - # Shortcut constructor. - # - # Instead of Arc.new(1,2) one can use Arc[1,2]. - def self.[](p_source, p_target, p_label = nil) - new(p_source, p_target, p_label) - end - - def inspect - "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{label.inspect}]" - end - end - - class MultiArc < Arc - include ArcNumber - end -end diff --git a/lib/graphy/arc_number.rb b/lib/graphy/arc_number.rb deleted file mode 100644 index 17fb329..0000000 --- a/lib/graphy/arc_number.rb +++ /dev/null @@ -1,52 +0,0 @@ -module Plexus - # This module handles internal numbering of edges in order to differente between mutliple edges. - module ArcNumber - - # Used to differentiate between mutli-edges - attr_accessor :number - - def initialize(p_source, p_target, p_number, p_label = nil) - self.number = p_number - super(p_source, p_target, p_label) - end - - # Returns (v,u) if self == (u,v). - def reverse - self.class.new(target, source, number, label) - end - - # Allow for hashing of self loops. - def hash - super ^ number.hash - end - - def to_s - super + "[#{number}]" - end - - def <=>(rhs) - (result = super(rhs)) == 0 ? number <=> rhs.number : result - end - - def inspect - "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{number.inspect},#{label.inspect}]" - end - - def eql?(rhs) - super(rhs) and (rhs.number.nil? or number.nil? or number == rhs.number) - end - - def ==(rhs) - eql?(rhs) - end - - # Shortcut constructor. Instead of Arc.new(1,2) one can use Arc[1,2] - def self.included(cl) - # FIXME: lacks a cl.class_eval, no? - def cl.[](p_source, p_target, p_number = nil, p_label = nil) - new(p_source, p_target, p_number, p_label) - end - end - - end # ArcNumber -end # Plexus diff --git a/lib/graphy/biconnected.rb b/lib/graphy/biconnected.rb deleted file mode 100644 index 278e88e..0000000 --- a/lib/graphy/biconnected.rb +++ /dev/null @@ -1,84 +0,0 @@ -module Plexus - - # Biconnected is a module for adding the biconnected algorithm to - # UndirectedGraphs - module Biconnected - - # biconnected computes the biconnected subgraphs - # of a graph using Tarjan's algorithm based on DFS. See: Robert E. Tarjan - # _Depth_First_Search_and_Linear_Graph_Algorithms_. SIAM Journal on - # Computing, 1(2):146-160, 1972 - # - # The output of the algorithm is a pair, the first value is an - # array of biconnected subgraphs. The second is the set of - # articulation vertices. - # - # A connected graph is biconnected if the removal of any single vertex - # (and all edges incident on that vertex) cannot disconnect the graph. - # More generally, the biconnected components of a graph are the maximal - # subsets of vertices such that the removal of a vertex from a particular - # component will not disconnect the component. Unlike connected components, - # vertices may belong to multiple biconnected components: those vertices - # that belong to more than one biconnected component are called articulation - # points or, equivalently, cut vertices. Articulation points are vertices - # whose removal would increase the number of connected components in the graph. - # Thus, a graph without articulation points is biconnected. - def biconnected - dfs_num = 0 - number = {}; predecessor = {}; low_point = {} - stack = []; result = []; articulation= [] - - root_vertex = Proc.new {|v| predecessor[v]=v } - enter_vertex = Proc.new {|u| number[u]=low_point[u]=(dfs_num+=1) } - tree_edge = Proc.new do |e| - stack.push(e) - predecessor[e.target] = e.source - end - back_edge = Proc.new do |e| - if e.target != predecessor[e.source] - stack.push(e) - low_point[e.source] = [low_point[e.source], number[e.target]].min - end - end - exit_vertex = Proc.new do |u| - parent = predecessor[u] - is_articulation_point = false - if number[parent] > number[u] - parent = predecessor[parent] - is_articulation_point = true - end - if parent == u - is_articulation_point = false if (number[u] + 1) == number[predecessor[u]] - else - low_point[parent] = [low_point[parent], low_point[u]].min - if low_point[u] >= number[parent] - if number[parent] > number[predecessor[parent]] - predecessor[u] = predecessor[parent] - predecessor[parent] = u - end - result << (component = self.class.new) - while number[stack[-1].source] >= number[u] - component.add_edge!(stack.pop) - end - component.add_edge!(stack.pop) - if stack.empty? - predecessor[u] = parent - predecessor[parent] = u - end - end - end - articulation << u if is_articulation_point - end - - # Execute depth first search - dfs({:root_vertex => root_vertex, - :enter_vertex => enter_vertex, - :tree_edge => tree_edge, - :back_edge => back_edge, - :exit_vertex => exit_vertex}) - - [result, articulation] - end # biconnected - - end # Biconnected -end # Plexus diff --git a/lib/graphy/chinese_postman.rb b/lib/graphy/chinese_postman.rb deleted file mode 100644 index ce34d10..0000000 --- a/lib/graphy/chinese_postman.rb +++ /dev/null @@ -1,91 +0,0 @@ -module Plexus - module ChinesePostman - - # Returns the shortest walk that traverses all arcs at least - # once, returning to the specified start node. - def closed_chinese_postman_tour(start, weight=nil, zero=0) - cost, path, delta = floyd_warshall(weight, zero) - return nil unless cp_valid_least_cost? cost, zero - positive, negative = cp_unbalanced(delta) - f = cp_find_feasible(delta, positive, negative, zero) - while cp_improve(f, positive, negative, cost, zero); end - cp_euler_circuit(start, f, path) - end - - private - - def cp_euler_circuit(start, f, path) # :nodoc: - circuit = [u=v=start] - bridge_taken = Hash.new {|h,k| h[k] = Hash.new} - until v.nil? - if v=f[u].keys.detect {|k| f[u][k] > 0} - f[u][v] -= 1 - circuit << (u = path[u][v]) while u != v - else - unless bridge_taken[u][bridge = path[u][start]] - v = vertices.detect {|v1| v1 != bridge && edge?(u,v1) && !bridge_taken[u][v1]} || bridge - bridge_taken[u][v] = true - circuit << v - end - end - u=v - end; circuit - end - - def cp_cancel_cycle(cost, path, f, start, zero) # :nodoc: - u = start; k = nil - begin - v = path[u][start] - k = f[v][u] if cost[u][v] < zero and (k.nil? || k > f[v][u]) - end until (u=v) != start - u = start - begin - v = path[u][start] - cost[u][v] < zero ? f[v][u] -= k : f[u][v] += k - end until (u=v) != start - true # This routine always returns true to make cp_improve easier - end - - def cp_improve(f, positive, negative, cost, zero) # :nodoc: - residual = self.class.new - negative.each do |u| - positive.each do |v| - residual.add_edge!(u,v,cost[u][v]) - residual.add_edge!(v,u,-cost[u][v]) if f[u][v] != 0 - end - end - r_cost, r_path, r_delta = residual.floyd_warshall(nil, zero) - i = residual.vertices.detect {|v| r_cost[v][v] and r_cost[v][v] < zero} - i ? cp_cancel_cycle(r_cost, r_path, f, i) : false - end - - def cp_find_feasible(delta, positive, negative, zero) # :nodoc: - f = Hash.new {|h,k| h[k] = Hash.new} - negative.each do |i| - positive.each do |j| - f[i][j] = -delta[i] < delta[j] ? -delta[i] : delta[j] - delta[i] += f[i][j] - delta[j] -= f[i][j] - end - end; f - end - - def cp_valid_least_cost?(c, zero) # :nodoc: - vertices.each do |i| - vertices.each do |j| - return false unless c[i][j] and c[i][j] >= zero - end - end; true - end - - def cp_unbalanced(delta) # :nodoc: - negative = []; positive = [] - vertices.each do |v| - negative << v if delta[v] < 0 - positive << v if delta[v] > 0 - end; [positive, negative] - end - - end # Chinese Postman -end # Plexus - diff --git a/lib/graphy/classes/graph_classes.rb b/lib/graphy/classes/graph_classes.rb deleted file mode 100644 index fc0218c..0000000 --- a/lib/graphy/classes/graph_classes.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Plexus - # A generic {GraphBuilder Graph} class you can inherit from. - class Graph; include GraphBuilder; end - - # A generic {AdjacencyGraphBuilder AdjacencyGraph} class you can inherit from. - class AdjacencyGraph < Graph; include AdjacencyGraphBuilder; end - - # A generic {DirectedGraphBuilder DirectedGraph} class you can inherit from. - class DirectedGraph < Graph; include DirectedGraphBuilder; end - - # A generic {DigraphBuilder Digraph} class you can inherit from. - class Digraph < Graph; include DigraphBuilder; end - - # A generic {DirectedPseudoGraphBuilder DirectedPseudoGraph} class you can inherit from. - class DirectedPseudoGraph < Graph; include DirectedPseudoGraphBuilder; end - - # A generic {DirectedMultiGraphBuilder DirectedMultiGraph} class you can inherit from. - class DirectedMultiGraph < Graph; include DirectedMultiGraphBuilder; end - - # A generic {UndirectedGraphBuilder UndirectedGraph} class you can inherit from. - class UndirectedGraph < Graph; include UndirectedGraphBuilder; end - - # A generic {UndirectedPseudoGraphBuilder UndirectedPseudoGraph} class you can inherit from. - class UndirectedPseudoGraph < Graph; include UndirectedPseudoGraphBuilder; end - - # A generic {UndirectedMultiGraphBuilder UndirectedMultiGraph} class you can inherit from. - class UndirectedMultiGraph < Graph; include UndirectedMultiGraphBuilder; end -end diff --git a/lib/graphy/common.rb b/lib/graphy/common.rb deleted file mode 100644 index 7ede4c6..0000000 --- a/lib/graphy/common.rb +++ /dev/null @@ -1,63 +0,0 @@ -module Plexus - - # This class defines a cycle graph of size n. - # This is easily done by using the base Graph - # class and implemeting the minimum methods needed to - # make it work. This is a good example to look - # at for making one's own graph classes. - module CycleBuilder - def initialize(n) - @size = n; - end - - def directed? - false - end - - def vertices - (1..@size).to_a - end - - def vertex?(v) - v > 0 and v <= @size - end - - def edge?(u,v = nil) - u, v = [u.source, v.target] if u.is_a? Plexus::Arc - vertex?(u) && vertex?(v) && ((v-u == 1) or (u == @size && v = 1)) - end - - def edges - Array.new(@size) { |i| Plexus::Edge[i+1, (i+1) == @size ? 1 : i+2]} - end - end # CycleBuilder - - # This class defines a complete graph of size n. - # This is easily done by using the base Graph - # class and implemeting the minimum methods needed to - # make it work. This is a good example to look - # at for making one's own graph classes. - module CompleteBuilder - include CycleBuilder - - def initialize(n) - @size = n - @edges = nil - end - - def edges - return @edges if @edges # cache edges - @edges = [] - @size.times do |u| - @size.times { |v| @edges << Plexus::Edge[u+1, v+1]} - end - @edges - end - - def edge?(u, v = nil) - u, v = [u.source, v.target] if u.kind_of? Plexus::Arc - vertex?(u) && vertex?(v) - end - end # CompleteBuilder - -end # Plexus diff --git a/lib/graphy/comparability.rb b/lib/graphy/comparability.rb deleted file mode 100644 index 41a038e..0000000 --- a/lib/graphy/comparability.rb +++ /dev/null @@ -1,63 +0,0 @@ -module Plexus - module Comparability - - # A comparability graph is an UndirectedGraph that has a transitive - # orientation. This returns a boolean that says if this graph - # is a comparability graph. - def comparability?() gamma_decomposition[1]; end - - # Returns an array with two values, the first being a hash of edges - # with a number containing their class assignment, the second valud - # is a boolean which states whether or not the graph is a - # comparability graph - # - # Complexity in time O(d*|E|) where d is the maximum degree of a vertex - # Complexity in space O(|V|+|E|) - def gamma_decomposition - k = 0; comparability=true; classification={} - edges.map {|edge| [edge.source,edge.target]}.each do |e| - if classification[e].nil? - k += 1 - classification[e] = k; classification[e.reverse] = -k - comparability &&= plexus_comparability_explore(e, k, classification) - end - end; [classification, comparability] - end - - # Returns one of the possible transitive orientations of - # the UndirectedGraph as a Digraph - def transitive_orientation(digraph_class=Digraph) - raise NotImplementError - end - - private - - # Taken from Figure 5.10, on pg. 130 of Martin Golumbic's, _Algorithmic_Graph_ - # _Theory_and_Perfect_Graphs. - def plexus_comparability_explore(edge, k, classification, space='') - ret = plexus_comparability_explore_inner(edge, k, classification, :forward, space) - plexus_comparability_explore_inner(edge.reverse, k, classification, :backward, space) && ret - end - - def plexus_comparability_explore_inner(edge, k, classification, direction,space) - comparability = true - adj_target = adjacent(edge[1]) - adjacent(edge[0]).select do |mt| - (classification[[edge[1],mt]] || k).abs < k or - not adj_target.any? {|adj_t| adj_t == mt} - end.each do |m| - e = (direction == :forward) ? [edge[0], m] : [m,edge[0]] - if classification[e].nil? - classification[e] = k - classification[e.reverse] = -k - comparability = plexus_comparability_explore(e, k, classification, ' '+space) && comparability - elsif classification[e] == -k - classification[e] = k - plexus_comparability_explore(e, k, classification, ' '+space) - comparability = false - end - end; comparability - end # plexus_comparability_explore_inner - - end # Comparability -end # Plexus diff --git a/lib/graphy/directed_graph.rb b/lib/graphy/directed_graph.rb deleted file mode 100644 index 1664b33..0000000 --- a/lib/graphy/directed_graph.rb +++ /dev/null @@ -1,78 +0,0 @@ -module Plexus - - # This implements a directed graph which does not allow parallel - # edges nor loops. That is, only one arc per nodes couple, - # and only one parent per node. Mimics the typical hierarchy - # structure. - module DirectedGraphBuilder - include GraphBuilder - - autoload :Algorithms, "plexus/directed_graph/algorithms" - autoload :Distance, "plexus/directed_graph/distance" - - # FIXME: DRY this snippet, I didn't find a clever way to - # to dit though - # TODO: well, extends_host_with do ... end would be cool, - # using Module.new.module_eval(&block) in the helper. - extends_host - - module ClassMethods - def [](*a) - self.new.from_array(*a) - end - end - - def initialize(*params) - # FIXME/TODO: setting args to the hash or {} while getting rid - # on the previous parameters prevents from passing another - # graph to the initializer, so you cannot do things like: - # UndirectedGraph.new(Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6]) - # As args must be a hash, if we're to allow such syntax, - # we should provide a way to handle the graph as a hash - # member. - args = (params.pop if params.last.kind_of? Hash) || {} - args[:algorithmic_category] = DirectedGraphBuilder::Algorithms - super *(params << args) - end - end - - # DirectedGraph is just an alias for Digraph should one desire - DigraphBuilder = DirectedGraphBuilder - - # This is a Digraph that allows for parallel edges, but does not - # allow loops. - module DirectedPseudoGraphBuilder - include DirectedGraphBuilder - extends_host - - module ClassMethods - def [](*a) - self.new.from_array(*a) - end - end - - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:parallel_edges] = true - super *(params << args) - end - end - - # This is a Digraph that allows for both parallel edges and loops. - module DirectedMultiGraphBuilder - include DirectedPseudoGraphBuilder - extends_host - - module ClassMethods - def [](*a) - self.new.from_array(*a) - end - end - - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:loops] = true - super *(params << args) - end - end -end diff --git a/lib/graphy/directed_graph/algorithms.rb b/lib/graphy/directed_graph/algorithms.rb deleted file mode 100644 index 4a6b156..0000000 --- a/lib/graphy/directed_graph/algorithms.rb +++ /dev/null @@ -1,95 +0,0 @@ -module Plexus - # Digraph is a directed graph which is a finite set of vertices - # and a finite set of edges connecting vertices. It cannot contain parallel - # edges going from the same source vertex to the same target. It also - # cannot contain loops, i.e. edges that go have the same vertex for source - # and target. - # - # DirectedPseudoGraph is a class that allows for parallel edges, and - # DirectedMultiGraph is a class that allows for parallel edges and loops - # as well. - module DirectedGraphBuilder - module Algorithms - include Search - include StrongComponents - include Distance - include ChinesePostman - - # A directed graph is directed by definition. - # - # @return [Boolean] always true - # - def directed? - true - end - - # A digraph uses the Arc class for edges. - # - # @return [Plexus::MultiArc, Plexus::Arc] `Plexus::MultiArc` if the graph allows for parallel edges, - # `Plexus::Arc` otherwise. - # - def edge_class - @parallel_edges ? Plexus::MultiArc : Plexus::Arc - end - - # Reverse all edges in a graph. - # - # @return [DirectedGraph] a copy of the receiver for which the direction of edges has - # been inverted. - # - def reversal - result = self.class.new - edges.inject(result) { |a,e| a << e.reverse} - vertices.each { |v| result.add_vertex!(v) unless result.vertex?(v) } - result - end - - # Check whether the graph is oriented or not. - # - # @return [Boolean] - # - def oriented? - e = edges - re = e.map { |x| x.reverse} - not e.any? { |x| re.include?(x)} - end - - # Balanced is the state when the out edges count is equal to the in edges count. - # - # @return [Boolean] - # - def balanced?(v) - out_degree(v) == in_degree(v) - end - - # Returns out_degree(v) - in_degree(v). - # - def delta(v) - out_degree(v) - in_degree(v) - end - - def community(node, direction) - nodes, stack = {}, adjacent(node, :direction => direction) - while n = stack.pop - unless nodes[n.object_id] || node == n - nodes[n.object_id] = n - stack += adjacent(n, :direction => direction) - end - end - nodes.values - end - - def descendants(node) - community(node, :out) - end - - def ancestors(node) - community(node, :in) - end - - def family(node) - community(node, :all) - end - end - end -end diff --git a/lib/graphy/directed_graph/distance.rb b/lib/graphy/directed_graph/distance.rb deleted file mode 100644 index 73b9f28..0000000 --- a/lib/graphy/directed_graph/distance.rb +++ /dev/null @@ -1,167 +0,0 @@ -module Plexus - module DirectedGraphBuilder - - # This module provides algorithms computing distance between - # vertices. - module Distance - - # Shortest path computation. - # - # From: Jorgen Band-Jensen and Gregory Gutin, - # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 53-54. - # Complexity `O(n+m)`. - # - # Requires the graph to be acyclic. If the graph is not acyclic, - # then see {Distance#dijkstras_algorithm} or {Distance#bellman_ford_moore} - # for possible solutions. - # - # @param [vertex] start the starting vertex - # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]` - # operator. If not a `Proc`, and if no label accessible through `[]`, it will - # default to using the value stored in the label for the {Arc}. If a `Proc`, it will - # pass the edge to the proc and use the resulting value. - # @param [Integer] zero used for math systems with a different definition of zero - # - # @return [Hash] a hash with the key being a vertex and the value being the - # distance. A missing vertex from the hash is equivalent to an infinite distance. - def shortest_path(start, weight = nil, zero = 0) - dist = { start => zero } - path = {} - topsort(start) do |vi| - next if vi == start - dist[vi], path[vi] = adjacent(vi, :direction => :in).map do |vj| - [dist[vj] + cost(vj,vi,weight), vj] - end.min { |a,b| a[0] <=> b[0]} - end; - dist.keys.size == vertices.size ? [dist, path] : nil - end - - # Finds the distance from a given vertex in a weighted digraph - # to the rest of the vertices, provided all the weights of arcs - # are non-negative. - # - # If negative arcs exist in the graph, two basic options exist: - # - # * modify all weights to be positive using an offset (temporary at least) - # * use the {Distance#bellman_ford_moore} algorithm. - # - # Also, if the graph is acyclic, use the {Distance#shortest_path algorithm}. - # - # From: Jorgen Band-Jensen and Gregory Gutin, - # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 53-54. - # - # Complexity `O(n*log(n) + m)`. - # - # @param [vertex] s - # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]` - # operator. If not a `Proc`, and if no label accessible through `[]`, it will - # default to using the value stored in the label for the {Arc}. If a `Proc`, it will - # pass the edge to the proc and use the resulting value. - # @param [Integer] zero used for math systems with a different definition of zero - # @return [Hash] a hash with the key being a vertex and the value being the - # distance. A missing vertex from the hash is equivalent to an infinite distance. - def dijkstras_algorithm(s, weight = nil, zero = 0) - q = vertices; distance = { s => zero } - path = {} - while not q.empty? - v = (q & distance.keys).inject(nil) { |a,k| (!a.nil?) && (distance[a] < distance[k]) ? a : k} - q.delete(v) - (q & adjacent(v)).each do |u| - c = cost(v, u, weight) - if distance[u].nil? or distance[u] > (c + distance[v]) - distance[u] = c + distance[v] - path[u] = v - end - end - end - [distance, path] - end - - # Finds the distances from a given vertex in a weighted digraph - # to the rest of the vertices, provided the graph has no negative cycle. - # - # If no negative weights exist, then {Distance#dijkstras_algorithm} is more - # efficient in time and space. Also, if the graph is acyclic, use the - # {Distance#shortest_path} algorithm. - # - # From: Jorgen Band-Jensen and Gregory Gutin, - # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 56-58.. - # - # Complexity `O(nm)`. - # - # @param [vertex] s - # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]` - # operator. If not a `Proc`, and if no label accessible through `[]`, it will - # default to using the value stored in the label for the {Arc}. If a `Proc`, it will - # pass the edge to the proc and use the resulting value. - # @param [Integer] zero used for math systems with a different definition of zero - # @return [Hash] a hash with the key being a vertex and the value being the - # distance. A missing vertex from the hash is equivalent to an infinite distance. - def bellman_ford_moore(start, weight = nil, zero = 0) - distance = { start => zero } - path = {} - 2.upto(vertices.size) do - edges.each do |e| - u, v = e[0], e[1] - unless distance[u].nil? - c = cost(u, v, weight) + distance[u] - if distance[v].nil? or c < distance[v] - distance[v] = c - path[v] = u - end - end - end - end - [distance, path] - end - - # Uses the Floyd-Warshall algorithm to efficiently find - # and record shortest paths while establishing at the same time - # the costs for all vertices in a graph. - # - # See S.Skiena, *The Algorithm Design Manual*, Springer Verlag, 1998 for more details. - # - # O(n^3) complexity in time. - # - # @param [Proc, nil] weight specifies how an edge weight is determined. - # If it's a `Proc`, the {Arc} is passed to it; if it's `nil`, it will just use - # the value in the label for the Arc; otherwise the weight is - # determined by applying the `[]` operator to the value in the - # label for the {Arc}. - # @param [Integer] zero defines the zero value in the math system used. - # This allows for no assumptions to be made about the math system and - # fully functional duck typing. - # @return [Array(matrice, matrice, Hash)] a pair of matrices and a hash of delta values. - # The matrices will be indexed by two vertices and are implemented as a Hash of Hashes. - # The first matrix is the cost, the second matrix is the shortest path spanning tree. - # The delta (difference of number of in-edges and out-edges) is indexed by vertex. - def floyd_warshall(weight = nil, zero = 0) - c = Hash.new { |h,k| h[k] = Hash.new } - path = Hash.new { |h,k| h[k] = Hash.new } - delta = Hash.new { |h,k| h[k] = 0 } - edges.each do |e| - delta[e.source] += 1 - delta[e.target] -= 1 - path[e.source][e.target] = e.target - c[e.source][e.target] = cost(e, weight) - end - vertices.each do |k| - vertices.each do |i| - if c[i][k] - vertices.each do |j| - if c[k][j] && - (c[i][j].nil? or c[i][j] > (c[i][k] + c[k][j])) - path[i][j] = path[i][k] - c[i][j] = c[i][k] + c[k][j] - return nil if i == j and c[i][j] < zero - end - end - end - end - end - [c, path, delta] - end - - end # Distance - end # DirectedGraph -end # Plexus diff --git a/lib/graphy/dot.rb b/lib/graphy/dot.rb deleted file mode 100644 index 28770d8..0000000 --- a/lib/graphy/dot.rb +++ /dev/null @@ -1,94 +0,0 @@ -module Plexus - module Dot - - #FIXME: don't really understood where we stand with the dot generators. - # RDoc ships with a dot.rb which seems pretty efficient. - # Are these helpers still needed, and if not, how should we replace them? - - # Creates a DOT::DOTDigraph for directed graphs or a DOT::DOTSubgraph for - # undirected graphs. - # - # @param [Hash] params can contain any graph property specified in - # rdot.rb. If an edge or vertex label is a kind of Hash, then the keys - # which match dot properties will be used as well. - # @return [DOT::DOTDigraph, DOT::DOTSubgraph] - def to_dot_graph(params = {}) - params['name'] ||= self.class.name.gsub(/:/, '_') - fontsize = params['fontsize'] ? params['fontsize'] : '8' - graph = (directed? ? DOT::DOTDigraph : DOT::DOTSubgraph).new(params) - edge_klass = directed? ? DOT::DOTDirectedArc : DOT::DOTArc - - vertices.each do |v| - name = v.to_s || v.__id__.to_s - name = name.dup.gsub(/"/, "'") - - params = { 'name' => '"'+ name +'"', - 'fontsize' => fontsize, - 'label' => name} - - v_label = vertex_label(v) - params.merge!(v_label) if v_label and v_label.kind_of? Hash - - graph << DOT::DOTNode.new(params) - end - - edges.each do |e| - if e.source.to_s.nil? - source_label = e.source.__id__.to_s - else - source_label = e.source.to_s.dup - end - - if e.target.to_s.nil? - target_label = e.target.__id__.to_s - else - target_label = e.target.to_s.dup - end - - source_label.gsub!(/"/, "'") - target_label.gsub!(/"/, "'") - - params = { 'from' => '"'+ source_label + '"', - 'to' => '"'+ target_label + '"', - 'fontsize' => fontsize } - - e_label = edge_label(e) - params.merge!(e_label) if e_label and e_label.kind_of? Hash - - graph << edge_klass.new(params) - end - - graph - end - - # Output the dot format as a string - def to_dot(params = {}) - to_dot_graph(params).to_s - end - - # Call +dotty+ for the graph which is written to the file 'graph.dot' - # in the # current directory. - def dotty(params = {}, dotfile = 'graph.dot') - File.open(dotfile, 'w') {|f| f << to_dot(params) } - system('dotty', dotfile) - end - - # Use +dot+ to create a graphical representation of the graph. Returns the - # filename of the graphics file. - def write_to_graphic_file(fmt = 'png', dotfile = 'graph') - src = dotfile + '.dot' - dot = dotfile + '.' + fmt - - # DOT::DOTSubgraph creates subgraphs, but that's broken. - buffer = self.to_dot - buffer.gsub!(/^subgraph/, "graph") - - File.open(src, 'w') {|f| f << buffer << "\n"} - system( "dot -T#{fmt} #{src} -o #{dot}" ) - - dot - end - alias as_dot_graphic write_to_graphic_file - - end # Dot -end # module Plexus diff --git a/lib/graphy/edge.rb b/lib/graphy/edge.rb deleted file mode 100644 index 600de87..0000000 --- a/lib/graphy/edge.rb +++ /dev/null @@ -1,36 +0,0 @@ -module Plexus - # An undirected edge is simply an undirected pair (source, target) used in - # undirected graphs. Edge[u,v] == Edge[v,u] - class Edge < Arc - - # Equality allows for the swapping of source and target - def eql?(other) - super or (self.class == other.class and target == other.source and source == other.target) - end - alias == eql? - - # Hash is defined such that source and target can be reversed and the - # hash value will be the same - def hash - source.hash ^ target.hash - end - - # Sort support - def <=>(rhs) - [[source,target].max, [source,target].min] <=> [[rhs.source,rhs.target].max, [rhs.source,rhs.target].min] - end - - # Edge[1,2].to_s == "(1=2 'label)" - def to_s - l = label ? " '#{label.to_s}'" : '' - s = source.to_s - t = target.to_s - "(#{[s,t].min}=#{[s,t].max}#{l})" - end - - end - - class MultiEdge < Edge - include ArcNumber - end -end diff --git a/lib/graphy/ext.rb b/lib/graphy/ext.rb deleted file mode 100644 index b29b150..0000000 --- a/lib/graphy/ext.rb +++ /dev/null @@ -1,79 +0,0 @@ -class Object - # Get the singleton class of the object. - # Depending on the object which requested its singleton class, - # a `module_eval` or a `class_eval` will be performed. - # Object of special constant type (`Fixnum`, `NilClass`, `TrueClass`, - # `FalseClass` and `Symbol`) return `nil` as they do not have a - # singleton class. - # - # @return the singleton class - def singleton_class - if self.respond_to? :module_eval - self.module_eval("class << self; self; end") - elsif self.respond_to? :instance_eval - begin - self.instance_eval("class << self; self; end") - rescue TypeError - nil - end - end - end - - # Check wether the object is of the specified kind. - # If the receiver has a singleton class, will also perform - # the check on its singleton class' ancestors, so as to catch - # any included modules for object instances. - # - # Example: - # - # class A; include Digraph; end - # a.singleton_class.ancestors - # # => [Plexus::GraphAPI, Plexus::DirectedGraph::Algorithms, ... - # Plexus::Labels, Enumerable, Object, Plexus, Kernel, BasicObject] - # a.is_a? Plexus::Graph - # # => true - # - # @param [Class] klass - # @return [Boolean] - def is_a? klass - sc = self.singleton_class - if not sc.nil? - self.singleton_class.ancestors.include?(klass) || super - else - super - end - end -end - -class Module - # Helper which purpose is, given a class including a module, - # to make each methods defined within a module's submodule `ClassMethods` - # available as class methods to the receiving class. - # - # Example: - # - # module A - # extends_host - # module ClassMethods - # def selfy; puts "class method for #{self}"; end - # end - # end - # - # class B; include A; end - # - # B.selfy - # # => class method for B - # - # @option *params [Symbol] :with (:ClassMethods) the name of the - # module to extend the receiver with - def extends_host(*params) - args = (params.pop if params.last.is_a? Hash) || {} - @_extension_module = args[:with] || :ClassMethods - - def included(base) - unless @_extension_module.nil? - base.extend(self.const_get(@_extension_module)) - end - end - end -end diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb deleted file mode 100644 index 517f285..0000000 --- a/lib/graphy/graph.rb +++ /dev/null @@ -1,626 +0,0 @@ -module Plexus - # Using the methods required by the {GraphAPI}, it implements all the - # *basic* functions of a {Graph} using *only* functions - # requested in {GraphAPI}. The process is under the control of the pattern - # {AdjacencyGraphBuilder}, unless a specific implementation is specified - # during initialization. - # - # An actual, complete implementation still needs to be done using this cheap result, - # hence {Digraph}, {UndirectedGraph} and their roomates. - module GraphBuilder - include Enumerable - include Labels - include Dot - - #def self.[](*a) - #puts self - #self.new.from_array(*a) - #end - # after the class->module transition, has been moved at implementation level, - # using a helper (extends_host) - extends_host - module ClassMethods - def [](*a) - self.new.from_array(*a) - end - end - - # Creates a generic graph. - # - # @param [Hash(Plexus::Graph, Array)] *params initialization parameters. - # See {AdjacencyGraphBuilder#implementation_initialize} for more details. - # @return [Graph] - def initialize(*params) - raise ArgumentError if params.any? do |p| - # FIXME: checking wether it's a GraphBuilder (module) is not sufficient - # and the is_a? redefinition trick (instance_evaling) should be - # completed by a clever way to check the actual class of p. - # Maybe using ObjectSpace to get the available Graph classes? - !(p.is_a? Plexus::GraphBuilder or p.is_a? Array or p.is_a? Hash) - end - - args = params.last || {} - - class << self - self - end.module_eval do - # These inclusions trigger some validations checks by the way. - include(args[:implementation] ? args[:implementation] : Plexus::AdjacencyGraphBuilder) - include(args[:algorithmic_category] ? args[:algorithmic_category] : Plexus::DigraphBuilder ) - include Plexus::GraphAPI - end - - implementation_initialize(*params) - end - - # Shortcut for creating a Graph. - # - # Using an arry of implicit {Arc}, specifying the vertices: - # - # Plexus::Graph[1,2, 2,3, 2,4, 4,5].edges.to_a.to_s - # # => "(1-2)(2-3)(2-4)(4-5)" - # - # Using a Hash for specifying labels along the way: - # - # Plexus::Graph[ [:a,:b] => 3, [:b,:c] => 4 ] (note: do not use for Multi or Pseudo graphs) - # - # @param [Array, Hash] *a - # @return [Graph] - def from_array(*a) - if a.size == 1 and a[0].is_a? Hash - # Convert to edge class - a[0].each do |k,v| - #FIXME, edge class shouldn't be assume here!!! - if edge_class.include? Plexus::ArcNumber - add_edge!(edge_class[k[0],k[1],nil,v]) - else - add_edge!(edge_class[k[0],k[1],v]) - end - end - #FIXME, edge class shouldn't be assume here!!! - elsif a[0].is_a? Plexus::Arc - a.each{ |e| add_edge!(e); self[e] = e.label} - elsif a.size % 2 == 0 - 0.step(a.size-1, 2) {|i| add_edge!(a[i], a[i+1])} - else - raise ArgumentError - end - self - end - - # Non destructive version of {AdjacencyGraphBuilder#add_vertex!} (works on a copy of the graph). - # - # @param [vertex] v - # @param [Label] l - # @return [Graph] a new graph with the supplementary vertex - def add_vertex(v, l = nil) - x = self.class.new(self) - x.add_vertex!(v, l) - end - - # Non destructive version {AdjacencyGraphBuilder#add_edge!} (works on a copy of the graph). - # - # @param [vertex] u - # @param [vertex] v - # @param [Label] l - # @return [Graph] a new graph with the supplementary edge - def add_edge(u, v = nil, l = nil) - x = self.class.new(self) - x.add_edge!(u, v, l) - end - alias add_arc add_edge - - # Non destructive version of {AdjacencyGraphBuilder#remove_vertex!} (works on a copy of the graph). - # - # @param [vertex] v - # @return [Graph] a new graph without the specified vertex - def remove_vertex(v) - x = self.class.new(self) - x.remove_vertex!(v) - end - - # Non destructive version {AdjacencyGraphBuilder#remove_edge!} (works on a copy of the graph). - # - # @param [vertex] u - # @param [vertex] v - # @return [Graph] a new graph without the specified edge - def remove_edge(u, v = nil) - x = self.class.new(self) - x.remove_edge!(u, v) - end - alias remove_arc remove_edge - - # Computes the adjacent portions of the Graph. - # - # The options specify the parameters about the adjacency search. - # Note: it is probably more efficently done in the implementation class. - # - # @param [vertex, Edge] x can either be a vertex an edge - # @option options [Symbol] :type (:vertices) can be either `:edges` or `:vertices` - # @option options [Symbol] :direction (:all) can be `:in`, `:out` or `:all` - # @return [Array] an array of the adjacent portions - # @fixme - def adjacent(x, options = {}) - d = directed? ? (options[:direction] || :out) : :all - - # Discharge the easy ones first. - return [x.source] if x.is_a? Arc and options[:type] == :vertices and d == :in - return [x.target] if x.is_a? Arc and options[:type] == :vertices and d == :out - return [x.source, x.target] if x.is_a? Arc and options[:type] != :edges and d == :all - - (options[:type] == :edges ? edges : to_a).select { |u| adjacent?(x,u,d) } - end - #FIXME: This is a hack around a serious problem - alias graph_adjacent adjacent - - # Adds all specified vertices to the vertex set. - # - # @param [#each] *a an Enumerable vertices set - # @return [Graph] `self` - def add_vertices!(*a) - a.each { |v| add_vertex! v } - self - end - - # Same as {GraphBuilder#add_vertices! add_vertices!} but works on copy of the receiver. - # - # @param [#each] *a - # @return [Graph] a modified copy of `self` - def add_vertices(*a) - x = self.class.new(self) - x.add_vertices!(*a) - self - end - - # Adds all edges mentionned in the specified Enumerable to the edge set. - # - # Elements of the Enumerable can be either two-element arrays or instances of - # {Edge} or {Arc}. - # - # @param [#each] *a an Enumerable edges set - # @return [Graph] `self` - def add_edges!(*a) - a.each { |edge| add_edge!(edge) } - self - end - alias add_arcs! add_edges! - - # Same as {GraphBuilder#add_egdes! add_edges!} but works on a copy of the receiver. - # - # @param [#each] *a an Enumerable edges set - # @return [Graph] a modified copy of `self` - def add_edges(*a) - x = self.class.new(self) - x.add_edges!(*a) - self - end - alias add_arcs add_edges - - # Removes all vertices mentionned in the specified Enumerable from the graph. - # - # The process relies on {GraphBuilder#remove_vertex! remove_vertex!}. - # - # @param [#each] *a an Enumerable vertices set - # @return [Graph] `self` - def remove_vertices!(*a) - a.each { |v| remove_vertex! v } - end - alias delete_vertices! remove_vertices! - - # Same as {GraphBuilder#remove_vertices! remove_vertices!} but works on a copy of the receiver. - # - # @param [#each] *a a vertex Enumerable set - # @return [Graph] a modified copy of `self` - def remove_vertices(*a) - x = self.class.new(self) - x.remove_vertices(*a) - end - alias delete_vertices remove_vertices - - # Removes all edges mentionned in the specified Enumerable from the graph. - # - # The process relies on {GraphBuilder#remove_edges! remove_edges!}. - # - # @param [#each] *a an Enumerable edges set - # @return [Graph] `self` - def remove_edges!(*a) - a.each { |e| remove_edge! e } - end - alias remove_arcs! remove_edges! - alias delete_edges! remove_edges! - alias delete_arcs! remove_edges! - - # Same as {GraphBuilder#remove_edges! remove_edges!} but works on a copy of the receiver. - # - # @param [#each] *a an Enumerable edges set - # @return [Graph] a modified copy of `self` - def remove_edges(*a) - x = self.class.new(self) - x.remove_edges!(*a) - end - alias remove_arcs remove_edges - alias delete_edges remove_edges - alias delete_arcs remove_edges - - # Executes the given block for each vertex. It allows for mixing Enumerable in. - def each(&block) - vertices.each(&block) - end - - # Returns true if the specified vertex belongs to the graph. - # - # This is a default implementation that is of O(n) average complexity. - # If a subclass uses a hash to store vertices, then this can be - # made into an O(1) average complexity operation. - # - # @param [vertex] v - # @return [Boolean] - def vertex?(v) - vertices.include?(v) - end - alias has_vertex? vertex? - # TODO: (has_)vertices? - - # Returns true if u or (u,v) is an {Edge edge} of the graph. - # - # @overload edge?(a) - # @param [Arc, Edge] a - # @overload edge?(u, v) - # @param [vertex] u - # @param [vertex] v - # @return [Boolean] - def edge?(*args) - edges.include?(edge_convert(*args)) - end - alias arc? edge? - alias has_edge? edge? - alias has_arc? edge? - - # Tests two objects to see if they are adjacent. - # - # Note that in this method, one is primarily concerned with finding - # all adjacent objects in a graph to a given object. The concern is primarily on seeing - # if two objects touch. For two vertexes, any edge between the two will usually do, but - # the direction can be specified if needed. - # - # @param [vertex] source - # @param [vertex] target - # @param [Symbol] direction (:all) constraint on the direction of adjacency; may be either `:in`, `:out` or `:all` - def adjacent?(source, target, direction = :all) - if source.is_a? Plexus::Arc - raise NoArcError unless edge? source - if target.is_a? Plexus::Arc - raise NoArcError unless edge? target - (direction != :out and source.source == target.target) or (direction != :in and source.target == target.source) - else - raise NoVertexError unless vertex? target - (direction != :out and source.source == target) or (direction != :in and source.target == target) - end - else - raise NoVertexError unless vertex? source - if target.is_a? Plexus::Arc - raise NoArcError unless edge? target - (direction != :out and source == target.target) or (direction != :in and source == target.source) - else - raise NoVertexError unless vertex? target - (direction != :out and edge?(target,source)) or (direction != :in and edge?(source,target)) - end - end - end - - # Is the graph connected? - # - # A graph is called connected if every pair of distinct vertices in the graph - # can be connected through some path. The exact definition depends on whether - # the graph is directed or not, hence this method should overriden in specific - # implementations. - # - # This methods implements a lazy routine using the internal vertices hash. - # If you ever want to check connectivity state using a bfs/dfs algorithm, use - # the `:algo => :bfs` or `:dfs` option. - # - # @return [Boolean] `true` if the graph is connected, `false` otherwise - def connected?(options = {}) - options = options.reverse_merge! :algo => :bfs - if options[:algo] == (:bfs || :dfs) - num_nodes = 0 - send(options[:algo]) { |n| num_nodes += 1 } - return num_nodes == @vertex_dict.size - else - !@vertex_dict.collect { |v| degree(v) > 0 }.any? { |check| check == false } - end - end - # TODO: do it! - # TODO: for directed graphs, add weakly_connected? and strongly_connected? (aliased as strong?) - # TODO: in the context of vertices/Arc, add connected_vertices? and disconnected_vertices? - # TODO: maybe implement some routine which would compute cuts and connectivity? tricky though, - # but would be useful (k_connected?(k)) - - # Returns true if the graph has no vertex. - # - # @return [Boolean] - def empty? - vertices.size.zero? - end - - # Returns true if the given object is a vertex or an {Arc arc} of the graph. - # - # @param [vertex, Arc] x - def include?(x) - x.is_a?(Plexus::Arc) ? edge?(x) : vertex?(x) - end - alias has? include? - - # Returns the neighborhood of the given vertex or {Arc arc}. - # - # This is equivalent to {GraphBuilder#adjacent adjacent}, but the type is based on the - # type of the specified object. - # - # @param [vertex, Arc] x - # @param [Symbol] direction (:all) can be either `:all`, `:in` or `:out` - def neighborhood(x, direction = :all) - adjacent(x, :direction => direction, :type => ((x.is_a? Plexus::Arc) ? :edges : :vertices )) - end - - # Union of all neighborhoods of vertices (or edges) in the Enumerable x minus the contents of x. - # - # Definition taken from: Jorgen Bang-Jensen, Gregory Gutin, *Digraphs: Theory, Algorithms and Applications*, pg. 4 - # - # @param [vertex] x - # @param [Symbol] direction can be either `:all`, `:in` or `:out` - def set_neighborhood(x, direction = :all) - x.inject(Set.new) { |a,v| a.merge(neighborhood(v, direction))}.reject { |v2| x.include?(v2) } - end - - # Union of all {GraphBuilder#set_neighborhood set_neighborhoods} reachable - # among the specified edges. - # - # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, *Digraphs: - # Theory, Algorithms and Applications*, pg. 46 - # - # @param [vertex] w - # @param [Edges] p - # @param [Symbol] direction can be `:all`, `:in`, or `:out` - def closed_pth_neighborhood(w, p, direction = :all) - if p <= 0 - w - elsif p == 1 - (w + set_neighborhood(w, direction)).uniq - else - n = set_neighborhood(w, direction) - (w + n + closed_pth_neighborhood(n, p-1, direction)).uniq - end - end - - # Returns the neighboorhoods reachable in a certain amount of steps from - # every vertex (or edge) in the specified Enumerable. - # - # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: - # Theory, Algorithms and Applications_, pg. 46 - # - # @param [Enumerable] x - # @param [Integer] p number of steps to perform - # @param [Symbol] direction can be `:all`, `:in`, or `:out` - def open_pth_neighborhood(x, p, direction = :all) - if p <= 0 - x - elsif p == 1 - set_neighborhood(x,direction) - else - set_neighborhood(open_pth_neighborhood(x, p-1, direction), direction) - - closed_pth_neighborhood(x, p-1, direction) - end - end - - # Returns the number of out-edges (for directed graphs) or the number of - # incident edges (for undirected graphs) of the specified vertex. - # - # @param [vertex] v - # @return [Integer] number of matching edges - def out_degree(v) - adjacent(v, :direction => :out).size - end - - # Returns the number of in-edges (for directed graphs) or the number of - # incident edges (for undirected graphs) of the specified vertex - # - # @param [vertex] v - # @return [Integer] number of matching edges - def in_degree(v) - adjacent(v, :direction => :in).size - end - - # Returns the sum of the number in and out edges for the specified vertex. - # - # @param [vertex] v - # @return [Integer] degree - def degree(v) - in_degree(v) + out_degree(v) - end - - # Minimum in-degree of the graph. - # - # @return [Integer, nil] returns `nil` if the graph is empty - def min_in_degree - return nil if to_a.empty? - to_a.map { |v| in_degree(v) }.min - end - - # Minimum out-degree of the graph. - # - # @return [Integer, nil] returns `nil` if the graph is empty - def min_out_degree - return nil if to_a.empty? - to_a.map {|v| out_degree(v)}.min - end - - # Minimum degree of all vertexes of the graph. - # - # @return [Integer] `min` between {GraphBuilder#min_in_degree min_in_degree} - # and {GraphBuilder#min_out_degree max_out_degree} - def min_degree - [min_in_degree, min_out_degree].min - end - - # Maximum in-degree of the graph. - # - # @return [Integer, nil] returns `nil` if the graph is empty - def max_in_degree - return nil if to_a.empty? - vertices.map { |v| in_degree(v)}.max - end - - # Maximum out-degree of the graph. - # - # @return [Integer, nil] returns nil if the graph is empty - def max_out_degree - return nil if to_a.empty? - vertices.map { |v| out_degree(v)}.max - end - - # Maximum degree of all vertexes of the graph. - # - # @return [Integer] `max` between {GraphBuilder#max_in_degree max_in_degree} - # and {GraphBuilder#max_out_degree max_out_degree} - def max_degree - [max_in_degree, max_out_degree].max - end - - # Is the graph regular, that is are its min degree and max degree equal? - # - # @return [Boolean] - def regular? - min_degree == max_degree - end - - # Number of vertices. - # - # @return [Integer] - def size - vertices.size - end - alias num_vertices size - alias number_of_vertices size - - # Number of vertices. - # - # @return [Integer] - def num_vertices - vertices.size - end - alias number_of_vertices num_vertices - - # Number of edges. - # - # @return [Integer] - def num_edges - edges.size - end - alias number_of_edges num_edges - - # Utility method to show a string representation of the edges of the graph. - #def to_s - #edges.to_s - #end - - # Equality is defined to be same set of edges and directed? - def eql?(g) - return false unless g.is_a? Plexus::Graph - - (directed? == g.directed?) and - (vertices.sort == g.vertices.sort) and - (edges.sort == g.edges.sort) - end - alias == eql? - - # Merges another graph into the receiver. - # - # @param [Graph] other the graph to merge in - # @return [Graph] `self` - def merge(other) - other.vertices.each { |v| add_vertex!(v) } - other.edges.each { |e| add_edge!(e) } - other.edges.each { |e| add_edge!(e.reverse) } if directed? and !other.directed? - self - end - - # A synonym for {GraphBuilder#merge merge}, but doesn't modify the current graph. - # - # @param [Graph, Arc] other - # @return [Graph] a new graph - def +(other) - result = self.class.new(self) - case other - when Plexus::Graph - result.merge(other) - when Plexus::Arc - result.add_edge!(other) - else - result.add_vertex!(other) - end - end - - # Removes all vertices in the specified graph. - # - # @param [Graph, Arc] other - # @return [Graph] - def -(other) - case other - when Plexus::Graph - induced_subgraph(vertices - other.vertices) - when Plexus::Arc - self.class.new(self).remove_edge!(other) - else - self.class.new(self).remove_vertex!(other) - end - end - - # A synonym for {AdjacencyGraphBuilder#add_edge! add_edge!}. - def <<(edge) - add_edge!(edge) - end - - # Computes the complement of the current graph. - # - # @return [Graph] - def complement - vertices.inject(self.class.new) do |a,v| - a.add_vertex!(v) - vertices.each { |v2| a.add_edge!(v, v2) unless edge?(v, v2) }; a - end - end - - # Given an array of vertices, computes the induced subgraph. - # - # @param [Array(vertex)] v - # @return [Graph] - def induced_subgraph(v) - edges.inject(self.class.new) do |a,e| - (v.include?(e.source) and v.include?(e.target)) ? (a << e) : a - end - end - - def inspect - ## FIXME: broken, it's not updated. The issue's not with inspect, but it's worth mentionning here. - ## Example: - ## dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] - ## dg.add_vertices! 1, 5, "yosh" - ## # => Plexus::Digraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[6,4,nil]] - ## dg.vertex?("yosh") - ## # => true - ## dg - ## # =>Plexus::Digraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[6,4,nil]] - ## the new vertex doesn't show up. - ## Actually this version of inspect is far too verbose IMO :) - l = vertices.select { |v| self[v]}.map { |u| "vertex_label_set(#{u.inspect}, #{self[u].inspect})"}.join('.') - self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l && l != '' ? '.'+l : '') - end - - private - - # ? - def edge_convert(*args) - args[0].is_a?(Plexus::Arc) ? args[0] : edge_class[*args] - end - end -end diff --git a/lib/graphy/graph_api.rb b/lib/graphy/graph_api.rb deleted file mode 100644 index bebc192..0000000 --- a/lib/graphy/graph_api.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Plexus - # This module defines the minimum set of functions required to make a graph that can - # use the algorithms defined by this library. - # - # Each implementation module must implement the following routines: - # - # * directed? # Is the graph directed? - # * add_vertex!(v,l=nil) # Add a vertex to the graph and return the graph. `l` is an optional label. - # * add_edge!(u,v=nil,l=nil) # Add an edge to the graph and return the graph. `u` can be an {Arc} or {Edge}, or `u,v` a {Edge} pair. The last parameter `l` is an optional label. - # * remove_vertex!(v) # Remove a vertex to the graph and return the graph. - # * remove_edge!(u,v=nil) # Remove an edge from the graph and return the graph. - # * vertices # Returns an array of of all vertices. - # * edges # Returns an array of all edges. - # * edge_class # Returns the class used to store edges. - module GraphAPI - # @raise if the API is not completely implemented - def self.included(klass) - @api_methods ||= [:directed?, :add_vertex!, :add_edge!, :remove_vertex!, :remove_edge!, :vertices, :edges, :edge_class] - ruby_18 { @api_methods.each { |m| m.to_s } } - - @api_methods.each do |meth| - raise "Must implement #{meth}" unless klass.instance_methods.include?(meth) - end - - klass.class_eval do - # Is this right? - alias remove_arc! remove_edge! - alias add_arc! add_edge! - alias arcs edges - alias arc_class edge_class - end - end - - end # GraphAPI -end # Plexus diff --git a/lib/graphy/labels.rb b/lib/graphy/labels.rb deleted file mode 100644 index 6d47521..0000000 --- a/lib/graphy/labels.rb +++ /dev/null @@ -1,113 +0,0 @@ -module Plexus - # This module add support for labels. - # - # The graph labeling process consist in assigning labels, traditionally represented - # by integers, to the edges or vertices, or both, of a graph. Plexus recommands you - # abide by this rule and do use integers as labels. - # - # Some algorithms can make use of labeling (sea {Plexus::Search} for instance). - module Labels - - # Return a label for an edge or vertex. - def [](u) - (u.is_a? Plexus::Arc) ? edge_label(u) : vertex_label(u) - end - - # Set a label for an edge or vertex. - def []=(u, value) - (u.is_a? Plexus::Arc) ? edge_label_set(u, value) : vertex_label_set(u, value) - end - - # Delete a label entirely. - def delete_label(u) - (u.is_a? Plexus::Arc) ? edge_label_delete(u) : vertex_label_delete(u) - end - - # Get the label for an edge. - def vertex_label(v) - vertex_label_dict[v] - end - - # Set the label for an edge. - def vertex_label_set(v, l) - vertex_label_dict[v] = l - self - end - - # Get the label for an edge. - def edge_label(u, v = nil, n = nil) - u = edge_convert(u,v,n) - edge_label_dict[u] - end - - # Set the label for an edge. - def edge_label_set(u, v = nil, l = nil, n = nil) - u.is_a?(Plexus::Arc) ? l = v : u = edge_convert(u, v, n) - edge_label_dict[u] = l - self - end - - # Delete all graph labels. - def clear_all_labels - @vertex_labels = {} - @edge_labels = {} - end - - # Delete an edge label. - def edge_label_delete(u, v = nil, n = nil) - u = edge_convert(u, v, n) - edge_label_dict.delete(u) - end - - # Delete a vertex label. - def vertex_label_delete(v) - vertex_label_dict.delete(v) - end - - protected - - def vertex_label_dict - @vertex_labels ||= {} - end - - def edge_label_dict - @edge_labels ||= {} - end - - # A generic cost function. - # - # It either calls the `weight` function with an edge constructed from the - # two specified nodes, or calls the `[]` operator of the label when given - # a single value. - # - # If no weight value is specified, the label itself is treated as the cost value. - # - # Note: This function will not work for Pseudo or Multi graphs at present. - # FIXME: Remove u,v interface to fix Pseudo Multi graph problems. - def cost(u, v = nil, weight = nil) - u.is_a?(Arc) ? weight = v : u = edge_class[u,v] - case weight - when Proc - weight.call(u) - when nil - self[u] - else - self[u][weight] - end - end - alias property cost # makes sense for property retrieval in general - - # A function to set properties specified by the user. - def property_set(u, name, value) - case name - when Proc - name.call(value) - when nil - self[u] = value - else - self[u][name] = value - end - end - - end # Labels -end # Plexus diff --git a/lib/graphy/maximum_flow.rb b/lib/graphy/maximum_flow.rb deleted file mode 100644 index 242fdbb..0000000 --- a/lib/graphy/maximum_flow.rb +++ /dev/null @@ -1,77 +0,0 @@ -module Plexus - class Network - include DigraphBuilder - - attr_accessor :lower, :upper, :cost, :flow - - def residual(residual_capacity, cost_property, zero = 0) - r = Digraph.new - edges.each do |e1| - [e1,e1.reverse].each do |e| - rij = property(e,self.upper) - property(e,self.flow) if edge? e - rij += property(e.reverse,self.flow) - property(e.reverse,self.lower) if edge? e.reverse - r.add_edge!(e) if rij > zero - r.property_set(e,residual_capacity, rij) - r.property_set(e,cost, cost(e,cost_property)) - end - end - r - end - - def maximum_flow() eliminate_lower_bounds.maximum_flow_prime.restore_lower_bounds(self); end - - private - - def eliminate_lower_bounds - no_lower_bounds = Digraph.new(self) - if self.upper.kind_of? Proc then - no_lower_bounds.upper = Proc.new {|e| self.upper.call(e) - property(e,self.lower) } - else - no_lower_bounds.edges.each {|e| no_lower_bounds[e][self.upper] -= property(e,self.lower)} - end - no_lower_bounds - end - - def restore_lower_bounds(src) - src.edges.each do |e| - (src.flow ? src[e][src.flow] : src[e]) = property(e, self.flow) + src.property(e, self.lower) - end - src - end - - def maximum_flow_prime - end - end # Network - - module GraphBuilder - module MaximumFlow - # Maximum flow, it returns an array with the maximum flow and a hash of flow per edge - # Currently a highly inefficient implementation, FIXME, This should use Goldberg and Tarjan's method. - def maximum_flow(s, t, capacity = nil, zero = 0) - flow = Hash.new(zero) - available = Hash.new(zero) - total = zero - edges.each {|e| available[e] = cost(e,capacity)} - adj_positive = Proc.new do |u| - adjacent(u).select {|r| available[edge_class[u,r]] > zero} - end - while (tree = bfs_tree_from_vertex(start))[t] - route = [t] - while route[-1] != s - route << tree[route[route[-1]]] - raise ArgumentError, "No route from #{s} to #{t} possible" - end; route.reverse - amt = route.map {|e| available[e]}.min - route.each do |e| - flow[e] += amt - available[e] -= amt - end - total += amt - end - - [total, flow] - end - - end # MaximumFlow - end # GraphBuilder -end # Plexus diff --git a/lib/graphy/ruby_compatibility.rb b/lib/graphy/ruby_compatibility.rb deleted file mode 100644 index 853f06d..0000000 --- a/lib/graphy/ruby_compatibility.rb +++ /dev/null @@ -1,17 +0,0 @@ -if RUBY_VERSION < "1.9" - def ruby_18 - yield - end - - def ruby_19 - false - end -else - def ruby_18 - false - end - - def ruby_19 - yield - end -end diff --git a/lib/graphy/search.rb b/lib/graphy/search.rb deleted file mode 100644 index bfcde4f..0000000 --- a/lib/graphy/search.rb +++ /dev/null @@ -1,510 +0,0 @@ -module Plexus - # **Search/traversal algorithms.** - # - # This module defines a collection of search/traversal algorithms, in a unified API. - # Read through the doc to get familiar with the calling pattern. - # - # Options are mostly callbacks passed in as a hash. The following are valid, - # anything else is ignored: - # - # * `:enter_vertex` => `Proc` Called upon entry of a vertex. - # * `:exit_vertex` => `Proc` Called upon exit of a vertex. - # * `:root_vertex` => `Proc` Called when a vertex is the root of a tree. - # * `:start_vertex` => `Proc` Called for the first vertex of the search. - # * `:examine_edge` => `Proc` Called when an edge is examined. - # * `:tree_edge` => `Proc` Called when the edge is a member of the tree. - # * `:back_edge` => `Proc` Called when the edge is a back edge. - # * `:forward_edge` => `Proc` Called when the edge is a forward edge. - # * `:adjacent` => `Proc` which, given a vertex, returns adjacent nodes, defaults to adjacent call of graph useful for changing the definition of adjacent in some algorithms. - # * `:start` => vertex Specifies the vertex to start search from. - # - # If a `&block` instead of an option hash is specified, it defines `:enter_vertex`. - # - # Each search algorithm returns the list of vertexes as reached by `enter_vertex`. - # This allows for calls like, `g.bfs.each { |v| ... }` - # - # Can also be called like `bfs_examine_edge { |e| ... }` or - # `dfs_back_edge { |e| ... }` for any of the callbacks. - # - # A full example usage is as follows: - # - # ev = Proc.new { |x| puts "Enter vertex #{x}" } - # xv = Proc.new { |x| puts "Exit vertex #{x}" } - # sv = Proc.new { |x| puts "Start vertex #{x}" } - # ee = Proc.new { |x| puts "Examine Arc #{x}" } - # te = Proc.new { |x| puts "Tree Arc #{x}" } - # be = Proc.new { |x| puts "Back Arc #{x}" } - # fe = Proc.new { |x| puts "Forward Arc #{x}" } - # Digraph[1,2, 2,3, 3,4].dfs({ - # :enter_vertex => ev, - # :exit_vertex => xv, - # :start_vertex => sv, - # :examine_edge => ee, - # :tree_edge => te, - # :back_edge => be, - # :forward_edge => fe }) - # - # Which outputs: - # - # Start vertex 1 - # Enter vertex 1 - # Examine Arc (1=2) - # Tree Arc (1=2) - # Enter vertex 2 - # Examine Arc (2=3) - # Tree Arc (2=3) - # Enter vertex 3 - # Examine Arc (3=4) - # Tree Arc (3=4) - # Enter vertex 4 - # Examine Arc (1=4) - # Back Arc (1=4) - # Exit vertex 4 - # Exit vertex 3 - # Exit vertex 2 - # Exit vertex 1 - # => [1, 2, 3, 4] - module Search - - # Performs a breadth-first search. - # - # @param [Hash] options - def bfs(options = {}, &block) - plexus_search_helper(:shift, options, &block) - end - alias :bread_first_search :bfs - - # Performs a depth-first search. - # - # @param [Hash] options - def dfs(options = {}, &block) - plexus_search_helper(:pop, options, &block) - end - alias :depth_first_search :dfs - - # Routine which computes a spanning forest for the given search method. - # Returns two values: a hash of predecessors and an array of root nodes. - # - # @param [vertex] start - # @param [Symbol] routine the search method (`:dfs`, `:bfs`) - # @return [Array] predecessors and root nodes - def spanning_forest(start, routine) - predecessor = {} - roots = [] - te = Proc.new { |e| predecessor[e.target] = e.source } - rv = Proc.new { |v| roots << v } - send routine, :start => start, :tree_edge => te, :root_vertex => rv - [predecessor, roots] - end - - # Returns the dfs spanning forest for the given start node, see {Search#spanning_forest spanning_forest}. - # - # @param [vertex] start - # @return [Array] predecessors and root nodes - def dfs_spanning_forest(start) - spanning_forest(start, :dfs) - end - - # Returns the bfs spanning forest for the given start node, see {Search#spanning_forest spanning_forest}. - # - # @param [vertex] start - # @return [Array] predecessors and root nodes - def bfs_spanning_forest(start) - spanning_forest(start, :bfs) - end - - # Returns a hash of predecessors in a tree rooted at the start node. If this is a connected graph, - # then it will be a spanning tree containing all vertices. An easier way to tell if it's a - # spanning tree is to use a {Search#spanning_forest spanning_forest} call and check if there is a - # single root node. - # - # @param [vertex] start - # @param [Symbol] routine the search method (`:dfs`, `:bfs`) - # @return [Hash] predecessors vertices - def tree_from_vertex(start, routine) - predecessor = {} - correct_tree = false - te = Proc.new { |e| predecessor[e.target] = e.source if correct_tree } - rv = Proc.new { |v| correct_tree = (v == start) } - send routine, :start => start, :tree_edge => te, :root_vertex => rv - predecessor - end - - # Returns a hash of predecessors for the depth-first search tree rooted at the given node. - # - # @param [vertex] start - # @return [Hash] predecessors vertices - def dfs_tree_from_vertex(start) - tree_from_vertex(start, :dfs) - end - - # Returns a hash of predecessors for the breadth-first search tree rooted at the given node. - # - # @param [Proc] start - # @return [Hash] predecessors vertices - def bfs_tree_from_vertex(start) - tree_from_vertex(start, :bfs) - end - - # An inner class used for greater efficiency in {Search#lexicograph_bfs}. - # - # Original design taken from Golumbic's, *Algorithmic Graph Theory and Perfect Graphs* pg. 87-89. - class LexicographicQueue - # Called with the initial values. - # - # @param [Array] initial vertices values - def initialize(values) - @node = Struct.new(:back, :forward, :data) - @node.class_eval do - def hash - @hash - end - @@cnt = 0 - end - @set = {} - @tail = @node.new(nil, nil, Array.new(values)) - @tail.instance_eval { @hash = (@@cnt += 1) } - values.each { |a| @set[a] = @tail } - end - - # Pops an entry with the maximum lexical value from the queue. - # - # @return [vertex] - def pop - return nil unless @tail - value = @tail[:data].pop - @tail = @tail[:forward] while @tail and @tail[:data].size == 0 - @set.delete(value) - value - end - - # Increase the lexical value of the given values. - # - # @param [Array] vertices values - def add_lexeme(values) - fix = {} - - values.select { |v| @set[v] }.each do |w| - sw = @set[w] - if fix[sw] - s_prime = sw[:back] - else - s_prime = @node.new(sw[:back], sw, []) - s_prime.instance_eval { @hash = (@@cnt += 1) } - @tail = s_prime if @tail == sw - sw[:back][:forward] = s_prime if sw[:back] - sw[:back] = s_prime - fix[sw] = true - end - - s_prime[:data] << w - sw[:data].delete(w) - @set[w] = s_prime - end - - fix.keys.select { |n| n[:data].size == 0 }.each do |e| - e[:forward][:back] = e[:back] if e[:forward] - e[:back][:forward] = e[:forward] if e[:back] - end - end - - end - - # Lexicographic breadth-first search. - # - # The usual queue of vertices is replaced by a queue of *unordered subsets* - # of the vertices, which is sometimes refined but never reordered. - # - # Originally developed by Rose, Tarjan, and Leuker, *Algorithmic - # aspects of vertex elimination on graphs*, SIAM J. Comput. 5, 266-283 - # MR53 #12077 - # - # Implementation taken from Golumbic's, *Algorithmic Graph Theory and - # Perfect Graphs*, pg. 84-90. - # - # @return [vertex] - def lexicograph_bfs(&block) - lex_q = Plexus::Search::LexicographicQueue.new(vertices) - result = [] - num_vertices.times do - v = lex_q.pop - result.unshift(v) - lex_q.add_lexeme(adjacent(v)) - end - result.each { |r| block.call(r) } if block - result - end - - # A* Heuristic best first search. - # - # `start` is the starting vertex for the search. - # - # `func` is a `Proc` that when passed a vertex returns the heuristic - # weight of sending the path through that node. It must always - # be equal to or less than the true cost. - # - # `options` are mostly callbacks passed in as a hash, the default block is - # `:discover_vertex` and the weight is assumed to be the label for the {Arc}. - # The following options are valid, anything else is ignored: - # - # * `:weight` => can be a `Proc`, or anything else is accessed using the `[]` for the - # the label or it defaults to using - # the value stored in the label for the {Arc}. If it is a `Proc` it will - # pass the edge to the proc and use the resulting value. - # * `:discover_vertex` => `Proc` invoked when a vertex is first discovered - # and is added to the open list. - # * `:examine_vertex` => `Proc` invoked when a vertex is popped from the - # queue (i.e., it has the lowest cost on the open list). - # * `:examine_edge` => `Proc` invoked on each out-edge of a vertex - # immediately after it is examined. - # * `:edge_relaxed` => `Proc` invoked on edge `(u,v) if d[u] + w(u,v) < d[v]`. - # * `:edge_not_relaxed`=> `Proc` invoked if the edge is not relaxed (see above). - # * `:black_target` => `Proc` invoked when a vertex that is on the closed - # list is "rediscovered" via a more efficient path, and is re-added - # to the open list. - # * `:finish_vertex` => Proc invoked on a vertex when it is added to the - # closed list, which happens after all of its out edges have been - # examined. - # - # Can also be called like `astar_examine_edge {|e| ... }` or - # `astar_edge_relaxed {|e| ... }` for any of the callbacks. - # - # The criteria for expanding a vertex on the open list is that it has the - # lowest `f(v) = g(v) + h(v)` value of all vertices on open. - # - # The time complexity of A* depends on the heuristic. It is exponential - # in the worst case, but is polynomial when the heuristic function h - # meets the following condition: `|h(x) - h*(x)| < O(log h*(x))` where `h*` - # is the optimal heuristic, i.e. the exact cost to get from `x` to the `goal`. - # - # See also: [A* search algorithm](http://en.wikipedia.org/wiki/A*_search_algorithm) on Wikipedia. - # - # @param [vertex] start the starting vertex for the search - # @param [vertex] goal the vertex to reach - # @param [Proc] func heuristic weight computing process - # @param [Hash] options - # @return [Array(vertices), call, nil] an array of nodes in path, or calls block on all nodes, - # upon failure returns `nil` - def astar(start, goal, func, options, &block) - options.instance_eval "def handle_callback(sym,u) self[sym].call(u) if self[sym]; end" - - # Initialize. - d = { start => 0 } - color = { start => :gray } # Open is :gray, closed is :black. - parent = Hash.new { |k| parent[k] = k } - f = { start => func.call(start) } - queue = PriorityQueue.new.push(start, f[start]) - block.call(start) if block - - # Process queue. - until queue.empty? - u, dummy = queue.delete_min - options.handle_callback(:examine_vertex, u) - - # Unravel solution if the goal is reached. - if u == goal - solution = [goal] - while u != start - solution << parent[u] - u = parent[u] - end - return solution.reverse - end - - adjacent(u, :type => :edges).each do |e| - v = e.source == u ? e.target : e.source - options.handle_callback(:examine_edge, e) - w = cost(e, options[:weight]) - raise ArgumentError unless w - - if d[v].nil? or (w + d[u]) < d[v] - options.handle_callback(:edge_relaxed, e) - d[v] = w + d[u] - f[v] = d[v] + func.call(v) - parent[v] = u - - unless color[v] == :gray - options.handle_callback(:black_target, v) if color[v] == :black - color[v] = :gray - options.handle_callback(:discover_vertex, v) - queue.push v, f[v] - block.call(v) if block - end - else - options.handle_callback(:edge_not_relaxed, e) - end - end # adjacent(u) - - color[u] = :black - options.handle_callback(:finish_vertex, u) - end # queue.empty? - - nil # failure, on fall through - end # astar - - # `best_first` has all the same options as {Search#astar astar}, with `func` set to `h(v) = 0`. - # There is an additional option, `zero`, which should be defined as the zero element - # for the `+` operation performed on the objects used in the computation of cost. - # - # @param [vertex] start the starting vertex for the search - # @param [vertex] goal the vertex to reach - # @param [Proc] func heuristic weight computing process - # @param [Hash] options - # @param [Integer] zero (0) - # @return [Array(vertices), call, nil] an array of nodes in path, or calls block on all nodes, - # upon failure returns `nil` - def best_first(start, goal, options, zero = 0, &block) - func = Proc.new { |v| zero } - astar(start, goal, func, options, &block) - end - - # @private - alias_method :pre_search_method_missing, :method_missing - def method_missing(sym, *args, &block) - m1 = /^dfs_(\w+)$/.match(sym.to_s) - dfs((args[0] || {}).merge({ m1.captures[0].to_sym => block })) if m1 - m2 = /^bfs_(\w+)$/.match(sym.to_s) - bfs((args[0] || {}).merge({ m2.captures[0].to_sym => block })) if m2 - pre_search_method_missing(sym, *args, &block) unless m1 or m2 - end - - private - - # Performs the search using a specific algorithm and a set of options. - # - # @param [Symbol] op the algorithm to be used te perform the search - # @param [Hash] options - # @return [Object] result - def plexus_search_helper(op, options = {}, &block) - return nil if size == 0 - result = [] - - # Create the options hash handling callbacks. - options = {:enter_vertex => block, :start => to_a[0]}.merge(options) - options.instance_eval "def handle_vertex(sym,u) self[sym].call(u) if self[sym]; end" - options.instance_eval "def handle_edge(sym,e) self[sym].call(e) if self[sym]; end" - - # Create a waiting list, which is a queue or a stack, depending on the op specified. - # The first entry is the start vertex. - waiting = [options[:start]] - waiting.instance_eval "def next; #{op.to_s}; end" - - # Create a color map, all elements set to "unvisited" except for start vertex, - # which will be set to waiting. - color_map = vertices.inject({}) { |a,v| a[v] = :unvisited; a } - color_map.merge!(waiting[0] => :waiting) - options.handle_vertex(:start_vertex, waiting[0]) - options.handle_vertex(:root_vertex, waiting[0]) - - # Perform the actual search until nothing is "waiting". - until waiting.empty? - # Loop till the search iterator exhausts the waiting list. - visited_edges = {} # This prevents retraversing edges in undirected graphs. - until waiting.empty? - plexus_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop) - end - # Waiting for the list to be exhausted, check if a new root vertex is available. - u = color_map.detect { |key,value| value == :unvisited } - waiting.push(u[0]) if u - options.handle_vertex(:root_vertex, u[0]) if u - end - - result - end - - # Performs a search iteration (step). - # - # @private - def plexus_search_iteration(options, waiting, color_map, visited_edges, result, recursive = false) - # Fetch the next waiting vertex in the list. - #sleep - u = waiting.next - options.handle_vertex(:enter_vertex, u) - result << u - - # Examine all adjacent outgoing edges, but only those not previously traversed. - adj_proc = options[:adjacent] || self.method(:adjacent).to_proc - adj_proc.call(u, :type => :edges, :direction => :out).reject { |w| visited_edges[w] }.each do |e| - e = e.reverse unless directed? or e.source == u # Preserves directionality where required. - v = e.target - options.handle_edge(:examine_edge, e) - visited_edges[e] = true - - case color_map[v] - # If it's unvisited, it goes into the waiting list. - when :unvisited - options.handle_edge(:tree_edge, e) - color_map[v] = :waiting - waiting.push(v) - # If it's recursive (i.e. dfs), then call self. - plexus_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive - when :waiting - options.handle_edge(:back_edge, e) - else - options.handle_edge(:forward_edge, e) - end - end - - # Done with this vertex! - options.handle_vertex(:exit_vertex, u) - color_map[u] = :visited - end - - public - - # Topological Sort Iterator. - # - # The topological sort algorithm creates a linear ordering of the vertices - # such that if edge (u,v) appears in the graph, then u comes before v in - # the ordering. The graph must be a directed acyclic graph (DAG). - # - # The iterator can also be applied to undirected graph or to a DG graph - # which contains a cycle. In this case, the Iterator does not reach all - # vertices. The implementation of acyclic? and cyclic? uses this fact. - # - # Can be called with a block as a standard ruby iterator, or can - # be used directly as it will return the result as an Array. - # - # @param [vertex] start (nil) the start vertex (nil will fallback on the first - # vertex inserted within the graph) - # @return [Array] a linear representation of the sorted graph - def topsort(start = nil, &block) - result = [] - go = true - back = Proc.new { |e| go = false } - push = Proc.new { |v| result.unshift(v) if go } - start ||= vertices[0] - dfs({ :exit_vertex => push, :back_edge => back, :start => start }) - result.each { |v| block.call(v) } if block - result - end - - # Does a top sort, but trudges forward if a cycle occurs. Use with caution. - # - # @param [vertex] start (nil) the start vertex (nil will fallback on the first - # vertex inserted within the graph) - # @return [Array] a linear representation of the sorted graph - def sort(start = nil, &block) - result = [] - push = Proc.new { |v| result.unshift(v) } - start ||= vertices[0] - dfs({ :exit_vertex => push, :start => start }) - result.each { |v| block.call(v) } if block - result - end - - # Returns true if a graph contains no cycles, false otherwise. - # - # @return [Boolean] - def acyclic? - topsort.size == size - end - - # Returns false if a graph contains no cycles, true otherwise. - # - # @return [Boolean] - def cyclic? - not acyclic? - end - end -end diff --git a/lib/graphy/strong_components.rb b/lib/graphy/strong_components.rb deleted file mode 100644 index 24f1fa7..0000000 --- a/lib/graphy/strong_components.rb +++ /dev/null @@ -1,93 +0,0 @@ -module Plexus - module StrongComponents - # strong_components computes the strongly connected components - # of a graph using Tarjan's algorithm based on DFS. See: Robert E. Tarjan - # _Depth_First_Search_and_Linear_Graph_Algorithms_. SIAM Journal on - # Computing, 1(2):146-160, 1972 - # - # The output of the algorithm is an array of components where is - # component is an array of vertices - # - # A strongly connected component of a directed graph G=(V,E) is a maximal - # set of vertices U which is in V such that for every pair of - # vertices u and v in U, we have both a path from u to v - # and path from v to u. That is to say that u and v are reachable - # from each other. - # - def strong_components - dfs_num = 0 - stack = []; result = []; root = {}; comp = {}; number = {} - - # Enter vertex callback - enter = Proc.new do |v| - root[v] = v - comp[v] = :new - number[v] = (dfs_num += 1) - stack.push(v) - end - - # Exit vertex callback - exit = Proc.new do |v| - adjacent(v).each do |w| - if comp[w] == :new - root[v] = (number[root[v]] < number[root[w]] ? root[v] : root[w]) - end - end - if root[v] == v - component = [] - begin - w = stack.pop - comp[w] = :assigned - component << w - end until w == v - result << component - end - end - - # Execute depth first search - dfs({:enter_vertex => enter, :exit_vertex => exit}); result - - end # strong_components - - # Returns a condensation graph of the strongly connected components - # Each node is an array of nodes from the original graph - def condensation - sc = strong_components - cg = DirectedMultiGraph.new - map = sc.inject({}) do |a,c| - c.each {|v| a[v] = c }; a - end - sc.each do |c| - c.each do |v| - adjacent(v).each {|v1| cg.add_edge!(c, map[v1]) unless cg.edge?(c, map[v1]) } - end - end; - cg - end - - # Compute transitive closure of a graph. That is any node that is reachable - # along a path is added as a directed edge. - def transitive_closure! - cgtc = condensation.plexus_inner_transitive_closure! - cgtc.each do |cgv| - cgtc.adjacent(cgv).each do |adj| - cgv.each do |u| - adj.each {|v| add_edge!(u,v)} - end - end - end; self - end - - # This returns the transitive closure of a graph. The original graph - # is not changed. - def transitive_closure() self.class.new(self).transitive_closure!; end - - def plexus_inner_transitive_closure! # :nodoc: - sort.reverse.each do |u| - adjacent(u).each do |v| - adjacent(v).each {|w| add_edge!(u,w) unless edge?(u,w)} - end - end; self - end - end # StrongComponents -end # Plexus diff --git a/lib/graphy/support/support.rb b/lib/graphy/support/support.rb deleted file mode 100644 index 24005d6..0000000 --- a/lib/graphy/support/support.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Plexus - # Errors - # TODO FIXME: must review all raise lines and streamline things - - # Base error class for the library. - class PlexusError < StandardError; end - - class NoArcError < PlexusError; end -end diff --git a/lib/graphy/undirected_graph.rb b/lib/graphy/undirected_graph.rb deleted file mode 100644 index cfbbf02..0000000 --- a/lib/graphy/undirected_graph.rb +++ /dev/null @@ -1,56 +0,0 @@ -module Plexus - module UndirectedGraphBuilder - autoload :Algorithms, "plexus/undirected_graph/algorithms" - - include Plexus::GraphBuilder - extends_host - - module ClassMethods - def [](*a) - self.new.from_array(*a) - end - end - - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:algorithmic_category] = Plexus::UndirectedGraphBuilder::Algorithms - super *(params << args) - end - end - - # This is a Digraph that allows for parallel edges, but does not allow loops. - module UndirectedPseudoGraphBuilder - include UndirectedGraphBuilder - extends_host - - module ClassMethods - def [](*a) - self.new.from_array(*a) - end - end - - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:parallel_edges] = true - super *(params << args) - end - end - - # This is a Digraph that allows for parallel edges and loops. - module UndirectedMultiGraphBuilder - include UndirectedPseudoGraphBuilder - extends_host - - module ClassMethods - def [](*a) - self.new.from_array(*a) - end - end - - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:loops] = true - super *(params << args) - end - end -end diff --git a/lib/graphy/undirected_graph/algorithms.rb b/lib/graphy/undirected_graph/algorithms.rb deleted file mode 100644 index 27655f8..0000000 --- a/lib/graphy/undirected_graph/algorithms.rb +++ /dev/null @@ -1,90 +0,0 @@ -module Plexus - module UndirectedGraphBuilder - module Algorithms - - include Search - include Biconnected - include Comparability - - # UndirectedGraph is by definition undirected, always returns false - def directed?() false; end - - # Redefine degree (default was sum) - def degree(v) in_degree(v); end - - # A vertex of an undirected graph is balanced by definition - def balanced?(v) true; end - - # UndirectedGraph uses Edge for the edge class. - def edge_class() @parallel_edges ? Plexus::MultiEdge : Plexus::Edge; end - - def remove_edge!(u, v=nil) - unless u.kind_of? Plexus::Arc - raise ArgumentError if @parallel_edges - u = edge_class[u,v] - end - super(u.reverse) unless u.source == u.target - super(u) - end - - # A triangulated graph is an undirected perfect graph that every cycle of length greater than - # three possesses a chord. They have also been called chordal, rigid circuit, monotone transitive, - # and perfect elimination graphs. - # - # Implementation taken from Golumbic's, "Algorithmic Graph Theory and - # Perfect Graphs" pg. 90 - def triangulated? - a = Hash.new {|h,k| h[k]=Set.new}; sigma=lexicograph_bfs - inv_sigma = sigma.inject({}) {|acc,val| acc[val] = sigma.index(val); acc} - sigma[0..-2].each do |v| - x = adjacent(v).select {|w| inv_sigma[v] < inv_sigma[w] } - unless x.empty? - u = sigma[x.map {|y| inv_sigma[y]}.min] - a[u].merge(x - [u]) - end - return false unless a[v].all? {|z| adjacent?(v,z)} - end - true - end - - def chromatic_number - return triangulated_chromatic_number if triangulated? - raise NotImplementedError - end - - # An interval graph can have its vertices into one-to-one - # correspondence with a set of intervals F of a linearly ordered - # set (like the real line) such that two vertices are connected - # by an edge of G if and only if their corresponding intervals - # have nonempty intersection. - def interval?() triangulated? and complement.comparability?; end - - # A permutation diagram consists of n points on each of two parallel - # lines and n straight line segments matchin the points. The intersection - # graph of the line segments is called a permutation graph. - def permutation?() comparability? and complement.comparability?; end - - # An undirected graph is defined to be split if there is a partition - # V = S + K of its vertex set into a stable set S and a complete set K. - def split?() triangulated? and complement.triangulated?; end - - private - # Implementation taken from Golumbic's, "Algorithmic Graph Theory and - # Perfect Graphs" pg. 99 - def triangulated_chromatic_number - chi = 1; s= Hash.new {|h,k| h[k]=0} - sigma=lexicograph_bfs - inv_sigma = sigma.inject({}) {|acc,val| acc[val] = sigma.index(val); acc} - sigma.each do |v| - x = adjacent(v).select {|w| inv_sigma[v] < inv_sigma[w] } - unless x.empty? - u = sigma[x.map {|y| inv_sigma[y]}.min] - s[u] = [s[u], x.size-1].max - chi = [chi, x.size+1].max if s[v] < x.size - end - end; chi - end - - end # UndirectedGraphAlgorithms - end # UndirectedGraphBuilder -end # Plexus diff --git a/lib/graphy/version.rb b/lib/graphy/version.rb deleted file mode 100644 index f1de255..0000000 --- a/lib/graphy/version.rb +++ /dev/null @@ -1,6 +0,0 @@ -module Plexus - MAJOR = 0 - MINOR = 5 - PATCH = 3 - VERSION = [MAJOR, MINOR, PATCH].join('.') -end From e5fd8c3c716d343fe9461ee29abab09f27f46d04 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Wed, 6 Jul 2011 19:49:42 +0200 Subject: [PATCH 32/44] README update --- README.md | 110 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 66 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 2a6778e..34853ec 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Plexus (aka. plexus). A framework for graph theory, graph data structures and associated algorithms. +# Plexus (was Graphy). A framework for graph theory, graph data structures and associated algorithms. Graph algorithms currently provided are: @@ -66,85 +66,107 @@ insertion and removal at the cost of extra space overhead, etc. Using IRB, first require the library: - require 'rubygems' # only if you are using ruby 1.8.x - require 'plexus' +``` bash +require 'rubygems' # only if you are using ruby 1.8.x +require 'plexus' +``` If you'd like to include all the classes in the current scope (so you don't have to prefix with `Plexus::`), just: - include Plexus +``` bash +include Plexus +``` Let's play with the library a bit in IRB: - >> dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] - => Plexus::Digraph[[2, 3], [1, 6], [2, 4], [4, 5], [1, 2], [6, 4]] +``` bash +>> dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] +=> Plexus::Digraph[[2, 3], [1, 6], [2, 4], [4, 5], [1, 2], [6, 4]] +``` A few properties of the graph we just created: - >> dg.directed? - => true - >> dg.vertex?(4) - => true - >> dg.edge?(2,4) - => true - >> dg.edge?(4,2) - => false - >> dg.vertices - => [1, 2, 3, 4, 5, 6] +``` bash +>> dg.directed? +=> true +>> dg.vertex?(4) +=> true +>> dg.edge?(2,4) +=> true +>> dg.edge?(4,2) +=> false +>> dg.vertices +=> [1, 2, 3, 4, 5, 6] +``` Every object could be a vertex, even the class object `Object`: - >> dg.vertex?(Object) - => false +``` bash +>> dg.vertex?(Object) +=> false - >> UndirectedGraph.new(dg).edges.sort.to_s - => "[Plexus::Edge[1,2,nil], Plexus::Edge[2,3,nil], Plexus::Edge[2,4,nil], - Plexus::Edge[4,5,nil], Plexus::Edge[1,6,nil], Plexus::Edge[6,4,nil]]" +>> UndirectedGraph.new(dg).edges.sort.to_s +=> "[Plexus::Edge[1,2,nil], Plexus::Edge[2,3,nil], Plexus::Edge[2,4,nil], + Plexus::Edge[4,5,nil], Plexus::Edge[1,6,nil], Plexus::Edge[6,4,nil]]" +``` Add inverse edge `(4-2)` to directed graph: - >> dg.add_edge!(4,2) - => Plexus::DirectedGraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], - Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[4,2,nil], - Plexus::Arc[6,4,nil]] +``` bash +>> dg.add_edge!(4,2) +=> Plexus::DirectedGraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], + Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[4,2,nil], + Plexus::Arc[6,4,nil]] +``` `(4-2) == (2-4)` in the undirected graph (4-2 doesn't show up): +``` bash >> UndirectedGraph.new(dg).edges.sort.to_s => "[Plexus::Edge[1,2,nil], Plexus::Edge[2,3,nil], Plexus::Edge[2,4,nil], Plexus::Edge[4,5,nil], Plexus::Edge[1,6,nil], Plexus::Edge[6,4,nil]]" +``` `(4-2) != (2-4)` in directed graphs (both show up): - >> dg.edges.sort.to_s - => "[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], - Plexus::Arc[2,4,nil], Plexus::Arc[4,2,nil], Plexus::Arc[4,5,nil], - Plexus::Arc[6,4,nil]]" +``` bash +>> dg.edges.sort.to_s +=> "[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], + Plexus::Arc[2,4,nil], Plexus::Arc[4,2,nil], Plexus::Arc[4,5,nil], + Plexus::Arc[6,4,nil]]" - >> dg.remove_edge! 4,2 - => Plexus::DirectedGraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], - Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[6,4,nil]] +>> dg.remove_edge! 4,2 +=> Plexus::DirectedGraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], + Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[6,4,nil]] +``` Topological sorting is realized with an iterator: - >> dg.topsort - => [1, 6, 2, 4, 5, 3] - >> y = 0; dg.topsort { |v| y += v }; y - => 21 +``` bash +>> dg.topsort +=> [1, 6, 2, 4, 5, 3] +>> y = 0; dg.topsort { |v| y += v }; y +=> 21 +``` You can use DOT to visualize the graph: - >> require 'plexus/dot' - >> dg.write_to_graphic_file('jpg','visualize') +``` bash +>> require 'plexus/dot' +>> dg.write_to_graphic_file('jpg','visualize') +``` Here's an example showing the module inheritance hierarchy: - >> module_graph = Digraph.new - >> ObjectSpace.each_object(Module) do |m| - >> m.ancestors.each {|a| module_graph.add_edge!(m,a) if m != a} - >> end - >> gv = module_graph.vertices.select {|v| v.to_s.match(/Plexus/) } - >> module_graph.induced_subgraph(gv).write_to_graphic_file('jpg','module_graph') +``` bash +>> module_graph = Digraph.new +>> ObjectSpace.each_object(Module) do |m| +>> m.ancestors.each {|a| module_graph.add_edge!(m,a) if m != a} +>> end +>> gv = module_graph.vertices.select {|v| v.to_s.match(/Plexus/) } +>> module_graph.induced_subgraph(gv).write_to_graphic_file('jpg','module_graph') +``` Look for more in the examples directory. From 5feca9025ba4f97bae742d1312c7206447616666 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Fri, 8 Jul 2011 17:16:01 +0200 Subject: [PATCH 33/44] white spaces --- lib/plexus/adjacency_graph.rb | 33 +++++++++++++++++---------------- lib/plexus/arc.rb | 3 ++- lib/plexus/arc_number.rb | 16 +++++++--------- lib/plexus/edge.rb | 4 +++- lib/plexus/graph.rb | 2 ++ lib/plexus/labels.rb | 11 +++++------ 6 files changed, 36 insertions(+), 33 deletions(-) diff --git a/lib/plexus/adjacency_graph.rb b/lib/plexus/adjacency_graph.rb index e7edc49..90d7764 100644 --- a/lib/plexus/adjacency_graph.rb +++ b/lib/plexus/adjacency_graph.rb @@ -8,7 +8,7 @@ module Plexus module AdjacencyGraphBuilder # Defines a useful `push` -> `add` alias for arrays. - class ArrayWithAdd < Array + class ArrayWithAdd < Array alias add push end @@ -23,8 +23,9 @@ class ArrayWithAdd < Array # * `:loops denotes` that loops are allowed # # @param *params [Hash] the initialization parameters + # def implementation_initialize(*params) - @vertex_dict = Hash.new + @vertex_dict = Hash.new clear_all_labels # FIXME: could definitely make use of the activesupport helper @@ -38,19 +39,19 @@ def implementation_initialize(*params) @edgelist_class = @parallel_edges ? ArrayWithAdd : Set if @parallel_edges @edge_number = Hash.new - @next_edge_number = 0 + @next_edge_number = 0 end # Copy any given graph into this graph. params.select { |p| p.is_a? Plexus::GraphBuilder }.each do |g| - g.edges.each do |e| + g.edges.each do |e| add_edge!(e) edge_label_set(e, edge_label(e)) if edge_label(e) end g.vertices.each do |v| add_vertex!(v) vertex_label_set(v, vertex_label(v)) if vertex_label(v) - end + end end # Add all array edges specified. @@ -90,7 +91,7 @@ def add_vertex!(vertex, label = nil) end # Adds an edge to the graph. - # + # # Can be called in two basic ways, label is optional: # @overload add_edge!(arc) # Using an explicit {Arc} @@ -104,6 +105,7 @@ def add_vertex!(vertex, label = nil) # @param [Integer] n (nil) {Arc arc} number of `(u, v)` (if `nil` and if `u` # has an {ArcNumber}, then it will be used) # @return [AdjacencyGraph] `self` + # def add_edge!(u, v = nil, l = nil, n = nil) n = u.number if u.class.include? ArcNumber and n.nil? u, v, l = u.source, u.target, u.label if u.is_a? Plexus::Arc @@ -130,14 +132,14 @@ def add_edge!(u, v = nil, l = nil, n = nil) # @param [vertex] v # @return [AdjacencyGraph] `self` def remove_vertex!(v) - # FIXME This is broken for multi graphs + # FIXME This is broken for multi graphs @vertex_dict.delete(v) @vertex_dict.each_value { |adjList| adjList.delete(v) } - @vertex_dict.keys.each do |u| - delete_label(edge_class[u,v]) + @vertex_dict.keys.each do |u| + delete_label(edge_class[u,v]) delete_label(edge_class[v,u]) end - delete_label(v) + delete_label(v) self end @@ -167,9 +169,9 @@ def remove_edge!(u, v = nil) index = @edge_number[u.source].index(u.number) raise NoArcError unless index @vertex_dict[u.source].delete_at(index) - @edge_number[u.source].delete_at(index) + @edge_number[u.source].delete_at(index) else - @vertex_dict[u.source].delete(u.target) + @vertex_dict[u.source].delete(u.target) end self end @@ -181,7 +183,7 @@ def vertices @vertex_dict.keys end - # Returns an array of edges, most likely of class {Arc} or {Edge} depending + # Returns an array of edges, most likely of class {Arc} or {Edge} depending # upon the type of graph. # # @return [Array] @@ -219,6 +221,5 @@ def adjacent(x, options = {}) graph_adjacent(x,options) end end - - end # Adjacency Graph -end # Plexus + end +end diff --git a/lib/plexus/arc.rb b/lib/plexus/arc.rb index d18d86f..4e9ea4d 100644 --- a/lib/plexus/arc.rb +++ b/lib/plexus/arc.rb @@ -27,7 +27,8 @@ def <=>(rhs) [source, target] <=> [rhs.source, rhs.target] end - # Arc.new[1,2].to_s => "(1-2 'label')" + # Arc[1,2].to_s => "(1-2)" + # Arc[1,2,'test'].to_s => "(1-2 test)" def to_s l = label ? " '#{label.to_s}'" : '' "(#{source}-#{target}#{l})" diff --git a/lib/plexus/arc_number.rb b/lib/plexus/arc_number.rb index 17fb329..bb9d46f 100644 --- a/lib/plexus/arc_number.rb +++ b/lib/plexus/arc_number.rb @@ -1,12 +1,11 @@ module Plexus # This module handles internal numbering of edges in order to differente between mutliple edges. module ArcNumber - # Used to differentiate between mutli-edges attr_accessor :number - + def initialize(p_source, p_target, p_number, p_label = nil) - self.number = p_number + self.number = p_number super(p_source, p_target, p_label) end @@ -14,7 +13,7 @@ def initialize(p_source, p_target, p_number, p_label = nil) def reverse self.class.new(target, source, number, label) end - + # Allow for hashing of self loops. def hash super ^ number.hash @@ -26,8 +25,8 @@ def to_s def <=>(rhs) (result = super(rhs)) == 0 ? number <=> rhs.number : result - end - + end + def inspect "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{number.inspect},#{label.inspect}]" end @@ -47,6 +46,5 @@ def cl.[](p_source, p_target, p_number = nil, p_label = nil) new(p_source, p_target, p_number, p_label) end end - - end # ArcNumber -end # Plexus + end +end diff --git a/lib/plexus/edge.rb b/lib/plexus/edge.rb index 600de87..77f9a36 100644 --- a/lib/plexus/edge.rb +++ b/lib/plexus/edge.rb @@ -20,7 +20,9 @@ def <=>(rhs) [[source,target].max, [source,target].min] <=> [[rhs.source,rhs.target].max, [rhs.source,rhs.target].min] end - # Edge[1,2].to_s == "(1=2 'label)" + # Edge[1,2].to_s => "(1=2)" + # Edge[2,1].to_s => "(1=2)" + # Edge[2,1,'test'].to_s => "(1=2 test)" def to_s l = label ? " '#{label.to_s}'" : '' s = source.to_s diff --git a/lib/plexus/graph.rb b/lib/plexus/graph.rb index 517f285..979e272 100644 --- a/lib/plexus/graph.rb +++ b/lib/plexus/graph.rb @@ -18,6 +18,7 @@ module GraphBuilder #end # after the class->module transition, has been moved at implementation level, # using a helper (extends_host) + # extends_host module ClassMethods def [](*a) @@ -30,6 +31,7 @@ def [](*a) # @param [Hash(Plexus::Graph, Array)] *params initialization parameters. # See {AdjacencyGraphBuilder#implementation_initialize} for more details. # @return [Graph] + # def initialize(*params) raise ArgumentError if params.any? do |p| # FIXME: checking wether it's a GraphBuilder (module) is not sufficient diff --git a/lib/plexus/labels.rb b/lib/plexus/labels.rb index 6d47521..dcb22db 100644 --- a/lib/plexus/labels.rb +++ b/lib/plexus/labels.rb @@ -41,7 +41,7 @@ def edge_label(u, v = nil, n = nil) end # Set the label for an edge. - def edge_label_set(u, v = nil, l = nil, n = nil) + def edge_label_set(u, v = nil, l = nil, n = nil) u.is_a?(Plexus::Arc) ? l = v : u = edge_convert(u, v, n) edge_label_dict[u] = l self @@ -82,10 +82,10 @@ def edge_label_dict # # If no weight value is specified, the label itself is treated as the cost value. # - # Note: This function will not work for Pseudo or Multi graphs at present. + # Note: This function will not work for Pseudo or Multi graphs at present. # FIXME: Remove u,v interface to fix Pseudo Multi graph problems. def cost(u, v = nil, weight = nil) - u.is_a?(Arc) ? weight = v : u = edge_class[u,v] + u.is_a?(Arc) ? weight = v : u = edge_class[u,v] case weight when Proc weight.call(u) @@ -108,6 +108,5 @@ def property_set(u, name, value) self[u][name] = value end end - - end # Labels -end # Plexus + end +end From 1d1b4585a8a9d16a270f9f26f239e99a417fa608 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Fri, 8 Jul 2011 17:17:06 +0200 Subject: [PATCH 34/44] v0.5.4 --- lib/plexus/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plexus/version.rb b/lib/plexus/version.rb index f1de255..3f0e842 100644 --- a/lib/plexus/version.rb +++ b/lib/plexus/version.rb @@ -1,6 +1,6 @@ module Plexus MAJOR = 0 MINOR = 5 - PATCH = 3 + PATCH = 4 VERSION = [MAJOR, MINOR, PATCH].join('.') end From 2806df540dcc14d684226f9ede2877404046281d Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Fri, 8 Jul 2011 17:55:12 +0200 Subject: [PATCH 35/44] fixed bad gemspec --- Gemfile.lock | 5 ++++- lib/plexus/version.rb | 2 +- plexus.gemspec | 6 +++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1812604..8c95ee1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,12 +1,15 @@ PATH remote: . specs: - plexus (0.5.3) + plexus (0.5.5) + activesupport facets + plexus GEM remote: http://rubygems.org/ specs: + activesupport (3.0.9) diff-lcs (1.1.2) facets (2.9.1) rspec (2.6.0) diff --git a/lib/plexus/version.rb b/lib/plexus/version.rb index 3f0e842..b203aed 100644 --- a/lib/plexus/version.rb +++ b/lib/plexus/version.rb @@ -1,6 +1,6 @@ module Plexus MAJOR = 0 MINOR = 5 - PATCH = 4 + PATCH = 5 VERSION = [MAJOR, MINOR, PATCH].join('.') end diff --git a/plexus.gemspec b/plexus.gemspec index e4bc8c2..5f6466f 100644 --- a/plexus.gemspec +++ b/plexus.gemspec @@ -16,8 +16,12 @@ Graph algorithms currently provided are: * Rural Chinese Postman * Biconnected } - s.email = %q{bruce@codefluency.com} + s.email = %q{jd@vauguet.fr} + s.files = Dir["lib/**/*"] + Dir["vendor/**/*"] + Dir["spec/**/*"] + ["Gemfile", "LICENSE", "Rakefile", "README.md"] + s.homepage = %q{http://github.com/chikamichi/plexus} + s.add_dependency "activesupport" s.add_dependency "facets" + s.add_dependency "plexus" s.add_development_dependency "rspec" s.add_development_dependency "yard" end From c54b16f61302de92349f0bf1b5ac0422d1ccee7b Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Fri, 8 Jul 2011 20:29:51 +0200 Subject: [PATCH 36/44] got rid of GraphAPI: quite useless, ineffective, causes issues with ruby 1.8 --- lib/plexus.rb | 2 -- lib/plexus/adjacency_graph.rb | 2 +- lib/plexus/ext.rb | 2 +- lib/plexus/graph.rb | 6 ++-- lib/plexus/graph_api.rb | 35 ----------------------- lib/plexus/undirected_graph/algorithms.rb | 4 +-- 6 files changed, 6 insertions(+), 45 deletions(-) delete mode 100644 lib/plexus/graph_api.rb diff --git a/lib/plexus.rb b/lib/plexus.rb index aa03264..66c1520 100644 --- a/lib/plexus.rb +++ b/lib/plexus.rb @@ -30,8 +30,6 @@ module Plexus # Plexus internals: graph builders and additionnal behaviors - autoload :GraphAPI, 'plexus/graph_api' - autoload :GraphBuilder, 'plexus/graph' autoload :AdjacencyGraphBuilder, 'plexus/adjacency_graph' diff --git a/lib/plexus/adjacency_graph.rb b/lib/plexus/adjacency_graph.rb index 90d7764..5b2ad91 100644 --- a/lib/plexus/adjacency_graph.rb +++ b/lib/plexus/adjacency_graph.rb @@ -4,7 +4,7 @@ module Plexus # {DigraphBuilder}, {UndirectedGraphBuilder}, {DirectedPseudoGraphBuilder}, # {UndirectedPseudoGraphBuilder}, {DirectedMultiGraphBuilder} and {UndirectedMultiGraphBuilder} # modules, each of them streamlining {AdjacencyGraphBuilder}'s behavior. Those - # implementations rely on the {GraphBuilder}, under the control of the {GraphAPI}. + # implementations rely on the {GraphBuilder}. module AdjacencyGraphBuilder # Defines a useful `push` -> `add` alias for arrays. diff --git a/lib/plexus/ext.rb b/lib/plexus/ext.rb index b29b150..4c42e43 100644 --- a/lib/plexus/ext.rb +++ b/lib/plexus/ext.rb @@ -28,7 +28,7 @@ def singleton_class # # class A; include Digraph; end # a.singleton_class.ancestors - # # => [Plexus::GraphAPI, Plexus::DirectedGraph::Algorithms, ... + # # => [Plexus::DirectedGraph::Algorithms, ... # Plexus::Labels, Enumerable, Object, Plexus, Kernel, BasicObject] # a.is_a? Plexus::Graph # # => true diff --git a/lib/plexus/graph.rb b/lib/plexus/graph.rb index 979e272..ba4edae 100644 --- a/lib/plexus/graph.rb +++ b/lib/plexus/graph.rb @@ -1,7 +1,6 @@ module Plexus - # Using the methods required by the {GraphAPI}, it implements all the - # *basic* functions of a {Graph} using *only* functions - # requested in {GraphAPI}. The process is under the control of the pattern + # Using only a basic methods set, it implements all the *basic* functions + # of a graph. The process is under the control of the pattern # {AdjacencyGraphBuilder}, unless a specific implementation is specified # during initialization. # @@ -49,7 +48,6 @@ class << self # These inclusions trigger some validations checks by the way. include(args[:implementation] ? args[:implementation] : Plexus::AdjacencyGraphBuilder) include(args[:algorithmic_category] ? args[:algorithmic_category] : Plexus::DigraphBuilder ) - include Plexus::GraphAPI end implementation_initialize(*params) diff --git a/lib/plexus/graph_api.rb b/lib/plexus/graph_api.rb deleted file mode 100644 index bebc192..0000000 --- a/lib/plexus/graph_api.rb +++ /dev/null @@ -1,35 +0,0 @@ -module Plexus - # This module defines the minimum set of functions required to make a graph that can - # use the algorithms defined by this library. - # - # Each implementation module must implement the following routines: - # - # * directed? # Is the graph directed? - # * add_vertex!(v,l=nil) # Add a vertex to the graph and return the graph. `l` is an optional label. - # * add_edge!(u,v=nil,l=nil) # Add an edge to the graph and return the graph. `u` can be an {Arc} or {Edge}, or `u,v` a {Edge} pair. The last parameter `l` is an optional label. - # * remove_vertex!(v) # Remove a vertex to the graph and return the graph. - # * remove_edge!(u,v=nil) # Remove an edge from the graph and return the graph. - # * vertices # Returns an array of of all vertices. - # * edges # Returns an array of all edges. - # * edge_class # Returns the class used to store edges. - module GraphAPI - # @raise if the API is not completely implemented - def self.included(klass) - @api_methods ||= [:directed?, :add_vertex!, :add_edge!, :remove_vertex!, :remove_edge!, :vertices, :edges, :edge_class] - ruby_18 { @api_methods.each { |m| m.to_s } } - - @api_methods.each do |meth| - raise "Must implement #{meth}" unless klass.instance_methods.include?(meth) - end - - klass.class_eval do - # Is this right? - alias remove_arc! remove_edge! - alias add_arc! add_edge! - alias arcs edges - alias arc_class edge_class - end - end - - end # GraphAPI -end # Plexus diff --git a/lib/plexus/undirected_graph/algorithms.rb b/lib/plexus/undirected_graph/algorithms.rb index 27655f8..d83de87 100644 --- a/lib/plexus/undirected_graph/algorithms.rb +++ b/lib/plexus/undirected_graph/algorithms.rb @@ -20,7 +20,7 @@ def edge_class() @parallel_edges ? Plexus::MultiEdge : Plexus::Edge; end def remove_edge!(u, v=nil) unless u.kind_of? Plexus::Arc - raise ArgumentError if @parallel_edges + raise ArgumentError if @parallel_edges u = edge_class[u,v] end super(u.reverse) unless u.source == u.target @@ -49,7 +49,7 @@ def triangulated? def chromatic_number return triangulated_chromatic_number if triangulated? - raise NotImplementedError + raise NotImplementedError end # An interval graph can have its vertices into one-to-one From a0f5b6303dc8e0c99eba44ce0b7cc866630d7353 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Fri, 8 Jul 2011 20:30:39 +0200 Subject: [PATCH 37/44] v0.5.6 --- lib/plexus/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plexus/version.rb b/lib/plexus/version.rb index b203aed..b3ceff4 100644 --- a/lib/plexus/version.rb +++ b/lib/plexus/version.rb @@ -1,6 +1,6 @@ module Plexus MAJOR = 0 MINOR = 5 - PATCH = 5 + PATCH = 6 VERSION = [MAJOR, MINOR, PATCH].join('.') end From dbd03c8685fd499994391b9b52ec7083641d2c4c Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 10 Jul 2011 02:53:25 +0200 Subject: [PATCH 38/44] fixed bug in #eql? for edges preventing inherited classes to match --- Gemfile.lock | 2 +- lib/plexus/adjacency_graph.rb | 2 +- lib/plexus/arc.rb | 3 ++- lib/plexus/edge.rb | 6 ++++-- lib/plexus/version.rb | 2 +- spec/spec_helper.rb | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8c95ee1..107a69c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - plexus (0.5.5) + plexus (0.5.6) activesupport facets plexus diff --git a/lib/plexus/adjacency_graph.rb b/lib/plexus/adjacency_graph.rb index 5b2ad91..7bef21b 100644 --- a/lib/plexus/adjacency_graph.rb +++ b/lib/plexus/adjacency_graph.rb @@ -110,7 +110,7 @@ def add_edge!(u, v = nil, l = nil, n = nil) n = u.number if u.class.include? ArcNumber and n.nil? u, v, l = u.source, u.target, u.label if u.is_a? Plexus::Arc - return self if not @allow_loops and u == v + return self if !@allow_loops && u == v n = (@next_edge_number += 1) unless n if @parallel_edges add_vertex!(u) diff --git a/lib/plexus/arc.rb b/lib/plexus/arc.rb index 4e9ea4d..9559dc3 100644 --- a/lib/plexus/arc.rb +++ b/lib/plexus/arc.rb @@ -13,7 +13,8 @@ def initialize(p_source, p_target, p_label = nil) # Ignore labels for equality. def eql?(other) - self.class == other.class and target == other.target and source == other.source + same_class = (self.class.ancestors.include? other.class) + same_class && target == other.target && source == other.source end alias == eql? diff --git a/lib/plexus/edge.rb b/lib/plexus/edge.rb index 77f9a36..924e115 100644 --- a/lib/plexus/edge.rb +++ b/lib/plexus/edge.rb @@ -3,9 +3,11 @@ module Plexus # undirected graphs. Edge[u,v] == Edge[v,u] class Edge < Arc - # Equality allows for the swapping of source and target + # Edge equality allows for the swapping of source and target. + # def eql?(other) - super or (self.class == other.class and target == other.source and source == other.target) + same_class = (self.class.ancestors.include? other.class) + super || (same_class && target == other.source && source == other.target) end alias == eql? diff --git a/lib/plexus/version.rb b/lib/plexus/version.rb index b3ceff4..57d1cbe 100644 --- a/lib/plexus/version.rb +++ b/lib/plexus/version.rb @@ -1,6 +1,6 @@ module Plexus MAJOR = 0 MINOR = 5 - PATCH = 6 + PATCH = 7 VERSION = [MAJOR, MINOR, PATCH].join('.') end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f12a8da..5e62dae 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -48,11 +48,11 @@ def related?(father,u,v) end RSpec.configure do |config| + config.include AncestryHelper # Remove this line if you don't want RSpec's should and should_not # methods or matchers require 'rspec/expectations' config.include RSpec::Matchers - config.include AncestryHelper # == Mock Framework config.mock_with :rspec From 35f2fba29500e729e446698343fb9434b9293647 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 10 Jul 2011 03:25:36 +0200 Subject: [PATCH 39/44] edge class equality should not take the order into account --- lib/plexus/arc.rb | 2 +- lib/plexus/edge.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plexus/arc.rb b/lib/plexus/arc.rb index 9559dc3..54233e3 100644 --- a/lib/plexus/arc.rb +++ b/lib/plexus/arc.rb @@ -13,7 +13,7 @@ def initialize(p_source, p_target, p_label = nil) # Ignore labels for equality. def eql?(other) - same_class = (self.class.ancestors.include? other.class) + same_class = self.class.ancestors.include?(other.class) || other.class.ancestors.include?(self.class) same_class && target == other.target && source == other.source end alias == eql? diff --git a/lib/plexus/edge.rb b/lib/plexus/edge.rb index 924e115..8ec51fa 100644 --- a/lib/plexus/edge.rb +++ b/lib/plexus/edge.rb @@ -6,7 +6,7 @@ class Edge < Arc # Edge equality allows for the swapping of source and target. # def eql?(other) - same_class = (self.class.ancestors.include? other.class) + same_class = self.class.ancestors.include?(other.class) || other.class.ancestors.include?(self.class) super || (same_class && target == other.source && source == other.target) end alias == eql? From d99412a892d776a42e0361ac1506060febbb3cd3 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Sun, 10 Jul 2011 03:26:19 +0200 Subject: [PATCH 40/44] v0.5.8 --- lib/plexus/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plexus/version.rb b/lib/plexus/version.rb index 57d1cbe..1924027 100644 --- a/lib/plexus/version.rb +++ b/lib/plexus/version.rb @@ -1,6 +1,6 @@ module Plexus MAJOR = 0 MINOR = 5 - PATCH = 7 + PATCH = 8 VERSION = [MAJOR, MINOR, PATCH].join('.') end From 370877e4fe4c4837931900fdb511fead1e388b4a Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Mon, 11 Jul 2011 01:19:17 +0200 Subject: [PATCH 41/44] recursivity for community helpers --- Gemfile.lock | 2 +- lib/plexus/adjacency_graph.rb | 1 + lib/plexus/directed_graph/algorithms.rb | 10 +++++----- lib/plexus/undirected_graph/algorithms.rb | 9 +++++---- lib/plexus/version.rb | 2 +- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 107a69c..c1b0e6f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - plexus (0.5.6) + plexus (0.5.8) activesupport facets plexus diff --git a/lib/plexus/adjacency_graph.rb b/lib/plexus/adjacency_graph.rb index 7bef21b..f04bf6b 100644 --- a/lib/plexus/adjacency_graph.rb +++ b/lib/plexus/adjacency_graph.rb @@ -208,6 +208,7 @@ def edges # @fixme def adjacent(x, options = {}) options[:direction] ||= :out + if !x.is_a?(Plexus::Arc) and (options[:direction] == :out || !directed?) if options[:type] == :edges i = -1 diff --git a/lib/plexus/directed_graph/algorithms.rb b/lib/plexus/directed_graph/algorithms.rb index 4a6b156..b874cd3 100644 --- a/lib/plexus/directed_graph/algorithms.rb +++ b/lib/plexus/directed_graph/algorithms.rb @@ -68,26 +68,26 @@ def delta(v) out_degree(v) - in_degree(v) end - def community(node, direction) + def community(node, direction, options = {:recursive => true}) nodes, stack = {}, adjacent(node, :direction => direction) while n = stack.pop unless nodes[n.object_id] || node == n nodes[n.object_id] = n - stack += adjacent(n, :direction => direction) + stack += adjacent(n, :direction => direction) if options[:recursive] end end nodes.values end - def descendants(node) + def descendants(node, options = {:recursive => true}) community(node, :out) end - def ancestors(node) + def ancestors(node, options = {:recursive => true}) community(node, :in) end - def family(node) + def family(node, options = {:recursive => true}) community(node, :all) end end diff --git a/lib/plexus/undirected_graph/algorithms.rb b/lib/plexus/undirected_graph/algorithms.rb index d83de87..6046acf 100644 --- a/lib/plexus/undirected_graph/algorithms.rb +++ b/lib/plexus/undirected_graph/algorithms.rb @@ -65,10 +65,11 @@ def interval?() triangulated? and complement.comparability?; end def permutation?() comparability? and complement.comparability?; end # An undirected graph is defined to be split if there is a partition - # V = S + K of its vertex set into a stable set S and a complete set K. + # V = S + K of its vertex set into a stable set S and a complete set K. def split?() triangulated? and complement.triangulated?; end private + # Implementation taken from Golumbic's, "Algorithmic Graph Theory and # Perfect Graphs" pg. 99 def triangulated_chromatic_number @@ -85,6 +86,6 @@ def triangulated_chromatic_number end; chi end - end # UndirectedGraphAlgorithms - end # UndirectedGraphBuilder -end # Plexus + end + end +end diff --git a/lib/plexus/version.rb b/lib/plexus/version.rb index 1924027..909c382 100644 --- a/lib/plexus/version.rb +++ b/lib/plexus/version.rb @@ -1,6 +1,6 @@ module Plexus MAJOR = 0 MINOR = 5 - PATCH = 8 + PATCH = 9 VERSION = [MAJOR, MINOR, PATCH].join('.') end From 90ff7d60bfbec8b1d47fede58d4b053d0c05ab98 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Mon, 11 Jul 2011 01:48:32 +0200 Subject: [PATCH 42/44] added rake dependency as required by travis-ci.org --- plexus.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/plexus.gemspec b/plexus.gemspec index 5f6466f..23d886b 100644 --- a/plexus.gemspec +++ b/plexus.gemspec @@ -19,6 +19,7 @@ Graph algorithms currently provided are: s.email = %q{jd@vauguet.fr} s.files = Dir["lib/**/*"] + Dir["vendor/**/*"] + Dir["spec/**/*"] + ["Gemfile", "LICENSE", "Rakefile", "README.md"] s.homepage = %q{http://github.com/chikamichi/plexus} + s.add_dependency "rake" s.add_dependency "activesupport" s.add_dependency "facets" s.add_dependency "plexus" From ad8d2410eb3a892bd3cbe6deefa8c3a96f29be85 Mon Sep 17 00:00:00 2001 From: Jean-Denis Vauguet Date: Tue, 12 Jul 2011 15:48:29 +0200 Subject: [PATCH 43/44] .gitignoring Gemfile.lock --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index efeb224..cd5d618 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ tmp* *.gem .gitignore TODO.md +Gemfile.lock From 5f8a1713b05738527f7197963ee56848d49ea225 Mon Sep 17 00:00:00 2001 From: Rodrigo Alvarez Date: Fri, 16 Mar 2012 01:06:13 +0100 Subject: [PATCH 44/44] Plexus can't require itself, otherwise it can't be installed (got "Error installing plexus: plexus requires plexus (>= 0)" on gem install plexus) --- plexus.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/plexus.gemspec b/plexus.gemspec index 23d886b..4508f8f 100644 --- a/plexus.gemspec +++ b/plexus.gemspec @@ -22,7 +22,6 @@ Graph algorithms currently provided are: s.add_dependency "rake" s.add_dependency "activesupport" s.add_dependency "facets" - s.add_dependency "plexus" s.add_development_dependency "rspec" s.add_development_dependency "yard" end