turbine-graph 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|