Skip to content

Commit

Permalink
dominoes: Implement exercise (exercism#726)
Browse files Browse the repository at this point in the history
* add dominoes/README.md

* Add test cases and example solution for dominoes

* add dominoes to config.json

* dominoes: add check for name == "__main__"

* dominoes: update canonical data version and formatting fixes in README

* dominoes: update README to latest description

RE: exercism/problem-specifications#972
  • Loading branch information
cmccandless authored and smalley committed Nov 12, 2017
1 parent 1a92219 commit 20b57c0
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 11 deletions.
33 changes: 22 additions & 11 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1116,17 +1116,28 @@
]
},
{
"uuid": "55c818d6-04a8-2480-de83-9f85fd0d2eafa71935f",
"slug": "pov",
"core": false,
"unlocked_by": null,
"difficulty": 9,
"topics": [
"graphs",
"recursion",
"searching",
"trees"
]
"uuid": "4354f631-0cf5-9980-75e7-86d1c3da1f0d3f5e619",
"slug": "dominoes",
"core": false,
"unlocked_by": null,
"difficulty": 7,
"topics": [
"tuples",
"lists"
]
},
{
"uuid": "55c818d6-04a8-2480-de83-9f85fd0d2eafa71935f",
"slug": "pov",
"core": false,
"unlocked_by": null,
"difficulty": 9,
"topics": [
"graphs",
"recursion",
"searching",
"trees"
]
},
{
"uuid": "e7351e8e-d3ff-4621-b818-cd55cf05bffd",
Expand Down
28 changes: 28 additions & 0 deletions exercises/dominoes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Dominoes

Make a chain of dominoes.

Compute a way to order a given set of dominoes in such a way that they form a
correct domino chain (the dots on one half of a stone match the dots on the
neighbouring half of an adjacent stone) and that dots on the halfs of the stones
which don't have a neighbour (the first and last stone) match each other.

For example given the stones `[2|1]`, `[2|3]` and `[1|3]` you should compute something
like `[1|2] [2|3] [3|1]` or `[3|2] [2|1] [1|3]` or `[1|3] [3|2] [2|1]` etc, where the first and last numbers are the same.

For stones `[1|2]`, `[4|1]` and `[2|3]` the resulting chain is not valid: `[4|1] [1|2] [2|3]`'s first and last numbers are not the same. 4 != 3

Some test cases may use duplicate stones in a chain solution, assume that multiple Domino sets are being used.

## Submitting Exercises

Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.

For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.


For more detailed information about running tests, code style and linting,
please see the [help page](http://exercism.io/languages/python).

## Submitting Incomplete Solutions
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
2 changes: 2 additions & 0 deletions exercises/dominoes/dominoes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def chain(dominoes):
pass
114 changes: 114 additions & 0 deletions exercises/dominoes/dominoes_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import unittest

from dominoes import chain


# Tests adapted from `problem-specifications//canonical-data.json` @ v2.0.0

class DominoesTest(unittest.TestCase):
def test_empty_input_empty_output(self):
input_dominoes = []
output_chain = chain(input_dominoes)
self.assert_correct_chain(input_dominoes, output_chain)

def test_singleton_input_singleton_output(self):
input_dominoes = [(1, 1)]
output_chain = chain(input_dominoes)
self.assert_correct_chain(input_dominoes, output_chain)

def test_singleton_cant_be_chained(self):
input_dominoes = [(1, 2)]
output_chain = chain(input_dominoes)
self.refute_correct_chain(input_dominoes, output_chain)

def test_three_elements(self):
input_dominoes = [(1, 2), (3, 1), (2, 3)]
output_chain = chain(input_dominoes)
self.assert_correct_chain(input_dominoes, output_chain)

def test_can_reverse_dominoes(self):
input_dominoes = [(1, 2), (1, 3), (2, 3)]
output_chain = chain(input_dominoes)
self.assert_correct_chain(input_dominoes, output_chain)

def test_cant_be_chained(self):
input_dominoes = [(1, 2), (4, 1), (2, 3)]
output_chain = chain(input_dominoes)
self.refute_correct_chain(input_dominoes, output_chain)

def test_disconnected_simple(self):
input_dominoes = [(1, 1), (2, 2)]
output_chain = chain(input_dominoes)
self.refute_correct_chain(input_dominoes, output_chain)

def test_disconnected_double_loop(self):
input_dominoes = [(1, 2), (2, 1), (3, 4), (4, 3)]
output_chain = chain(input_dominoes)
self.refute_correct_chain(input_dominoes, output_chain)

def test_disconnected_single_isolated(self):
input_dominoes = [(1, 2), (2, 3), (3, 1), (4, 4)]
output_chain = chain(input_dominoes)
self.refute_correct_chain(input_dominoes, output_chain)

def test_need_backtrack(self):
input_dominoes = [(1, 2), (2, 3), (3, 1), (2, 4), (2, 4)]
output_chain = chain(input_dominoes)
self.assert_correct_chain(input_dominoes, output_chain)

def test_separate_loops(self):
input_dominoes = [(1, 2), (2, 3), (3, 1), (1, 1), (2, 2), (3, 3)]
output_chain = chain(input_dominoes)
self.assert_correct_chain(input_dominoes, output_chain)

def test_nine_elements(self):
input_dominoes = [(1, 2), (5, 3), (3, 1), (1, 2), (2, 4), (1, 6),
(2, 3), (3, 4), (5, 6)]
output_chain = chain(input_dominoes)
self.assert_correct_chain(input_dominoes, output_chain)

# Utility methods

def normalize_dominoes(self, dominoes):
return list(sorted(tuple(sorted(domino)) for domino in dominoes))

def assert_same_dominoes(self, input_dominoes, output_chain):
msg = ('Dominoes used in the output must be the same '
'as the ones given in the input')
input_normal = self.normalize_dominoes(input_dominoes)
output_normal = self.normalize_dominoes(output_chain)
self.assertEqual(input_normal, output_normal, msg)

def assert_consecutive_dominoes_match(self, output_chain):
for i in range(len(output_chain) - 1):
msg = ("In chain {}, right end of domino {} ({}) "
"and left end of domino {} ({}) must match")
msg = msg.format(output_chain,
i,
output_chain[i],
i + 1,
output_chain[i + 1])
self.assertEqual(output_chain[i][1], output_chain[i + 1][0], msg)

def assert_dominoes_at_ends_match(self, output_chain):
msg = ("In chain {}, left end of first domino ({}) and "
"right end of last domino ({}) must match")
msg = msg.format(output_chain, output_chain[0], output_chain[-1])
self.assertEqual(output_chain[0][0], output_chain[-1][1], msg)

def assert_correct_chain(self, input_dominoes, output_chain):
msg = 'There should be a chain for {}'.format(input_dominoes)
self.assertIsNotNone(output_chain, msg)
self.assert_same_dominoes(input_dominoes, output_chain)
if not any(output_chain):
return
self.assert_consecutive_dominoes_match(output_chain)
self.assert_dominoes_at_ends_match(output_chain)

def refute_correct_chain(self, input_dominoes, output_chain):
msg = 'There should be no valid chain for {}'.format(input_dominoes)
self.assertIsNone(output_chain, msg)


if __name__ == '__main__':
unittest.main()
30 changes: 30 additions & 0 deletions exercises/dominoes/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from itertools import permutations
from functools import reduce


def swap(a, b):
return (b, a)


def build_chain(chain, domino):
if chain is not None:
last = chain[-1]
if len(chain) == 1 and last[0] == domino[0]:
return [swap(*last), domino]
elif len(chain) == 1 and last[0] == domino[1]:
return [swap(*last), swap(*domino)]
elif last[1] == domino[0]:
return chain + [domino]
elif last[1] == domino[1]:
return chain + [swap(*domino)]
return None


def chain(dominoes):
if not any(dominoes):
return []
for perm in permutations(dominoes):
chain = reduce(build_chain, perm[1:], [perm[0]])
if chain is not None and chain[0][0] == chain[-1][1]:
return chain
return None

0 comments on commit 20b57c0

Please sign in to comment.