Skip to content

Commit

Permalink
dominoes: Add generator and example
Browse files Browse the repository at this point in the history
Note that this exercise appears to differ from other xruby exercises in
the following significant manner:

Verification that the student function under test has produced a correct
answer is NOT done by comparing the observed output to an expected
output (or even a list of expected outputs), because the set of
acceptable outputs is quite large even for a small input set.

Thus, verification is done by running the observed output against a
function that verifies various properties about the output.

https://github.com/exercism/x-common/blob/master/exercises/dominoes/canonical-data.json
explains the approach taken.
  • Loading branch information
petertseng committed Nov 8, 2016
1 parent 9e9977a commit add7611
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 0 deletions.
6 changes: 6 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,12 @@
"difficulty": 1,
"topics": [
]
},
{
"slug": "dominoes",
"difficulty": 1,
"topics": [
]
}
],
"deprecated": [
Expand Down
1 change: 1 addition & 0 deletions exercises/dominoes/.version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
138 changes: 138 additions & 0 deletions exercises/dominoes/dominoes_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#!/usr/bin/env ruby
gem 'minitest', '>= 5.0.0'
require 'minitest/autorun'
require_relative 'dominoes'

# Test data version:
# 018c48d
class DominoesTest < Minitest::Test
def assert_correct_chain(input_dominoes, output_chain)
refute_nil output_chain, "There should be a chain for #{input_dominoes}"

input_normal = input_dominoes.map(&:sort).sort
output_normal = output_chain.map(&:sort).sort
assert_equal input_normal, output_normal,
'Dominoes used in the output must be the same as the ones given in the input'

output_chain.each_cons(2).with_index { |(d1, d2), i|
_, d1_right = d1
d2_left, _ = d2
assert_equal d1_right, d2_left,
"In chain #{output_chain}, right end of domino #{i} (#{d1}) and left end of domino #{i + 1} (#{d2}) must match"
}

return if output_chain.empty?

first_domino = output_chain.first
last_domino = output_chain.last
d1_left, _ = first_domino
_, d2_right = last_domino
assert_equal d1_left, d2_right,
"In chain #{output_chain}, left end of first domino (#{first_domino}) and right end of last domino (#{last_domino}) must match"
end

def test_empty_input_empty_output
# skip
input_dominoes = []
output_chain = Dominoes.chain(input_dominoes)
assert_correct_chain(input_dominoes, output_chain)
end

def test_singleton_input_singleton_output
skip
input_dominoes = [[1, 1]]
output_chain = Dominoes.chain(input_dominoes)
assert_correct_chain(input_dominoes, output_chain)
end

def test_singleton_that_can_t_be_chained
skip
input_dominoes = [[1, 2]]
output_chain = Dominoes.chain(input_dominoes)
assert_equal nil, output_chain, "There should be no chain for #{input_dominoes}"
end

def test_three_elements
skip
input_dominoes = [[1, 2], [3, 1], [2, 3]]
output_chain = Dominoes.chain(input_dominoes)
assert_correct_chain(input_dominoes, output_chain)
end

def test_can_reverse_dominoes
skip
input_dominoes = [[1, 2], [1, 3], [2, 3]]
output_chain = Dominoes.chain(input_dominoes)
assert_correct_chain(input_dominoes, output_chain)
end

def test_can_t_be_chained
skip
input_dominoes = [[1, 2], [4, 1], [2, 3]]
output_chain = Dominoes.chain(input_dominoes)
assert_equal nil, output_chain, "There should be no chain for #{input_dominoes}"
end

def test_disconnected_simple
skip
input_dominoes = [[1, 1], [2, 2]]
output_chain = Dominoes.chain(input_dominoes)
assert_equal nil, output_chain, "There should be no chain for #{input_dominoes}"
end

def test_disconnected_double_loop
skip
input_dominoes = [[1, 2], [2, 1], [3, 4], [4, 3]]
output_chain = Dominoes.chain(input_dominoes)
assert_equal nil, output_chain, "There should be no chain for #{input_dominoes}"
end

def test_disconnected_single_isolated
skip
input_dominoes = [[1, 2], [2, 3], [3, 1], [4, 4]]
output_chain = Dominoes.chain(input_dominoes)
assert_equal nil, output_chain, "There should be no chain for #{input_dominoes}"
end

def test_need_backtrack
skip
input_dominoes = [[1, 2], [2, 3], [3, 1], [2, 4], [2, 4]]
output_chain = Dominoes.chain(input_dominoes)
assert_correct_chain(input_dominoes, output_chain)
end

def test_separate_loops
skip
input_dominoes = [[1, 2], [2, 3], [3, 1], [1, 1], [2, 2], [3, 3]]
output_chain = Dominoes.chain(input_dominoes)
assert_correct_chain(input_dominoes, output_chain)
end

def test_ten_elements
skip
input_dominoes = [[1, 2], [5, 3], [3, 1], [1, 2], [2, 4], [1, 6], [2, 3], [3, 4], [5, 6]]
output_chain = Dominoes.chain(input_dominoes)
assert_correct_chain(input_dominoes, output_chain)
end

# Problems in exercism evolve over time, as we find better ways to ask
# questions.
# The version number refers to the version of the problem you solved,
# not your solution.
#
# Define a constant named VERSION inside of the top level BookKeeping
# module, which may be placed near the end of your file.
#
# In your file, it will look like this:
#
# module BookKeeping
# VERSION = 1 # Where the version number matches the one in the test.
# end
#
# If you are curious, read more about constants on RubyDoc:
# http://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/constants.html
def test_bookkeeping
skip
assert_equal 1, BookKeeping::VERSION
end
end
37 changes: 37 additions & 0 deletions exercises/dominoes/example.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module Dominoes
def self.chain(dominoes)
return dominoes if dominoes.empty?

first = dominoes.first

subchain = try_subchain(dominoes.drop(1), *first)
subchain && [first] + subchain
end

def self.try_subchain(dominoes, chain_left, chain_right)
return chain_left == chain_right ? [] : nil if dominoes.empty?

dominoes.each_with_index { |domino, i|
other_dominoes = dominoes[0...i] + dominoes[(i + 1)..-1]
domino_left, domino_right = domino
if domino_left == chain_right
# Try to add domino (unflipped) to chain
if (subchain = try_subchain(other_dominoes, chain_left, domino_right))
return [domino] + subchain
end
elsif domino_right == chain_right
# Try to add domino (flipped) to chain
if (subchain = try_subchain(other_dominoes, chain_left, domino_left))
return [[domino_right, domino_left]] + subchain
end
end
}

# Found no suitable chain.
return nil
end
end

module BookKeeping
VERSION = 1
end
46 changes: 46 additions & 0 deletions exercises/dominoes/example.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env ruby
gem 'minitest', '>= 5.0.0'
require 'minitest/autorun'
require_relative 'dominoes'

# Test data version:
# <%= sha1 %>
class DominoesTest < Minitest::Test
def assert_correct_chain(input_dominoes, output_chain)
refute_nil output_chain, "There should be a chain for #{input_dominoes}"

input_normal = input_dominoes.map(&:sort).sort
output_normal = output_chain.map(&:sort).sort
assert_equal input_normal, output_normal,
'Dominoes used in the output must be the same as the ones given in the input'

output_chain.each_cons(2).with_index { |(d1, d2), i|
_, d1_right = d1
d2_left, _ = d2
assert_equal d1_right, d2_left,
"In chain #{output_chain}, right end of domino #{i} (#{d1}) and left end of domino #{i + 1} (#{d2}) must match"
}

return if output_chain.empty?

first_domino = output_chain.first
last_domino = output_chain.last
d1_left, _ = first_domino
_, d2_right = last_domino
assert_equal d1_left, d2_right,
"In chain #{output_chain}, left end of first domino (#{first_domino}) and right end of last domino (#{last_domino}) must match"
end

<% test_cases.each do |test_case| %>
def <%= test_case.test_name %>
<%= test_case.skipped %>
<%= test_case.workload %>
end
<% end %>
<%= IO.read(XRUBY_LIB + '/bookkeeping.md') %>
def test_bookkeeping
skip
assert_equal <%= version.next %>, BookKeeping::VERSION
end
end
24 changes: 24 additions & 0 deletions lib/dominoes_cases.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class DominoesCase < OpenStruct
def test_name
'test_%s' % description.gsub(/['= -]+/, '_')
end

def workload

<<-WL.chomp
input_dominoes = #{input}
output_chain = Dominoes.chain(input_dominoes)
#{can_chain ? 'assert_correct_chain(input_dominoes, output_chain)' : 'assert_equal nil, output_chain, "There should be no chain for #{input_dominoes}"'}
WL
end

def skipped
index.zero? ? '# skip' : 'skip'
end
end

DominoesCases = proc do |data|
JSON.parse(data)['cases'].map.with_index do |row, i|
DominoesCase.new(row.merge('index' => i))
end
end

0 comments on commit add7611

Please sign in to comment.