Skip to content
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

Implement exercise dominoes #726

Merged
merged 11 commits into from
Nov 5, 2017
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