Skip to content

Commit

Permalink
Add dominoes exercise (#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
ageron authored Sep 22, 2024
1 parent aa2e596 commit e1d79d0
Show file tree
Hide file tree
Showing 8 changed files with 295 additions and 0 deletions.
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,14 @@
"prerequisites": [],
"difficulty": 7
},
{
"slug": "dominoes",
"name": "Dominoes",
"uuid": "baeabe4e-6d9b-4fa5-b8a7-22b1977d840e",
"practices": [],
"prerequisites": [],
"difficulty": 8
},
{
"slug": "grep",
"name": "Grep",
Expand Down
13 changes: 13 additions & 0 deletions exercises/practice/dominoes/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Instructions

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 neighboring half of an adjacent stone) and that dots on the halves of the stones which don't have a neighbor (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.
33 changes: 33 additions & 0 deletions exercises/practice/dominoes/.meta/Example.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module [findChain]

Domino : (U8, U8)

findChain : List Domino -> Result (List Domino) [NoChainExists]
findChain = \dominoes ->
help = \used, available ->
when available is
[] ->
when used is
[] -> Ok []
[single] ->
if single.0 == single.1 then Ok used else Err NoChainExists

[first, .., last] -> if first.0 == last.1 then Ok used else Err NoChainExists

[firstAvailable, .. as restAvailable] ->
when used is
[] -> help [firstAvailable] restAvailable
[.., lastUsed] ->
available
|> List.walkWithIndexUntil (Err NoChainExists) \_, domino, index ->
maybeChain =
if lastUsed.1 == domino.0 then
help (used |> List.append domino) (available |> List.dropAt index)
else if lastUsed.1 == domino.1 then
help (used |> List.append (domino.1, domino.0)) (available |> List.dropAt index)
else
Err NoChainExists
when maybeChain is
Ok chain -> Break (Ok chain)
Err NoChainExists -> Continue (Err NoChainExists)
help [] dominoes
17 changes: 17 additions & 0 deletions exercises/practice/dominoes/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"authors": [
"ageron"
],
"files": {
"solution": [
"Dominoes.roc"
],
"test": [
"dominoes-test.roc"
],
"example": [
".meta/Example.roc"
]
},
"blurb": "Make a chain of dominoes."
}
58 changes: 58 additions & 0 deletions exercises/practice/dominoes/.meta/template.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{%- import "generator_macros.j2" as macros with context -%}
{{ macros.canonical_ref() }}
{{ macros.header() }}

import {{ exercise | to_pascal }} exposing [findChain]

{% macro to_list_of_pairs(dominoes) -%}
[
{%- for domino in dominoes -%}
({{ domino[0] }}, {{ domino[1] }}),
{%- endfor -%}
]
{%- endmacro %}

Domino : (U8, U8)

## Rotate each domino if needed to ensure that the small side is on the left
canonicalize : List Domino -> List Domino
canonicalize = \dominoes ->
dominoes
|> List.map \domino ->
if domino.0 > domino.1 then (domino.1, domino.0) else domino

## Ensure that the given result is Ok and is a valid chain for the
## given list of dominoes
isValidChainFor : Result (List Domino) _, List Domino -> Bool
isValidChainFor = \maybeChain, dominoes ->
when maybeChain is
Err _ -> Bool.false
Ok chain ->
if Set.fromList (canonicalize chain) == Set.fromList (canonicalize dominoes) then
when chain is
[] -> Bool.true
[.., last] ->
chain
|> List.walkUntil (Ok last) \state, domino ->
when state is
Err InvalidChain -> crash "Unreachable"
Ok previous ->
if previous.1 == domino.0 then
Continue (Ok domino)
else
Break (Err InvalidChain)
|> Result.isOk
else
Bool.false

{% for case in cases -%}
# {{ case["description"] }}
expect
result = findChain {{ to_list_of_pairs(case["input"]["dominoes"]) }}
{%- if case["expected"] %}
result |> isValidChainFor {{ to_list_of_pairs(case["input"]["dominoes"]) }}
{%- else %}
result |> Result.isErr
{%- endif %}

{% endfor %}
49 changes: 49 additions & 0 deletions exercises/practice/dominoes/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# This is an auto-generated file.
#
# Regenerating this file via `configlet sync` will:
# - Recreate every `description` key/value pair
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
# - Preserve any other key/value pair
#
# As user-added comments (using the # character) will be removed when this file
# is regenerated, comments can be added via a `comment` key.

[31a673f2-5e54-49fe-bd79-1c1dae476c9c]
description = "empty input = empty output"

[4f99b933-367b-404b-8c6d-36d5923ee476]
description = "singleton input = singleton output"

[91122d10-5ec7-47cb-b759-033756375869]
description = "singleton that can't be chained"

[be8bc26b-fd3d-440b-8e9f-d698a0623be3]
description = "three elements"

[99e615c6-c059-401c-9e87-ad7af11fea5c]
description = "can reverse dominoes"

[51f0c291-5d43-40c5-b316-0429069528c9]
description = "can't be chained"

[9a75e078-a025-4c23-8c3a-238553657f39]
description = "disconnected - simple"

[0da0c7fe-d492-445d-b9ef-1f111f07a301]
description = "disconnected - double loop"

[b6087ff0-f555-4ea0-a71c-f9d707c5994a]
description = "disconnected - single isolated"

[2174fbdc-8b48-4bac-9914-8090d06ef978]
description = "need backtrack"

[167bb480-dfd1-4318-a20d-4f90adb4a09f]
description = "separate loops"

[cd061538-6046-45a7-ace9-6708fe8f6504]
description = "nine elements"

[44704c7c-3adb-4d98-bd30-f45527cf8b49]
description = "separate three-domino loops"
7 changes: 7 additions & 0 deletions exercises/practice/dominoes/Dominoes.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module [findChain]

Domino : (U8, U8)

findChain : List Domino -> Result (List Domino) _
findChain = \dominoes ->
crash "Please implement the 'findChain' function"
110 changes: 110 additions & 0 deletions exercises/practice/dominoes/dominoes-test.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# These tests are auto-generated with test data from:
# https://github.com/exercism/problem-specifications/tree/main/exercises/dominoes/canonical-data.json
# File last updated on 2024-09-21
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br",
}

main =
Task.ok {}

import Dominoes exposing [findChain]

Domino : (U8, U8)

## Rotate each domino if needed to ensure that the small side is on the left
canonicalize : List Domino -> List Domino
canonicalize = \dominoes ->
dominoes
|> List.map \domino ->
if domino.0 > domino.1 then (domino.1, domino.0) else domino

## Ensure that the given result is Ok and is a valid chain for the
## given list of dominoes
isValidChainFor : Result (List Domino) _, List Domino -> Bool
isValidChainFor = \maybeChain, dominoes ->
when maybeChain is
Err _ -> Bool.false
Ok chain ->
if Set.fromList (canonicalize chain) == Set.fromList (canonicalize dominoes) then
when chain is
[] -> Bool.true
[.., last] ->
chain
|> List.walkUntil (Ok last) \state, domino ->
when state is
Err InvalidChain -> crash "Unreachable"
Ok previous ->
if previous.1 == domino.0 then
Continue (Ok domino)
else
Break (Err InvalidChain)
|> Result.isOk
else
Bool.false

# empty input = empty output
expect
result = findChain []
result |> isValidChainFor []

# singleton input = singleton output
expect
result = findChain [(1, 1)]
result |> isValidChainFor [(1, 1)]

# singleton that can't be chained
expect
result = findChain [(1, 2)]
result |> Result.isErr

# three elements
expect
result = findChain [(1, 2), (3, 1), (2, 3)]
result |> isValidChainFor [(1, 2), (3, 1), (2, 3)]

# can reverse dominoes
expect
result = findChain [(1, 2), (1, 3), (2, 3)]
result |> isValidChainFor [(1, 2), (1, 3), (2, 3)]

# can't be chained
expect
result = findChain [(1, 2), (4, 1), (2, 3)]
result |> Result.isErr

# disconnected - simple
expect
result = findChain [(1, 1), (2, 2)]
result |> Result.isErr

# disconnected - double loop
expect
result = findChain [(1, 2), (2, 1), (3, 4), (4, 3)]
result |> Result.isErr

# disconnected - single isolated
expect
result = findChain [(1, 2), (2, 3), (3, 1), (4, 4)]
result |> Result.isErr

# need backtrack
expect
result = findChain [(1, 2), (2, 3), (3, 1), (2, 4), (2, 4)]
result |> isValidChainFor [(1, 2), (2, 3), (3, 1), (2, 4), (2, 4)]

# separate loops
expect
result = findChain [(1, 2), (2, 3), (3, 1), (1, 1), (2, 2), (3, 3)]
result |> isValidChainFor [(1, 2), (2, 3), (3, 1), (1, 1), (2, 2), (3, 3)]

# nine elements
expect
result = findChain [(1, 2), (5, 3), (3, 1), (1, 2), (2, 4), (1, 6), (2, 3), (3, 4), (5, 6)]
result |> isValidChainFor [(1, 2), (5, 3), (3, 1), (1, 2), (2, 4), (1, 6), (2, 3), (3, 4), (5, 6)]

# separate three-domino loops
expect
result = findChain [(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)]
result |> Result.isErr

0 comments on commit e1d79d0

Please sign in to comment.