Gray Soft / Rubies in the Rough / Dave's No Tests Challengetag:graysoftinc.com,2014-03-20:/posts/1222014-07-20T22:41:00ZJames Edward Gray IIDave's No Tests Challengetag:graysoftinc.com,2014-07-20:/posts/1222014-07-20T22:41:00ZAn exploration of what we can learn from the recent challenge that Dave Thomas gave to the Ruby Rogues: "Stop writing tests."<p>I've mentioned before <a href="http://graysoftinc.com/non-code/ipsc-2014-postmortem">my difficulties in the 2014 IPSC</a>. But taking one beating is no reason not to try again. The first loss just showed me that the contest still had more to teach me.</p>
<p>A buddy of mine has spent some time with <a href="http://ipsc.ksp.sk/2014/real/problems/k.html">the crossword problem</a> and told me that he enjoyed it. I didn't try this problem during the actual event, but I was a little familiar with it from my friend's description.</p>
<p>To add to the fun, I decided this would be a great excuse to take up <a href="http://rubyrogues.com/164-rr-staying-sharp-with-dave-thomas/">the recent challenge Dave Thomas gave to the Ruby Rogues</a>: "Stop writing tests."</p>
<h4>Step 1: Feedback Loops</h4>
<p>Without tests to guide me, I really want to see what's going on. One of the biggest advantages of tests, in my opinion, is the feedback loop it provides. So I set out to provide my own feedback.</p>
<p>Since the problem at hand involves filling in a crossword board, the easiest feedback loop I could think of was to see the board as it fills in. The final board is also the required output. Therefor, I decided a good first step would just be to read the board into some data structure and write it back out. Once I had that, I could insert code between those steps to fill it in. And constantly seeing the board evolve would let me eyeball things for obvious mistakes.</p>
<p>I set out to make that happen, with the minimal amount of effort. First I created a project:</p>
<pre><code>$ mkdir -p daves_no_tests_challenge/{bin,data,lib}
$ cd daves_no_tests_challenge/
$ git init
Initialized empty Git repository in /Users/jeg2/Desktop/daves_no_tests_challenge/.git/
</code></pre>
<p>Then I built a minimal data structure:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">Crossword</span>
<span class="k">class</span> <span class="nc">Board</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">squares</span><span class="p">)</span>
<span class="vi">@squares</span> <span class="o">=</span> <span class="n">squares</span>
<span class="k">end</span>
<span class="kp">attr_reader</span> <span class="ss">:squares</span>
<span class="kp">private</span> <span class="ss">:squares</span>
<span class="k">def</span> <span class="nf">to_s</span>
<span class="n">squares</span><span class="o">.</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">row</span><span class="o">|</span> <span class="n">row</span><span class="o">.</span><span class="n">join</span> <span class="p">}</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">"</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>That code just takes in and stringifies (for output) some rows of crossword squares. Eventually, this code will need to be able to add answers, but that isn't needed for this first pass.</p>
<p>Next I wrapped my fledgling data structure in some reading and writing code:</p>
<div class="highlight highlight-ruby"><pre><span class="n">require_relative</span> <span class="s2">"board"</span>
<span class="k">module</span> <span class="nn">Crossword</span>
<span class="k">class</span> <span class="nc">Solver</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span>
<span class="vi">@input</span> <span class="o">=</span> <span class="n">input</span>
<span class="vi">@output</span> <span class="o">=</span> <span class="n">output</span>
<span class="vi">@board</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="k">end</span>
<span class="kp">attr_reader</span> <span class="ss">:input</span><span class="p">,</span> <span class="ss">:output</span><span class="p">,</span> <span class="ss">:board</span>
<span class="kp">private</span> <span class="ss">:input</span><span class="p">,</span> <span class="ss">:output</span><span class="p">,</span> <span class="ss">:board</span>
<span class="k">def</span> <span class="nf">solve</span>
<span class="n">parse_board</span>
<span class="n">write_board</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">parse_board</span>
<span class="n">rows</span><span class="p">,</span> <span class="n">columns</span> <span class="o">=</span> <span class="n">input</span><span class="o">.</span><span class="n">gets</span><span class="o">.</span><span class="n">scan</span><span class="p">(</span><span class="sr">/\d+/</span><span class="p">)</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&</span><span class="ss">:to_i</span><span class="p">)</span>
<span class="vi">@board</span> <span class="o">=</span> <span class="no">Board</span><span class="o">.</span><span class="n">new</span><span class="p">(</span>
<span class="nb">Array</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">rows</span><span class="p">)</span> <span class="p">{</span> <span class="n">input</span><span class="o">.</span><span class="n">gets</span><span class="o">.</span><span class="n">chars</span><span class="o">.</span><span class="n">first</span><span class="p">(</span><span class="n">columns</span><span class="p">)</span> <span class="p">}</span>
<span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">write_board</span>
<span class="n">output</span><span class="o">.</span><span class="n">puts</span> <span class="n">board</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>That code just parses the <code>Board</code> using the dimensions in the input file and writes it back out. This class gives me a place to hang some clue solving code, when I'm ready for that.</p>
<p>In preparation for adding an executable, I collapsed this process into a simple method call:</p>
<div class="highlight highlight-ruby"><pre><span class="n">require_relative</span> <span class="s2">"crossword/solver"</span>
<span class="k">module</span> <span class="nn">Crossword</span>
<span class="kp">module_function</span>
<span class="k">def</span> <span class="nf">solve</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="no">Solver</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span><span class="o">.</span><span class="n">solve</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Finally, I added the minimal binary:</p>
<div class="highlight highlight-ruby"><pre><span class="c1">#!/usr/bin/env ruby -w</span>
<span class="n">require_relative</span> <span class="s2">"../lib/crossword"</span>
<span class="no">Crossword</span><span class="o">.</span><span class="n">solve</span><span class="p">(</span><span class="no">ARGF</span><span class="p">,</span> <span class="vg">$stdout</span><span class="p">)</span>
</pre></div>
<p>If we make that file executable and point it at the easy input, we can see our first empty crossword:</p>
<pre><code>$ chmod +x bin/solve
$ bin/solve data/k1.in
........#.#...#.#.#####.......
......######..#.#..#...####...
..####..#.#..#######...#......
....#.######..#.#..#.#.#......
....#...#.#...#.#.######......
....#...#.########.#.#.#####..
.#######....#...#..#.#.#......
.#..#.#.#######......#....#...
.#....#.....#.#.......#...#...
.#....#.########..#########...
.######.#.#.#.#.#.....#...#...
.#....#.#.#.#.#.#.##########..
.....########.#.#.....#...#...
.#..#.#.#.#...#.#########.#...
######...####.#.......#.#.#...
.#..#.#.#.#....############..#
.#..########.#....#...#.#....#
.#..#.#.#...#####.##########.#
.#..#.#.#..#.#....#.....#.#..#
...#########.#########..#.#..#
....#.#.#..#.#....#.#.########
......#.#..#.#########..#.#..#
.#.#..#.#..#.#..#.#.#...#.#...
.#.#.......#.#..#.#.#.#...#...
#####.#########.#.#.#.#...#.#.
.#.#..#.#..#....#..########.#.
.######.#..#######..#.#...#.#.
...#..#.#.......#...#.#.....#.
....######...######...#.....#.
......#.#.............#######.
</code></pre>
<p>Seeing this made me realize that there are two sides to crosswords: the board and the clues. I now had some visibility into one side, but what about clues?</p>
<p>For clues I wanted to know, what am I handling and what am I not. If I printed out the not yet handled stuff, I could scan such a list for similar clues and then write the code to handle them. I should then see the unhandled clue list shrink and some answers appear on the board. I could then just repeat that process until I am done.</p>
<p>This requires just two changes to my <code>Solver</code>:</p>
<div class="highlight highlight-ruby"><pre><span class="c1"># ...</span>
<span class="k">module</span> <span class="nn">Crossword</span>
<span class="k">class</span> <span class="nc">Solver</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">solve</span>
<span class="n">parse_board</span>
<span class="n">solve_clues</span>
<span class="n">write_board</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">solve_clues</span>
<span class="o">%</span><span class="n">i</span><span class="o">[</span><span class="n">across</span> <span class="n">down</span><span class="o">].</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">direction</span><span class="o">|</span>
<span class="n">count</span> <span class="o">=</span> <span class="nb">gets</span><span class="o">.</span><span class="n">to_i</span>
<span class="n">count</span><span class="o">.</span><span class="n">times</span> <span class="k">do</span>
<span class="n">row</span><span class="p">,</span> <span class="n">column</span><span class="p">,</span> <span class="n">clue</span> <span class="o">=</span> <span class="nb">gets</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">" "</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span><span class="o">.</span><span class="n">map</span><span class="o">.</span><span class="n">with_index</span> <span class="p">{</span> <span class="o">|</span><span class="n">str</span><span class="p">,</span> <span class="n">i</span><span class="o">|</span>
<span class="n">i</span> <span class="o"><</span> <span class="mi">2</span> <span class="o">?</span> <span class="n">str</span><span class="o">.</span><span class="n">to_i</span> <span class="p">:</span> <span class="n">str</span><span class="o">.</span><span class="n">strip</span>
<span class="p">}</span>
<span class="k">if</span> <span class="kp">false</span> <span class="c1"># FIXME: match a clue</span>
<span class="c1"># FIXME: write the answer to the board</span>
<span class="k">else</span>
<span class="nb">warn</span> <span class="s2">"No match: </span><span class="si">#{</span><span class="n">clue</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>I added a <code>solve_clues()</code> step to the overall process, then fleshed out that method just enough to parse clues and consider all of them failures. Notice that I got a bit hand wavy with how the solving stuff was actually going to work. It didn't matter yet. I also put the clue warnings on <code>STDERR</code> because they aren't legally part of the output and I wanted to be able to redirect them and the board independently.</p>
<p>Now I had a lot of output, but it showed me exactly what I needed to do:</p>
<pre><code>No match: Pokemon which evolves from Oddish
No match: Pokemon which evolves from Spearow
No match: Chemical element with the symbol Au
No match: Pokemon with number #63
No match: Pokemon with number #137
No match: Last name of the President of the United States who held the office in 1987
No match: Chemical element with the symbol Na
No match: Chemical element with the symbol W
No match: Pokemon which evolves into Arbok
No match: Pokemon which evolves from Machoke
…
........#.#...#.#.#####.......
......######..#.#..#...####...
..####..#.#..#######...#......
....#.######..#.#..#.#.#......
....#...#.#...#.#.######......
....#...#.########.#.#.#####..
.#######....#...#..#.#.#......
.#..#.#.#######......#....#...
.#....#.....#.#.......#...#...
.#....#.########..#########...
.######.#.#.#.#.#.....#...#...
.#....#.#.#.#.#.#.##########..
.....########.#.#.....#...#...
.#..#.#.#.#...#.#########.#...
######...####.#.......#.#.#...
.#..#.#.#.#....############..#
.#..########.#....#...#.#....#
.#..#.#.#...#####.##########.#
.#..#.#.#..#.#....#.....#.#..#
...#########.#########..#.#..#
....#.#.#..#.#....#.#.########
......#.#..#.#########..#.#..#
.#.#..#.#..#.#..#.#.#...#.#...
.#.#.......#.#..#.#.#.#...#...
#####.#########.#.#.#.#...#.#.
.#.#..#.#..#....#..########.#.
.######.#..#######..#.#...#.#.
...#..#.#.......#...#.#.....#.
....######...######...#.....#.
......#.#.............#######.
</code></pre>
<h4>Step 2: Strategies</h4>
<p>Satisfied that I could now follow my process without using a test suite, it was time to make this problem easy to solve.</p>
<p>I realized that different clues would need different strategies to solve and I might need to write several of those. This made me want to add a little bit of infrastructure for loading and matching strategies. I figured I could solve that problem now, then focus in entirely on clue specifics.</p>
<p>I sketched out a base class for this approach:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">Crossword</span>
<span class="k">class</span> <span class="nc">Strategy</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">strategies</span>
<span class="vi">@strategies</span> <span class="o">||=</span> <span class="o">[</span> <span class="o">]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">inherited</span><span class="p">(</span><span class="n">subclass</span><span class="p">)</span>
<span class="n">strategies</span> <span class="o"><<</span> <span class="n">subclass</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">load</span><span class="p">(</span><span class="n">dir</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">__dir__</span><span class="p">,</span> <span class="s2">"strategies"</span><span class="p">))</span>
<span class="no">Dir</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">dir</span><span class="si">}</span><span class="s2">/*.rb"</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">path</span><span class="o">|</span>
<span class="no">Kernel</span><span class="o">.</span><span class="n">load</span> <span class="n">path</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">match</span><span class="p">(</span><span class="n">clue</span><span class="p">)</span>
<span class="n">strategies</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">subclass</span><span class="o">|</span>
<span class="n">strategy</span> <span class="o">=</span> <span class="n">subclass</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">clue</span><span class="p">)</span>
<span class="k">return</span> <span class="n">strategy</span> <span class="k">if</span> <span class="n">strategy</span><span class="o">.</span><span class="n">match?</span>
<span class="k">end</span>
<span class="kp">nil</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">clue</span><span class="p">)</span>
<span class="vi">@clue</span> <span class="o">=</span> <span class="n">clue</span>
<span class="vi">@match_data</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="k">end</span>
<span class="kp">attr_reader</span> <span class="ss">:clue</span><span class="p">,</span> <span class="ss">:match_data</span>
<span class="kp">private</span> <span class="ss">:clue</span><span class="p">,</span> <span class="ss">:match_data</span>
<span class="k">def</span> <span class="nf">match?</span>
<span class="vi">@match_data</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">const_get</span><span class="p">(</span><span class="ss">:CLUE_REGEX</span><span class="p">)</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">clue</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">answer</span>
<span class="nb">fail</span> <span class="no">NotImplementedError</span><span class="p">,</span> <span class="s2">"Strategies must override answer()"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>The four class methods all work together to support this simple process:</p>
<ol>
<li>I plan to define subclasses of <code>Strategy</code>
</li>
<li>And I'll throw all of them in the same directory</li>
<li>So dynamically load everything in there</li>
<li>Then search through all subclasses to find one that matches the current clue</li>
</ol><p>The two instance methods handle matching and generating answers for clues. Originally I considered forcing subclasses to implement both. However a glance at the clues made me feel that at least some could be matched with a simple regex. I decided to make that the default behavior. Subclasses can still override <code>match?()</code> if they need more complex logic.</p>
<p>My use of a constant for the regex is a little tricky. Had I used a bare constant, Ruby would have resolved it based on the current scope. Put simply, it would have searched for a constant in this class. (That's an over simplification, but true enough here.) It would not find a constant defined in a <code>Strategy</code> subclass, where I wanted to put them. Because of that, I look up the constant manually, using Ruby's reflection capabilities. This code will find a constant in the subclass. The truth is, it will fail with an error if the subclass does not define the constant, but I expect <code>match?()</code> to be overriden in those cases.</p>
<p>Plugging these strategies into the <code>Solver</code> is just four lines of code, explained inline:</p>
<div class="highlight highlight-ruby"><pre><span class="n">require_relative</span> <span class="s2">"board"</span>
<span class="n">require_relative</span> <span class="s2">"strategy"</span> <span class="c1"># 1: load the dependency</span>
<span class="k">module</span> <span class="nn">Crossword</span>
<span class="k">class</span> <span class="nc">Solver</span>
<span class="c1"># ...</span>
<span class="kp">private</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">solve_clues</span>
<span class="no">Strategy</span><span class="o">.</span><span class="n">load</span> <span class="c1"># 2: dynamically load all available strategies</span>
<span class="o">%</span><span class="n">i</span><span class="o">[</span><span class="n">across</span> <span class="n">down</span><span class="o">].</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">direction</span><span class="o">|</span>
<span class="n">count</span> <span class="o">=</span> <span class="nb">gets</span><span class="o">.</span><span class="n">to_i</span>
<span class="n">count</span><span class="o">.</span><span class="n">times</span> <span class="k">do</span>
<span class="n">row</span><span class="p">,</span> <span class="n">column</span><span class="p">,</span> <span class="n">clue</span> <span class="o">=</span> <span class="nb">gets</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">" "</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span><span class="o">.</span><span class="n">map</span><span class="o">.</span><span class="n">with_index</span> <span class="p">{</span> <span class="o">|</span><span class="n">str</span><span class="p">,</span> <span class="n">i</span><span class="o">|</span>
<span class="n">i</span> <span class="o"><</span> <span class="mi">2</span> <span class="o">?</span> <span class="n">str</span><span class="o">.</span><span class="n">to_i</span> <span class="p">:</span> <span class="n">str</span><span class="o">.</span><span class="n">strip</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">strategy</span> <span class="o">=</span> <span class="no">Strategy</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">clue</span><span class="p">))</span> <span class="c1"># 3: find a match for the clue</span>
<span class="c1"># 4: record the answer on the Board:</span>
<span class="n">board</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s2">"record_</span><span class="si">#{</span><span class="n">direction</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="n">row</span><span class="p">,</span> <span class="n">column</span><span class="p">,</span> <span class="n">strategy</span><span class="o">.</span><span class="n">answer</span><span class="p">)</span>
<span class="k">else</span>
<span class="nb">warn</span> <span class="s2">"No match: </span><span class="si">#{</span><span class="n">clue</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>As you can see, this required the <code>Board</code> to gain some new methods as well, for recording answers:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">Crossword</span>
<span class="k">class</span> <span class="nc">Board</span>
<span class="c1"># ...</span>
<span class="k">def</span> <span class="nf">record_across</span><span class="p">(</span><span class="n">row</span><span class="p">,</span> <span class="n">column</span><span class="p">,</span> <span class="n">answer</span><span class="p">)</span>
<span class="n">answer</span><span class="o">.</span><span class="n">downcase</span><span class="o">.</span><span class="n">chars</span><span class="o">.</span><span class="n">each_with_index</span> <span class="k">do</span> <span class="o">|</span><span class="n">char</span><span class="p">,</span> <span class="n">i</span><span class="o">|</span>
<span class="n">record</span><span class="p">(</span><span class="n">row</span><span class="p">,</span> <span class="n">column</span> <span class="o">+</span> <span class="n">i</span><span class="p">,</span> <span class="n">char</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">record_down</span><span class="p">(</span><span class="n">row</span><span class="p">,</span> <span class="n">column</span><span class="p">,</span> <span class="n">answer</span><span class="p">)</span>
<span class="n">answer</span><span class="o">.</span><span class="n">downcase</span><span class="o">.</span><span class="n">chars</span><span class="o">.</span><span class="n">each_with_index</span> <span class="k">do</span> <span class="o">|</span><span class="n">char</span><span class="p">,</span> <span class="n">i</span><span class="o">|</span>
<span class="n">record</span><span class="p">(</span><span class="n">row</span> <span class="o">+</span> <span class="n">i</span><span class="p">,</span> <span class="n">column</span><span class="p">,</span> <span class="n">char</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># ...</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">record</span><span class="p">(</span><span class="n">row</span><span class="p">,</span> <span class="n">column</span><span class="p">,</span> <span class="n">char</span><span class="p">)</span>
<span class="n">square</span> <span class="o">=</span> <span class="n">squares</span><span class="o">[</span><span class="n">row</span><span class="o">][</span><span class="n">column</span><span class="o">]</span>
<span class="nb">fail</span> <span class="s2">"Outside of board"</span> <span class="k">if</span> <span class="n">square</span><span class="o">.</span><span class="n">nil?</span>
<span class="nb">fail</span> <span class="s2">"Outside of answer"</span> <span class="k">if</span> <span class="n">square</span> <span class="o">==</span> <span class="s2">"."</span>
<span class="nb">fail</span> <span class="s2">"Answer mismatch"</span> <span class="k">if</span> <span class="o">![</span><span class="s2">"#"</span><span class="p">,</span> <span class="n">char</span><span class="o">].</span><span class="n">include?</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
<span class="n">square</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="n">char</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>I did notice that <code>record_across()</code> and <code>record_down()</code> had some duplication that could be removed with some <code>yield</code> magic. This is a pretty simple case though and I decided to let it ride.</p>
<p>You'll noticed that I cranked the paranoia up a bit in <code>record()</code>. Placing letters in a crossword puzzle is a great time to notice several potential problems like overruns or answers that don't agree. I don't know if I would have added all of these guard clauses in the presence of a strong test suite, but I liked seeing them here in the end. I think the extra checks here, where they mattered most, helped me write the rest of the code with more confidence.</p>
<p>This felt like enough infrastructure to start solving actual clues.</p>
<p>I decided to start with the Pokémon questions. Glancing through the lists, I saw two different flavors of clues. So I went hunting for <a href="https://raw.githubusercontent.com/veekun/pokedex/master/pokedex/data/csv/pokemon.csv">a data file that could answer both types of questions in an easy-to-read format</a>. Having found that, I needed some code to parse the data and allow me to search through it:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"csv"</span>
<span class="k">module</span> <span class="nn">Crossword</span>
<span class="k">class</span> <span class="nc">PokemonList</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">loaded</span>
<span class="vi">@loaded</span> <span class="o">||=</span> <span class="kp">new</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">method_missing</span><span class="p">(</span><span class="nb">method</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="k">if</span> <span class="n">loaded</span><span class="o">.</span><span class="n">respond_to?</span><span class="p">(</span><span class="nb">method</span><span class="p">)</span>
<span class="n">loaded</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="nb">method</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="k">else</span>
<span class="k">super</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">path</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">__dir__</span><span class="p">,</span> <span class="o">*</span><span class="sx">%w[.. .. data pokemon.csv]</span><span class="p">))</span>
<span class="vi">@pokemon</span> <span class="o">=</span> <span class="no">CSV</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="kp">true</span><span class="p">,</span> <span class="n">header_converters</span><span class="p">:</span> <span class="ss">:symbol</span><span class="p">)</span>
<span class="k">end</span>
<span class="kp">attr_reader</span> <span class="ss">:pokemon</span>
<span class="kp">private</span> <span class="ss">:pokemon</span>
<span class="k">def</span> <span class="nf">find_by_number</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
<span class="n">pokemon</span><span class="o">.</span><span class="n">find</span> <span class="p">{</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span> <span class="n">record</span><span class="o">[</span><span class="ss">:id</span><span class="o">]</span> <span class="o">==</span> <span class="n">number</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>I do the actual parse in <code>initialize()</code>, just handing off to the standard <code>CSV</code> library. The class methods setup a system of fowarding to the instance methods for searching through a loaded data set. The only search I needed to drop the first set of clues was an ability to find a Pokémon by number.</p>
<p>With this database ready to go, the actual strategy for solving the clue is almost a non-event:</p>
<div class="highlight highlight-ruby"><pre><span class="n">require_relative</span> <span class="s2">"../pokemon_list"</span>
<span class="k">module</span> <span class="nn">Crossword</span>
<span class="k">class</span> <span class="nc">PokemonNumber</span> <span class="o"><</span> <span class="no">Strategy</span>
<span class="no">CLUE_REGEX</span> <span class="o">=</span> <span class="sr">/\APokemon with number #(?<number>\d+)\z/</span>
<span class="k">def</span> <span class="nf">answer</span>
<span class="no">PokemonList</span><span class="o">.</span><span class="n">find_by_number</span><span class="p">(</span><span class="n">match_data</span><span class="o">[</span><span class="ss">:number</span><span class="o">]</span><span class="p">)</span><span class="o">[</span><span class="ss">:identifier</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>You can finally see how my stategies pay off here. I write a regex to match the clue, capturing the key bits, then use those bits to find the right answer when called upon to do so.</p>
<p>It was time to check in on those feedback loops:</p>
<pre><code>No match: Pokemon which evolves from Oddish
No match: Pokemon which evolves from Spearow
No match: Chemical element with the symbol Au
…
........#.#...#.#.#####.......
......######..#.#..#...g###...
..abra..#.#..porygon...r......
....#.######..#.#..#.d.i......
....#...#.#...#.#.###i#m......
....#...#.########.#.t.e####..
.m######....#...#..#.t.r......
.e..#.#.#######......o....#...
.w....#.....#.#.......#...#...
.t....#.##a#####..#########...
.w#####.#.r.#.#.#.....#...#...
.o....#.#.t.#.#.#.hitmonchan..
.....#####i##.#.#.....#...#...
.m..#.#.#.c...#.#########.#...
#a####...#u##.#.......#.#.#...
.c..#.#.#.n....############..#
.h..######o#.s....#...#.#....#
.o..#.#.#...#a###.charmander.#
.p..#.#.#..#.n....#.....#.#..#
...#########.dragonite..#.#..#
....#.#.#..#.s....#.#.primeape
......#.#..#.lickitung..#.#..#
.#.w..#.#..#.a..#.#.#...#.#...
.#.e.......#.s..#.#.#.#...#...
###e#.growlithe.#.#.#.#...#.k.
.#.d..#.#..#....#..########.r.
.##l###.#..#######..#.#...#.a.
...e..#.#.......#...#.#.....b.
....######...pinsir...#.....b.
......#.#.............######y.
</code></pre>
<p>There are two things to notice here:</p>
<ul>
<li>There are still plenty of unsolved clues, including another flavor of Pokémon clues</li>
<li>The board is starting to fill in and we even see some clues sharing squares</li>
</ul><h4>Step 3: The Real Problem</h4>
<p>The rest of this puzzle was just about writing strategies to solve the various clues and it went pretty smoothly. I'm not going to show all of them. Checkout <a href="https://github.com/JEG2/daves_no_test_challenge">the repository on GitHub</a>, if you're curious.</p>
<p>I'll show you the most challenging clue type though, just for fun. Check out this one:</p>
<pre><code>No match: Last name of the next President of the United States after Harry S. Truman
</code></pre>
<p>This clue is still just a lookup in a database, but names are involved. Names can be quite a curve ball. Trust the guy named James Edward Gray II and married to Dana Ann Leslie Gray. There's not a form in the world that handles both of our names gracefully. I knew enough to fear this part, because there was very little chance of the database perfectly matching the clues. Given that, I built an object just to handle name comparisons:</p>
<div class="highlight highlight-ruby"><pre><span class="k">module</span> <span class="nn">Crossword</span>
<span class="k">class</span> <span class="nc">Name</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">full_name</span><span class="p">)</span>
<span class="vi">@full_name</span> <span class="o">=</span> <span class="n">full_name</span>
<span class="k">end</span>
<span class="kp">attr_reader</span> <span class="ss">:full_name</span>
<span class="kp">private</span> <span class="ss">:full_name</span>
<span class="k">def</span> <span class="nf">first_name</span>
<span class="n">full_name</span><span class="o">[</span><span class="sr">/\A\w+/</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">last_name</span>
<span class="n">full_name</span><span class="o">[</span><span class="sr">/\w+\z/</span><span class="o">]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">==</span><span class="p">(</span><span class="n">other</span><span class="p">)</span>
<span class="n">first_name</span> <span class="o">==</span> <span class="n">other</span><span class="o">.</span><span class="n">first_name</span> <span class="o">&&</span>
<span class="n">last_name</span> <span class="o">==</span> <span class="n">other</span><span class="o">.</span><span class="n">last_name</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>Now I'll be honest: I cheated here. A lot. I didn't need to worry about suffixes because my database didn't include them and I didn't need to consider middle names or initials in the comparisons for these easy clues. (That's not true in the hard input!) I was lucky that both lists were small enough for me to visually scan for issues like that. It saved me a fair bit of code.</p>
<p>The president database is similar to what you saw for Pokémon:</p>
<div class="highlight highlight-ruby"><pre><span class="nb">require</span> <span class="s2">"csv"</span>
<span class="n">require_relative</span> <span class="s2">"name"</span>
<span class="k">module</span> <span class="nn">Crossword</span>
<span class="k">class</span> <span class="nc">PresidentList</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">loaded</span>
<span class="vi">@loaded</span> <span class="o">||=</span> <span class="kp">new</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">method_missing</span><span class="p">(</span><span class="nb">method</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="k">if</span> <span class="n">loaded</span><span class="o">.</span><span class="n">respond_to?</span><span class="p">(</span><span class="nb">method</span><span class="p">)</span>
<span class="n">loaded</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="nb">method</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="k">else</span>
<span class="k">super</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">path</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">__dir__</span><span class="p">,</span> <span class="o">*</span><span class="sx">%w[.. .. data presidents.csv]</span><span class="p">))</span>
<span class="vi">@presidents</span> <span class="o">=</span> <span class="no">CSV</span><span class="o">.</span><span class="n">read</span><span class="p">(</span>
<span class="n">path</span><span class="p">,</span>
<span class="ss">headers</span><span class="p">:</span> <span class="kp">true</span><span class="p">,</span>
<span class="n">header_converters</span><span class="p">:</span> <span class="ss">:symbol</span><span class="p">,</span>
<span class="ss">converters</span><span class="p">:</span> <span class="nb">lambda</span> <span class="p">{</span> <span class="o">|</span><span class="n">field</span><span class="p">,</span> <span class="n">field_info</span><span class="o">|</span>
<span class="k">if</span> <span class="n">field_info</span><span class="o">.</span><span class="n">header</span> <span class="o">==</span> <span class="ss">:president</span>
<span class="no">Name</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">field</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="s2">" (2nd term)"</span><span class="p">,</span> <span class="s2">""</span><span class="p">))</span>
<span class="k">else</span>
<span class="n">field</span>
<span class="k">end</span>
<span class="p">}</span>
<span class="p">)</span>
<span class="k">end</span>
<span class="kp">attr_reader</span> <span class="ss">:presidents</span>
<span class="kp">private</span> <span class="ss">:presidents</span>
<span class="k">def</span> <span class="nf">find_by_name</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
<span class="n">presidents</span><span class="o">.</span><span class="n">find</span> <span class="p">{</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span> <span class="n">record</span><span class="o">[</span><span class="ss">:president</span><span class="o">]</span> <span class="o">==</span> <span class="nb">name</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">find_by_number</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
<span class="n">presidents</span><span class="o">.</span><span class="n">find</span> <span class="p">{</span> <span class="o">|</span><span class="n">record</span><span class="o">|</span> <span class="n">record</span><span class="o">[</span><span class="ss">:presidency</span><span class="o">]</span> <span class="o">==</span> <span class="n">number</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>You can see that I used the same class method forwarding trick here. In fact, I used it in all three databases that I built to solve this problem. Were I not just playing around here, I'm pretty sure I would have felt compelled to extract the common code into a superclass, but I knew there was almost no chance I would need to change it during this exercise. Copy and paste coding turned out to be good enough for this case.</p>
<p>The main difference in the load here is that I had <code>CSV</code> convert the names into <code>Name</code> objects as they were read in. This allows for easier comparisons later on.</p>
<p>The two instance methods are the searches I needed to find some president by name and then look for a later president. Here's the strategy that actually does that:</p>
<div class="highlight highlight-ruby"><pre><span class="n">require_relative</span> <span class="s2">"../president_list"</span>
<span class="n">require_relative</span> <span class="s2">"../name"</span>
<span class="k">module</span> <span class="nn">Crossword</span>
<span class="k">class</span> <span class="nc">PresidentAfter</span> <span class="o"><</span> <span class="no">Strategy</span>
<span class="no">CLUE_REGEX</span> <span class="o">=</span> <span class="sr">/</span>
<span class="sr"> \A (?<name>First|Last) \s+ name \s+ of \s+ the \s+</span>
<span class="sr"> next \s+ President \s+ of \s+ the \s+ United \s+ States \s+ after \s+</span>
<span class="sr"> (?<before>.+) \z</span>
<span class="sr"> /x</span>
<span class="k">def</span> <span class="nf">answer</span>
<span class="nb">id</span> <span class="o">=</span> <span class="no">PresidentList</span><span class="o">.</span><span class="n">find_by_name</span><span class="p">(</span>
<span class="no">Name</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">match_data</span><span class="o">[</span><span class="ss">:before</span><span class="o">]</span><span class="p">)</span>
<span class="p">)</span><span class="o">[</span><span class="ss">:presidency</span><span class="o">].</span><span class="n">to_i</span> <span class="o">+</span> <span class="mi">1</span>
<span class="no">PresidentList</span>
<span class="o">.</span><span class="n">find_by_number</span><span class="p">(</span><span class="nb">id</span><span class="o">.</span><span class="n">to_s</span><span class="p">)</span><span class="o">[</span><span class="ss">:president</span><span class="o">]</span>
<span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">match_data</span><span class="o">[</span><span class="ss">:name</span><span class="o">].</span><span class="n">downcase</span><span class="si">}</span><span class="s2">_name"</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</pre></div>
<p>These queries pick up some oddities of their individual databases, that could definitely be cleaned up, if this wasn't contest code. That said, the strategy code is pretty short even for this trickier clue. I'm not feeling much pain.</p>
<h4>Conclusion</h4>
<p>Overall, I really liked the process I slipped into here:</p>
<ol>
<li>Setup feedback loops</li>
<li>Make the problem easy to solve</li>
<li>Solve the problem</li>
</ol><p>Testing can be a viable way to handle feedback loops, but it's not the only available solution. Sure, I had a couple of times during this experiment when I introduced a bug that I found on the next run, but there weren't many of those and they were easily dealt with. I didn't feel super hindered in this approach. I need to keep exploring this idea though, so I know what the limitations are.</p>
<p>In the end, solving just this easier portion of the problem took me roughly three and a half hours. That's not fair though, because I was able to do a little thinking about how I should handle it before I started.</p>James Edward Gray II