mendel 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +119 -0
- data/Rakefile +52 -0
- data/TODO.md +15 -0
- data/benchmark/addition_combiner.rb +6 -0
- data/benchmark/benchmarker.rb +58 -0
- data/benchmark/graph.rb +33 -0
- data/benchmark/run_six_lists.rb +17 -0
- data/benchmark/run_two_lists.rb +14 -0
- data/benchmark/simple.rb +34 -0
- data/lib/mendel.rb +5 -0
- data/lib/mendel/combiner.rb +174 -0
- data/lib/mendel/min_priority_queue.rb +48 -0
- data/lib/mendel/observable_combiner.rb +24 -0
- data/lib/mendel/version.rb +3 -0
- data/lib/mendel/visualizers/ascii.rb +54 -0
- data/lib/mendel/visualizers/base.rb +41 -0
- data/mendel.gemspec +28 -0
- data/spec/fixtures/example_input.rb +13 -0
- data/spec/fixtures/example_output/different_lengths.rb +303 -0
- data/spec/fixtures/example_output/inc_integers_w_inc_decimals.rb +10003 -0
- data/spec/fixtures/example_output/inc_integers_w_repeats.rb +10003 -0
- data/spec/fixtures/example_output/inc_integers_w_repeats_and_skips.rb +10003 -0
- data/spec/fixtures/example_output/inc_integers_w_skips.rb +10003 -0
- data/spec/mendel/combiner_spec.rb +256 -0
- data/spec/mendel/min_priority_queue_spec.rb +70 -0
- data/spec/mendel/observable_combiner_spec.rb +42 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/support/foosball_team.rb +24 -0
- data/visualizer_spec/ascii_spec.rb +119 -0
- data/visualizer_spec/base_spec.rb +74 -0
- metadata +175 -0
@@ -0,0 +1,256 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
require "mendel/combiner"
|
3
|
+
require "fixtures/example_input"
|
4
|
+
require "support/foosball_team"
|
5
|
+
|
6
|
+
describe Mendel::Combiner do
|
7
|
+
|
8
|
+
let(:combiner_class) {
|
9
|
+
Class.new do
|
10
|
+
include Mendel::Combiner
|
11
|
+
def score_combination(items)
|
12
|
+
items.reduce(0) { |sum, item| sum += item }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
}
|
17
|
+
let(:combiner) { combiner_class.new(list1, list2) }
|
18
|
+
let(:list1) { [1.0, 2.0, 3.0] }
|
19
|
+
let(:list2) { [1.1, 2.1, 3.1] }
|
20
|
+
|
21
|
+
describe "requiring the including class to define `score_combination`" do
|
22
|
+
|
23
|
+
let(:combiner_class) {
|
24
|
+
Class.new do
|
25
|
+
include Mendel::Combiner
|
26
|
+
end
|
27
|
+
}
|
28
|
+
|
29
|
+
it "raises NotImplementedError otherwise" do
|
30
|
+
expect{combiner.take(1)}.to raise_error(NotImplementedError)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "producing correct output" do
|
36
|
+
|
37
|
+
let(:list1) { EXAMPLE_INPUT[:incrementing_integers] }
|
38
|
+
let(:all_results) { combiner.to_a }
|
39
|
+
|
40
|
+
describe "when both lists increment smoothly" do
|
41
|
+
let(:list2) { EXAMPLE_INPUT[:incrementing_decimals] }
|
42
|
+
|
43
|
+
it "has a complete result set that is ordered correctly" do
|
44
|
+
require "fixtures/example_output/inc_integers_w_inc_decimals"
|
45
|
+
expect(all_results).to be_sorted_like($inc_integers_w_inc_decimals)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "when the second list has repeats" do
|
51
|
+
let(:list2) { EXAMPLE_INPUT[:repeats] }
|
52
|
+
|
53
|
+
it "has a complete result set that is ordered correctly" do
|
54
|
+
require "fixtures/example_output/inc_integers_w_repeats"
|
55
|
+
expect(all_results).to be_sorted_like($inc_integers_w_repeats)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "when the second list has skips" do
|
61
|
+
let(:list2) { EXAMPLE_INPUT[:skips] }
|
62
|
+
|
63
|
+
it "has a complete result set that is ordered correctly" do
|
64
|
+
require "fixtures/example_output/inc_integers_w_skips"
|
65
|
+
expect(all_results).to be_sorted_like($inc_integers_w_skips)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "when the second list has repeats AND skips" do
|
71
|
+
let(:list2) { EXAMPLE_INPUT[:repeats_and_skips] }
|
72
|
+
|
73
|
+
it "has a complete result set that is ordered correctly" do
|
74
|
+
require "fixtures/example_output/inc_integers_w_repeats_and_skips"
|
75
|
+
expect(all_results).to be_sorted_like($inc_integers_w_repeats_and_skips)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "when the lists are different lengths" do
|
81
|
+
let(:list2) { EXAMPLE_INPUT[:short_list] }
|
82
|
+
|
83
|
+
it "has a complete result set that is ordered correctly" do
|
84
|
+
require "fixtures/example_output/different_lengths"
|
85
|
+
expect(all_results).to be_sorted_like($different_lengths)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
context "when there are more than 2 lists" do
|
91
|
+
|
92
|
+
let(:list3) { [1.2, 2.2, 3.2] }
|
93
|
+
let(:list4) { [1.3, 2.3, 3.3] }
|
94
|
+
|
95
|
+
let(:combiner) { combiner_class.new(list1, list2, list3, list4) }
|
96
|
+
|
97
|
+
it "can produce valid combinations" do
|
98
|
+
expect(combiner.first).to eq([[1.0, 1.1, 1.2, 1.3], 4.6])
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
context "when given lists of non-numeric items" do
|
104
|
+
|
105
|
+
let(:list1) { [{name: 'Jimmy', age: 10}, {name: 'Susan', age: 12}] }
|
106
|
+
let(:list2) { [{name: 'Roger', age: 8}, {name: 'Carla', age: 14}] }
|
107
|
+
let(:sorted_combos) {
|
108
|
+
combos = []
|
109
|
+
list1.each do |player_1|
|
110
|
+
list2.each do |player_2|
|
111
|
+
combos << [
|
112
|
+
FoosballTeam.new(player_1, player_2),
|
113
|
+
(player_1[:age] + player_2[:age]) / 2.0
|
114
|
+
]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
combos.sort_by {|c| c.last}
|
118
|
+
}
|
119
|
+
|
120
|
+
context "when the combiner class has custom combination and scoring methods" do
|
121
|
+
|
122
|
+
let(:combiner_class) {
|
123
|
+
Class.new do
|
124
|
+
include Mendel::Combiner
|
125
|
+
|
126
|
+
def build_combination(items)
|
127
|
+
FoosballTeam.new(*items)
|
128
|
+
end
|
129
|
+
|
130
|
+
def score_combination(combination)
|
131
|
+
combination.average_age
|
132
|
+
end
|
133
|
+
end
|
134
|
+
}
|
135
|
+
|
136
|
+
it "has a complete result set that is ordered correctly" do
|
137
|
+
expect(all_results).to be_sorted_like(sorted_combos)
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
context "when the input lists are not sorted in ascending order" do
|
145
|
+
|
146
|
+
let(:list1) { EXAMPLE_INPUT[:incrementing_integers].reverse }
|
147
|
+
let(:list2) { EXAMPLE_INPUT[:incrementing_decimals].shuffle }
|
148
|
+
|
149
|
+
it "does not produce correct output" do
|
150
|
+
require "fixtures/example_output/inc_integers_w_inc_decimals"
|
151
|
+
expect(all_results).not_to be_sorted_like($inc_integers_w_inc_decimals)
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
describe "enumeration" do
|
159
|
+
|
160
|
+
it "is Enumerable" do
|
161
|
+
expect(combiner).to be_a(Enumerable)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "supports normal enumerable operations" do
|
165
|
+
expect(combiner.take(3).map {|c| c[-1] }).to eq([2.1, 3.1, 3.1])
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
describe "dumping and loading state" do
|
171
|
+
|
172
|
+
context "when it has produced some combinations" do
|
173
|
+
|
174
|
+
before :each do
|
175
|
+
combiner.take(3)
|
176
|
+
end
|
177
|
+
|
178
|
+
let(:dumped) {
|
179
|
+
{
|
180
|
+
'input' => [list1, list2], 'seen' => [[0, 0], [1, 0], [0, 1], [2, 0], [1, 1], [0, 2]],
|
181
|
+
'queued' => [
|
182
|
+
[{'combo'=>[2.0, 2.1], "coordinates"=>[1, 1]}, 4.1],
|
183
|
+
[{"combo"=>[3.0, 1.1], "coordinates"=>[2, 0]}, 4.1],
|
184
|
+
[{"combo"=>[1.0, 3.1], "coordinates"=>[0, 2]}, 4.1],
|
185
|
+
]
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
it "can dump its state" do
|
190
|
+
expect(combiner.dump).to eq(dumped)
|
191
|
+
end
|
192
|
+
|
193
|
+
it "can dump its state as JSON" do
|
194
|
+
expect(combiner.dump_json).to eq(JSON.dump(dumped))
|
195
|
+
end
|
196
|
+
|
197
|
+
context "when state has been dumped somewhere" do
|
198
|
+
|
199
|
+
context "as a hash" do
|
200
|
+
|
201
|
+
let!(:dumped_data) { combiner.dump }
|
202
|
+
|
203
|
+
it "can load state" do
|
204
|
+
expect(combiner_class.load(dumped_data)).to be_a(combiner_class)
|
205
|
+
end
|
206
|
+
|
207
|
+
it "can begin producing combinations again from that point" do
|
208
|
+
combiner = combiner_class.load(dumped_data)
|
209
|
+
expect(combiner.take(3)).to be_sorted_like([[[2.0, 2.1], 4.1], [[3.0, 1.1], 4.1], [[1.0, 3.1], 4.1]])
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
|
214
|
+
context "as json" do
|
215
|
+
|
216
|
+
let!(:dumped_json) { combiner.dump_json }
|
217
|
+
|
218
|
+
it "can load state" do
|
219
|
+
expect(combiner_class.load_json(dumped_json)).to be_a(combiner_class)
|
220
|
+
end
|
221
|
+
|
222
|
+
it "can begin producing combinations again from that point" do
|
223
|
+
combiner = combiner_class.load_json(dumped_json)
|
224
|
+
expect(combiner.take(3)).to be_sorted_like([[[2.0, 2.1], 4.1], [[3.0, 1.1], 4.1], [[1.0, 3.1], 4.1]])
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
describe "other methods" do
|
236
|
+
|
237
|
+
it "can return its queue length" do
|
238
|
+
expect(combiner.queue_length).to eq(1) # item at 0,0
|
239
|
+
combiner.take(1)
|
240
|
+
expect(combiner.queue_length).to eq(2) # queued its children
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
describe "when given empty lists" do
|
246
|
+
|
247
|
+
let(:list1) { [] }
|
248
|
+
let(:list2) { [] }
|
249
|
+
|
250
|
+
it "raises an error" do
|
251
|
+
expect{combiner}.to raise_error(Mendel::Combiner::EmptyList)
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
require "mendel/min_priority_queue"
|
3
|
+
|
4
|
+
describe Mendel::MinPriorityQueue do
|
5
|
+
|
6
|
+
let(:queue) { described_class.new }
|
7
|
+
|
8
|
+
describe "basic functionality" do
|
9
|
+
|
10
|
+
it "can add items and pop an item of the lowest priority" do
|
11
|
+
queue.push('b', 2)
|
12
|
+
queue.push('a', 1)
|
13
|
+
queue.push('c', 3)
|
14
|
+
expect(queue.pop).to eq(['a', 1])
|
15
|
+
end
|
16
|
+
|
17
|
+
it "can report its length" do
|
18
|
+
expect(queue.length).to eq(0)
|
19
|
+
queue.push('b', 2)
|
20
|
+
queue.push('a', 1)
|
21
|
+
expect(queue.length).to eq(2)
|
22
|
+
queue.pop
|
23
|
+
expect(queue.length).to eq(1)
|
24
|
+
2.times { queue.pop }
|
25
|
+
expect(queue.length).to eq(0) # not -1
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "dumping and loading" do
|
31
|
+
|
32
|
+
describe "dumping" do
|
33
|
+
|
34
|
+
let(:queue) {
|
35
|
+
described_class.new.tap { |q|
|
36
|
+
q.push('c', 3)
|
37
|
+
q.push('a', 1)
|
38
|
+
q.push('b', 2)
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
it "can dump its contents as an array" do
|
43
|
+
expect(queue.dump).to eq([['a', 1], ['b', 2], ['c', 3]])
|
44
|
+
end
|
45
|
+
|
46
|
+
it "can dump its contents as json" do
|
47
|
+
expect(queue.dump_json).to eq("[[\"a\",1],[\"b\",2],[\"c\",3]]")
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "loading" do
|
53
|
+
|
54
|
+
it "can load its contents from an array" do
|
55
|
+
queue.load([['a', 1], ['b', 2], ['c', 3]])
|
56
|
+
expect(queue.length).to eq(3)
|
57
|
+
expect(queue.pop).to eq(['a', 1])
|
58
|
+
end
|
59
|
+
|
60
|
+
it "can load its contents from JSON" do
|
61
|
+
queue.load_json("[[\"a\",1],[\"b\",2],[\"c\",3]]")
|
62
|
+
expect(queue.length).to eq(3)
|
63
|
+
expect(queue.pop).to eq(['a', 1])
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require_relative "../spec_helper"
|
2
|
+
require "mendel/observable_combiner"
|
3
|
+
|
4
|
+
describe Mendel::ObservableCombiner do
|
5
|
+
|
6
|
+
let(:combiner_class) {
|
7
|
+
Class.new(Mendel::ObservableCombiner) do
|
8
|
+
def score_combination(items)
|
9
|
+
items.reduce(0) { |sum, item| sum += item }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
}
|
13
|
+
let(:combiner) { combiner_class.new(list1, list2) }
|
14
|
+
let(:list1) { [1.0, 2.0, 3.0] }
|
15
|
+
let(:list2) { [1.1, 2.1, 3.1] }
|
16
|
+
|
17
|
+
describe "notification of events" do
|
18
|
+
|
19
|
+
describe "when a combination is returned" do
|
20
|
+
|
21
|
+
it "notifies that the combo's children have been scored and that the combo has been returned" do
|
22
|
+
# 2 times because with two lists there are two child coordinates
|
23
|
+
expect(combiner).to receive(:notify).exactly(2).times.with(
|
24
|
+
:scored, a_hash_including(
|
25
|
+
'coordinates' => an_instance_of(Array),
|
26
|
+
'score' => a_kind_of(Numeric)
|
27
|
+
)
|
28
|
+
)
|
29
|
+
expect(combiner).to receive(:notify).exactly(1).times.with(
|
30
|
+
:returned, a_hash_including(
|
31
|
+
'coordinates' => an_instance_of(Array),
|
32
|
+
'score' => a_kind_of(Numeric)
|
33
|
+
)
|
34
|
+
)
|
35
|
+
combiner.take(1)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
File.dirname(__FILE__).tap {|this_dir| $LOAD_PATH << this_dir unless $LOAD_PATH.include?(this_dir) }
|
2
|
+
|
3
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
4
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
5
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
6
|
+
# loaded once.
|
7
|
+
RSpec::Matchers.define :be_sorted_like do |expected_array|
|
8
|
+
require 'set'
|
9
|
+
# Items with same totals are ordered unpredictably; we only care that
|
10
|
+
# items with different totals are ordered correctly
|
11
|
+
match do |actual_array|
|
12
|
+
same_length = actual_array.length == expected_array.length
|
13
|
+
same_uniques = Set.new(actual_array) == Set.new(expected_array)
|
14
|
+
same_sort_keys = actual_array.map(&:last) == expected_array.map(&:last)
|
15
|
+
# puts "#{same_length} && #{same_uniques} && #{same_sort_keys}"
|
16
|
+
same_length && same_uniques && same_sort_keys
|
17
|
+
end
|
18
|
+
end
|
19
|
+
#
|
20
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.raise_errors_for_deprecations!
|
23
|
+
config.run_all_when_everything_filtered = true
|
24
|
+
config.filter_run :focus
|
25
|
+
|
26
|
+
# Run specs in random order to surface order dependencies. If you find an
|
27
|
+
# order dependency and want to debug it, you can fix the order by providing
|
28
|
+
# the seed, which is printed after each run.
|
29
|
+
# --seed 1234
|
30
|
+
config.order = 'random'
|
31
|
+
end
|
32
|
+
|
33
|
+
def sorted_combos(list1, list2)
|
34
|
+
results = []
|
35
|
+
list1.each do |l1|
|
36
|
+
list2.each do |l2|
|
37
|
+
results << [l1, l2, l1 + l2]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
results.sort_by(&:last)
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class FoosballTeam
|
2
|
+
attr_accessor :players
|
3
|
+
|
4
|
+
def initialize(*players)
|
5
|
+
self.players = players
|
6
|
+
end
|
7
|
+
|
8
|
+
def average_age
|
9
|
+
players.reduce(0){ |total, player|
|
10
|
+
total += player.fetch(:age)
|
11
|
+
} / 2.0
|
12
|
+
end
|
13
|
+
|
14
|
+
# So that one Set of FoosballTeams will be equal to another
|
15
|
+
# if the teams have the same players
|
16
|
+
def eql?(other)
|
17
|
+
other.class == self.class && other.players == players
|
18
|
+
end
|
19
|
+
|
20
|
+
def hash
|
21
|
+
players.hash
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require_relative "../../spec_helper"
|
2
|
+
require "mendel"
|
3
|
+
require "mendel/observable_combiner"
|
4
|
+
require "mendel/visualizers/ascii"
|
5
|
+
|
6
|
+
describe Mendel::Visualizers::ASCII do
|
7
|
+
|
8
|
+
let(:klass) { described_class }
|
9
|
+
let(:combiner_class) {
|
10
|
+
Class.new(Mendel::ObservableCombiner) do
|
11
|
+
def score_combination(items)
|
12
|
+
items.reduce(0) { |sum, item| sum += item }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
}
|
16
|
+
|
17
|
+
let(:combiner) { combiner_class.new(list1, list2) }
|
18
|
+
let(:list1) { (1..10).to_a }
|
19
|
+
let(:list2) { (1..10).map { |i| i + 0.1} }
|
20
|
+
let(:klass) { described_class }
|
21
|
+
let!(:visualizer) { klass.new(combiner) }
|
22
|
+
|
23
|
+
describe "after initialization" do
|
24
|
+
|
25
|
+
describe "output" do
|
26
|
+
|
27
|
+
describe "axis labels" do
|
28
|
+
|
29
|
+
it "converts the list item to a string" do
|
30
|
+
item = '1'
|
31
|
+
expect(item).to receive(:to_s).and_call_original
|
32
|
+
visualizer.axis_label_for(item)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "pads the output to be 8 characters wide" do
|
36
|
+
item = 1
|
37
|
+
expect(visualizer.axis_label_for(item)).to eq(
|
38
|
+
' 1'
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "limits the output to 8 characters" do
|
43
|
+
item = '0123456789'
|
44
|
+
expect(visualizer.axis_label_for(item)).to eq(
|
45
|
+
'01234567'
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "grid points" do
|
52
|
+
|
53
|
+
it "represents :unscored as a blank 8 spaces wide" do
|
54
|
+
expect(visualizer.grid_point_for(:unscored)).to eq(' ')
|
55
|
+
end
|
56
|
+
|
57
|
+
it "represents :scored as a blue number" do
|
58
|
+
expect(visualizer.grid_point_for(:scored, 5)).to eq(' 5'.blue)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "represents :returned as a green number" do
|
62
|
+
expect(visualizer.grid_point_for(:returned, 873)).to eq(' 873'.green)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "output" do
|
68
|
+
|
69
|
+
let(:empty_grid) {
|
70
|
+
<<-WOO
|
71
|
+
10
|
72
|
+
|
73
|
+
|
74
|
+
9
|
75
|
+
|
76
|
+
|
77
|
+
8
|
78
|
+
|
79
|
+
|
80
|
+
7
|
81
|
+
|
82
|
+
|
83
|
+
6
|
84
|
+
|
85
|
+
|
86
|
+
5
|
87
|
+
|
88
|
+
|
89
|
+
4
|
90
|
+
|
91
|
+
|
92
|
+
3
|
93
|
+
|
94
|
+
|
95
|
+
2
|
96
|
+
|
97
|
+
|
98
|
+
1
|
99
|
+
1.1 2.1 3.1 4.1 5.1 6.1 7.1 8.1 9.1 10.1
|
100
|
+
WOO
|
101
|
+
}
|
102
|
+
|
103
|
+
it "shows an empty grid when initialized" do
|
104
|
+
expect(visualizer.output).to eq(empty_grid)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "shows something else after enumerating" do
|
108
|
+
combiner.take(3)
|
109
|
+
expect(visualizer.output).not_to eq(empty_grid)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|