-
-
Notifications
You must be signed in to change notification settings - Fork 525
dominoes: Add generator and example #484
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0073eb4
1ece052
f271187
bf41baa
4d082ee
aed667c
43aa6f8
644efa5
aa1307f
4ad379d
d5571b8
9d2448c
abd83db
eabc138
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
#!/usr/bin/env ruby | ||
gem 'minitest', '>= 5.0.0' | ||
require 'minitest/autorun' | ||
require_relative 'dominoes' | ||
|
||
# Test data version: 82eb00d | ||
class DominoesTest < Minitest::Test | ||
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_not_be_chained | ||
skip | ||
input_dominoes = [[1, 2]] | ||
output_chain = Dominoes.chain(input_dominoes) | ||
refute_correct_chain(input_dominoes, output_chain) | ||
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_not_be_chained | ||
skip | ||
input_dominoes = [[1, 2], [4, 1], [2, 3]] | ||
output_chain = Dominoes.chain(input_dominoes) | ||
refute_correct_chain(input_dominoes, output_chain) | ||
end | ||
|
||
def test_disconnected_simple | ||
skip | ||
input_dominoes = [[1, 1], [2, 2]] | ||
output_chain = Dominoes.chain(input_dominoes) | ||
refute_correct_chain(input_dominoes, output_chain) | ||
end | ||
|
||
def test_disconnected_double_loop | ||
skip | ||
input_dominoes = [[1, 2], [2, 1], [3, 4], [4, 3]] | ||
output_chain = Dominoes.chain(input_dominoes) | ||
refute_correct_chain(input_dominoes, output_chain) | ||
end | ||
|
||
def test_disconnected_single_isolated | ||
skip | ||
input_dominoes = [[1, 2], [2, 3], [3, 1], [4, 4]] | ||
output_chain = Dominoes.chain(input_dominoes) | ||
refute_correct_chain(input_dominoes, output_chain) | ||
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 | ||
|
||
# It's infeasible to use example-based tests for this exercise, | ||
# because the list of acceptable answers for a given input can be quite large. | ||
# Instead, we verify certain properties of a correct chain. | ||
|
||
def assert_correct_chain(input_dominoes, output_chain) | ||
refute_nil output_chain, "There should be a chain for #{input_dominoes}" | ||
assert_same_dominoes(input_dominoes, output_chain) | ||
return if output_chain.empty? | ||
assert_consecutive_dominoes_match(output_chain) | ||
assert_dominoes_at_end_match(output_chain) | ||
end | ||
|
||
def assert_same_dominoes(input_dominoes, output_chain) | ||
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' | ||
end | ||
|
||
def assert_consecutive_dominoes_match(chain) | ||
chain.each_cons(2).with_index { |(d1, d2), i| | ||
assert_equal d1.last, d2.first, | ||
"In chain #{chain}, right end of domino #{i} (#{d1}) and left end of domino #{i + 1} (#{d2}) must match" | ||
} | ||
end | ||
|
||
def assert_dominoes_at_end_match(chain) | ||
first_domino = chain.first | ||
last_domino = chain.last | ||
assert_equal first_domino.first, last_domino.last, | ||
"In chain #{chain}, left end of first domino (#{first_domino}) and right end of last domino (#{last_domino}) must match" | ||
end | ||
|
||
def refute_correct_chain(input_dominoes, output_chain) | ||
assert_nil output_chain, "There should be no chain for #{input_dominoes}" | ||
end | ||
end |
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.take(i) + dominoes.drop(i + 1) | ||
# Try adding the domino either flipped or unflipped. | ||
[domino, domino.reverse].each { |candidate| | ||
domino_left, domino_right = candidate | ||
if domino_left == chain_right | ||
if (subchain = try_subchain(other_dominoes, chain_left, domino_right)) | ||
return [candidate] + subchain | ||
end | ||
end | ||
} | ||
} | ||
|
||
# Found no suitable chain. | ||
# Note that for "no chain" we have to use nil instead of []. | ||
# This is because [] is the valid answer for `Dominoes.chain([])`. | ||
# If we used [] for "no chain", then the meaning of [] is ambiguous. | ||
nil | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unfortunately, it's going to have to be Why? Because for the input There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be useful to add this in a comment in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. I'll note that that comment was partially targeting #469 (comment) to explain why in this case it cannot be that this method always returns the same object - it has to sometimes return nil instead of list. It's useful to have the comment somewhere, so |
||
end | ||
end | ||
|
||
module BookKeeping | ||
VERSION = 1 | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
#!/usr/bin/env ruby | ||
gem 'minitest', '>= 5.0.0' | ||
require 'minitest/autorun' | ||
require_relative 'dominoes' | ||
|
||
# Test data version: <%= sha1 %> | ||
class DominoesTest < Minitest::Test | ||
<% 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 | ||
|
||
# It's infeasible to use example-based tests for this exercise, | ||
# because the list of acceptable answers for a given input can be quite large. | ||
# Instead, we verify certain properties of a correct chain. | ||
|
||
def assert_correct_chain(input_dominoes, output_chain) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you move this to the bottom of the class please. In real life I'd put it at the top, but here it would be good if the first thing the student saw was the first test they need to implement. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that's probably better. I imagine the student might wonder "what's |
||
refute_nil output_chain, "There should be a chain for #{input_dominoes}" | ||
assert_same_dominoes(input_dominoes, output_chain) | ||
return if output_chain.empty? | ||
assert_consecutive_dominoes_match(output_chain) | ||
assert_dominoes_at_end_match(output_chain) | ||
end | ||
|
||
def assert_same_dominoes(input_dominoes, output_chain) | ||
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' | ||
end | ||
|
||
def assert_consecutive_dominoes_match(chain) | ||
chain.each_cons(2).with_index { |(d1, d2), i| | ||
assert_equal d1.last, d2.first, | ||
"In chain #{chain}, right end of domino #{i} (#{d1}) and left end of domino #{i + 1} (#{d2}) must match" | ||
} | ||
end | ||
|
||
def assert_dominoes_at_end_match(chain) | ||
first_domino = chain.first | ||
last_domino = chain.last | ||
assert_equal first_domino.first, last_domino.last, | ||
"In chain #{chain}, left end of first domino (#{first_domino}) and right end of last domino (#{last_domino}) must match" | ||
end | ||
|
||
def refute_correct_chain(input_dominoes, output_chain) | ||
assert_nil output_chain, "There should be no chain for #{input_dominoes}" | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
class DominoesCase < OpenStruct | ||
def test_name | ||
'test_%s' % description.gsub("can't", 'can not').gsub(/[= -]+/, '_') | ||
end | ||
|
||
def workload | ||
<<-WL.chomp | ||
input_dominoes = #{input} | ||
output_chain = Dominoes.chain(input_dominoes) | ||
#{can_chain ? 'assert' : 'refute' }_correct_chain(input_dominoes, output_chain) | ||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be worth inserting the comment about why we can't just use input/output tests in between the bookkeeping and the custom asserts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems reasonable. Added.