Skip to content

Commit 685d05d

Browse files
Add line-up exercise (exercism#349)
1 parent 3dc2735 commit 685d05d

File tree

9 files changed

+370
-0
lines changed

9 files changed

+370
-0
lines changed

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,14 @@
345345
"prerequisites": [],
346346
"difficulty": 3
347347
},
348+
{
349+
"slug": "line-up",
350+
"name": "Line Up",
351+
"uuid": "6b707608-65f8-4cb7-9f2f-e1a19ac48ad8",
352+
"practices": [],
353+
"prerequisites": [],
354+
"difficulty": 3
355+
},
348356
{
349357
"slug": "perfect-numbers",
350358
"name": "Perfect Numbers",
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Instructions
2+
3+
Given a name and a number, your task is to produce a sentence using that name and that number as an [ordinal numeral][ordinal-numeral].
4+
Yaʻqūb expects to use numbers from 1 up to 999.
5+
6+
Rules:
7+
8+
- Numbers ending in 1 (except for 11) → `"st"`
9+
- Numbers ending in 2 (except for 12) → `"nd"`
10+
- Numbers ending in 3 (except for 13) → `"rd"`
11+
- All other numbers → `"th"`
12+
13+
Examples:
14+
15+
- `"Mary", 1``"Mary, you are the 1st customer we serve today. Thank you!"`
16+
- `"John", 12``"John, you are the 12th customer we serve today. Thank you!"`
17+
- `"Dahir", 162``"Dahir, you are the 162nd customer we serve today. Thank you!"`
18+
19+
[ordinal-numeral]: https://en.wikipedia.org/wiki/Ordinal_numeral
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Introduction
2+
3+
Your friend Yaʻqūb works the counter at a deli in town, slicing, weighing, and wrapping orders for a line of hungry customers that gets longer every day.
4+
Waiting customers are starting to lose track of who is next, so he wants numbered tickets they can use to track the order in which they arrive.
5+
6+
To make the customers feel special, he does not want the ticket to have only a number on it.
7+
They shall get a proper English sentence with their name and number on it.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"authors": [
3+
"keiravillekode"
4+
],
5+
"files": {
6+
"solution": [
7+
"line-up.sml"
8+
],
9+
"test": [
10+
"test.sml"
11+
],
12+
"example": [
13+
".meta/example.sml"
14+
]
15+
},
16+
"blurb": "Help lining up customers at Yaʻqūb's Deli.",
17+
"source": "mk-mxp, based on previous work from Exercism contributors codedge and neenjaw",
18+
"source_url": "https://forum.exercism.org/t/new-exercise-ordinal-numbers/19147"
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
fun suffix number =
2+
let
3+
val units = number mod 10
4+
val tens = number div 10 mod 10
5+
in
6+
if units = 1 andalso tens <> 1 then "st"
7+
else if units = 2 andalso tens <> 1 then "nd"
8+
else if units = 3 andalso tens <> 1 then "rd"
9+
else "th"
10+
end;
11+
12+
fun format name number =
13+
String.concat [
14+
name,
15+
", you are the ",
16+
(Int.toString number),
17+
(suffix number),
18+
" customer we serve today. Thank you!"
19+
]
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
[7760d1b8-4864-4db4-953b-0fa7c047dbc0]
13+
description = "format smallest non-exceptional ordinal numeral 4"
14+
15+
[e8b7c715-6baa-4f7b-8fb3-2fa48044ab7a]
16+
description = "format greatest single digit non-exceptional ordinal numeral 9"
17+
18+
[f370aae9-7ae7-4247-90ce-e8ff8c6934df]
19+
description = "format non-exceptional ordinal numeral 5"
20+
21+
[37f10dea-42a2-49de-bb92-0b690b677908]
22+
description = "format non-exceptional ordinal numeral 6"
23+
24+
[d8dfb9a2-3a1f-4fee-9dae-01af3600054e]
25+
description = "format non-exceptional ordinal numeral 7"
26+
27+
[505ec372-1803-42b1-9377-6934890fd055]
28+
description = "format non-exceptional ordinal numeral 8"
29+
30+
[8267072d-be1f-4f70-b34a-76b7557a47b9]
31+
description = "format exceptional ordinal numeral 1"
32+
33+
[4d8753cb-0364-4b29-84b8-4374a4fa2e3f]
34+
description = "format exceptional ordinal numeral 2"
35+
36+
[8d44c223-3a7e-4f48-a0ca-78e67bf98aa7]
37+
description = "format exceptional ordinal numeral 3"
38+
39+
[6c4f6c88-b306-4f40-bc78-97cdd583c21a]
40+
description = "format smallest two digit non-exceptional ordinal numeral 10"
41+
42+
[e257a43f-d2b1-457a-97df-25f0923fc62a]
43+
description = "format non-exceptional ordinal numeral 11"
44+
45+
[bb1db695-4d64-457f-81b8-4f5a2107e3f4]
46+
description = "format non-exceptional ordinal numeral 12"
47+
48+
[60a3187c-9403-4835-97de-4f10ebfd63e2]
49+
description = "format non-exceptional ordinal numeral 13"
50+
51+
[2bdcebc5-c029-4874-b6cc-e9bec80d603a]
52+
description = "format exceptional ordinal numeral 21"
53+
54+
[74ee2317-0295-49d2-baf0-d56bcefa14e3]
55+
description = "format exceptional ordinal numeral 62"
56+
57+
[b37c332d-7f68-40e3-8503-e43cbd67a0c4]
58+
description = "format exceptional ordinal numeral 100"
59+
60+
[0375f250-ce92-4195-9555-00e28ccc4d99]
61+
description = "format exceptional ordinal numeral 101"
62+
63+
[0d8a4974-9a8a-45a4-aca7-a9fb473c9836]
64+
description = "format non-exceptional ordinal numeral 112"
65+
66+
[06b62efe-199e-4ce7-970d-4bf73945713f]
67+
description = "format exceptional ordinal numeral 123"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
fun format name number =
2+
raise Fail "'format' is not implemented"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
(* version 1.0.0 *)
2+
3+
use "testlib.sml";
4+
use "line-up.sml";
5+
6+
infixr |>
7+
fun x |> f = f x
8+
9+
val testsuite =
10+
describe "line-up" [
11+
test "format smallest non-exceptional ordinal numeral 4"
12+
(fn _ => format "Gianna" 4 |> Expect.equalTo "Gianna, you are the 4th customer we serve today. Thank you!"),
13+
14+
test "format greatest single digit non-exceptional ordinal numeral 9"
15+
(fn _ => format "Maarten" 9 |> Expect.equalTo "Maarten, you are the 9th customer we serve today. Thank you!"),
16+
17+
test "format non-exceptional ordinal numeral 5"
18+
(fn _ => format "Petronila" 5 |> Expect.equalTo "Petronila, you are the 5th customer we serve today. Thank you!"),
19+
20+
test "format non-exceptional ordinal numeral 6"
21+
(fn _ => format "Attakullakulla" 6 |> Expect.equalTo "Attakullakulla, you are the 6th customer we serve today. Thank you!"),
22+
23+
test "format non-exceptional ordinal numeral 7"
24+
(fn _ => format "Kate" 7 |> Expect.equalTo "Kate, you are the 7th customer we serve today. Thank you!"),
25+
26+
test "format non-exceptional ordinal numeral 8"
27+
(fn _ => format "Maximiliano" 8 |> Expect.equalTo "Maximiliano, you are the 8th customer we serve today. Thank you!"),
28+
29+
test "format exceptional ordinal numeral 1"
30+
(fn _ => format "Mary" 1 |> Expect.equalTo "Mary, you are the 1st customer we serve today. Thank you!"),
31+
32+
test "format exceptional ordinal numeral 2"
33+
(fn _ => format "Haruto" 2 |> Expect.equalTo "Haruto, you are the 2nd customer we serve today. Thank you!"),
34+
35+
test "format exceptional ordinal numeral 3"
36+
(fn _ => format "Henriette" 3 |> Expect.equalTo "Henriette, you are the 3rd customer we serve today. Thank you!"),
37+
38+
test "format smallest two digit non-exceptional ordinal numeral 10"
39+
(fn _ => format "Alvarez" 10 |> Expect.equalTo "Alvarez, you are the 10th customer we serve today. Thank you!"),
40+
41+
test "format non-exceptional ordinal numeral 11"
42+
(fn _ => format "Jacqueline" 11 |> Expect.equalTo "Jacqueline, you are the 11th customer we serve today. Thank you!"),
43+
44+
test "format non-exceptional ordinal numeral 12"
45+
(fn _ => format "Juan" 12 |> Expect.equalTo "Juan, you are the 12th customer we serve today. Thank you!"),
46+
47+
test "format non-exceptional ordinal numeral 13"
48+
(fn _ => format "Patricia" 13 |> Expect.equalTo "Patricia, you are the 13th customer we serve today. Thank you!"),
49+
50+
test "format exceptional ordinal numeral 21"
51+
(fn _ => format "Washi" 21 |> Expect.equalTo "Washi, you are the 21st customer we serve today. Thank you!"),
52+
53+
test "format exceptional ordinal numeral 62"
54+
(fn _ => format "Nayra" 62 |> Expect.equalTo "Nayra, you are the 62nd customer we serve today. Thank you!"),
55+
56+
test "format exceptional ordinal numeral 100"
57+
(fn _ => format "John" 100 |> Expect.equalTo "John, you are the 100th customer we serve today. Thank you!"),
58+
59+
test "format exceptional ordinal numeral 101"
60+
(fn _ => format "Zeinab" 101 |> Expect.equalTo "Zeinab, you are the 101st customer we serve today. Thank you!"),
61+
62+
test "format non-exceptional ordinal numeral 112"
63+
(fn _ => format "Knud" 112 |> Expect.equalTo "Knud, you are the 112th customer we serve today. Thank you!"),
64+
65+
test "format exceptional ordinal numeral 123"
66+
(fn _ => format "Yma" 123 |> Expect.equalTo "Yma, you are the 123rd customer we serve today. Thank you!")
67+
]
68+
69+
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)