-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Changes from 7 commits
9de7f4c
43c4d4a
a57f26c
dd582d6
57bf4e8
a4606c7
dbad5a9
44e2e00
dc3eb5b
c2cbbe9
520d8ac
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,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. | ||
|
||
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 | ||
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. Can you move this heading to level 2 ( |
||
|
||
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
def chain(dominoes): | ||
pass |
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 | ||
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. There's a new format for the version string to reflect the name-change from
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. 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() |
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 |
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.
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.
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.
See pull request in problem-specifications.