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
11 changes: 11 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,17 @@
"trees"
]
},
{
"uuid": "4354f631-0cf5-9980-75e7-86d1c3da1f0d3f5e619",
"slug": "dominoes",
"core": false,
"unlocked_by": null,
"difficulty": 7,
"topics": [
"tuples",
"lists"
]
},
{
"uuid": "e7351e8e-d3ff-4621-b818-cd55cf05bffd",
"slug": "accumulate",
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 `21`, `23` and `13` you should compute something
like `12 23 31` or `32 21 13` or `13 32 21` etc, where the first and last numbers are the same.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it weird that they are specified like two digit numbers when these numbers are supposed to be separate things - I definitely prefer your implementation with tuples. Can you update the README to use tuples instead? It would be worth submitting this change to problem-specifications as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


For stones 12, 41 and 23 the resulting chain is not valid: 41 12 23'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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this heading to level 2 (##) to save me doing it in #950?


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


# test cases adapted from `x-common//canonical-data.json` @ version: 1.0.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a new format for the version string to reflect the name-change from x-common to problem-specifications that happened a while back (#784).

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually noticed that these tests weren't even based off of 1.0.0; it was the current version, 2.0.0. This has been updated.


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