topiary 1.0.2 → 1.0.3

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.
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