diff --git a/config.json b/config.json index 50df3e154f..f95243ff36 100644 --- a/config.json +++ b/config.json @@ -502,6 +502,12 @@ "difficulty": 1, "topics": [ ] + }, + { + "slug": "dominoes", + "difficulty": 1, + "topics": [ + ] } ], "deprecated": [ diff --git a/exercises/dominoes/.version b/exercises/dominoes/.version new file mode 100644 index 0000000000..56a6051ca2 --- /dev/null +++ b/exercises/dominoes/.version @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/exercises/dominoes/dominoes_test.rb b/exercises/dominoes/dominoes_test.rb new file mode 100755 index 0000000000..fcb06def6e --- /dev/null +++ b/exercises/dominoes/dominoes_test.rb @@ -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 diff --git a/exercises/dominoes/example.rb b/exercises/dominoes/example.rb new file mode 100644 index 0000000000..ac631a764c --- /dev/null +++ b/exercises/dominoes/example.rb @@ -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 + end +end + +module BookKeeping + VERSION = 1 +end diff --git a/exercises/dominoes/example.tt b/exercises/dominoes/example.tt new file mode 100644 index 0000000000..086203bd6f --- /dev/null +++ b/exercises/dominoes/example.tt @@ -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) + 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 diff --git a/lib/dominoes_cases.rb b/lib/dominoes_cases.rb new file mode 100644 index 0000000000..1bc60629e2 --- /dev/null +++ b/lib/dominoes_cases.rb @@ -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