turbine-graph 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +15 -0
- data/Guardfile +6 -0
- data/LICENSE +27 -0
- data/README.md +189 -0
- data/Rakefile +126 -0
- data/examples/energy.rb +80 -0
- data/examples/family.rb +125 -0
- data/lib/turbine.rb +29 -0
- data/lib/turbine/algorithms/filtered_tarjan.rb +33 -0
- data/lib/turbine/algorithms/tarjan.rb +50 -0
- data/lib/turbine/edge.rb +94 -0
- data/lib/turbine/errors.rb +74 -0
- data/lib/turbine/graph.rb +113 -0
- data/lib/turbine/node.rb +246 -0
- data/lib/turbine/pipeline/README.mdown +31 -0
- data/lib/turbine/pipeline/dsl.rb +275 -0
- data/lib/turbine/pipeline/expander.rb +67 -0
- data/lib/turbine/pipeline/filter.rb +52 -0
- data/lib/turbine/pipeline/journal.rb +130 -0
- data/lib/turbine/pipeline/journal_filter.rb +71 -0
- data/lib/turbine/pipeline/pump.rb +19 -0
- data/lib/turbine/pipeline/segment.rb +175 -0
- data/lib/turbine/pipeline/sender.rb +51 -0
- data/lib/turbine/pipeline/split.rb +132 -0
- data/lib/turbine/pipeline/trace.rb +55 -0
- data/lib/turbine/pipeline/transform.rb +49 -0
- data/lib/turbine/pipeline/traversal.rb +34 -0
- data/lib/turbine/pipeline/unique.rb +47 -0
- data/lib/turbine/properties.rb +48 -0
- data/lib/turbine/traversal/base.rb +133 -0
- data/lib/turbine/traversal/breadth_first.rb +49 -0
- data/lib/turbine/traversal/depth_first.rb +46 -0
- data/lib/turbine/version.rb +4 -0
- data/turbine.gemspec +84 -0
- metadata +120 -0
data/examples/family.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'turbine'
|
2
|
+
|
3
|
+
module Turbine
|
4
|
+
|
5
|
+
# This example is taken from a TV show called 'Modern Family' with complex
|
6
|
+
# family relations, in other words: ideal to test a complex graph structure.
|
7
|
+
#
|
8
|
+
# http://en.wikipedia.org/wiki/List_of_Modern_Family_characters
|
9
|
+
def self.family_stub
|
10
|
+
graph = Turbine::Graph.new
|
11
|
+
|
12
|
+
phil = graph.add(Turbine::Node.new(:phil, gender: :male))
|
13
|
+
claire = graph.add(Turbine::Node.new(:claire, gender: :female))
|
14
|
+
haley = graph.add(Turbine::Node.new(:haley, gender: :female))
|
15
|
+
alex = graph.add(Turbine::Node.new(:alex, gender: :female))
|
16
|
+
luke = graph.add(Turbine::Node.new(:luke, gender: :male))
|
17
|
+
|
18
|
+
jay = graph.add(Turbine::Node.new(:jay, gender: :male))
|
19
|
+
gloria = graph.add(Turbine::Node.new(:gloria, gender: :female))
|
20
|
+
manny = graph.add(Turbine::Node.new(:manny, gender: :male))
|
21
|
+
unnamed = graph.add(Turbine::Node.new(:unnamed))
|
22
|
+
|
23
|
+
mitchell = graph.add(Turbine::Node.new(:mitchell, gender: :male))
|
24
|
+
cameron = graph.add(Turbine::Node.new(:cameron, gender: :male))
|
25
|
+
lily = graph.add(Turbine::Node.new(:lily, gender: :female))
|
26
|
+
|
27
|
+
dede = graph.add(Turbine::Node.new(:dede, gender: :female))
|
28
|
+
javier = graph.add(Turbine::Node.new(:javier, gender: :male))
|
29
|
+
|
30
|
+
frank = graph.add(Turbine::Node.new(:frank, gender: :male))
|
31
|
+
sarah = graph.add(Turbine::Node.new(:sarah, gender: :female))
|
32
|
+
|
33
|
+
# Dunphy -----------------------------------------------------------------
|
34
|
+
|
35
|
+
# Phil
|
36
|
+
phil.connect_to(claire, :spouse)
|
37
|
+
phil.connect_to(haley, :child)
|
38
|
+
phil.connect_to(alex, :child)
|
39
|
+
phil.connect_to(luke, :child)
|
40
|
+
|
41
|
+
# Claire
|
42
|
+
claire.connect_to(phil, :spouse)
|
43
|
+
claire.connect_to(haley, :child)
|
44
|
+
claire.connect_to(alex, :child)
|
45
|
+
claire.connect_to(luke, :child)
|
46
|
+
|
47
|
+
# Pritchett --------------------------------------------------------------
|
48
|
+
|
49
|
+
# Jay
|
50
|
+
jay.connect_to(gloria, :spouse)
|
51
|
+
jay.connect_to(claire, :child)
|
52
|
+
jay.connect_to(mitchell, :child)
|
53
|
+
jay.connect_to(unnamed, :child)
|
54
|
+
jay.connect_to(dede, :divorced)
|
55
|
+
|
56
|
+
# Gloria
|
57
|
+
gloria.connect_to(jay, :spouse)
|
58
|
+
gloria.connect_to(manny, :child)
|
59
|
+
gloria.connect_to(unnamed, :child)
|
60
|
+
gloria.connect_to(javier, :divorced)
|
61
|
+
|
62
|
+
# Tucker-Pritchett -------------------------------------------------------
|
63
|
+
|
64
|
+
mitchell.connect_to(cameron, :spouse)
|
65
|
+
mitchell.connect_to(lily, :child)
|
66
|
+
|
67
|
+
cameron.connect_to(mitchell, :spouse)
|
68
|
+
cameron.connect_to(lily, :child)
|
69
|
+
|
70
|
+
# Others -----------------------------------------------------------------
|
71
|
+
|
72
|
+
dede.connect_to(claire, :child)
|
73
|
+
dede.connect_to(mitchell, :child)
|
74
|
+
|
75
|
+
javier.connect_to(manny, :child)
|
76
|
+
|
77
|
+
frank.connect_to(phil, :child)
|
78
|
+
frank.connect_to(sarah, :spouse)
|
79
|
+
|
80
|
+
sarah.connect_to(phil, :child)
|
81
|
+
sarah.connect_to(frank, :spouse)
|
82
|
+
|
83
|
+
graph
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Find out that Manny is Jay's step-child with:
|
88
|
+
#
|
89
|
+
# jay = Turbine.stub.node(:jay)
|
90
|
+
# jay.out(:spouse).out(:child) - jay.out(:child)
|
91
|
+
#
|
92
|
+
# # => #<Turbine::Collection {#<Turbine::Node key=:manny>}>
|
93
|
+
#
|
94
|
+
# ... or that Alex has two siblings:
|
95
|
+
#
|
96
|
+
# alex = Turbine.stub.node(:alex)
|
97
|
+
# alex.in(:child).out(:child) - [alex]
|
98
|
+
#
|
99
|
+
# ... or that Jay and Gloria have a single "common" child:
|
100
|
+
#
|
101
|
+
# graph = Turbine.stub
|
102
|
+
# jay, gloria = graph.node(:jay), graph.node(:gloria)
|
103
|
+
#
|
104
|
+
# jay.out(:child) & gloria.out(:child)
|
105
|
+
#
|
106
|
+
# # => #<Turbine::Collection {#<Turbine::Node key=:unnamed>}>
|
107
|
+
#
|
108
|
+
# ... or who are Luke's uncles and aunts:
|
109
|
+
#
|
110
|
+
# graph = Turbine.stub
|
111
|
+
# luke = graph.node(:luke)
|
112
|
+
#
|
113
|
+
# parents = luke.in(:child)
|
114
|
+
# grandparents = parents.in(:child)
|
115
|
+
#
|
116
|
+
# # Remove Claire, Luke's mother:
|
117
|
+
# uncles_and_aunts = grandparents.out(:child) - parents
|
118
|
+
#
|
119
|
+
# # And add the uncle and aunt spouses for the full list.
|
120
|
+
# uncles_and_aunts + uncles_and_aunts.out(:spouse)
|
121
|
+
#
|
122
|
+
# # => #<Turbine::Collection {
|
123
|
+
# #<Turbine::Node key=:mitchell>,
|
124
|
+
# #<Turbine::Node key=:unnamed>,
|
125
|
+
# #<Turbine::Node key=:cameron>}>
|
data/lib/turbine.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'tsort'
|
2
|
+
require 'set'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
# On with the library...
|
6
|
+
require 'turbine/properties'
|
7
|
+
require 'turbine/algorithms/tarjan'
|
8
|
+
require 'turbine/algorithms/filtered_tarjan'
|
9
|
+
require 'turbine/edge'
|
10
|
+
require 'turbine/errors'
|
11
|
+
require 'turbine/graph'
|
12
|
+
require 'turbine/pipeline/dsl'
|
13
|
+
require 'turbine/pipeline/segment'
|
14
|
+
require 'turbine/pipeline/trace'
|
15
|
+
require 'turbine/pipeline/expander'
|
16
|
+
require 'turbine/pipeline/filter'
|
17
|
+
require 'turbine/pipeline/journal'
|
18
|
+
require 'turbine/pipeline/journal_filter'
|
19
|
+
require 'turbine/pipeline/pump'
|
20
|
+
require 'turbine/pipeline/transform'
|
21
|
+
require 'turbine/pipeline/traversal'
|
22
|
+
require 'turbine/pipeline/sender'
|
23
|
+
require 'turbine/pipeline/split'
|
24
|
+
require 'turbine/pipeline/unique'
|
25
|
+
require 'turbine/traversal/base'
|
26
|
+
require 'turbine/traversal/breadth_first'
|
27
|
+
require 'turbine/traversal/depth_first'
|
28
|
+
require 'turbine/node'
|
29
|
+
require 'turbine/version'
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Turbine
|
2
|
+
module Algorithms
|
3
|
+
# Internal: A wrapper around the Ruby stdlib implementation of Tarjan's
|
4
|
+
# strongly connected components and topological sort algorithms. Restricts
|
5
|
+
# the sort to only those edges which match the filter.
|
6
|
+
class FilteredTarjan < Tarjan
|
7
|
+
|
8
|
+
# Public: Creates a FilteredTarjan instance. If you simply wish to
|
9
|
+
# filter by the edge label, the standard Tarjan class will do this with
|
10
|
+
# better performance.
|
11
|
+
#
|
12
|
+
# graph - A Turbine graph whose nodes are to be sorted.
|
13
|
+
# &filter - Block which sleects the edges used to perform the sort.
|
14
|
+
#
|
15
|
+
# Returns a FilteredTarjan.
|
16
|
+
def initialize(graph, &filter)
|
17
|
+
super(graph, nil)
|
18
|
+
@filter = filter
|
19
|
+
end
|
20
|
+
|
21
|
+
#######
|
22
|
+
private
|
23
|
+
#######
|
24
|
+
|
25
|
+
# Internal: Used by TSort to iterate through each +out+ node.
|
26
|
+
#
|
27
|
+
# Returns nothing.
|
28
|
+
def tsort_each_child(node)
|
29
|
+
node.out_edges.select(&@filter).each { |edge| yield edge.to }
|
30
|
+
end
|
31
|
+
end # FilteredTarjan
|
32
|
+
end # Algorithms
|
33
|
+
end # Turbine
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Turbine
|
2
|
+
module Algorithms
|
3
|
+
# Internal: A wrapper around the Ruby stdlib implementation of Tarjan's
|
4
|
+
# strongly connected components and topological sort algorithms.
|
5
|
+
class Tarjan
|
6
|
+
include TSort
|
7
|
+
|
8
|
+
# Public: Creates a new Tarjan instance used for topologically sorting
|
9
|
+
# graphs.
|
10
|
+
#
|
11
|
+
# graph - A Turbine graph whose nodes are to be sorted.
|
12
|
+
# label - An optional label which will be used when traversing from a
|
13
|
+
# node to its +out+ nodes.
|
14
|
+
#
|
15
|
+
# For example
|
16
|
+
#
|
17
|
+
# Turbine::Algorithms::Tarjan.new(graph).tsort
|
18
|
+
# # => [ [ #<Node key=one>, #<Node key=two>, #<Node key=three> ],
|
19
|
+
# [ #<Node key=five> ],
|
20
|
+
# [ #<Node key=six>, #<Node key=seven> ] ]
|
21
|
+
#
|
22
|
+
# Turbine::Algorithms::Tarjan.new(graph, :spouse).tsort
|
23
|
+
# # => [ ... ]
|
24
|
+
#
|
25
|
+
# Returns a Tarjan.
|
26
|
+
def initialize(graph, label = nil)
|
27
|
+
@nodes = graph.nodes
|
28
|
+
@label = label
|
29
|
+
end
|
30
|
+
|
31
|
+
#######
|
32
|
+
private
|
33
|
+
#######
|
34
|
+
|
35
|
+
# Internal: Used by TSort to iterate through each node in the graph.
|
36
|
+
#
|
37
|
+
# Returns nothing.
|
38
|
+
def tsort_each_node
|
39
|
+
@nodes.each { |node| yield node }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Internal: Used by TSort to iterate through each +out+ node.
|
43
|
+
#
|
44
|
+
# Returns nothing.
|
45
|
+
def tsort_each_child(node)
|
46
|
+
node.nodes(:out, @label).each { |out| yield out }
|
47
|
+
end
|
48
|
+
end # Tarjan
|
49
|
+
end # Algorithms
|
50
|
+
end # Turbine
|
data/lib/turbine/edge.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module Turbine
|
2
|
+
# Edges represent a connection between an +from+ node and an +to+ node.
|
3
|
+
#
|
4
|
+
# Note that simply creating an Edge does not actually establish the
|
5
|
+
# connection between the two nodes. Rather than creating the Edge
|
6
|
+
# instance manually, Node can do this for you with +Node#connect_to+ and
|
7
|
+
# +Node#connect_via+:
|
8
|
+
#
|
9
|
+
# jay = Turbine::Node.new(:jay)
|
10
|
+
# gloria = Turbine::Node.new(:gloria)
|
11
|
+
#
|
12
|
+
# jay.connect_to(gloria, :spouse)
|
13
|
+
# gloria.connect_to(jay, :spouse)
|
14
|
+
#
|
15
|
+
# However, if you want to do it manually (perhaps with a subclass of Edge),
|
16
|
+
# you should use +Node#connect_via+:
|
17
|
+
#
|
18
|
+
# jay = Turbine::Node.new(:jay)
|
19
|
+
# gloria = Turbine::Node.new(:gloria)
|
20
|
+
#
|
21
|
+
# jay_married_to_gloria = Turbine::Edge.new(jay, gloria, :spouse)
|
22
|
+
# gloria_married_to_jay = Turbine::Edge.new(gloria, jay, :spouse)
|
23
|
+
#
|
24
|
+
# jay.connect_via(jay_married_to_gloria)
|
25
|
+
# gloria.connect_via(jay_married_to_gloria)
|
26
|
+
#
|
27
|
+
# jay.connect_via(gloria_married_to_jay)
|
28
|
+
# gloria.connect_via(gloria_married_to_jay)
|
29
|
+
#
|
30
|
+
class Edge
|
31
|
+
include Properties
|
32
|
+
|
33
|
+
# Public: The node which the edge leaves; the edge is an +out_edge+ on
|
34
|
+
# this node.
|
35
|
+
attr_reader :from
|
36
|
+
|
37
|
+
# Public: The node to which the edge points; the edge is an +in_edge+ on
|
38
|
+
# this node.
|
39
|
+
attr_reader :to
|
40
|
+
|
41
|
+
# Public: Returns the optional label assigned to the edge.
|
42
|
+
attr_reader :label
|
43
|
+
|
44
|
+
# Attribute aliases.
|
45
|
+
alias_method :parent, :from
|
46
|
+
alias_method :child, :to
|
47
|
+
|
48
|
+
# Public: Creates a new Edge.
|
49
|
+
#
|
50
|
+
# from_node - The Node from which the edge originates.
|
51
|
+
# in_node - The Node to which the edge points.
|
52
|
+
# label - An optional label for describing the nature of the
|
53
|
+
# relationship between the two nodes.
|
54
|
+
# properties - Optional key/value properties to be associated with the
|
55
|
+
# edge.
|
56
|
+
#
|
57
|
+
def initialize(from_node, to_node, label = nil, properties = nil)
|
58
|
+
@from = from_node
|
59
|
+
@to = to_node
|
60
|
+
@label = label
|
61
|
+
|
62
|
+
self.properties = properties
|
63
|
+
end
|
64
|
+
|
65
|
+
# Public: Determines if the +other+ edge is similar to this one.
|
66
|
+
#
|
67
|
+
# Two edges are considered similar if have the same +to+ and +from+ nodes,
|
68
|
+
# and their label is identical.
|
69
|
+
#
|
70
|
+
# Returns true or false.
|
71
|
+
def similar?(other)
|
72
|
+
other && other.to == @to && other.from == @from && other.label == @label
|
73
|
+
end
|
74
|
+
|
75
|
+
# Public: Returns a human-readable version of the edge.
|
76
|
+
def inspect
|
77
|
+
"#<#{ self.class.name } #{ to_s }>".sub('>', "\u27A4")
|
78
|
+
end
|
79
|
+
|
80
|
+
# Public: Returns a human-readable version of the edge by showing the from
|
81
|
+
# and to nodes, connected by the label.
|
82
|
+
def to_s
|
83
|
+
"#{ @from.key.inspect } -#{ @label.inspect }-> #{ @to.key.inspect }"
|
84
|
+
end
|
85
|
+
|
86
|
+
# Internal: A low-level method which retrieves the node in a given
|
87
|
+
# direction. Used for compatibility with Pipeline.
|
88
|
+
#
|
89
|
+
# Returns a Node.
|
90
|
+
def nodes(direction, *)
|
91
|
+
direction == :to ? @to : @from
|
92
|
+
end
|
93
|
+
end # Edge
|
94
|
+
end # Turbine
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Turbine
|
2
|
+
# Error class which serves as a base for all errors which occur in Turbine.
|
3
|
+
TurbineError = Class.new(StandardError)
|
4
|
+
|
5
|
+
# Internal: Creates a new error class which inherits from TurbineError,
|
6
|
+
# whose message is created by evaluating the block you give.
|
7
|
+
#
|
8
|
+
# For example
|
9
|
+
#
|
10
|
+
# MyError = error_class do |weight, limit|
|
11
|
+
# "#{ weight } exceeds #{ limit }"
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# raise MyError.new(5000, 2500)
|
15
|
+
# # => #<Turbine::MyError: 5000 exceeds 2500>
|
16
|
+
#
|
17
|
+
# Returns an exception class.
|
18
|
+
def self.error_class(superclass = TurbineError, &block)
|
19
|
+
Class.new(superclass) do
|
20
|
+
def initialize(*args) ; super(make_message(*args)) ; end
|
21
|
+
define_method(:make_message, &block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Added a node to a graph, when one already exists with the same key.
|
26
|
+
DuplicateNodeError = error_class do |key|
|
27
|
+
"Graph already has a node with the key #{ key.inspect }"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Attempted an operation on a Node which does not exist.
|
31
|
+
NoSuchNodeError = error_class do |key|
|
32
|
+
"Graph does not contain a node with the key #{ key.inspect }"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Added an edge between two nodes which was too similar to an existing edge.
|
36
|
+
# See Edge#similar?
|
37
|
+
DuplicateEdgeError = error_class do |node, edge|
|
38
|
+
"Another edge already exists on #{ node.inspect } which is too similar " \
|
39
|
+
"to #{ edge.inspect }"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Attempted to set properties on an object, when the properties were not in
|
43
|
+
# the form of a hash, or were otherwise invalid in some way.
|
44
|
+
InvalidPropertiesError = error_class do |model, properties|
|
45
|
+
"Tried to assign properties #{ properties.inspect } on " \
|
46
|
+
"#{ model.inspect } - it must be a Hash, or subclass of Hash"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Tried to access values from a non-existant Journal segment.
|
50
|
+
NoSuchJournalError = error_class do |name|
|
51
|
+
"No such upstream journal: #{ name.inspect }"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Attempted to get trace information from a pipeline when tracing has not
|
55
|
+
# been enabled.
|
56
|
+
TracingNotEnabledError = error_class do |segment|
|
57
|
+
"You cannot get trace information from the #{ segment.inspect } " \
|
58
|
+
"segment as tracing is not enabled"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Tried to enable tracing on a segment which doesn't support it.
|
62
|
+
NotTraceableError = error_class do |segment|
|
63
|
+
"You cannot enable tracing on pipelines with #{ segment.class.name } " \
|
64
|
+
"segments"
|
65
|
+
end
|
66
|
+
|
67
|
+
# Tried to topologically sort a graph which contains loops.
|
68
|
+
class CyclicError < TurbineError
|
69
|
+
def initialize(orig_exception)
|
70
|
+
set_backtrace(orig_exception.backtrace)
|
71
|
+
super(orig_exception.message)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end # Turbine
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Turbine
|
2
|
+
# Contains nodes and edges.
|
3
|
+
class Graph
|
4
|
+
|
5
|
+
# Public: Creates a new graph.
|
6
|
+
def initialize
|
7
|
+
@nodes = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
# Public: Adds the +node+ to the graph.
|
11
|
+
#
|
12
|
+
# node - The node to be added.
|
13
|
+
#
|
14
|
+
# Raises a DuplicateNodeError if the graph already contains a node with
|
15
|
+
# the same key.
|
16
|
+
#
|
17
|
+
# Returns the node.
|
18
|
+
def add(node)
|
19
|
+
if @nodes.key?(node.key)
|
20
|
+
raise DuplicateNodeError.new(node.key)
|
21
|
+
end
|
22
|
+
|
23
|
+
@nodes[node.key] = node
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: Removes the +node+ from the graph and disconnects any nodes
|
27
|
+
# which have edges to the +node+.
|
28
|
+
#
|
29
|
+
# node - The node to be deleted.
|
30
|
+
#
|
31
|
+
# Raises a NoSuchNodeError if the graph does not contain the given +node+.
|
32
|
+
#
|
33
|
+
# Returns the node.
|
34
|
+
def delete(node)
|
35
|
+
unless @nodes.key?(node.key)
|
36
|
+
raise NoSuchNodeError.new(node.key)
|
37
|
+
end
|
38
|
+
|
39
|
+
(node.edges(:out) + node.edges(:in)).each do |edge|
|
40
|
+
edge.from.disconnect_via(edge)
|
41
|
+
edge.to.disconnect_via(edge)
|
42
|
+
end
|
43
|
+
|
44
|
+
@nodes.delete(node.key)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: Retrieves the node whose key is +key+.
|
48
|
+
#
|
49
|
+
# key - The key of the desired node.
|
50
|
+
#
|
51
|
+
# Returns the node, or nil if no such node is known.
|
52
|
+
def node(key)
|
53
|
+
@nodes[key]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: All of the nodes in an array.
|
57
|
+
#
|
58
|
+
# Generally speaking, the nodes will be returned in the same order as
|
59
|
+
# they were added to the graph, however this may very depending on your
|
60
|
+
# Ruby implementation.
|
61
|
+
#
|
62
|
+
# Returns an array of nodes.
|
63
|
+
def nodes
|
64
|
+
@nodes.values
|
65
|
+
end
|
66
|
+
|
67
|
+
# Public: Topologically sorts the nodes in the graph so that nodes with
|
68
|
+
# no in edges appear at the beginning of the array, and those deeper
|
69
|
+
# within the graph are at the end.
|
70
|
+
#
|
71
|
+
# label - An optional label used to limit the edges used when traversing
|
72
|
+
# from one node to its outward nodes.
|
73
|
+
#
|
74
|
+
# Raises CyclicError if the graph contains loops.
|
75
|
+
#
|
76
|
+
# Returns an array.
|
77
|
+
def tsort(label = nil)
|
78
|
+
Algorithms::Tarjan.new(self, label).tsort
|
79
|
+
rescue TSort::Cyclic => exception
|
80
|
+
raise CyclicError.new(exception)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Public: Uses Tarjan's strongly-connected components algorithm to detect
|
84
|
+
# nodes which are interrelated.
|
85
|
+
#
|
86
|
+
# label - An optional label used to limit the edges used when traversing
|
87
|
+
# from one node to its outward nodes.
|
88
|
+
#
|
89
|
+
# For example
|
90
|
+
#
|
91
|
+
# graph.strongly_connected_components
|
92
|
+
# # => [ [ #<Node key=one> ],
|
93
|
+
# [ #<Node key=two>, #<Node key=three>, #<Node key=four> ],
|
94
|
+
# [ #<Node key=five>, #<Node key=six ] ]
|
95
|
+
#
|
96
|
+
# Returns an array.
|
97
|
+
def strongly_connected_components(label = nil)
|
98
|
+
Algorithms::Tarjan.new(self, label).strongly_connected_components
|
99
|
+
end
|
100
|
+
|
101
|
+
# Public: A human-readable version of the graph.
|
102
|
+
#
|
103
|
+
# Returns a string.
|
104
|
+
def inspect
|
105
|
+
edge_count = @nodes.values.each_with_object(Set.new) do |node, edges|
|
106
|
+
edges.merge(node.edges(:out))
|
107
|
+
end.length
|
108
|
+
|
109
|
+
"#<#{self.class} (#{ @nodes.length } nodes, #{ edge_count } edges)>"
|
110
|
+
end
|
111
|
+
|
112
|
+
end # Graph
|
113
|
+
end # Turbine
|