Skip to content

Commit 029098a

Browse files
Add dominoes exercise (#362)
1 parent 91f6037 commit 029098a

9 files changed

Lines changed: 372 additions & 0 deletions

File tree

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,14 @@
713713
"prerequisites": [],
714714
"difficulty": 7
715715
},
716+
{
717+
"slug": "dominoes",
718+
"name": "Dominoes",
719+
"uuid": "d5b502c3-f2f6-47e9-be82-b808bc76e598",
720+
"practices": [],
721+
"prerequisites": [],
722+
"difficulty": 7
723+
},
716724
{
717725
"slug": "circular-buffer",
718726
"name": "Circular Buffer",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Instructions
2+
3+
Make a chain of dominoes.
4+
5+
Compute a way to order a given set of domino stones so that they form a correct domino chain.
6+
In the chain, the dots on one half of a stone must match the dots on the neighboring half of an adjacent stone.
7+
Additionally, the dots on the halves of the stones without neighbors (the first and last stone) must match each other.
8+
9+
For example given the stones `[2|1]`, `[2|3]` and `[1|3]` you should compute something
10+
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.
11+
12+
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.
13+
4 != 3
14+
15+
Some test cases may use duplicate stones in a chain solution, assume that multiple Domino sets are being used.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Introduction
2+
3+
In Toyland, the trains are always busy delivering treasures across the city, from shiny marbles to rare building blocks.
4+
The tracks they run on are made of colorful domino-shaped pieces, each marked with two numbers.
5+
For the trains to move, the dominoes must form a perfect chain where the numbers match.
6+
7+
Today, an urgent delivery of rare toys is on hold.
8+
You've been handed a set of track pieces to inspect.
9+
If they can form a continuous chain, the train will be on its way, bringing smiles across Toyland.
10+
If not, the set will be discarded, and another will be tried.
11+
12+
The toys are counting on you to solve this puzzle.
13+
Will the dominoes connect the tracks and send the train rolling, or will the set be left behind?
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"authors": [
3+
"keiravillekode"
4+
],
5+
"files": {
6+
"solution": [
7+
"dominoes.sml"
8+
],
9+
"test": [
10+
"test.sml"
11+
],
12+
"example": [
13+
".meta/example.sml"
14+
]
15+
},
16+
"blurb": "Make a chain of dominoes."
17+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
fun canChain (dominoes: (int * int) list): bool =
2+
let
3+
val n = 7 (* values 0..6 *)
4+
val parent = Array.tabulate (n, fn i => i)
5+
val size = Array.array (n, 1)
6+
val tally = Array.array (n, 0)
7+
8+
fun increment (arr, i) = Array.update (arr, i, Array.sub (arr, i) + 1)
9+
10+
fun find x =
11+
let
12+
val p = Array.sub (parent, x)
13+
in
14+
if p = x then x
15+
else
16+
let
17+
val gp = Array.sub (parent, p)
18+
in
19+
Array.update (parent, x, gp);
20+
find gp
21+
end
22+
end
23+
24+
fun union (a, b) =
25+
let
26+
val ra = find a
27+
val rb = find b
28+
in
29+
if ra = rb then ()
30+
else
31+
let
32+
val sa = Array.sub (size, ra)
33+
val sb = Array.sub (size, rb)
34+
in
35+
if sa < sb then
36+
(Array.update (parent, ra, rb);
37+
Array.update (size, rb, sa + sb))
38+
else
39+
(Array.update (parent, rb, ra);
40+
Array.update (size, ra, sa + sb))
41+
end
42+
end
43+
44+
val _ = List.app (fn (a, b) => (
45+
increment (tally, a);
46+
increment (tally, b);
47+
union (a, b))) dominoes
48+
49+
(* Check all vertices have even tally *)
50+
val allEven = Array.foldl (fn (d, acc) => acc andalso d mod 2 = 0) true tally
51+
52+
(* Count roots among vertices with nonzero tally *)
53+
val roots = Array.foldli (fn (i, d, acc) =>
54+
if d > 0 andalso Array.sub (parent, i) = i then acc + 1 else acc) 0 tally
55+
in
56+
allEven andalso roots <= 1
57+
end
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[31a673f2-5e54-49fe-bd79-1c1dae476c9c]
13+
description = "empty input = empty output"
14+
15+
[4f99b933-367b-404b-8c6d-36d5923ee476]
16+
description = "singleton input = singleton output"
17+
18+
[91122d10-5ec7-47cb-b759-033756375869]
19+
description = "singleton that can't be chained"
20+
21+
[be8bc26b-fd3d-440b-8e9f-d698a0623be3]
22+
description = "three elements"
23+
24+
[99e615c6-c059-401c-9e87-ad7af11fea5c]
25+
description = "can reverse dominoes"
26+
27+
[51f0c291-5d43-40c5-b316-0429069528c9]
28+
description = "can't be chained"
29+
30+
[9a75e078-a025-4c23-8c3a-238553657f39]
31+
description = "disconnected - simple"
32+
33+
[0da0c7fe-d492-445d-b9ef-1f111f07a301]
34+
description = "disconnected - double loop"
35+
36+
[b6087ff0-f555-4ea0-a71c-f9d707c5994a]
37+
description = "disconnected - single isolated"
38+
39+
[2174fbdc-8b48-4bac-9914-8090d06ef978]
40+
description = "need backtrack"
41+
42+
[167bb480-dfd1-4318-a20d-4f90adb4a09f]
43+
description = "separate loops"
44+
45+
[cd061538-6046-45a7-ace9-6708fe8f6504]
46+
description = "nine elements"
47+
48+
[44704c7c-3adb-4d98-bd30-f45527cf8b49]
49+
description = "separate three-domino loops"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fun canChain (dominoes: (int * int) list): bool =
2+
raise Fail "'canChain' is not implemented"
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
(* version 1.0.0 *)
2+
3+
use "testlib.sml";
4+
use "dominoes.sml";
5+
6+
infixr |>
7+
fun x |> f = f x
8+
9+
val testsuite =
10+
describe "dominoes" [
11+
test "empty input = empty output"
12+
(fn _ => canChain [] |> Expect.truthy),
13+
14+
test "singleton input = singleton output"
15+
(fn _ => canChain [(1, 1)] |> Expect.truthy),
16+
17+
test "singleton that can't be chained"
18+
(fn _ => canChain [(1, 2)] |> Expect.falsy),
19+
20+
test "three elements"
21+
(fn _ => canChain [(1, 2), (3, 1), (2, 3)] |> Expect.truthy),
22+
23+
test "can reverse dominoes"
24+
(fn _ => canChain [(1, 2), (1, 3), (2, 3)] |> Expect.truthy),
25+
26+
test "can't be chained"
27+
(fn _ => canChain [(1, 2), (4, 1), (2, 3)] |> Expect.falsy),
28+
29+
test "disconnected - simple"
30+
(fn _ => canChain [(1, 1), (2, 2)] |> Expect.falsy),
31+
32+
test "disconnected - double loop"
33+
(fn _ => canChain [(1, 2), (2, 1), (3, 4), (4, 3)] |> Expect.falsy),
34+
35+
test "disconnected - single isolated"
36+
(fn _ => canChain [(1, 2), (2, 3), (3, 1), (4, 4)] |> Expect.falsy),
37+
38+
test "need backtrack"
39+
(fn _ => canChain [(1, 2), (2, 3), (3, 1), (2, 4), (2, 4)] |> Expect.truthy),
40+
41+
test "separate loops"
42+
(fn _ => canChain [(1, 2), (2, 3), (3, 1), (1, 1), (2, 2), (3, 3)] |> Expect.truthy),
43+
44+
test "nine elements"
45+
(fn _ => canChain [(1, 2), (5, 3), (3, 1), (1, 2), (2, 4), (1, 6), (2, 3), (3, 4), (5, 6)] |> Expect.truthy),
46+
47+
test "separate three-domino loops"
48+
(fn _ => canChain [(1, 2), (2, 3), (3, 1), (4, 5), (5, 6), (6, 4)] |> Expect.falsy)
49+
]
50+
51+
val _ = Test.run testsuite
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
structure Expect =
2+
struct
3+
datatype expectation = Pass | Fail of string * string
4+
5+
local
6+
fun failEq b a =
7+
Fail ("Expected: " ^ b, "Got: " ^ a)
8+
9+
fun failExn b a =
10+
Fail ("Expected: " ^ b, "Raised: " ^ a)
11+
12+
fun exnName (e: exn): string = General.exnName e
13+
in
14+
fun truthy a =
15+
if a
16+
then Pass
17+
else failEq "true" "false"
18+
19+
fun falsy a =
20+
if a
21+
then failEq "false" "true"
22+
else Pass
23+
24+
fun equalTo b a =
25+
if a = b
26+
then Pass
27+
else failEq (PolyML.makestring b) (PolyML.makestring a)
28+
29+
fun nearTo delta b a =
30+
if Real.abs (a - b) <= delta * Real.abs a orelse
31+
Real.abs (a - b) <= delta * Real.abs b
32+
then Pass
33+
else failEq (Real.toString b ^ " +/- " ^ Real.toString delta) (Real.toString a)
34+
35+
fun anyError f =
36+
(
37+
f ();
38+
failExn "an exception" "Nothing"
39+
) handle _ => Pass
40+
41+
fun error e f =
42+
(
43+
f ();
44+
failExn (exnName e) "Nothing"
45+
) handle e' => if exnMessage e' = exnMessage e
46+
then Pass
47+
else failExn (exnMessage e) (exnMessage e')
48+
end
49+
end
50+
51+
structure TermColor =
52+
struct
53+
datatype color = Red | Green | Yellow | Normal
54+
55+
fun f Red = "\027[31m"
56+
| f Green = "\027[32m"
57+
| f Yellow = "\027[33m"
58+
| f Normal = "\027[0m"
59+
60+
fun colorize color s = (f color) ^ s ^ (f Normal)
61+
62+
val redit = colorize Red
63+
64+
val greenit = colorize Green
65+
66+
val yellowit = colorize Yellow
67+
end
68+
69+
structure Test =
70+
struct
71+
datatype testnode = TestGroup of string * testnode list
72+
| Test of string * (unit -> Expect.expectation)
73+
74+
local
75+
datatype evaluation = Success of string
76+
| Failure of string * string * string
77+
| Error of string * string
78+
79+
fun indent n s = (implode (List.tabulate (n, fn _ => #" "))) ^ s
80+
81+
fun fmt indentlvl ev =
82+
let
83+
val check = TermColor.greenit "\226\156\148 " (**)
84+
val cross = TermColor.redit "\226\156\150 " (**)
85+
val indentlvl = indentlvl * 2
86+
in
87+
case ev of
88+
Success descr => indent indentlvl (check ^ descr)
89+
| Failure (descr, exp, got) =>
90+
String.concatWith "\n" [indent indentlvl (cross ^ descr),
91+
indent (indentlvl + 2) exp,
92+
indent (indentlvl + 2) got]
93+
| Error (descr, reason) =>
94+
String.concatWith "\n" [indent indentlvl (cross ^ descr),
95+
indent (indentlvl + 2) (TermColor.redit reason)]
96+
end
97+
98+
fun eval (TestGroup _) = raise Fail "Only a 'Test' can be evaluated"
99+
| eval (Test (descr, thunk)) =
100+
(
101+
case thunk () of
102+
Expect.Pass => ((1, 0, 0), Success descr)
103+
| Expect.Fail (s, s') => ((0, 1, 0), Failure (descr, s, s'))
104+
)
105+
handle e => ((0, 0, 1), Error (descr, "Unexpected error: " ^ exnMessage e))
106+
107+
fun flatten depth testnode =
108+
let
109+
fun sum (x, y, z) (a, b, c) = (x + a, y + b, z + c)
110+
111+
fun aux (t, (counter, acc)) =
112+
let
113+
val (counter', texts) = flatten (depth + 1) t
114+
in
115+
(sum counter' counter, texts :: acc)
116+
end
117+
in
118+
case testnode of
119+
TestGroup (descr, ts) =>
120+
let
121+
val (counter, texts) = foldr aux ((0, 0, 0), []) ts
122+
in
123+
(counter, (indent (depth * 2) descr) :: List.concat texts)
124+
end
125+
| Test _ =>
126+
let
127+
val (counter, evaluation) = eval testnode
128+
in
129+
(counter, [fmt depth evaluation])
130+
end
131+
end
132+
133+
fun println s = print (s ^ "\n")
134+
in
135+
fun run suite =
136+
let
137+
val ((succeeded, failed, errored), texts) = flatten 0 suite
138+
139+
val summary = String.concatWith ", " [
140+
TermColor.greenit ((Int.toString succeeded) ^ " passed"),
141+
TermColor.redit ((Int.toString failed) ^ " failed"),
142+
TermColor.redit ((Int.toString errored) ^ " errored"),
143+
(Int.toString (succeeded + failed + errored)) ^ " total"
144+
]
145+
146+
val status = if failed = 0 andalso errored = 0
147+
then OS.Process.success
148+
else OS.Process.failure
149+
150+
in
151+
List.app println texts;
152+
println "";
153+
println ("Tests: " ^ summary);
154+
OS.Process.exit status
155+
end
156+
end
157+
end
158+
159+
fun describe description tests = Test.TestGroup (description, tests)
160+
fun test description thunk = Test.Test (description, thunk)

0 commit comments

Comments
 (0)