topiary 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0741356d352846be28e643f7440a8ba1fb073b087c5157fe7ffcdf15395b6278
4
- data.tar.gz: 2719dc88e25e2625817b8c718b6feec9259b65e42733171df82939baa10701ab
3
+ metadata.gz: 2fa0495670fc22ed0936ec8bacf285e54c1d4f500b252ab4f9baea449f97f2e8
4
+ data.tar.gz: 32f52665a134b24547f509fd504422e24fe49d25573480aeb0e9e5e4493b01c0
5
5
  SHA512:
6
- metadata.gz: '05303108e626d064d1b854d9e81ef97ae76748b6882702f09a3757d37f0dc68efd35c91808490be72d265edb0e9a71f545be0fbe48e990548cd8b8955a54f81f'
7
- data.tar.gz: 98db335bf595a0f42a081ee010fac7fb33cb01b8790c2cd4fcfd035eeb80aa793a6483aaaa369b498e0e0ba5d87b28f159301b55646f5fef546bb8492453def5
6
+ metadata.gz: 77f118805255160a9ef603ccf42baba867d3dc7abbbecb6f7c9e2be14c28fc19d78419172bafd958610f534a96142b4bf1d59ff6c8a668079c65f75234f4f93e
7
+ data.tar.gz: de070cd5fbbaa730233025ad160d308a2d8e8dadc866838335f6530c14de982e4adb6c9408271ab33162638ed0117a1d5a43995c3026ab85446bb15b2fbd4bca
@@ -24,13 +24,17 @@ Layout/SpaceInsideHashLiteralBraces:
24
24
  Layout/SpaceAroundEqualsInParameterDefault:
25
25
  EnforcedStyle: no_space
26
26
 
27
+ Layout/ExtraSpacing:
28
+ Enabled: false
29
+
27
30
  Metrics/BlockLength:
28
31
  Exclude:
29
32
  - "spec/**/*"
30
33
  ExcludedMethods: describe
34
+ Max: 30
31
35
 
32
36
  Metrics/AbcSize:
33
- Max: 30
37
+ Max: 40
34
38
 
35
39
  Metrics/CyclomaticComplexity:
36
40
  Max: 7
@@ -41,7 +45,10 @@ Metrics/LineLength:
41
45
  Metrics/MethodLength:
42
46
  Exclude:
43
47
  - "spec/**/*"
44
- Max: 30
48
+ Max: 35
49
+
50
+ Metrics/ClassLength:
51
+ Max: 1000
45
52
 
46
53
  Style/AsciiComments:
47
54
  Enabled: false
@@ -53,7 +60,9 @@ Style/StringLiterals:
53
60
  Enabled: false
54
61
 
55
62
  Style/BlockDelimiters:
56
- EnforcedStyle: semantic
63
+ EnforcedStyle: line_count_based
64
+ Exclude:
65
+ - "spec/**/*"
57
66
 
58
67
  Style/Documentation:
59
68
  Exclude:
@@ -81,5 +90,14 @@ Style/AndOr:
81
90
  Style/IfUnlessModifier:
82
91
  Enabled: false
83
92
 
93
+ Style/NegatedIf:
94
+ Enabled: false
95
+
96
+ Style/TrailingCommaInArrayLiteral:
97
+ EnforcedStyleForMultiline: comma
98
+
84
99
  Naming/UncommunicativeMethodParamName:
85
100
  Enabled: false
101
+
102
+ Lint/EmptyWhen:
103
+ Enabled: false
data/Changelog CHANGED
@@ -1,3 +1,10 @@
1
+ 1.0.3
2
+ -----
3
+
4
+ - Added Graph and DirectedGraph, exceptions, functions to compute all
5
+ possible {acyclic,} digraphs of V vertices
6
+ - Added Edge class and topologically_distinct function
7
+
1
8
  1.0.2
2
9
  -----
3
10
 
@@ -1,5 +1,9 @@
1
1
  require 'topiary/version'
2
+ require 'topiary/exceptions'
2
3
  require 'topiary/node'
4
+ require 'topiary/edge'
5
+ require 'topiary/graph'
6
+ require 'topiary/directed_graph'
3
7
  require 'set'
4
8
 
5
9
  # {Topiary} provides a topological sort function for Directed Acyclic Graphs.
@@ -44,7 +48,7 @@ module Topiary
44
48
  # Make sure there were no cycles
45
49
  node_list.each do |n2|
46
50
  if n2.needs.any? or n2.feeds.any?
47
- raise "Leftover edges found: this graph has a cycle"
51
+ raise InvalidGraph, "Leftover edges found: this graph has a cycle"
48
52
  end
49
53
  end
50
54
 
@@ -0,0 +1,193 @@
1
+ # Ruby has no factorial function,
2
+ # but fact(n) == Math.gamma(n + 1)
3
+ def factorial(n)
4
+ Math.gamma(n + 1)
5
+ end
6
+
7
+ module Topiary
8
+
9
+ # A graph whose edges are directed instead of directionless
10
+ class DirectedGraph < Graph
11
+
12
+ def self.all_from_node_count(node_count)
13
+ all_from_node_list(1.upto(node_count).map{|i| Node.new i.to_s})
14
+ end
15
+
16
+ # Assumes that all input nodes are edgeless.
17
+ def self.all_from_node_list(nodes=[])
18
+ Enumerator.new do |y|
19
+ if nodes.empty?
20
+ # There is one graph with zero nodes:
21
+ y.yield DirectedGraph.new
22
+ else
23
+ # Find the number of combinations with replacement,
24
+ # denoted
25
+ # n
26
+ # (( ))
27
+ # k
28
+ #
29
+ # which is
30
+ #
31
+ # n + k - 1
32
+ # ( )
33
+ # k
34
+ #
35
+ # and where
36
+ #
37
+ # n n!
38
+ # ( ) = ------------
39
+ # k k!(n - k)!
40
+ node_count = nodes.size
41
+ n = node_count + 2 - 1
42
+ pair_count = factorial(n) /
43
+ (factorial(2) * factorial(n - 2))
44
+ # Between every two nodes u & v there are 3 choices:
45
+ #
46
+ # - no edge
47
+ # - edge from u to v
48
+ # - edge from v to u
49
+ #
50
+ # Except if u & v are the same node,
51
+ # choice 2 & choice 3 are the same, so skip one of them.
52
+ %w[. < >].repeated_permutation(pair_count).each do |edges|
53
+ our_nodes = nodes.map(&:clone)
54
+ pairs = our_nodes.repeated_combination(2).to_a
55
+ g = DirectedGraph.new our_nodes
56
+ skip = false
57
+ edges.each_with_index do |dir, i|
58
+ case dir
59
+ when '.'
60
+ # no edge
61
+ when '<'
62
+ pairs[i][0].need! pairs[i][1]
63
+ when '>'
64
+ # If the two nodes are the same,
65
+ # we already did this:
66
+ if pairs[i][0] == pairs[i][1]
67
+ skip = true
68
+ break
69
+ end
70
+ pairs[i][0].feed! pairs[i][1]
71
+ end
72
+ end
73
+ y.yield g unless skip
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ def self.acyclic_from_node_count(node_count)
80
+ acyclic_from_node_list(1.upto(node_count).map{|i| Node.new i.to_s})
81
+ end
82
+
83
+ def self.acyclic_from_node_list(nodes=[])
84
+ all_from_node_list(nodes).reject(&:has_cycles?).each(&:forbid_cycles!)
85
+ end
86
+
87
+ # Returns only those graphs that are topologically distinct
88
+ def self.topologically_distinct(graphs)
89
+ already_seen = Set.new
90
+ Enumerator.new do |y|
91
+ graphs.each do |g|
92
+ is_new = true
93
+ already_seen.each do |g2|
94
+ if g2.topologically_equivalent? g
95
+ is_new = false
96
+ break
97
+ end
98
+ end
99
+ if is_new
100
+ y.yield g
101
+ already_seen << g
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ def initialize(nodes=[])
108
+ super
109
+ @forbid_cycles = false
110
+ end
111
+
112
+ def add_edge!(from_node, to_node)
113
+ from_node.feed! to_node
114
+ maybe_forbid_cycles!
115
+ end
116
+
117
+ def edges
118
+ Enumerator.new do |y|
119
+ nodes.each do |n|
120
+ n.feeds.each do |n2|
121
+ y.yield Edge.new(n, n2)
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ # Two graphs A and B are topologically equivalent
128
+ # if there is a bijection phi on the vertices
129
+ # such that edge phi(u)phi(v) is in B iff uv is in A.
130
+ def topologically_equivalent?(other)
131
+ our_nodes = nodes.to_a
132
+ other_nodes = other.nodes.to_a
133
+ return false if our_nodes.size != other_nodes.size
134
+ our_edges = edges.to_a
135
+ other_edges = other.edges.to_a
136
+ return false if our_edges.size != other_edges.size
137
+
138
+ our_node_numbers = Hash[
139
+ our_nodes.each_with_index.map{|n, i| [n, i]}
140
+ ]
141
+
142
+ # Since there are no permutations,
143
+ # we have to special case graphs with 0 or 1 node:
144
+ case our_nodes.size
145
+ when 0
146
+ true
147
+ when 1
148
+ true # since we already know they have the same number of edges
149
+ else
150
+ # Now we have to try all permutations of the nodes:
151
+ 0.upto(nodes.size - 1).to_a.permutation.each do |phi|
152
+ equivalent = true
153
+ catch :answered do
154
+ our_nodes.each_with_index do |u, i|
155
+ phi_u = other_nodes[phi[i]]
156
+ u.feeds.each do |v|
157
+ phi_v = other_nodes[phi[our_node_numbers[v]]]
158
+ if not phi_u.feeds.include?(phi_v)
159
+ equivalent = false
160
+ throw :answered
161
+ end
162
+ end
163
+ end
164
+ end
165
+ return true if equivalent
166
+ end
167
+ false
168
+ end
169
+ end
170
+
171
+ # rubocop:disable Naming/PredicateName
172
+ def has_cycles?
173
+ Topiary.sort nodes
174
+ false
175
+ rescue InvalidGraph
176
+ true
177
+ end
178
+ # rubocop:enable Naming/PredicateName
179
+
180
+ def forbid_cycles!(v=true)
181
+ @forbid_cycles = v
182
+ maybe_forbid_cycles! if v
183
+ end
184
+
185
+ private
186
+
187
+ def maybe_forbid_cycles!
188
+ raise InvalidGraph, "Cycles found!" if @forbid_cycles and has_cycles?
189
+ end
190
+
191
+ end
192
+
193
+ end
@@ -0,0 +1,18 @@
1
+ module Topiary
2
+ # Represents an edge between two nodes in a graph.
3
+ class Edge
4
+ attr_reader :feeder, :needer
5
+
6
+ def initialize(feeder, needer)
7
+ @feeder = feeder
8
+ @needer = needer
9
+ end
10
+
11
+ def to_s
12
+ [
13
+ feeder.name,
14
+ needer.name,
15
+ ].join("->")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ module Topiary
2
+
3
+ class TopiaryError < StandardError
4
+ end
5
+
6
+ class InvalidGraph < TopiaryError
7
+ end
8
+
9
+ end
@@ -0,0 +1,21 @@
1
+ module Topiary
2
+ # Represents a Graph
3
+ class Graph
4
+ attr_reader :nodes
5
+
6
+ def initialize(nodes=[])
7
+ @nodes = nodes
8
+ end
9
+
10
+ def to_s
11
+ ar = nodes.map(&:to_s)
12
+ if ar.empty?
13
+ "{ }"
14
+ else
15
+ "{ #{ar.join('; ')} }"
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -64,7 +64,7 @@ class Topiary::Node
64
64
  [
65
65
  name,
66
66
  "needs:[" + needs.map(&:name).join(",") + "]",
67
- "feeds:[" + feeds.map(&:name).join(",") + "]"
67
+ "feeds:[" + feeds.map(&:name).join(",") + "]",
68
68
  ].join(" ")
69
69
  end
70
70
 
@@ -72,4 +72,8 @@ class Topiary::Node
72
72
  to_s
73
73
  end
74
74
 
75
+ def clone
76
+ Topiary::Node.new(data, needs, feeds)
77
+ end
78
+
75
79
  end
@@ -1,3 +1,3 @@
1
1
  module Topiary
2
- VERSION = '1.0.2'.freeze
2
+ VERSION = '1.0.3'.freeze
3
3
  end
@@ -0,0 +1,903 @@
1
+ describe Topiary::DirectedGraph do
2
+
3
+ context "when allowing cycles" do
4
+ context "#add_edge!" do
5
+ it "adds an edge" do
6
+ n1 = Topiary::Node.new "n1"
7
+ n2 = Topiary::Node.new "n2"
8
+ graph = Topiary::DirectedGraph.new([n1, n2])
9
+ graph.add_edge!(n1, n2)
10
+ expect(graph.to_s).to eq "{ n1 needs:[] feeds:[n2]; n2 needs:[n1] feeds:[] }"
11
+ end
12
+ it "allows cycles" do
13
+ n1 = Topiary::Node.new "n1"
14
+ graph = Topiary::DirectedGraph.new([n1])
15
+ graph.add_edge!(n1, n1)
16
+ expect(graph.to_s).to eq "{ n1 needs:[n1] feeds:[n1] }"
17
+ end
18
+ end
19
+ end
20
+
21
+ context "when forbidding cycles" do
22
+ context "#add_edge!" do
23
+ it "adds an edge" do
24
+ n1 = Topiary::Node.new "n1"
25
+ n2 = Topiary::Node.new "n2"
26
+ graph = Topiary::DirectedGraph.new([n1, n2])
27
+ graph.forbid_cycles!
28
+ graph.add_edge!(n1, n2)
29
+ expect(graph.to_s).to eq "{ n1 needs:[] feeds:[n2]; n2 needs:[n1] feeds:[] }"
30
+ end
31
+
32
+ it "forbids cycles" do
33
+ n1 = Topiary::Node.new "n1"
34
+ graph = Topiary::DirectedGraph.new([n1])
35
+ graph.forbid_cycles!
36
+ expect {
37
+ graph.add_edge!(n1, n1)
38
+ }.to raise_error Topiary::InvalidGraph, "Cycles found!"
39
+ end
40
+ end
41
+ end
42
+
43
+ context ".all_from_node_count" do
44
+
45
+ it "builds 0-node graphs" do
46
+ expect(Topiary::DirectedGraph.all_from_node_count(0).map(&:to_s)).to eq [
47
+ "{ }",
48
+ ]
49
+ end
50
+
51
+ it "builds 1-node graphs" do
52
+ expect(Topiary::DirectedGraph.all_from_node_count(1).map(&:to_s)).to eq [
53
+ "{ 1 needs:[] feeds:[] }",
54
+ "{ 1 needs:[1] feeds:[1] }",
55
+ ]
56
+ end
57
+
58
+ it "builds 2-node graphs" do
59
+ expect(Topiary::DirectedGraph.all_from_node_count(2).map(&:to_s)).to eq [
60
+ "{ 1 needs:[] feeds:[]; 2 needs:[] feeds:[] }",
61
+ "{ 1 needs:[] feeds:[]; 2 needs:[2] feeds:[2] }",
62
+ "{ 1 needs:[2] feeds:[]; 2 needs:[] feeds:[1] }",
63
+ "{ 1 needs:[2] feeds:[]; 2 needs:[2] feeds:[1,2] }",
64
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1] feeds:[] }",
65
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1,2] feeds:[2] }",
66
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[] feeds:[] }",
67
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[2] feeds:[2] }",
68
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[] feeds:[1] }",
69
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[2] feeds:[1,2] }",
70
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1] feeds:[] }",
71
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1,2] feeds:[2] }",
72
+ ]
73
+ end
74
+
75
+ it "builds 3-node graphs" do
76
+ expect(Topiary::DirectedGraph.all_from_node_count(3).map(&:to_s)).to eq [
77
+ "{ 1 needs:[] feeds:[]; 2 needs:[] feeds:[]; 3 needs:[] feeds:[] }",
78
+ "{ 1 needs:[] feeds:[]; 2 needs:[] feeds:[]; 3 needs:[3] feeds:[3] }",
79
+ "{ 1 needs:[] feeds:[]; 2 needs:[3] feeds:[]; 3 needs:[] feeds:[2] }",
80
+ "{ 1 needs:[] feeds:[]; 2 needs:[3] feeds:[]; 3 needs:[3] feeds:[2,3] }",
81
+ "{ 1 needs:[] feeds:[]; 2 needs:[] feeds:[3]; 3 needs:[2] feeds:[] }",
82
+ "{ 1 needs:[] feeds:[]; 2 needs:[] feeds:[3]; 3 needs:[2,3] feeds:[3] }",
83
+ "{ 1 needs:[] feeds:[]; 2 needs:[2] feeds:[2]; 3 needs:[] feeds:[] }",
84
+ "{ 1 needs:[] feeds:[]; 2 needs:[2] feeds:[2]; 3 needs:[3] feeds:[3] }",
85
+ "{ 1 needs:[] feeds:[]; 2 needs:[2,3] feeds:[2]; 3 needs:[] feeds:[2] }",
86
+ "{ 1 needs:[] feeds:[]; 2 needs:[2,3] feeds:[2]; 3 needs:[3] feeds:[2,3] }",
87
+ "{ 1 needs:[] feeds:[]; 2 needs:[2] feeds:[2,3]; 3 needs:[2] feeds:[] }",
88
+ "{ 1 needs:[] feeds:[]; 2 needs:[2] feeds:[2,3]; 3 needs:[2,3] feeds:[3] }",
89
+ "{ 1 needs:[3] feeds:[]; 2 needs:[] feeds:[]; 3 needs:[] feeds:[1] }",
90
+ "{ 1 needs:[3] feeds:[]; 2 needs:[] feeds:[]; 3 needs:[3] feeds:[1,3] }",
91
+ "{ 1 needs:[3] feeds:[]; 2 needs:[3] feeds:[]; 3 needs:[] feeds:[1,2] }",
92
+ "{ 1 needs:[3] feeds:[]; 2 needs:[3] feeds:[]; 3 needs:[3] feeds:[1,2,3] }",
93
+ "{ 1 needs:[3] feeds:[]; 2 needs:[] feeds:[3]; 3 needs:[2] feeds:[1] }",
94
+ "{ 1 needs:[3] feeds:[]; 2 needs:[] feeds:[3]; 3 needs:[2,3] feeds:[1,3] }",
95
+ "{ 1 needs:[3] feeds:[]; 2 needs:[2] feeds:[2]; 3 needs:[] feeds:[1] }",
96
+ "{ 1 needs:[3] feeds:[]; 2 needs:[2] feeds:[2]; 3 needs:[3] feeds:[1,3] }",
97
+ "{ 1 needs:[3] feeds:[]; 2 needs:[2,3] feeds:[2]; 3 needs:[] feeds:[1,2] }",
98
+ "{ 1 needs:[3] feeds:[]; 2 needs:[2,3] feeds:[2]; 3 needs:[3] feeds:[1,2,3] }",
99
+ "{ 1 needs:[3] feeds:[]; 2 needs:[2] feeds:[2,3]; 3 needs:[2] feeds:[1] }",
100
+ "{ 1 needs:[3] feeds:[]; 2 needs:[2] feeds:[2,3]; 3 needs:[2,3] feeds:[1,3] }",
101
+ "{ 1 needs:[] feeds:[3]; 2 needs:[] feeds:[]; 3 needs:[1] feeds:[] }",
102
+ "{ 1 needs:[] feeds:[3]; 2 needs:[] feeds:[]; 3 needs:[1,3] feeds:[3] }",
103
+ "{ 1 needs:[] feeds:[3]; 2 needs:[3] feeds:[]; 3 needs:[1] feeds:[2] }",
104
+ "{ 1 needs:[] feeds:[3]; 2 needs:[3] feeds:[]; 3 needs:[1,3] feeds:[2,3] }",
105
+ "{ 1 needs:[] feeds:[3]; 2 needs:[] feeds:[3]; 3 needs:[1,2] feeds:[] }",
106
+ "{ 1 needs:[] feeds:[3]; 2 needs:[] feeds:[3]; 3 needs:[1,2,3] feeds:[3] }",
107
+ "{ 1 needs:[] feeds:[3]; 2 needs:[2] feeds:[2]; 3 needs:[1] feeds:[] }",
108
+ "{ 1 needs:[] feeds:[3]; 2 needs:[2] feeds:[2]; 3 needs:[1,3] feeds:[3] }",
109
+ "{ 1 needs:[] feeds:[3]; 2 needs:[2,3] feeds:[2]; 3 needs:[1] feeds:[2] }",
110
+ "{ 1 needs:[] feeds:[3]; 2 needs:[2,3] feeds:[2]; 3 needs:[1,3] feeds:[2,3] }",
111
+ "{ 1 needs:[] feeds:[3]; 2 needs:[2] feeds:[2,3]; 3 needs:[1,2] feeds:[] }",
112
+ "{ 1 needs:[] feeds:[3]; 2 needs:[2] feeds:[2,3]; 3 needs:[1,2,3] feeds:[3] }",
113
+ "{ 1 needs:[2] feeds:[]; 2 needs:[] feeds:[1]; 3 needs:[] feeds:[] }",
114
+ "{ 1 needs:[2] feeds:[]; 2 needs:[] feeds:[1]; 3 needs:[3] feeds:[3] }",
115
+ "{ 1 needs:[2] feeds:[]; 2 needs:[3] feeds:[1]; 3 needs:[] feeds:[2] }",
116
+ "{ 1 needs:[2] feeds:[]; 2 needs:[3] feeds:[1]; 3 needs:[3] feeds:[2,3] }",
117
+ "{ 1 needs:[2] feeds:[]; 2 needs:[] feeds:[1,3]; 3 needs:[2] feeds:[] }",
118
+ "{ 1 needs:[2] feeds:[]; 2 needs:[] feeds:[1,3]; 3 needs:[2,3] feeds:[3] }",
119
+ "{ 1 needs:[2] feeds:[]; 2 needs:[2] feeds:[1,2]; 3 needs:[] feeds:[] }",
120
+ "{ 1 needs:[2] feeds:[]; 2 needs:[2] feeds:[1,2]; 3 needs:[3] feeds:[3] }",
121
+ "{ 1 needs:[2] feeds:[]; 2 needs:[2,3] feeds:[1,2]; 3 needs:[] feeds:[2] }",
122
+ "{ 1 needs:[2] feeds:[]; 2 needs:[2,3] feeds:[1,2]; 3 needs:[3] feeds:[2,3] }",
123
+ "{ 1 needs:[2] feeds:[]; 2 needs:[2] feeds:[1,2,3]; 3 needs:[2] feeds:[] }",
124
+ "{ 1 needs:[2] feeds:[]; 2 needs:[2] feeds:[1,2,3]; 3 needs:[2,3] feeds:[3] }",
125
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[] feeds:[1]; 3 needs:[] feeds:[1] }",
126
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[] feeds:[1]; 3 needs:[3] feeds:[1,3] }",
127
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[3] feeds:[1]; 3 needs:[] feeds:[1,2] }",
128
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[3] feeds:[1]; 3 needs:[3] feeds:[1,2,3] }",
129
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[] feeds:[1,3]; 3 needs:[2] feeds:[1] }",
130
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[] feeds:[1,3]; 3 needs:[2,3] feeds:[1,3] }",
131
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[2] feeds:[1,2]; 3 needs:[] feeds:[1] }",
132
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[2] feeds:[1,2]; 3 needs:[3] feeds:[1,3] }",
133
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[2,3] feeds:[1,2]; 3 needs:[] feeds:[1,2] }",
134
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[2,3] feeds:[1,2]; 3 needs:[3] feeds:[1,2,3] }",
135
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[2] feeds:[1,2,3]; 3 needs:[2] feeds:[1] }",
136
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[2] feeds:[1,2,3]; 3 needs:[2,3] feeds:[1,3] }",
137
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[] feeds:[1]; 3 needs:[1] feeds:[] }",
138
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[] feeds:[1]; 3 needs:[1,3] feeds:[3] }",
139
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[3] feeds:[1]; 3 needs:[1] feeds:[2] }",
140
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[3] feeds:[1]; 3 needs:[1,3] feeds:[2,3] }",
141
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[] feeds:[1,3]; 3 needs:[1,2] feeds:[] }",
142
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[] feeds:[1,3]; 3 needs:[1,2,3] feeds:[3] }",
143
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[2] feeds:[1,2]; 3 needs:[1] feeds:[] }",
144
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[2] feeds:[1,2]; 3 needs:[1,3] feeds:[3] }",
145
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[2,3] feeds:[1,2]; 3 needs:[1] feeds:[2] }",
146
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[2,3] feeds:[1,2]; 3 needs:[1,3] feeds:[2,3] }",
147
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[2] feeds:[1,2,3]; 3 needs:[1,2] feeds:[] }",
148
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[2] feeds:[1,2,3]; 3 needs:[1,2,3] feeds:[3] }",
149
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1] feeds:[]; 3 needs:[] feeds:[] }",
150
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1] feeds:[]; 3 needs:[3] feeds:[3] }",
151
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1,3] feeds:[]; 3 needs:[] feeds:[2] }",
152
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1,3] feeds:[]; 3 needs:[3] feeds:[2,3] }",
153
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1] feeds:[3]; 3 needs:[2] feeds:[] }",
154
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1] feeds:[3]; 3 needs:[2,3] feeds:[3] }",
155
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1,2] feeds:[2]; 3 needs:[] feeds:[] }",
156
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1,2] feeds:[2]; 3 needs:[3] feeds:[3] }",
157
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1,2,3] feeds:[2]; 3 needs:[] feeds:[2] }",
158
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1,2,3] feeds:[2]; 3 needs:[3] feeds:[2,3] }",
159
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1,2] feeds:[2,3]; 3 needs:[2] feeds:[] }",
160
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1,2] feeds:[2,3]; 3 needs:[2,3] feeds:[3] }",
161
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1] feeds:[]; 3 needs:[] feeds:[1] }",
162
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1] feeds:[]; 3 needs:[3] feeds:[1,3] }",
163
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1,3] feeds:[]; 3 needs:[] feeds:[1,2] }",
164
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1,3] feeds:[]; 3 needs:[3] feeds:[1,2,3] }",
165
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1] feeds:[3]; 3 needs:[2] feeds:[1] }",
166
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1] feeds:[3]; 3 needs:[2,3] feeds:[1,3] }",
167
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1,2] feeds:[2]; 3 needs:[] feeds:[1] }",
168
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1,2] feeds:[2]; 3 needs:[3] feeds:[1,3] }",
169
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1,2,3] feeds:[2]; 3 needs:[] feeds:[1,2] }",
170
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1,2,3] feeds:[2]; 3 needs:[3] feeds:[1,2,3] }",
171
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1,2] feeds:[2,3]; 3 needs:[2] feeds:[1] }",
172
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1,2] feeds:[2,3]; 3 needs:[2,3] feeds:[1,3] }",
173
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1] feeds:[]; 3 needs:[1] feeds:[] }",
174
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1] feeds:[]; 3 needs:[1,3] feeds:[3] }",
175
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1,3] feeds:[]; 3 needs:[1] feeds:[2] }",
176
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1,3] feeds:[]; 3 needs:[1,3] feeds:[2,3] }",
177
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1] feeds:[3]; 3 needs:[1,2] feeds:[] }",
178
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1] feeds:[3]; 3 needs:[1,2,3] feeds:[3] }",
179
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1,2] feeds:[2]; 3 needs:[1] feeds:[] }",
180
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1,2] feeds:[2]; 3 needs:[1,3] feeds:[3] }",
181
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1,2,3] feeds:[2]; 3 needs:[1] feeds:[2] }",
182
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1,2,3] feeds:[2]; 3 needs:[1,3] feeds:[2,3] }",
183
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1,2] feeds:[2,3]; 3 needs:[1,2] feeds:[] }",
184
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1,2] feeds:[2,3]; 3 needs:[1,2,3] feeds:[3] }",
185
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[] feeds:[]; 3 needs:[] feeds:[] }",
186
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[] feeds:[]; 3 needs:[3] feeds:[3] }",
187
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[3] feeds:[]; 3 needs:[] feeds:[2] }",
188
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[3] feeds:[]; 3 needs:[3] feeds:[2,3] }",
189
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[] feeds:[3]; 3 needs:[2] feeds:[] }",
190
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[] feeds:[3]; 3 needs:[2,3] feeds:[3] }",
191
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[2] feeds:[2]; 3 needs:[] feeds:[] }",
192
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[2] feeds:[2]; 3 needs:[3] feeds:[3] }",
193
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[2,3] feeds:[2]; 3 needs:[] feeds:[2] }",
194
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[2,3] feeds:[2]; 3 needs:[3] feeds:[2,3] }",
195
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[2] feeds:[2,3]; 3 needs:[2] feeds:[] }",
196
+ "{ 1 needs:[1] feeds:[1]; 2 needs:[2] feeds:[2,3]; 3 needs:[2,3] feeds:[3] }",
197
+ "{ 1 needs:[1,3] feeds:[1]; 2 needs:[] feeds:[]; 3 needs:[] feeds:[1] }",
198
+ "{ 1 needs:[1,3] feeds:[1]; 2 needs:[] feeds:[]; 3 needs:[3] feeds:[1,3] }",
199
+ "{ 1 needs:[1,3] feeds:[1]; 2 needs:[3] feeds:[]; 3 needs:[] feeds:[1,2] }",
200
+ "{ 1 needs:[1,3] feeds:[1]; 2 needs:[3] feeds:[]; 3 needs:[3] feeds:[1,2,3] }",
201
+ "{ 1 needs:[1,3] feeds:[1]; 2 needs:[] feeds:[3]; 3 needs:[2] feeds:[1] }",
202
+ "{ 1 needs:[1,3] feeds:[1]; 2 needs:[] feeds:[3]; 3 needs:[2,3] feeds:[1,3] }",
203
+ "{ 1 needs:[1,3] feeds:[1]; 2 needs:[2] feeds:[2]; 3 needs:[] feeds:[1] }",
204
+ "{ 1 needs:[1,3] feeds:[1]; 2 needs:[2] feeds:[2]; 3 needs:[3] feeds:[1,3] }",
205
+ "{ 1 needs:[1,3] feeds:[1]; 2 needs:[2,3] feeds:[2]; 3 needs:[] feeds:[1,2] }",
206
+ "{ 1 needs:[1,3] feeds:[1]; 2 needs:[2,3] feeds:[2]; 3 needs:[3] feeds:[1,2,3] }",
207
+ "{ 1 needs:[1,3] feeds:[1]; 2 needs:[2] feeds:[2,3]; 3 needs:[2] feeds:[1] }",
208
+ "{ 1 needs:[1,3] feeds:[1]; 2 needs:[2] feeds:[2,3]; 3 needs:[2,3] feeds:[1,3] }",
209
+ "{ 1 needs:[1] feeds:[1,3]; 2 needs:[] feeds:[]; 3 needs:[1] feeds:[] }",
210
+ "{ 1 needs:[1] feeds:[1,3]; 2 needs:[] feeds:[]; 3 needs:[1,3] feeds:[3] }",
211
+ "{ 1 needs:[1] feeds:[1,3]; 2 needs:[3] feeds:[]; 3 needs:[1] feeds:[2] }",
212
+ "{ 1 needs:[1] feeds:[1,3]; 2 needs:[3] feeds:[]; 3 needs:[1,3] feeds:[2,3] }",
213
+ "{ 1 needs:[1] feeds:[1,3]; 2 needs:[] feeds:[3]; 3 needs:[1,2] feeds:[] }",
214
+ "{ 1 needs:[1] feeds:[1,3]; 2 needs:[] feeds:[3]; 3 needs:[1,2,3] feeds:[3] }",
215
+ "{ 1 needs:[1] feeds:[1,3]; 2 needs:[2] feeds:[2]; 3 needs:[1] feeds:[] }",
216
+ "{ 1 needs:[1] feeds:[1,3]; 2 needs:[2] feeds:[2]; 3 needs:[1,3] feeds:[3] }",
217
+ "{ 1 needs:[1] feeds:[1,3]; 2 needs:[2,3] feeds:[2]; 3 needs:[1] feeds:[2] }",
218
+ "{ 1 needs:[1] feeds:[1,3]; 2 needs:[2,3] feeds:[2]; 3 needs:[1,3] feeds:[2,3] }",
219
+ "{ 1 needs:[1] feeds:[1,3]; 2 needs:[2] feeds:[2,3]; 3 needs:[1,2] feeds:[] }",
220
+ "{ 1 needs:[1] feeds:[1,3]; 2 needs:[2] feeds:[2,3]; 3 needs:[1,2,3] feeds:[3] }",
221
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[] feeds:[1]; 3 needs:[] feeds:[] }",
222
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[] feeds:[1]; 3 needs:[3] feeds:[3] }",
223
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[3] feeds:[1]; 3 needs:[] feeds:[2] }",
224
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[3] feeds:[1]; 3 needs:[3] feeds:[2,3] }",
225
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[] feeds:[1,3]; 3 needs:[2] feeds:[] }",
226
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[] feeds:[1,3]; 3 needs:[2,3] feeds:[3] }",
227
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[2] feeds:[1,2]; 3 needs:[] feeds:[] }",
228
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[2] feeds:[1,2]; 3 needs:[3] feeds:[3] }",
229
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[2,3] feeds:[1,2]; 3 needs:[] feeds:[2] }",
230
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[2,3] feeds:[1,2]; 3 needs:[3] feeds:[2,3] }",
231
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[2] feeds:[1,2,3]; 3 needs:[2] feeds:[] }",
232
+ "{ 1 needs:[1,2] feeds:[1]; 2 needs:[2] feeds:[1,2,3]; 3 needs:[2,3] feeds:[3] }",
233
+ "{ 1 needs:[1,2,3] feeds:[1]; 2 needs:[] feeds:[1]; 3 needs:[] feeds:[1] }",
234
+ "{ 1 needs:[1,2,3] feeds:[1]; 2 needs:[] feeds:[1]; 3 needs:[3] feeds:[1,3] }",
235
+ "{ 1 needs:[1,2,3] feeds:[1]; 2 needs:[3] feeds:[1]; 3 needs:[] feeds:[1,2] }",
236
+ "{ 1 needs:[1,2,3] feeds:[1]; 2 needs:[3] feeds:[1]; 3 needs:[3] feeds:[1,2,3] }",
237
+ "{ 1 needs:[1,2,3] feeds:[1]; 2 needs:[] feeds:[1,3]; 3 needs:[2] feeds:[1] }",
238
+ "{ 1 needs:[1,2,3] feeds:[1]; 2 needs:[] feeds:[1,3]; 3 needs:[2,3] feeds:[1,3] }",
239
+ "{ 1 needs:[1,2,3] feeds:[1]; 2 needs:[2] feeds:[1,2]; 3 needs:[] feeds:[1] }",
240
+ "{ 1 needs:[1,2,3] feeds:[1]; 2 needs:[2] feeds:[1,2]; 3 needs:[3] feeds:[1,3] }",
241
+ "{ 1 needs:[1,2,3] feeds:[1]; 2 needs:[2,3] feeds:[1,2]; 3 needs:[] feeds:[1,2] }",
242
+ "{ 1 needs:[1,2,3] feeds:[1]; 2 needs:[2,3] feeds:[1,2]; 3 needs:[3] feeds:[1,2,3] }",
243
+ "{ 1 needs:[1,2,3] feeds:[1]; 2 needs:[2] feeds:[1,2,3]; 3 needs:[2] feeds:[1] }",
244
+ "{ 1 needs:[1,2,3] feeds:[1]; 2 needs:[2] feeds:[1,2,3]; 3 needs:[2,3] feeds:[1,3] }",
245
+ "{ 1 needs:[1,2] feeds:[1,3]; 2 needs:[] feeds:[1]; 3 needs:[1] feeds:[] }",
246
+ "{ 1 needs:[1,2] feeds:[1,3]; 2 needs:[] feeds:[1]; 3 needs:[1,3] feeds:[3] }",
247
+ "{ 1 needs:[1,2] feeds:[1,3]; 2 needs:[3] feeds:[1]; 3 needs:[1] feeds:[2] }",
248
+ "{ 1 needs:[1,2] feeds:[1,3]; 2 needs:[3] feeds:[1]; 3 needs:[1,3] feeds:[2,3] }",
249
+ "{ 1 needs:[1,2] feeds:[1,3]; 2 needs:[] feeds:[1,3]; 3 needs:[1,2] feeds:[] }",
250
+ "{ 1 needs:[1,2] feeds:[1,3]; 2 needs:[] feeds:[1,3]; 3 needs:[1,2,3] feeds:[3] }",
251
+ "{ 1 needs:[1,2] feeds:[1,3]; 2 needs:[2] feeds:[1,2]; 3 needs:[1] feeds:[] }",
252
+ "{ 1 needs:[1,2] feeds:[1,3]; 2 needs:[2] feeds:[1,2]; 3 needs:[1,3] feeds:[3] }",
253
+ "{ 1 needs:[1,2] feeds:[1,3]; 2 needs:[2,3] feeds:[1,2]; 3 needs:[1] feeds:[2] }",
254
+ "{ 1 needs:[1,2] feeds:[1,3]; 2 needs:[2,3] feeds:[1,2]; 3 needs:[1,3] feeds:[2,3] }",
255
+ "{ 1 needs:[1,2] feeds:[1,3]; 2 needs:[2] feeds:[1,2,3]; 3 needs:[1,2] feeds:[] }",
256
+ "{ 1 needs:[1,2] feeds:[1,3]; 2 needs:[2] feeds:[1,2,3]; 3 needs:[1,2,3] feeds:[3] }",
257
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1] feeds:[]; 3 needs:[] feeds:[] }",
258
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1] feeds:[]; 3 needs:[3] feeds:[3] }",
259
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1,3] feeds:[]; 3 needs:[] feeds:[2] }",
260
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1,3] feeds:[]; 3 needs:[3] feeds:[2,3] }",
261
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1] feeds:[3]; 3 needs:[2] feeds:[] }",
262
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1] feeds:[3]; 3 needs:[2,3] feeds:[3] }",
263
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1,2] feeds:[2]; 3 needs:[] feeds:[] }",
264
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1,2] feeds:[2]; 3 needs:[3] feeds:[3] }",
265
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1,2,3] feeds:[2]; 3 needs:[] feeds:[2] }",
266
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1,2,3] feeds:[2]; 3 needs:[3] feeds:[2,3] }",
267
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1,2] feeds:[2,3]; 3 needs:[2] feeds:[] }",
268
+ "{ 1 needs:[1] feeds:[1,2]; 2 needs:[1,2] feeds:[2,3]; 3 needs:[2,3] feeds:[3] }",
269
+ "{ 1 needs:[1,3] feeds:[1,2]; 2 needs:[1] feeds:[]; 3 needs:[] feeds:[1] }",
270
+ "{ 1 needs:[1,3] feeds:[1,2]; 2 needs:[1] feeds:[]; 3 needs:[3] feeds:[1,3] }",
271
+ "{ 1 needs:[1,3] feeds:[1,2]; 2 needs:[1,3] feeds:[]; 3 needs:[] feeds:[1,2] }",
272
+ "{ 1 needs:[1,3] feeds:[1,2]; 2 needs:[1,3] feeds:[]; 3 needs:[3] feeds:[1,2,3] }",
273
+ "{ 1 needs:[1,3] feeds:[1,2]; 2 needs:[1] feeds:[3]; 3 needs:[2] feeds:[1] }",
274
+ "{ 1 needs:[1,3] feeds:[1,2]; 2 needs:[1] feeds:[3]; 3 needs:[2,3] feeds:[1,3] }",
275
+ "{ 1 needs:[1,3] feeds:[1,2]; 2 needs:[1,2] feeds:[2]; 3 needs:[] feeds:[1] }",
276
+ "{ 1 needs:[1,3] feeds:[1,2]; 2 needs:[1,2] feeds:[2]; 3 needs:[3] feeds:[1,3] }",
277
+ "{ 1 needs:[1,3] feeds:[1,2]; 2 needs:[1,2,3] feeds:[2]; 3 needs:[] feeds:[1,2] }",
278
+ "{ 1 needs:[1,3] feeds:[1,2]; 2 needs:[1,2,3] feeds:[2]; 3 needs:[3] feeds:[1,2,3] }",
279
+ "{ 1 needs:[1,3] feeds:[1,2]; 2 needs:[1,2] feeds:[2,3]; 3 needs:[2] feeds:[1] }",
280
+ "{ 1 needs:[1,3] feeds:[1,2]; 2 needs:[1,2] feeds:[2,3]; 3 needs:[2,3] feeds:[1,3] }",
281
+ "{ 1 needs:[1] feeds:[1,2,3]; 2 needs:[1] feeds:[]; 3 needs:[1] feeds:[] }",
282
+ "{ 1 needs:[1] feeds:[1,2,3]; 2 needs:[1] feeds:[]; 3 needs:[1,3] feeds:[3] }",
283
+ "{ 1 needs:[1] feeds:[1,2,3]; 2 needs:[1,3] feeds:[]; 3 needs:[1] feeds:[2] }",
284
+ "{ 1 needs:[1] feeds:[1,2,3]; 2 needs:[1,3] feeds:[]; 3 needs:[1,3] feeds:[2,3] }",
285
+ "{ 1 needs:[1] feeds:[1,2,3]; 2 needs:[1] feeds:[3]; 3 needs:[1,2] feeds:[] }",
286
+ "{ 1 needs:[1] feeds:[1,2,3]; 2 needs:[1] feeds:[3]; 3 needs:[1,2,3] feeds:[3] }",
287
+ "{ 1 needs:[1] feeds:[1,2,3]; 2 needs:[1,2] feeds:[2]; 3 needs:[1] feeds:[] }",
288
+ "{ 1 needs:[1] feeds:[1,2,3]; 2 needs:[1,2] feeds:[2]; 3 needs:[1,3] feeds:[3] }",
289
+ "{ 1 needs:[1] feeds:[1,2,3]; 2 needs:[1,2,3] feeds:[2]; 3 needs:[1] feeds:[2] }",
290
+ "{ 1 needs:[1] feeds:[1,2,3]; 2 needs:[1,2,3] feeds:[2]; 3 needs:[1,3] feeds:[2,3] }",
291
+ "{ 1 needs:[1] feeds:[1,2,3]; 2 needs:[1,2] feeds:[2,3]; 3 needs:[1,2] feeds:[] }",
292
+ "{ 1 needs:[1] feeds:[1,2,3]; 2 needs:[1,2] feeds:[2,3]; 3 needs:[1,2,3] feeds:[3] }",
293
+ ]
294
+ end
295
+
296
+ end
297
+
298
+ context ".acyclic_from_node_count" do
299
+
300
+ it "builds 0-node graphs" do
301
+ expect(Topiary::DirectedGraph.acyclic_from_node_count(0).map(&:to_s)).to eq [
302
+ "{ }",
303
+ ]
304
+ end
305
+
306
+ it "builds 1-node graphs" do
307
+ expect(Topiary::DirectedGraph.acyclic_from_node_count(1).map(&:to_s)).to eq [
308
+ "{ 1 needs:[] feeds:[] }",
309
+ ]
310
+ end
311
+
312
+ it "builds 2-node graphs" do
313
+ expect(Topiary::DirectedGraph.acyclic_from_node_count(2).map(&:to_s)).to eq [
314
+ "{ 1 needs:[] feeds:[]; 2 needs:[] feeds:[] }",
315
+ "{ 1 needs:[2] feeds:[]; 2 needs:[] feeds:[1] }",
316
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1] feeds:[] }",
317
+ ]
318
+ end
319
+
320
+ it "builds 3-node graphs" do
321
+ expect(Topiary::DirectedGraph.acyclic_from_node_count(3).map(&:to_s)).to eq [
322
+ "{ 1 needs:[] feeds:[]; 2 needs:[] feeds:[]; 3 needs:[] feeds:[] }",
323
+ "{ 1 needs:[] feeds:[]; 2 needs:[3] feeds:[]; 3 needs:[] feeds:[2] }",
324
+ "{ 1 needs:[] feeds:[]; 2 needs:[] feeds:[3]; 3 needs:[2] feeds:[] }",
325
+ "{ 1 needs:[3] feeds:[]; 2 needs:[] feeds:[]; 3 needs:[] feeds:[1] }",
326
+ "{ 1 needs:[3] feeds:[]; 2 needs:[3] feeds:[]; 3 needs:[] feeds:[1,2] }",
327
+ "{ 1 needs:[3] feeds:[]; 2 needs:[] feeds:[3]; 3 needs:[2] feeds:[1] }",
328
+ "{ 1 needs:[] feeds:[3]; 2 needs:[] feeds:[]; 3 needs:[1] feeds:[] }",
329
+ "{ 1 needs:[] feeds:[3]; 2 needs:[3] feeds:[]; 3 needs:[1] feeds:[2] }",
330
+ "{ 1 needs:[] feeds:[3]; 2 needs:[] feeds:[3]; 3 needs:[1,2] feeds:[] }",
331
+ "{ 1 needs:[2] feeds:[]; 2 needs:[] feeds:[1]; 3 needs:[] feeds:[] }",
332
+ "{ 1 needs:[2] feeds:[]; 2 needs:[3] feeds:[1]; 3 needs:[] feeds:[2] }",
333
+ "{ 1 needs:[2] feeds:[]; 2 needs:[] feeds:[1,3]; 3 needs:[2] feeds:[] }",
334
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[] feeds:[1]; 3 needs:[] feeds:[1] }",
335
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[3] feeds:[1]; 3 needs:[] feeds:[1,2] }",
336
+ "{ 1 needs:[2,3] feeds:[]; 2 needs:[] feeds:[1,3]; 3 needs:[2] feeds:[1] }",
337
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[] feeds:[1]; 3 needs:[1] feeds:[] }",
338
+ "{ 1 needs:[2] feeds:[3]; 2 needs:[] feeds:[1,3]; 3 needs:[1,2] feeds:[] }",
339
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1] feeds:[]; 3 needs:[] feeds:[] }",
340
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1,3] feeds:[]; 3 needs:[] feeds:[2] }",
341
+ "{ 1 needs:[] feeds:[2]; 2 needs:[1] feeds:[3]; 3 needs:[2] feeds:[] }",
342
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1] feeds:[]; 3 needs:[] feeds:[1] }",
343
+ "{ 1 needs:[3] feeds:[2]; 2 needs:[1,3] feeds:[]; 3 needs:[] feeds:[1,2] }",
344
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1] feeds:[]; 3 needs:[1] feeds:[] }",
345
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1,3] feeds:[]; 3 needs:[1] feeds:[2] }",
346
+ "{ 1 needs:[] feeds:[2,3]; 2 needs:[1] feeds:[3]; 3 needs:[1,2] feeds:[] }",
347
+ ]
348
+ end
349
+
350
+ end
351
+
352
+ context "#edges" do
353
+ it "gives edges for a 0-node graph" do
354
+ expect(Topiary::DirectedGraph.new.edges.to_a).to eq []
355
+ end
356
+
357
+ it "gives edges for a 1-node graph" do
358
+ n1 = Topiary::Node.new "n1"
359
+ g = Topiary::DirectedGraph.new [n1]
360
+ expect(g.edges.map(&:to_s)).to eq []
361
+ g.add_edge! n1, n1
362
+ expect(g.edges.map(&:to_s)).to eq ["n1->n1"]
363
+ end
364
+
365
+ it "gives edges for a 2-node graph" do
366
+ n1 = Topiary::Node.new "n1"
367
+ n2 = Topiary::Node.new "n2"
368
+ g = Topiary::DirectedGraph.new [n1, n2]
369
+ expect(g.edges.map(&:to_s)).to eq []
370
+ g.add_edge! n1, n1
371
+ g.add_edge! n1, n2
372
+ expect(g.edges.map(&:to_s)).to eq ["n1->n1", "n1->n2"]
373
+ g.add_edge! n2, n1
374
+ expect(g.edges.map(&:to_s)).to eq ["n1->n1", "n1->n2", "n2->n1"]
375
+ end
376
+ end
377
+
378
+ context "#topologically_equivalent?" do
379
+ it "is false for graphs with different node counts" do
380
+ g1 = Topiary::DirectedGraph.new
381
+ n1 = Topiary::Node.new "n1"
382
+ g2 = Topiary::DirectedGraph.new [n1]
383
+ expect(g1).not_to be_topologically_equivalent(g2)
384
+ expect(g2).not_to be_topologically_equivalent(g1)
385
+ end
386
+
387
+ it "is false for graphs with different edge counts" do
388
+ n1 = Topiary::Node.new "n1"
389
+ g1 = Topiary::DirectedGraph.new [n1]
390
+ g1.add_edge! n1, n1
391
+ n2 = Topiary::Node.new "n2"
392
+ g2 = Topiary::DirectedGraph.new [n2]
393
+ expect(g1).not_to be_topologically_equivalent(g2)
394
+ expect(g2).not_to be_topologically_equivalent(g1)
395
+ end
396
+
397
+ it "is true for graphs with 0 nodes" do
398
+ g1 = Topiary::DirectedGraph.new
399
+ g2 = Topiary::DirectedGraph.new
400
+ expect(g1).to be_topologically_equivalent(g1)
401
+ expect(g1).to be_topologically_equivalent(g2)
402
+ expect(g2).to be_topologically_equivalent(g1)
403
+ end
404
+
405
+ it "is true for graphs with 1 node and 0 edges" do
406
+ n1 = Topiary::Node.new "n1"
407
+ g1 = Topiary::DirectedGraph.new [n1]
408
+ n2 = Topiary::Node.new "n2"
409
+ g2 = Topiary::DirectedGraph.new [n2]
410
+ expect(g1).to be_topologically_equivalent(g1)
411
+ expect(g1).to be_topologically_equivalent(g2)
412
+ expect(g2).to be_topologically_equivalent(g1)
413
+ end
414
+
415
+ it "is true for graphs with 1 node and 1 edge" do
416
+ n1 = Topiary::Node.new "n1"
417
+ g1 = Topiary::DirectedGraph.new [n1]
418
+ g1.add_edge! n1, n1
419
+ n2 = Topiary::Node.new "n2"
420
+ g2 = Topiary::DirectedGraph.new [n2]
421
+ g2.add_edge! n2, n2
422
+ expect(g1).to be_topologically_equivalent(g1)
423
+ expect(g1).to be_topologically_equivalent(g2)
424
+ expect(g2).to be_topologically_equivalent(g1)
425
+ end
426
+
427
+ it "is true for graphs with 2 nodes and 0 edges" do
428
+ n1 = Topiary::Node.new "n1"
429
+ n2 = Topiary::Node.new "n2"
430
+ g1 = Topiary::DirectedGraph.new [n1, n2]
431
+ n3 = Topiary::Node.new "n3"
432
+ n4 = Topiary::Node.new "n4"
433
+ g2 = Topiary::DirectedGraph.new [n3, n4]
434
+ expect(g1).to be_topologically_equivalent(g1)
435
+ expect(g1).to be_topologically_equivalent(g2)
436
+ expect(g2).to be_topologically_equivalent(g1)
437
+ end
438
+
439
+ it "is true for some graphs with 2 nodes and 1 edge" do
440
+ n1 = Topiary::Node.new "n1"
441
+ n2 = Topiary::Node.new "n2"
442
+ g1 = Topiary::DirectedGraph.new [n1, n2]
443
+ g1.add_edge! n1, n1
444
+ n3 = Topiary::Node.new "n3"
445
+ n4 = Topiary::Node.new "n4"
446
+ g2 = Topiary::DirectedGraph.new [n3, n4]
447
+ g2.add_edge! n3, n3
448
+ expect(g1).to be_topologically_equivalent(g1)
449
+ expect(g1).to be_topologically_equivalent(g2)
450
+ expect(g2).to be_topologically_equivalent(g1)
451
+
452
+ n1 = Topiary::Node.new "n1"
453
+ n2 = Topiary::Node.new "n2"
454
+ g1 = Topiary::DirectedGraph.new [n1, n2]
455
+ g1.add_edge! n1, n2
456
+ n3 = Topiary::Node.new "n3"
457
+ n4 = Topiary::Node.new "n4"
458
+ g2 = Topiary::DirectedGraph.new [n3, n4]
459
+ g2.add_edge! n3, n4
460
+ expect(g1).to be_topologically_equivalent(g1)
461
+ expect(g1).to be_topologically_equivalent(g2)
462
+ expect(g2).to be_topologically_equivalent(g1)
463
+ end
464
+
465
+ it "is false for some graphs with 2 nodes and 1 edge" do
466
+ n1 = Topiary::Node.new "n1"
467
+ n2 = Topiary::Node.new "n2"
468
+ g1 = Topiary::DirectedGraph.new [n1, n2]
469
+ g1.add_edge! n1, n1
470
+ n3 = Topiary::Node.new "n3"
471
+ n4 = Topiary::Node.new "n4"
472
+ g2 = Topiary::DirectedGraph.new [n3, n4]
473
+ g2.add_edge! n3, n4
474
+ expect(g1).not_to be_topologically_equivalent(g2)
475
+ expect(g2).not_to be_topologically_equivalent(g1)
476
+ end
477
+
478
+ it "is true for some graphs with 2 nodes and 2 edges" do
479
+ n1 = Topiary::Node.new "n1"
480
+ n2 = Topiary::Node.new "n2"
481
+ g1 = Topiary::DirectedGraph.new [n1, n2]
482
+ g1.add_edge! n1, n1
483
+ g1.add_edge! n2, n2
484
+ n3 = Topiary::Node.new "n3"
485
+ n4 = Topiary::Node.new "n4"
486
+ g2 = Topiary::DirectedGraph.new [n3, n4]
487
+ g2.add_edge! n3, n3
488
+ g2.add_edge! n4, n4
489
+ expect(g1).to be_topologically_equivalent(g1)
490
+ expect(g1).to be_topologically_equivalent(g2)
491
+ expect(g2).to be_topologically_equivalent(g1)
492
+
493
+ n1 = Topiary::Node.new "n1"
494
+ n2 = Topiary::Node.new "n2"
495
+ g1 = Topiary::DirectedGraph.new [n1, n2]
496
+ g1.add_edge! n1, n1
497
+ g1.add_edge! n1, n2
498
+ n3 = Topiary::Node.new "n3"
499
+ n4 = Topiary::Node.new "n4"
500
+ g2 = Topiary::DirectedGraph.new [n3, n4]
501
+ g2.add_edge! n3, n3
502
+ g2.add_edge! n3, n4
503
+ expect(g1).to be_topologically_equivalent(g1)
504
+ expect(g1).to be_topologically_equivalent(g2)
505
+ expect(g2).to be_topologically_equivalent(g1)
506
+
507
+ n1 = Topiary::Node.new "n1"
508
+ n2 = Topiary::Node.new "n2"
509
+ g1 = Topiary::DirectedGraph.new [n1, n2]
510
+ g1.add_edge! n1, n2
511
+ g1.add_edge! n2, n1
512
+ n3 = Topiary::Node.new "n3"
513
+ n4 = Topiary::Node.new "n4"
514
+ g2 = Topiary::DirectedGraph.new [n3, n4]
515
+ g2.add_edge! n3, n4
516
+ g2.add_edge! n4, n3
517
+ expect(g1).to be_topologically_equivalent(g1)
518
+ expect(g1).to be_topologically_equivalent(g2)
519
+ expect(g2).to be_topologically_equivalent(g1)
520
+ end
521
+
522
+ it "is false for some graphs with 2 nodes and 2 edges" do
523
+ n1 = Topiary::Node.new "n1"
524
+ n2 = Topiary::Node.new "n2"
525
+ g1 = Topiary::DirectedGraph.new [n1, n2]
526
+ g1.add_edge! n1, n1
527
+ g1.add_edge! n1, n2
528
+ n3 = Topiary::Node.new "n3"
529
+ n4 = Topiary::Node.new "n4"
530
+ g2 = Topiary::DirectedGraph.new [n3, n4]
531
+ g2.add_edge! n3, n4
532
+ g2.add_edge! n4, n3
533
+ expect(g1).not_to be_topologically_equivalent(g2)
534
+ expect(g2).not_to be_topologically_equivalent(g1)
535
+ end
536
+
537
+ it "is true for graphs with 3 nodes and 0 edges" do
538
+ n1 = Topiary::Node.new "n1"
539
+ n2 = Topiary::Node.new "n2"
540
+ n3 = Topiary::Node.new "n3"
541
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
542
+ n4 = Topiary::Node.new "n4"
543
+ n5 = Topiary::Node.new "n5"
544
+ n6 = Topiary::Node.new "n6"
545
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
546
+ expect(g1).to be_topologically_equivalent(g1)
547
+ expect(g1).to be_topologically_equivalent(g2)
548
+ expect(g2).to be_topologically_equivalent(g1)
549
+ end
550
+
551
+ it "is true for some graphs with 3 nodes and 1 edge" do
552
+ n1 = Topiary::Node.new "n1"
553
+ n2 = Topiary::Node.new "n2"
554
+ n3 = Topiary::Node.new "n3"
555
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
556
+ g1.add_edge! n1, n1
557
+ n4 = Topiary::Node.new "n4"
558
+ n5 = Topiary::Node.new "n5"
559
+ n6 = Topiary::Node.new "n6"
560
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
561
+ g1.add_edge! n5, n5
562
+ expect(g1).to be_topologically_equivalent(g1)
563
+ expect(g1).to be_topologically_equivalent(g2)
564
+ expect(g2).to be_topologically_equivalent(g1)
565
+
566
+ n1 = Topiary::Node.new "n1"
567
+ n2 = Topiary::Node.new "n2"
568
+ n3 = Topiary::Node.new "n3"
569
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
570
+ g1.add_edge! n1, n3
571
+ n4 = Topiary::Node.new "n4"
572
+ n5 = Topiary::Node.new "n5"
573
+ n6 = Topiary::Node.new "n6"
574
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
575
+ g1.add_edge! n6, n5
576
+ expect(g1).to be_topologically_equivalent(g1)
577
+ expect(g1).to be_topologically_equivalent(g2)
578
+ expect(g2).to be_topologically_equivalent(g1)
579
+ end
580
+
581
+ it "is false for some graphs with 3 nodes and 1 edge" do
582
+ n1 = Topiary::Node.new "n1"
583
+ n2 = Topiary::Node.new "n2"
584
+ n3 = Topiary::Node.new "n3"
585
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
586
+ g1.add_edge! n1, n1
587
+ n4 = Topiary::Node.new "n4"
588
+ n5 = Topiary::Node.new "n5"
589
+ n6 = Topiary::Node.new "n6"
590
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
591
+ g1.add_edge! n6, n5
592
+ expect(g1).not_to be_topologically_equivalent(g2)
593
+ expect(g2).not_to be_topologically_equivalent(g1)
594
+ end
595
+
596
+ it "is true for some graphs with 3 nodes and 2 edges" do
597
+ n1 = Topiary::Node.new "n1"
598
+ n2 = Topiary::Node.new "n2"
599
+ n3 = Topiary::Node.new "n3"
600
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
601
+ g1.add_edge! n1, n1
602
+ g1.add_edge! n2, n2
603
+ n4 = Topiary::Node.new "n4"
604
+ n5 = Topiary::Node.new "n5"
605
+ n6 = Topiary::Node.new "n6"
606
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
607
+ g1.add_edge! n4, n4
608
+ g1.add_edge! n6, n6
609
+ expect(g1).to be_topologically_equivalent(g1)
610
+ expect(g1).to be_topologically_equivalent(g2)
611
+ expect(g2).to be_topologically_equivalent(g1)
612
+
613
+ n1 = Topiary::Node.new "n1"
614
+ n2 = Topiary::Node.new "n2"
615
+ n3 = Topiary::Node.new "n3"
616
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
617
+ g1.add_edge! n1, n1
618
+ g1.add_edge! n1, n3
619
+ n4 = Topiary::Node.new "n4"
620
+ n5 = Topiary::Node.new "n5"
621
+ n6 = Topiary::Node.new "n6"
622
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
623
+ g1.add_edge! n6, n6
624
+ g1.add_edge! n6, n5
625
+ expect(g1).to be_topologically_equivalent(g1)
626
+ expect(g1).to be_topologically_equivalent(g2)
627
+ expect(g2).to be_topologically_equivalent(g1)
628
+
629
+ n1 = Topiary::Node.new "n1"
630
+ n2 = Topiary::Node.new "n2"
631
+ n3 = Topiary::Node.new "n3"
632
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
633
+ g1.add_edge! n1, n1
634
+ g1.add_edge! n3, n1
635
+ n4 = Topiary::Node.new "n4"
636
+ n5 = Topiary::Node.new "n5"
637
+ n6 = Topiary::Node.new "n6"
638
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
639
+ g1.add_edge! n4, n4
640
+ g1.add_edge! n5, n4
641
+ expect(g1).to be_topologically_equivalent(g1)
642
+ expect(g1).to be_topologically_equivalent(g2)
643
+ expect(g2).to be_topologically_equivalent(g1)
644
+
645
+ n1 = Topiary::Node.new "n1"
646
+ n2 = Topiary::Node.new "n2"
647
+ n3 = Topiary::Node.new "n3"
648
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
649
+ g1.add_edge! n1, n1
650
+ g1.add_edge! n2, n3
651
+ n4 = Topiary::Node.new "n4"
652
+ n5 = Topiary::Node.new "n5"
653
+ n6 = Topiary::Node.new "n6"
654
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
655
+ g1.add_edge! n6, n6
656
+ g1.add_edge! n4, n5
657
+ expect(g1).to be_topologically_equivalent(g1)
658
+ expect(g1).to be_topologically_equivalent(g2)
659
+ expect(g2).to be_topologically_equivalent(g1)
660
+
661
+ n1 = Topiary::Node.new "n1"
662
+ n2 = Topiary::Node.new "n2"
663
+ n3 = Topiary::Node.new "n3"
664
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
665
+ g1.add_edge! n1, n2
666
+ g1.add_edge! n1, n3
667
+ n4 = Topiary::Node.new "n4"
668
+ n5 = Topiary::Node.new "n5"
669
+ n6 = Topiary::Node.new "n6"
670
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
671
+ g1.add_edge! n5, n6
672
+ g1.add_edge! n5, n4
673
+ expect(g1).to be_topologically_equivalent(g1)
674
+ expect(g1).to be_topologically_equivalent(g2)
675
+ expect(g2).to be_topologically_equivalent(g1)
676
+
677
+ n1 = Topiary::Node.new "n1"
678
+ n2 = Topiary::Node.new "n2"
679
+ n3 = Topiary::Node.new "n3"
680
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
681
+ g1.add_edge! n2, n1
682
+ g1.add_edge! n3, n1
683
+ n4 = Topiary::Node.new "n4"
684
+ n5 = Topiary::Node.new "n5"
685
+ n6 = Topiary::Node.new "n6"
686
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
687
+ g1.add_edge! n5, n4
688
+ g1.add_edge! n6, n4
689
+ expect(g1).to be_topologically_equivalent(g1)
690
+ expect(g1).to be_topologically_equivalent(g2)
691
+ expect(g2).to be_topologically_equivalent(g1)
692
+
693
+ n1 = Topiary::Node.new "n1"
694
+ n2 = Topiary::Node.new "n2"
695
+ n3 = Topiary::Node.new "n3"
696
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
697
+ g1.add_edge! n1, n2
698
+ g1.add_edge! n2, n3
699
+ n4 = Topiary::Node.new "n4"
700
+ n5 = Topiary::Node.new "n5"
701
+ n6 = Topiary::Node.new "n6"
702
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
703
+ g1.add_edge! n5, n6
704
+ g1.add_edge! n6, n4
705
+ expect(g1).to be_topologically_equivalent(g1)
706
+ expect(g1).to be_topologically_equivalent(g2)
707
+ expect(g2).to be_topologically_equivalent(g1)
708
+ end
709
+
710
+ it "is false for some graphs with 3 nodes and 2 edges" do
711
+ n1 = Topiary::Node.new "n1"
712
+ n2 = Topiary::Node.new "n2"
713
+ n3 = Topiary::Node.new "n3"
714
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
715
+ g1.add_edge! n1, n1
716
+ g1.add_edge! n2, n1
717
+ n4 = Topiary::Node.new "n4"
718
+ n5 = Topiary::Node.new "n5"
719
+ n6 = Topiary::Node.new "n6"
720
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
721
+ g1.add_edge! n5, n4
722
+ g1.add_edge! n6, n4
723
+ expect(g1).not_to be_topologically_equivalent(g2)
724
+ expect(g2).not_to be_topologically_equivalent(g1)
725
+
726
+ n1 = Topiary::Node.new "n1"
727
+ n2 = Topiary::Node.new "n2"
728
+ n3 = Topiary::Node.new "n3"
729
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
730
+ g1.add_edge! n1, n1
731
+ g1.add_edge! n1, n2
732
+ n4 = Topiary::Node.new "n4"
733
+ n5 = Topiary::Node.new "n5"
734
+ n6 = Topiary::Node.new "n6"
735
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
736
+ g1.add_edge! n5, n4
737
+ g1.add_edge! n6, n4
738
+ expect(g1).not_to be_topologically_equivalent(g2)
739
+ expect(g2).not_to be_topologically_equivalent(g1)
740
+
741
+ # not exhaustive...
742
+ end
743
+
744
+ it "is true for some graphs with 3 nodes and 3 edges" do
745
+ n1 = Topiary::Node.new "n1"
746
+ n2 = Topiary::Node.new "n2"
747
+ n3 = Topiary::Node.new "n3"
748
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
749
+ g1.add_edge! n1, n2
750
+ g1.add_edge! n2, n3
751
+ g1.add_edge! n3, n1
752
+ n4 = Topiary::Node.new "n4"
753
+ n5 = Topiary::Node.new "n5"
754
+ n6 = Topiary::Node.new "n6"
755
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
756
+ g1.add_edge! n5, n6
757
+ g1.add_edge! n6, n4
758
+ g1.add_edge! n4, n5
759
+ expect(g1).to be_topologically_equivalent(g1)
760
+ expect(g1).to be_topologically_equivalent(g2)
761
+ expect(g2).to be_topologically_equivalent(g1)
762
+
763
+ # not exhaustive...
764
+ end
765
+
766
+ it "is false for some graphs with 3 nodes and 3 edges" do
767
+ n1 = Topiary::Node.new "n1"
768
+ n2 = Topiary::Node.new "n2"
769
+ n3 = Topiary::Node.new "n3"
770
+ g1 = Topiary::DirectedGraph.new [n1, n2, n3]
771
+ g1.add_edge! n1, n2
772
+ g1.add_edge! n2, n3
773
+ g1.add_edge! n3, n1
774
+ n4 = Topiary::Node.new "n4"
775
+ n5 = Topiary::Node.new "n5"
776
+ n6 = Topiary::Node.new "n6"
777
+ g2 = Topiary::DirectedGraph.new [n4, n5, n6]
778
+ g1.add_edge! n4, n5
779
+ g1.add_edge! n5, n6
780
+ g1.add_edge! n4, n6
781
+ expect(g1).not_to be_topologically_equivalent(g2)
782
+ expect(g2).not_to be_topologically_equivalent(g1)
783
+
784
+ # not exhaustive...
785
+ end
786
+ end
787
+
788
+ context ".topologically_distinct" do
789
+ it "find all graphs with 3 nodes and 2 edges" do
790
+ graphs = Topiary::DirectedGraph.topologically_distinct(
791
+ Topiary::DirectedGraph.all_from_node_count(3).select{|g| g.edges.count == 2}
792
+ ).map(&:to_s)
793
+ expect(graphs).to eq [
794
+ "{ 1 needs:[] feeds:[]; 2 needs:[3] feeds:[]; 3 needs:[3] feeds:[2,3] }",
795
+ "{ 1 needs:[] feeds:[]; 2 needs:[] feeds:[3]; 3 needs:[2,3] feeds:[3] }",
796
+ "{ 1 needs:[] feeds:[]; 2 needs:[2] feeds:[2]; 3 needs:[3] feeds:[3] }",
797
+ "{ 1 needs:[3] feeds:[]; 2 needs:[3] feeds:[]; 3 needs:[] feeds:[1,2] }",
798
+ "{ 1 needs:[3] feeds:[]; 2 needs:[] feeds:[3]; 3 needs:[2] feeds:[1] }",
799
+ "{ 1 needs:[3] feeds:[]; 2 needs:[2] feeds:[2]; 3 needs:[] feeds:[1] }",
800
+ "{ 1 needs:[] feeds:[3]; 2 needs:[] feeds:[3]; 3 needs:[1,2] feeds:[] }",
801
+ ]
802
+ end
803
+
804
+ it "find acyclic graphs with 4 nodes and 0 edges" do
805
+ graphs = Topiary::DirectedGraph.topologically_distinct(
806
+ Topiary::DirectedGraph.acyclic_from_node_count(4).select{|g| g.edges.count == 0}
807
+ ).map(&:to_s)
808
+ expect(graphs).to eq [
809
+ "{ 1 needs:[] feeds:[]; 2 needs:[] feeds:[]; 3 needs:[] feeds:[]; 4 needs:[] feeds:[] }",
810
+ ]
811
+ end
812
+ it "find acyclic graphs with 4 nodes and 1 edge" do
813
+ graphs = Topiary::DirectedGraph.topologically_distinct(
814
+ Topiary::DirectedGraph.acyclic_from_node_count(4).select{|g| g.edges.count == 1}
815
+ ).map(&:to_s)
816
+ expect(graphs).to eq [
817
+ "{ 1 needs:[] feeds:[]; 2 needs:[] feeds:[]; 3 needs:[4] feeds:[]; 4 needs:[] feeds:[3] }",
818
+ ]
819
+ end
820
+
821
+ it "find acyclic graphs with 4 nodes and 2 edges" do
822
+ graphs = Topiary::DirectedGraph.topologically_distinct(
823
+ Topiary::DirectedGraph.acyclic_from_node_count(4).select{|g| g.edges.count == 2}
824
+ ).map(&:to_s)
825
+ expect(graphs).to eq [
826
+ "{ 1 needs:[] feeds:[]; 2 needs:[4] feeds:[]; 3 needs:[4] feeds:[]; 4 needs:[] feeds:[2,3] }",
827
+ "{ 1 needs:[] feeds:[]; 2 needs:[4] feeds:[]; 3 needs:[] feeds:[4]; 4 needs:[3] feeds:[2] }",
828
+ "{ 1 needs:[] feeds:[]; 2 needs:[] feeds:[4]; 3 needs:[] feeds:[4]; 4 needs:[2,3] feeds:[] }",
829
+ "{ 1 needs:[4] feeds:[]; 2 needs:[3] feeds:[]; 3 needs:[] feeds:[2]; 4 needs:[] feeds:[1] }",
830
+ ]
831
+ end
832
+
833
+ it "find acyclic graphs with 4 nodes and 3 edges" do
834
+ graphs = Topiary::DirectedGraph.topologically_distinct(
835
+ Topiary::DirectedGraph.acyclic_from_node_count(4).select{|g| g.edges.count == 3}
836
+ ).map(&:to_s)
837
+ expect(graphs).to eq [
838
+ "{ 1 needs:[] feeds:[]; 2 needs:[3,4] feeds:[]; 3 needs:[4] feeds:[2]; 4 needs:[] feeds:[2,3] }",
839
+ "{ 1 needs:[4] feeds:[]; 2 needs:[4] feeds:[]; 3 needs:[4] feeds:[]; 4 needs:[] feeds:[1,2,3] }",
840
+ "{ 1 needs:[4] feeds:[]; 2 needs:[4] feeds:[]; 3 needs:[] feeds:[4]; 4 needs:[3] feeds:[1,2] }",
841
+
842
+ "{ 1 needs:[4] feeds:[]; 2 needs:[] feeds:[4]; 3 needs:[] feeds:[4]; 4 needs:[2,3] feeds:[1] }",
843
+ "{ 1 needs:[4] feeds:[]; 2 needs:[3] feeds:[]; 3 needs:[4] feeds:[2]; 4 needs:[] feeds:[1,3] }",
844
+ "{ 1 needs:[4] feeds:[]; 2 needs:[3,4] feeds:[]; 3 needs:[] feeds:[2]; 4 needs:[] feeds:[1,2] }",
845
+
846
+ "{ 1 needs:[4] feeds:[]; 2 needs:[3] feeds:[4]; 3 needs:[] feeds:[2]; 4 needs:[2] feeds:[1] }",
847
+ "{ 1 needs:[] feeds:[4]; 2 needs:[] feeds:[4]; 3 needs:[] feeds:[4]; 4 needs:[1,2,3] feeds:[] }",
848
+ "{ 1 needs:[] feeds:[4]; 2 needs:[3,4] feeds:[]; 3 needs:[] feeds:[2]; 4 needs:[1] feeds:[2] }",
849
+ ]
850
+ end
851
+
852
+ it "find acyclic graphs with 4 nodes and 4 edges" do
853
+ graphs = Topiary::DirectedGraph.topologically_distinct(
854
+ Topiary::DirectedGraph.acyclic_from_node_count(4).select{|g| g.edges.count == 4}
855
+ ).map(&:to_s)
856
+ expect(graphs).to eq [
857
+ "{ 1 needs:[4] feeds:[]; 2 needs:[3,4] feeds:[]; 3 needs:[4] feeds:[2]; 4 needs:[] feeds:[1,2,3] }",
858
+ "{ 1 needs:[4] feeds:[]; 2 needs:[3,4] feeds:[]; 3 needs:[] feeds:[2,4]; 4 needs:[3] feeds:[1,2] }",
859
+ "{ 1 needs:[4] feeds:[]; 2 needs:[3] feeds:[4]; 3 needs:[] feeds:[2,4]; 4 needs:[2,3] feeds:[1] }",
860
+
861
+ "{ 1 needs:[] feeds:[4]; 2 needs:[3,4] feeds:[]; 3 needs:[4] feeds:[2]; 4 needs:[1] feeds:[2,3] }",
862
+ "{ 1 needs:[] feeds:[4]; 2 needs:[3,4] feeds:[]; 3 needs:[] feeds:[2,4]; 4 needs:[1,3] feeds:[2] }",
863
+ "{ 1 needs:[] feeds:[4]; 2 needs:[3] feeds:[4]; 3 needs:[] feeds:[2,4]; 4 needs:[1,2,3] feeds:[] }",
864
+
865
+ "{ 1 needs:[3,4] feeds:[]; 2 needs:[3,4] feeds:[]; 3 needs:[] feeds:[1,2]; 4 needs:[] feeds:[1,2] }",
866
+ "{ 1 needs:[3,4] feeds:[]; 2 needs:[3] feeds:[4]; 3 needs:[] feeds:[1,2]; 4 needs:[2] feeds:[1] }",
867
+ "{ 1 needs:[3,4] feeds:[]; 2 needs:[] feeds:[3,4]; 3 needs:[2] feeds:[1]; 4 needs:[2] feeds:[1] }",
868
+ ]
869
+ end
870
+
871
+ it "find acyclic graphs with 4 nodes and 5 edges" do
872
+ graphs = Topiary::DirectedGraph.topologically_distinct(
873
+ Topiary::DirectedGraph.acyclic_from_node_count(4).select{|g| g.edges.count == 5}
874
+ ).map(&:to_s)
875
+ expect(graphs).to eq [
876
+ "{ 1 needs:[3,4] feeds:[]; 2 needs:[3,4] feeds:[]; 3 needs:[4] feeds:[1,2]; 4 needs:[] feeds:[1,2,3] }",
877
+ "{ 1 needs:[3,4] feeds:[]; 2 needs:[3] feeds:[4]; 3 needs:[] feeds:[1,2,4]; 4 needs:[2,3] feeds:[1] }",
878
+ "{ 1 needs:[3,4] feeds:[]; 2 needs:[] feeds:[3,4]; 3 needs:[2,4] feeds:[1]; 4 needs:[2] feeds:[1,3] }",
879
+
880
+ "{ 1 needs:[3] feeds:[4]; 2 needs:[3] feeds:[4]; 3 needs:[] feeds:[1,2,4]; 4 needs:[1,2,3] feeds:[] }",
881
+ "{ 1 needs:[3] feeds:[4]; 2 needs:[] feeds:[3,4]; 3 needs:[2] feeds:[1,4]; 4 needs:[1,2,3] feeds:[] }",
882
+ "{ 1 needs:[] feeds:[3,4]; 2 needs:[] feeds:[3,4]; 3 needs:[1,2,4] feeds:[]; 4 needs:[1,2] feeds:[3] }",
883
+ ]
884
+ end
885
+
886
+ it "find acyclic graphs with 4 nodes and 6 edges" do
887
+ graphs = Topiary::DirectedGraph.topologically_distinct(
888
+ Topiary::DirectedGraph.acyclic_from_node_count(4).select{|g| g.edges.count == 6}
889
+ ).map(&:to_s)
890
+ expect(graphs).to eq [
891
+ "{ 1 needs:[2,3,4] feeds:[]; 2 needs:[3,4] feeds:[1]; 3 needs:[4] feeds:[1,2]; 4 needs:[] feeds:[1,2,3] }",
892
+ ]
893
+ end
894
+
895
+ it "find acyclic graphs with 4 nodes and 7 edges" do
896
+ graphs = Topiary::DirectedGraph.topologically_distinct(
897
+ Topiary::DirectedGraph.acyclic_from_node_count(4).select{|g| g.edges.count == 7}
898
+ ).map(&:to_s)
899
+ expect(graphs).to eq []
900
+ end
901
+ end
902
+
903
+ end