doku 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +32 -0
- data/README.rdoc +15 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/doku.rb +4 -0
- data/lib/doku/dancing_links.rb +561 -0
- data/lib/doku/grid.rb +241 -0
- data/lib/doku/hexadoku.rb +56 -0
- data/lib/doku/hexamurai.rb +95 -0
- data/lib/doku/puzzle.rb +250 -0
- data/lib/doku/solver.rb +103 -0
- data/lib/doku/sudoku.rb +42 -0
- data/spec/dancing_links_spec.rb +212 -0
- data/spec/hexadoku_spec.rb +71 -0
- data/spec/hexamurai_spec.rb +38 -0
- data/spec/puzzle_spec.rb +147 -0
- data/spec/solution_spec.rb +278 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/sudoku_spec.rb +52 -0
- data/spec/watch.rb +9 -0
- metadata +198 -0
data/spec/puzzle_spec.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'backports' unless defined? require_relative
|
2
|
+
require_relative 'spec_helper'
|
3
|
+
|
4
|
+
class TestPuzzle < Doku::Puzzle
|
5
|
+
has_squares [1,2,3,4]
|
6
|
+
has_glyphs [true,false]
|
7
|
+
|
8
|
+
define_group [1,2]
|
9
|
+
define_group [2,3]
|
10
|
+
define_group [3,4]
|
11
|
+
define_group [4,1]
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "Puzzle instance" do
|
15
|
+
before do
|
16
|
+
@puzzle = TestPuzzle.new(1 => true)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "is basically a hash of square => glyph" do
|
20
|
+
@puzzle[1].should == true
|
21
|
+
@puzzle[4].should == nil
|
22
|
+
|
23
|
+
@puzzle[1] = false
|
24
|
+
@puzzle[1].should == false
|
25
|
+
|
26
|
+
@puzzle[1] = nil
|
27
|
+
@puzzle[1].should == nil
|
28
|
+
end
|
29
|
+
|
30
|
+
it "has an each method that excludes nil values" do
|
31
|
+
TestPuzzle.new(2 => nil).each { raise }
|
32
|
+
end
|
33
|
+
|
34
|
+
it "only allows squares as keys" do
|
35
|
+
lambda { @puzzle[0] }.should raise_error IndexError
|
36
|
+
lambda { @puzzle[0] = false }.should raise_error IndexError
|
37
|
+
end
|
38
|
+
|
39
|
+
it "only allows glyphs or nil as values" do
|
40
|
+
lambda { @puzzle[1] = 10 }.should raise_error ArgumentError
|
41
|
+
end
|
42
|
+
|
43
|
+
it "can not be initialized with bad data" do
|
44
|
+
lambda { TestPuzzle.new(:foo => true) }.should raise_error IndexError
|
45
|
+
lambda { TestPuzzle.new(1 => :foo) }.should raise_error ArgumentError
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "comparison operators say" do
|
49
|
+
it "A == B iff the square=>glyph pairs in A and B are the same" do
|
50
|
+
@puzzle.should_not == TestPuzzle.new
|
51
|
+
TestPuzzle.new.should_not == @puzzle.should
|
52
|
+
|
53
|
+
puzzle2 = TestPuzzle.new(1 => true)
|
54
|
+
@puzzle.should == puzzle2
|
55
|
+
puzzle2.should == @puzzle
|
56
|
+
|
57
|
+
(TestPuzzle.new(1 => true) != @puzzle).should == false
|
58
|
+
end
|
59
|
+
|
60
|
+
it "A eql? B is the same as A == B" do
|
61
|
+
@puzzle.should_not eql TestPuzzle.new
|
62
|
+
TestPuzzle.new.should_not eql @puzzle.should
|
63
|
+
|
64
|
+
puzzle2 = TestPuzzle.new(1 => true)
|
65
|
+
@puzzle.should eql puzzle2
|
66
|
+
puzzle2.should eql @puzzle
|
67
|
+
end
|
68
|
+
|
69
|
+
it "A.subset?(B) iff square=>glyph pairs in A are a subset of those in B" do
|
70
|
+
@puzzle.should_not be_a_subset TestPuzzle.new
|
71
|
+
TestPuzzle.new.should be_a_subset @puzzle
|
72
|
+
|
73
|
+
@puzzle.should be_a_subset @puzzle
|
74
|
+
|
75
|
+
@puzzle.should be_a_subset TestPuzzle.new(1 => true, 3 => false)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "#filled?" do
|
80
|
+
it "returns true iff all squares have been assigned a glyph" do
|
81
|
+
TestPuzzle.new(1 => true, 2=> true, 3 => false, 4 => false).should be_filled
|
82
|
+
@puzzle.should_not be_filled
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#valid?" do
|
87
|
+
it "returns false iff a group has the same glyph in it twice" do
|
88
|
+
@puzzle.should be_valid
|
89
|
+
TestPuzzle.new(1 => true, 2=> true).should_not be_valid
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#solution?" do
|
94
|
+
it "returns true iff it is filled and valid" do
|
95
|
+
puzzle2 = TestPuzzle.new(1 => false, 2 => true, 3 => false, 4 => true)
|
96
|
+
puzzle2.should be_a_solution
|
97
|
+
|
98
|
+
puzzle3 = TestPuzzle.new(1 => true, 2=> true, 3 => false, 4 => false)
|
99
|
+
puzzle3.should_not be_a_solution
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "#solution_for?(puzzle)" do
|
104
|
+
it "returns true if this puzzle is a solution and a super set of the other puzzle" do
|
105
|
+
puzzle4 = TestPuzzle.new(1 => false, 2 => true, 3 => false, 4 => true)
|
106
|
+
puzzle4.should_not be_a_solution_for @puzzle
|
107
|
+
|
108
|
+
puzzle5 = TestPuzzle.new(1 => true, 2 => false, 3 => true, 4 => false)
|
109
|
+
puzzle5.should be_a_solution_for @puzzle
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
shared_examples_for "copied puzzle" do
|
114
|
+
it "produces an equal puzzle" do
|
115
|
+
@copy.should == @puzzle
|
116
|
+
@copy.should_not equal @puzzle
|
117
|
+
end
|
118
|
+
|
119
|
+
it "produces a puzzle with a different glyph_state hash" do
|
120
|
+
@copy.glyph_state.should_not equal @puzzle.glyph_state
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "dup" do
|
125
|
+
before do
|
126
|
+
@copy = @puzzle.dup
|
127
|
+
end
|
128
|
+
|
129
|
+
it_should_behave_like "copied puzzle"
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "clone" do
|
133
|
+
before do
|
134
|
+
@copy = @puzzle.clone
|
135
|
+
end
|
136
|
+
|
137
|
+
it_should_behave_like "copied puzzle"
|
138
|
+
end
|
139
|
+
|
140
|
+
it "equality and hash codes are not affected by nil values" do
|
141
|
+
puzzle2 = TestPuzzle.new(1 => true, 3 => nil)
|
142
|
+
@puzzle[2] = nil
|
143
|
+
puzzle2.should == @puzzle
|
144
|
+
puzzle2.hash.should == @puzzle.hash
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'backports' unless defined? require_relative
|
2
|
+
require_relative 'spec_helper'
|
3
|
+
|
4
|
+
describe "Puzzle#solve" do
|
5
|
+
context 'given the sudoku puzzle' do
|
6
|
+
before do
|
7
|
+
@puzzle = Doku::Sudoku.new <<END
|
8
|
+
...|..8|...
|
9
|
+
..7|.35|..9
|
10
|
+
5..|4.6|8..
|
11
|
+
---+---+---
|
12
|
+
...|..4|2..
|
13
|
+
4..|...|.37
|
14
|
+
8..|...|5..
|
15
|
+
---+---+---
|
16
|
+
.9.|.67|...
|
17
|
+
..3|...|1.5
|
18
|
+
...|...|..3
|
19
|
+
END
|
20
|
+
|
21
|
+
@solution = @puzzle.solve
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'solves the puzzle' do
|
25
|
+
@solution.solution_for?(@puzzle).should == true
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'is the correct solution' do
|
29
|
+
@solution.to_grid_string.strip.should == <<END.strip
|
30
|
+
964|278|351
|
31
|
+
287|135|649
|
32
|
+
531|496|872
|
33
|
+
---+---+---
|
34
|
+
319|754|286
|
35
|
+
452|681|937
|
36
|
+
876|923|514
|
37
|
+
---+---+---
|
38
|
+
195|367|428
|
39
|
+
723|849|165
|
40
|
+
648|512|793
|
41
|
+
END
|
42
|
+
end
|
43
|
+
|
44
|
+
it "can also find the solution using Donald Knuth's recursive DLX" do
|
45
|
+
sm = @puzzle.to_link_matrix
|
46
|
+
exact_cover = sm.find_exact_cover_recursive
|
47
|
+
solution = @puzzle.exact_cover_to_solution exact_cover
|
48
|
+
solution.should == @solution
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'given a sudoku puzzle with two solutions' do
|
53
|
+
before do
|
54
|
+
@puzzle = Doku::Sudoku.new <<END
|
55
|
+
...|..8|...
|
56
|
+
..7|.3.|..9
|
57
|
+
5..|4.6|8..
|
58
|
+
---+---+---
|
59
|
+
...|..4|2..
|
60
|
+
4..|...|.37
|
61
|
+
8..|...|5..
|
62
|
+
---+---+---
|
63
|
+
.9.|.67|...
|
64
|
+
..3|...|1.5
|
65
|
+
...|...|..3
|
66
|
+
END
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'finds two solutions' do
|
70
|
+
solutions = @puzzle.solutions.to_a
|
71
|
+
solutions.size.should == 2
|
72
|
+
|
73
|
+
solutions.should include Doku::Sudoku.new <<END
|
74
|
+
964|278|351
|
75
|
+
287|135|649
|
76
|
+
531|496|872
|
77
|
+
---+---+---
|
78
|
+
319|754|286
|
79
|
+
452|681|937
|
80
|
+
876|923|514
|
81
|
+
---+---+---
|
82
|
+
195|367|428
|
83
|
+
723|849|165
|
84
|
+
648|512|793
|
85
|
+
END
|
86
|
+
|
87
|
+
solutions.should include Doku::Sudoku.new <<END
|
88
|
+
964|278|351
|
89
|
+
287|531|649
|
90
|
+
531|496|872
|
91
|
+
---+---+---
|
92
|
+
359|714|286
|
93
|
+
412|685|937
|
94
|
+
876|923|514
|
95
|
+
---+---+---
|
96
|
+
195|367|428
|
97
|
+
723|849|165
|
98
|
+
648|152|793
|
99
|
+
END
|
100
|
+
|
101
|
+
solutions[0].should be_a_solution_for @puzzle
|
102
|
+
solutions[1].should be_a_solution_for @puzzle
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'given a sudoku puzzle with NO solutions' do
|
108
|
+
before do
|
109
|
+
@puzzle = Doku::Sudoku.new <<END
|
110
|
+
123|...|...
|
111
|
+
456|...|...
|
112
|
+
78.|...|...
|
113
|
+
---+---+---
|
114
|
+
..1|...|...
|
115
|
+
...|9..|...
|
116
|
+
..2|...|...
|
117
|
+
---+---+---
|
118
|
+
..4|...|...
|
119
|
+
...|9..|...
|
120
|
+
..5|...|...
|
121
|
+
END
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'finds no solutions' do
|
125
|
+
@puzzle.solve.should == nil
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'can not tell instantly there is no solution, in this case' do
|
129
|
+
sm = @puzzle.to_link_matrix
|
130
|
+
sc = sm.columns.min_by(&:size)
|
131
|
+
sc.size.should > 0
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'finds no solutions using the recursive algorithm' do
|
135
|
+
sm = @puzzle.to_link_matrix
|
136
|
+
sm.find_exact_cover_recursive.should == nil
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context 'given a hexadoku puzzle' do
|
141
|
+
before do
|
142
|
+
# Elektor Hexadoku 2011
|
143
|
+
@puzzle = Doku::Hexadoku.new <<END
|
144
|
+
2A.7|.C..|9D64|8...
|
145
|
+
..3.|A..D|7...|.2F.
|
146
|
+
.1..|..0.|8...|.4AB
|
147
|
+
....|.7.2|..BC|.0.3
|
148
|
+
----+----+----+----
|
149
|
+
C2.8|.D3.|..4E|....
|
150
|
+
..FA|7.2.|B.3.|1C04
|
151
|
+
....|4..F|..1.|...E
|
152
|
+
9..B|1...|....|23..
|
153
|
+
----+----+----+----
|
154
|
+
..8C|....|...0|3..D
|
155
|
+
6...|.F..|1..A|....
|
156
|
+
D37E|.0.1|.9.8|AF..
|
157
|
+
....|3B..|.2D.|C.80
|
158
|
+
----+----+----+----
|
159
|
+
F.B.|51..|2.A.|....
|
160
|
+
3CA.|...7|.E..|..6.
|
161
|
+
.E4.|...9|3..5|.D..
|
162
|
+
...1|F3A4|..9.|5.E2
|
163
|
+
END
|
164
|
+
|
165
|
+
@solution = @puzzle.solve
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'finds the correct solution' do
|
169
|
+
@solution.to_grid_string.should == <<END.strip
|
170
|
+
2A07|BCF3|9D64|8E15
|
171
|
+
B839|A54D|70E1|62FC
|
172
|
+
51CD|E906|8F23|74AB
|
173
|
+
46EF|8712|A5BC|D093
|
174
|
+
----+----+----+----
|
175
|
+
C218|6D3B|074E|9A5F
|
176
|
+
EDFA|7825|B639|1C04
|
177
|
+
0753|4A9F|C812|B6DE
|
178
|
+
946B|1EC0|DA5F|2378
|
179
|
+
----+----+----+----
|
180
|
+
158C|946A|EBF0|372D
|
181
|
+
6B20|CFD8|137A|E549
|
182
|
+
D37E|2051|49C8|AFB6
|
183
|
+
AF94|3B7E|52D6|C180
|
184
|
+
----+----+----+----
|
185
|
+
F9B6|51EC|24AD|0837
|
186
|
+
3CA5|D287|FE0B|4961
|
187
|
+
7E42|06B9|3185|FDCA
|
188
|
+
80D1|F3A4|6C97|5BE2
|
189
|
+
END
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context "given a Hexamurai puzzle" do
|
194
|
+
before do
|
195
|
+
# From Elektor 2011-07 (with extra hints so it can be solved quickly).
|
196
|
+
@puzzle = Doku::Hexamurai.new <<END
|
197
|
+
|39DEA62B|80C1745F|
|
198
|
+
|..213...|5.B4....|
|
199
|
+
|..0.....|......2.|
|
200
|
+
|.....4.5|..9A1.E.|
|
201
|
+
|..3C....|.1654.0.|
|
202
|
+
|.1..2...|3......6|
|
203
|
+
|...90...|BD4.25..|
|
204
|
+
|2.57....|ACE03.D1|
|
205
|
+
--------+--------+--------+--------
|
206
|
+
52.0..3.|A.....B.|0.....3.|78A....6
|
207
|
+
4.8.1...|..9.....|...E..4.|2.063...
|
208
|
+
.7......|.4F.50.9|.7.2..8E|.1..4...
|
209
|
+
..6...0.|.....C3.|...6.7.0|5..3..21
|
210
|
+
A1D24E6B|9083C.5F|E62BAD14|.....8..
|
211
|
+
E0347.28|CFA5BD96|14830E72|.9....5A
|
212
|
+
8B96FC50|ED.2134A|950CB6F8|.2.1.4.3
|
213
|
+
7.FC3A9D|1B64E2.0|FA7..9C3|0..E.2..
|
214
|
+
--------+--------+--------+--------
|
215
|
+
.3.1..7.|B.5F2.E4|31......|B.6....4
|
216
|
+
C.......|4..7..0D|..6...B.|9.40.178
|
217
|
+
..28....|.C.A..73|...49..1|..3.5.0.
|
218
|
+
047.5...|.6.1....|...045..|1....3..
|
219
|
+
..036.4.|.1.C.EA.|60473..9|........
|
220
|
+
1......2|8.4D.96.|A3EF..2.|4.5.0...
|
221
|
+
..4..51.|.7.E3...|B291..5.|....C637
|
222
|
+
6.5...8.|...9...1|8DC5..0.|........
|
223
|
+
--------+--------+--------+--------
|
224
|
+
|...B..39|..0..2..|
|
225
|
+
|5.804..2|.6.3.E7.|
|
226
|
+
|...6....|....5.3.|
|
227
|
+
|...2.7..|C4.....0|
|
228
|
+
|..685340|.729A1..|
|
229
|
+
|7..51...|.....6.3|
|
230
|
+
|...4....|.B..0...|
|
231
|
+
|...3....|0......2|
|
232
|
+
END
|
233
|
+
end
|
234
|
+
|
235
|
+
it "can be solved correctly" do
|
236
|
+
solution = @puzzle.solve
|
237
|
+
solution.should be_solution_for @puzzle
|
238
|
+
solution.to_s.should == <<END.chomp
|
239
|
+
|39DEA62B|80C1745F|
|
240
|
+
|87213FDC|5EB4906A|
|
241
|
+
|450A79E1|63DF8B2C|
|
242
|
+
|6CBF8405|729A13ED|
|
243
|
+
|BA3CFE7D|21654809|
|
244
|
+
|014D25A8|39F7ECB6|
|
245
|
+
|F6E901C3|BD4825A7|
|
246
|
+
|28579B64|ACE03FD1|
|
247
|
+
--------+--------+--------+--------
|
248
|
+
52109D3F|AEC648B7|0F19D235|78A4BEC6
|
249
|
+
4E8D1BC5|73906AF2|D85EC14B|2F0637A9
|
250
|
+
37CA82E6|D4FB5019|C7326A8E|D19B40F5
|
251
|
+
B96FA704|5218DC3E|4BA6F790|5CE38D21
|
252
|
+
A1D24E6B|9083C75F|E62BAD14|C3F57890
|
253
|
+
E0347128|CFA5BD96|14830E72|69BDFC5A
|
254
|
+
8B96FC50|ED72134A|950CB6F8|A271E4D3
|
255
|
+
75FC3A9D|1B64E280|FA7D59C3|048E126B
|
256
|
+
--------+--------+--------+--------
|
257
|
+
D3A1087C|B95F26E4|31D870AC|B5629FE4
|
258
|
+
CFB526A3|48E7910D|5C6A23BF|9E40D178
|
259
|
+
9628E4B1|0CDAF573|2EB49861|FD375A0C
|
260
|
+
047E5FD9|3621ABC8|79F045ED|1AC863B2
|
261
|
+
FD036947|21BC8EA5|60473FD9|8B2CA51E
|
262
|
+
1AE7C3F2|854D096B|A3EF1C27|46590B8D
|
263
|
+
2849B51A|670E3FDC|B291845A|E0DFC637
|
264
|
+
6C5BD08E|FA397421|8DC5EB06|371A294F
|
265
|
+
--------+--------+--------+--------
|
266
|
+
|AF7BC839|E50D6214|
|
267
|
+
|5D804AF2|9613CE7B|
|
268
|
+
|94C6B01E|FA725D38|
|
269
|
+
|E312D756|C48BFA90|
|
270
|
+
|CB685340|D729A1FE|
|
271
|
+
|709512BF|48AED6C3|
|
272
|
+
|D2F4EC9A|1B360785|
|
273
|
+
|1EA36D87|0F5CB942|
|
274
|
+
END
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$LOAD_PATH.unshift File.join File.dirname(__FILE__), '..', 'lib'
|
2
|
+
require 'rspec'
|
3
|
+
require 'doku'
|
4
|
+
|
5
|
+
# Change this to true if a test is failing and you want more clues about why.
|
6
|
+
EXTRA_ASSERTS = false
|
7
|
+
|
8
|
+
if EXTRA_ASSERTS
|
9
|
+
# Add any patches here that help check the validity of the algorithm but are
|
10
|
+
# not necessary to have in the production code.
|
11
|
+
|
12
|
+
class Doku::DancingLinks::LinkMatrix::Column
|
13
|
+
def size=(num)
|
14
|
+
raise "bad class" unless Fixnum === num
|
15
|
+
raise "negative value #{num}" if num < 0
|
16
|
+
@size = num
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/spec/sudoku_spec.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'backports' unless defined? require_relative
|
2
|
+
require_relative 'spec_helper'
|
3
|
+
|
4
|
+
describe Doku::Sudoku do
|
5
|
+
before do
|
6
|
+
@puzzle = Doku::Sudoku.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'has 81 squares' do
|
10
|
+
Doku::Sudoku.squares.size.should == 81
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has 9 squares in the first row' do
|
14
|
+
first_row = Doku::Sudoku.squares.select { |s| s.matches?(:y => 0) }
|
15
|
+
first_row.size.should == 9
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'has the right number of groups' do
|
19
|
+
Doku::Sudoku.groups.size.should == 3*9
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'has methods for getting and setting glyphs by coordinates' do
|
23
|
+
# (just like any puzzle class that includes the PuzzleOnGrid module)
|
24
|
+
|
25
|
+
@puzzle.get(0, 1).should == nil
|
26
|
+
@puzzle.set(0, 1, 4)
|
27
|
+
@puzzle.set(5, 7, 9)
|
28
|
+
@puzzle.set(6, 6, 1)
|
29
|
+
@puzzle.set(6, 6, nil)
|
30
|
+
@puzzle.get(0, 1).should == 4
|
31
|
+
@puzzle.to_s.should == <<END.strip
|
32
|
+
...|...|...
|
33
|
+
4..|...|...
|
34
|
+
...|...|...
|
35
|
+
---+---+---
|
36
|
+
...|...|...
|
37
|
+
...|...|...
|
38
|
+
...|...|...
|
39
|
+
---+---+---
|
40
|
+
...|...|...
|
41
|
+
...|..9|...
|
42
|
+
...|...|...
|
43
|
+
END
|
44
|
+
end
|
45
|
+
|
46
|
+
it "the get method has a good error message" do
|
47
|
+
msg = "Square not found in Doku::Sudoku: Square(19, david)."
|
48
|
+
lambda { @puzzle.get(19, 'david') }.should raise_error IndexError, msg
|
49
|
+
lambda { @puzzle.set(19, 'david', 10) }.should raise_error IndexError, msg
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|