trackler 2.2.1.36 → 2.2.1.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/trackler/version.rb +1 -1
- data/tracks/bash/config.json +1 -0
- data/tracks/ecmascript/README.md +2 -2
- data/tracks/haskell/config/maintainers.json +2 -2
- data/tracks/java/config.json +15 -3
- data/tracks/java/exercises/difference-of-squares/src/test/java/DifferenceOfSquaresCalculatorTest.java +9 -9
- data/tracks/ruby/exercises/palindrome-products/README.md +12 -6
- data/tracks/ruby/exercises/space-age/.meta/generator/test_template.erb +1 -1
- data/tracks/ruby/lib/generator/template_values.rb +6 -0
- data/tracks/ruby/lib/generator/test_template.erb +1 -1
- data/tracks/ruby/test/generator/template_values_test.rb +3 -3
- data/tracks/rust/exercises/accumulate/.gitignore +7 -0
- data/tracks/rust/exercises/clock/.gitignore +7 -0
- data/tracks/rust/exercises/nth-prime/.gitignore +7 -0
- data/tracks/rust/exercises/pig-latin/.gitignore +7 -0
- data/tracks/rust/exercises/prime-factors/.gitignore +7 -0
- data/tracks/rust/exercises/proverb/.gitignore +7 -0
- data/tracks/rust/exercises/pythagorean-triplet/.gitignore +7 -0
- data/tracks/rust/exercises/run-length-encoding/.gitignore +7 -0
- data/tracks/rust/exercises/say/.gitignore +7 -0
- data/tracks/scala/config.json +1 -0
- data/tracks/scala/exercises/clock/src/test/scala/ClockTest.scala +73 -79
- data/tracks/scala/exercises/forth/src/test/scala/ForthTest.scala +128 -133
- data/tracks/scala/exercises/kindergarten-garden/example.scala +1 -1
- data/tracks/scala/exercises/kindergarten-garden/src/test/scala/GardenTest.scala +77 -52
- data/tracks/scala/exercises/nucleotide-count/src/main/scala/.keep +0 -0
- data/tracks/scala/exercises/nucleotide-count/src/test/scala/NucleotideCountTest.scala +21 -61
- data/tracks/scala/exercises/palindrome-products/example.scala +7 -4
- data/tracks/scala/exercises/palindrome-products/src/test/scala/PalindromeProductsTest.scala +54 -34
- data/tracks/scala/testgen/src/main/scala/ClockTestGenerator.scala +66 -0
- data/tracks/scala/testgen/src/main/scala/ForthTestGenerator.scala +66 -0
- data/tracks/scala/testgen/src/main/scala/KindergartenGardenTestGenerator.scala +48 -0
- data/tracks/scala/testgen/src/main/scala/NucleotideCountTestGenerator.scala +34 -0
- data/tracks/scala/testgen/src/main/scala/PalindromeProductsTestGenerator.scala +50 -0
- data/tracks/scala/testgen/src/main/scala/testgen/CanonicalDataParser.scala +7 -6
- data/tracks/sml/config.json +21 -0
- data/tracks/sml/exercises/phone-number/README.md +64 -0
- data/tracks/sml/exercises/phone-number/example.sml +18 -0
- data/tracks/sml/exercises/phone-number/phone-number.sml +2 -0
- data/tracks/sml/exercises/phone-number/test.sml +50 -0
- data/tracks/sml/exercises/phone-number/testlib.sml +159 -0
- data/tracks/sml/exercises/roman-numerals/README.md +79 -0
- data/tracks/sml/exercises/roman-numerals/example.sml +27 -0
- data/tracks/sml/exercises/roman-numerals/roman-numerals.sml +2 -0
- data/tracks/sml/exercises/roman-numerals/test.sml +66 -0
- data/tracks/sml/exercises/roman-numerals/testlib.sml +159 -0
- metadata +26 -3
- data/tracks/scala/exercises/nucleotide-count/src/main/scala/DNA.scala +0 -6
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import java.io.File
|
|
2
|
+
|
|
3
|
+
import testgen.TestSuiteBuilder.{toString, _}
|
|
4
|
+
import testgen._
|
|
5
|
+
|
|
6
|
+
object PalindromeProductsTestGenerator {
|
|
7
|
+
def main(args: Array[String]): Unit = {
|
|
8
|
+
val file = new File("src/main/resources/palindrome-products.json")
|
|
9
|
+
|
|
10
|
+
def toStringFromMap(m: Map[String, Any]): String =
|
|
11
|
+
s"Some((${m("value").toString}, " +
|
|
12
|
+
s"Set(${m("factors").asInstanceOf[List[List[Int]]].map(xs => "(" + xs.map(_.toString).mkString(", ") + ")").mkString(", ")})))"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def toString(expected: CanonicalDataParser.Expected): String = {
|
|
16
|
+
expected match {
|
|
17
|
+
case Left(_) => "None"
|
|
18
|
+
case Right(m: Map[String, any]) => toStringFromMap(m)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def fromLabeledTest(argNames: String*): ToTestCaseData =
|
|
23
|
+
withLabeledTest { sut =>
|
|
24
|
+
labeledTest =>
|
|
25
|
+
val args = sutArgs(labeledTest.result, argNames: _*)
|
|
26
|
+
val property = labeledTest.property
|
|
27
|
+
val sutCall = s"""$sut($args).$property"""
|
|
28
|
+
val expected = toString(labeledTest.expected)
|
|
29
|
+
TestCaseData(labeledTest.description, sutCall, expected)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
val code =
|
|
33
|
+
TestSuiteBuilder.build(file, fromLabeledTest("input_min", "input_max"),
|
|
34
|
+
Seq(),
|
|
35
|
+
Seq("// PalindromeProducts largest call is expecting a return type of",
|
|
36
|
+
"// Option[(Int, Set[(Int, Int)])] - None is expected for error cases.",
|
|
37
|
+
"// Some is expected for valid cases. The first element of the tuple ",
|
|
38
|
+
"// is the largest palindrome product value. The second element of the",
|
|
39
|
+
"// tuple is the list of factors.",
|
|
40
|
+
"// PalindromeProducts smallest call is expecting a return type of",
|
|
41
|
+
"// Option[(Int, Set[(Int, Int)])] - None is expected for error cases.",
|
|
42
|
+
"// Some is expected for valid cases. The first element of the tuple ",
|
|
43
|
+
"// is the smallest palindrome product value. The second element of the",
|
|
44
|
+
"// tuple is the list of factors."))
|
|
45
|
+
|
|
46
|
+
println(s"-------------")
|
|
47
|
+
println(code)
|
|
48
|
+
println(s"-------------")
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -30,7 +30,6 @@ object CanonicalDataParser {
|
|
|
30
30
|
val rawParseResult =
|
|
31
31
|
JSON.parseFull(fileContents).get.asInstanceOf[ParseResult]
|
|
32
32
|
val parseResult = rawParseResult mapValues restoreInts
|
|
33
|
-
println(parseResult)
|
|
34
33
|
parseResult
|
|
35
34
|
}
|
|
36
35
|
|
|
@@ -59,15 +58,17 @@ object Exercise {
|
|
|
59
58
|
val cases: Cases =
|
|
60
59
|
getRequired[Seq[ParseResult]](result, "cases") map LabeledTestItem.fromParseResult
|
|
61
60
|
Exercise(getRequired(result, "exercise"), getRequired(result, "version"),
|
|
62
|
-
flattenCases(cases), getOptional(result, "comments"))
|
|
61
|
+
flattenCases(cases, List()), getOptional(result, "comments"))
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
// so far there are to few LabeledTestGroups to handle them separately
|
|
66
|
-
private def flattenCases(cases: Cases): Cases =
|
|
65
|
+
private def flattenCases(cases: Cases, parentDescriptions: List[String]): Cases =
|
|
67
66
|
cases match {
|
|
68
67
|
case Seq() => Seq()
|
|
69
|
-
case (ltg: LabeledTestGroup) +: xs => flattenCases(ltg.cases) ++
|
|
70
|
-
|
|
68
|
+
case (ltg: LabeledTestGroup) +: xs => flattenCases(ltg.cases, ltg.description :: parentDescriptions) ++
|
|
69
|
+
flattenCases(xs, parentDescriptions)
|
|
70
|
+
case (lt: LabeledTest) +: xs => LabeledTest(lt.description, lt.property, lt.expected, lt.result, parentDescriptions) +:
|
|
71
|
+
flattenCases(xs, parentDescriptions)
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -79,7 +80,7 @@ object LabeledTestItem {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
case class LabeledTest(description: Description, property: Property,
|
|
82
|
-
expected: Expected, result: ParseResult) extends LabeledTestItem
|
|
83
|
+
expected: Expected, result: ParseResult, parentDescriptions: List[String] = List()) extends LabeledTestItem
|
|
83
84
|
object LabeledTest {
|
|
84
85
|
implicit def fromParseResult(result: ParseResult): LabeledTest = {
|
|
85
86
|
val expected: Expected = {
|
data/tracks/sml/config.json
CHANGED
|
@@ -214,6 +214,27 @@
|
|
|
214
214
|
|
|
215
215
|
]
|
|
216
216
|
},
|
|
217
|
+
{
|
|
218
|
+
"uuid": "f923bffb-109f-4257-957c-cea8cae2d5c5",
|
|
219
|
+
"slug": "phone-number",
|
|
220
|
+
"core": false,
|
|
221
|
+
"unlocked_by": null,
|
|
222
|
+
"difficulty": 1,
|
|
223
|
+
"topics": [
|
|
224
|
+
|
|
225
|
+
]
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
|
|
229
|
+
"uuid": "b4359037-8457-49ea-ac33-44710ada3b4d",
|
|
230
|
+
"slug": "roman-numerals",
|
|
231
|
+
"core": false,
|
|
232
|
+
"unlocked_by": null,
|
|
233
|
+
"difficulty": 1,
|
|
234
|
+
"topics": [
|
|
235
|
+
|
|
236
|
+
]
|
|
237
|
+
},
|
|
217
238
|
{
|
|
218
239
|
"uuid": "225cfd7d-81a3-4a58-b1aa-1de2e40e7a93",
|
|
219
240
|
"slug": "binary",
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Phone Number
|
|
2
|
+
|
|
3
|
+
Clean up user-entered phone numbers so that they can be sent SMS messages.
|
|
4
|
+
|
|
5
|
+
The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. All NANP-countries share the same international country code: `1`.
|
|
6
|
+
|
|
7
|
+
NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as *area code*, followed by a seven-digit local number. The first three digits of the local number represent the *exchange code*, followed by the unique four-digit number which is the *subscriber number*.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
The format is usually represented as
|
|
11
|
+
```
|
|
12
|
+
(NXX)-NXX-XXXX
|
|
13
|
+
```
|
|
14
|
+
where `N` is any digit from 2 through 9 and `X` is any digit from 0 through 9.
|
|
15
|
+
|
|
16
|
+
Your task is to clean up differently formated telephone numbers by removing punctuation and the country code (1) if present.
|
|
17
|
+
|
|
18
|
+
For example, the inputs
|
|
19
|
+
- `+1 (613)-995-0253`
|
|
20
|
+
- `613-995-0253`
|
|
21
|
+
- `1 613 995 0253`
|
|
22
|
+
- `613.995.0253`
|
|
23
|
+
|
|
24
|
+
should all produce the output
|
|
25
|
+
|
|
26
|
+
`6139950253`
|
|
27
|
+
|
|
28
|
+
**Note:** As this exercise only deals with telephone numbers used in NANP-countries, only 1 is considered a valid country code.
|
|
29
|
+
|
|
30
|
+
## Loading your exercise implementation in PolyML
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
$ poly --use {exercise}.sml
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
$ poly
|
|
40
|
+
> use "{exercise}.sml";
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Note:** You have to replace {exercise}.
|
|
44
|
+
|
|
45
|
+
## Running the tests
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
$ poly -q --use test.sml
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Feedback, Issues, Pull Requests
|
|
52
|
+
|
|
53
|
+
The [exercism/sml](https://github.com/exercism/sml) repository on
|
|
54
|
+
GitHub is the home for all of the Standard ML exercises.
|
|
55
|
+
|
|
56
|
+
If you have feedback about an exercise, or want to help implementing a new
|
|
57
|
+
one, head over there and create an issue. We'll do our best to help you!
|
|
58
|
+
|
|
59
|
+
## Source
|
|
60
|
+
|
|
61
|
+
Event Manager by JumpstartLab [http://tutorials.jumpstartlab.com/projects/eventmanager.html](http://tutorials.jumpstartlab.com/projects/eventmanager.html)
|
|
62
|
+
|
|
63
|
+
## Submitting Incomplete Solutions
|
|
64
|
+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
fun clean text =
|
|
2
|
+
let
|
|
3
|
+
fun valid (#"0" :: _) = false
|
|
4
|
+
| valid (#"1" :: _) = false
|
|
5
|
+
| valid digits = List.nth (digits, 3) > #"1"
|
|
6
|
+
|
|
7
|
+
fun check digits =
|
|
8
|
+
case length digits of
|
|
9
|
+
10 => if valid digits
|
|
10
|
+
then SOME (implode digits)
|
|
11
|
+
else NONE
|
|
12
|
+
| 11 => if hd digits = #"1"
|
|
13
|
+
then check (tl digits)
|
|
14
|
+
else NONE
|
|
15
|
+
| _ => NONE
|
|
16
|
+
in
|
|
17
|
+
check (List.filter Char.isDigit (explode text))
|
|
18
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
(* version 1.2.0 *)
|
|
2
|
+
|
|
3
|
+
use "testlib.sml";
|
|
4
|
+
use "phone-number.sml";
|
|
5
|
+
|
|
6
|
+
infixr |>
|
|
7
|
+
fun x |> f = f x
|
|
8
|
+
|
|
9
|
+
val testsuite =
|
|
10
|
+
describe "phone-number" [
|
|
11
|
+
describe "Cleanup user-entered phone numbers" [
|
|
12
|
+
test "cleans the number"
|
|
13
|
+
(fn _ => clean ("(223) 456-7890") |> Expect.equalTo (SOME "2234567890")),
|
|
14
|
+
|
|
15
|
+
test "cleans numbers with dots"
|
|
16
|
+
(fn _ => clean ("223.456.7890") |> Expect.equalTo (SOME "2234567890")),
|
|
17
|
+
|
|
18
|
+
test "cleans numbers with multiple spaces"
|
|
19
|
+
(fn _ => clean ("223 456 7890 ") |> Expect.equalTo (SOME "2234567890")),
|
|
20
|
+
|
|
21
|
+
test "invalid when 9 digits"
|
|
22
|
+
(fn _ => clean ("123456789") |> Expect.equalTo NONE),
|
|
23
|
+
|
|
24
|
+
test "invalid when 11 digits does not start with a 1"
|
|
25
|
+
(fn _ => clean ("22234567890") |> Expect.equalTo NONE),
|
|
26
|
+
|
|
27
|
+
test "valid when 11 digits and starting with 1"
|
|
28
|
+
(fn _ => clean ("12234567890") |> Expect.equalTo (SOME "2234567890")),
|
|
29
|
+
|
|
30
|
+
test "valid when 11 digits and starting with 1 even with punctuation"
|
|
31
|
+
(fn _ => clean ("+1 (223) 456-7890") |> Expect.equalTo (SOME "2234567890")),
|
|
32
|
+
|
|
33
|
+
test "invalid when more than 11 digits"
|
|
34
|
+
(fn _ => clean ("321234567890") |> Expect.equalTo NONE),
|
|
35
|
+
|
|
36
|
+
test "invalid with letters"
|
|
37
|
+
(fn _ => clean ("123-abc-7890") |> Expect.equalTo NONE),
|
|
38
|
+
|
|
39
|
+
test "invalid with punctuations"
|
|
40
|
+
(fn _ => clean ("123-@:!-7890") |> Expect.equalTo NONE),
|
|
41
|
+
|
|
42
|
+
test "invalid if area code does not start with 2-9"
|
|
43
|
+
(fn _ => clean ("(123) 456-7890") |> Expect.equalTo NONE),
|
|
44
|
+
|
|
45
|
+
test "invalid if exchange code does not start with 2-9"
|
|
46
|
+
(fn _ => clean ("(223) 056-7890") |> Expect.equalTo NONE)
|
|
47
|
+
]
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
val _ = Test.run testsuite
|
|
@@ -0,0 +1,159 @@
|
|
|
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 b a =
|
|
30
|
+
if Real.== (a, b)
|
|
31
|
+
then Pass
|
|
32
|
+
else failEq (Real.toString b) (Real.toString a)
|
|
33
|
+
|
|
34
|
+
fun anyError f =
|
|
35
|
+
(
|
|
36
|
+
f ();
|
|
37
|
+
failExn "an exception" "Nothing"
|
|
38
|
+
) handle _ => Pass
|
|
39
|
+
|
|
40
|
+
fun error e f =
|
|
41
|
+
(
|
|
42
|
+
f ();
|
|
43
|
+
failExn (exnName e) "Nothing"
|
|
44
|
+
) handle e' => if exnMessage e' = exnMessage e
|
|
45
|
+
then Pass
|
|
46
|
+
else failExn (exnMessage e) (exnMessage e')
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
structure TermColor =
|
|
51
|
+
struct
|
|
52
|
+
datatype color = Red | Green | Yellow | Normal
|
|
53
|
+
|
|
54
|
+
fun f Red = "\027[31m"
|
|
55
|
+
| f Green = "\027[32m"
|
|
56
|
+
| f Yellow = "\027[33m"
|
|
57
|
+
| f Normal = "\027[0m"
|
|
58
|
+
|
|
59
|
+
fun colorize color s = (f color) ^ s ^ (f Normal)
|
|
60
|
+
|
|
61
|
+
val redit = colorize Red
|
|
62
|
+
|
|
63
|
+
val greenit = colorize Green
|
|
64
|
+
|
|
65
|
+
val yellowit = colorize Yellow
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
structure Test =
|
|
69
|
+
struct
|
|
70
|
+
datatype testnode = TestGroup of string * testnode list
|
|
71
|
+
| Test of string * (unit -> Expect.expectation)
|
|
72
|
+
|
|
73
|
+
local
|
|
74
|
+
datatype evaluation = Success of string
|
|
75
|
+
| Failure of string * string * string
|
|
76
|
+
| Error of string * string
|
|
77
|
+
|
|
78
|
+
fun indent n s = (implode (List.tabulate (n, fn _ => #" "))) ^ s
|
|
79
|
+
|
|
80
|
+
fun fmt indentlvl ev =
|
|
81
|
+
let
|
|
82
|
+
val check = TermColor.greenit "\226\156\148 " (* ✔ *)
|
|
83
|
+
val cross = TermColor.redit "\226\156\150 " (* ✖ *)
|
|
84
|
+
val indentlvl = indentlvl * 2
|
|
85
|
+
in
|
|
86
|
+
case ev of
|
|
87
|
+
Success descr => indent indentlvl (check ^ descr)
|
|
88
|
+
| Failure (descr, exp, got) =>
|
|
89
|
+
String.concatWith "\n" [indent indentlvl (cross ^ descr),
|
|
90
|
+
indent (indentlvl + 2) exp,
|
|
91
|
+
indent (indentlvl + 2) got]
|
|
92
|
+
| Error (descr, reason) =>
|
|
93
|
+
String.concatWith "\n" [indent indentlvl (cross ^ descr),
|
|
94
|
+
indent (indentlvl + 2) (TermColor.redit reason)]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
fun eval (TestGroup _) = raise Fail "Only a 'Test' can be evaluated"
|
|
98
|
+
| eval (Test (descr, thunk)) =
|
|
99
|
+
(
|
|
100
|
+
case thunk () of
|
|
101
|
+
Expect.Pass => ((1, 0, 0), Success descr)
|
|
102
|
+
| Expect.Fail (s, s') => ((0, 1, 0), Failure (descr, s, s'))
|
|
103
|
+
)
|
|
104
|
+
handle e => ((0, 0, 1), Error (descr, "Unexpected error: " ^ exnMessage e))
|
|
105
|
+
|
|
106
|
+
fun flatten depth testnode =
|
|
107
|
+
let
|
|
108
|
+
fun sum (x, y, z) (a, b, c) = (x + a, y + b, z + c)
|
|
109
|
+
|
|
110
|
+
fun aux (t, (counter, acc)) =
|
|
111
|
+
let
|
|
112
|
+
val (counter', texts) = flatten (depth + 1) t
|
|
113
|
+
in
|
|
114
|
+
(sum counter' counter, texts :: acc)
|
|
115
|
+
end
|
|
116
|
+
in
|
|
117
|
+
case testnode of
|
|
118
|
+
TestGroup (descr, ts) =>
|
|
119
|
+
let
|
|
120
|
+
val (counter, texts) = foldr aux ((0, 0, 0), []) ts
|
|
121
|
+
in
|
|
122
|
+
(counter, (indent (depth * 2) descr) :: List.concat texts)
|
|
123
|
+
end
|
|
124
|
+
| Test _ =>
|
|
125
|
+
let
|
|
126
|
+
val (counter, evaluation) = eval testnode
|
|
127
|
+
in
|
|
128
|
+
(counter, [fmt depth evaluation])
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
fun println s = print (s ^ "\n")
|
|
133
|
+
in
|
|
134
|
+
fun run suite =
|
|
135
|
+
let
|
|
136
|
+
val ((succeeded, failed, errored), texts) = flatten 0 suite
|
|
137
|
+
|
|
138
|
+
val summary = String.concatWith ", " [
|
|
139
|
+
TermColor.greenit ((Int.toString succeeded) ^ " passed"),
|
|
140
|
+
TermColor.redit ((Int.toString failed) ^ " failed"),
|
|
141
|
+
TermColor.redit ((Int.toString errored) ^ " errored"),
|
|
142
|
+
(Int.toString (succeeded + failed + errored)) ^ " total"
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
val status = if failed = 0 andalso errored = 0
|
|
146
|
+
then OS.Process.success
|
|
147
|
+
else OS.Process.failure
|
|
148
|
+
|
|
149
|
+
in
|
|
150
|
+
List.app println texts;
|
|
151
|
+
println "";
|
|
152
|
+
println ("Tests: " ^ summary);
|
|
153
|
+
OS.Process.exit status
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
fun describe description tests = Test.TestGroup (description, tests)
|
|
159
|
+
fun test description thunk = Test.Test (description, thunk)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Roman Numerals
|
|
2
|
+
|
|
3
|
+
Write a function to convert from normal numbers to Roman Numerals.
|
|
4
|
+
|
|
5
|
+
The Romans were a clever bunch. They conquered most of Europe and ruled
|
|
6
|
+
it for hundreds of years. They invented concrete and straight roads and
|
|
7
|
+
even bikinis. One thing they never discovered though was the number
|
|
8
|
+
zero. This made writing and dating extensive histories of their exploits
|
|
9
|
+
slightly more challenging, but the system of numbers they came up with
|
|
10
|
+
is still in use today. For example the BBC uses Roman numerals to date
|
|
11
|
+
their programmes.
|
|
12
|
+
|
|
13
|
+
The Romans wrote numbers using letters - I, V, X, L, C, D, M. (notice
|
|
14
|
+
these letters have lots of straight lines and are hence easy to hack
|
|
15
|
+
into stone tablets).
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
1 => I
|
|
19
|
+
10 => X
|
|
20
|
+
7 => VII
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
There is no need to be able to convert numbers larger than about 3000.
|
|
24
|
+
(The Romans themselves didn't tend to go any higher)
|
|
25
|
+
|
|
26
|
+
Wikipedia says: Modern Roman numerals ... are written by expressing each
|
|
27
|
+
digit separately starting with the left most digit and skipping any
|
|
28
|
+
digit with a value of zero.
|
|
29
|
+
|
|
30
|
+
To see this in practice, consider the example of 1990.
|
|
31
|
+
|
|
32
|
+
In Roman numerals 1990 is MCMXC:
|
|
33
|
+
|
|
34
|
+
1000=M
|
|
35
|
+
900=CM
|
|
36
|
+
90=XC
|
|
37
|
+
|
|
38
|
+
2008 is written as MMVIII:
|
|
39
|
+
|
|
40
|
+
2000=MM
|
|
41
|
+
8=VIII
|
|
42
|
+
|
|
43
|
+
See also: http://www.novaroma.org/via_romana/numbers.html
|
|
44
|
+
|
|
45
|
+
## Loading your exercise implementation in PolyML
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
$ poly --use {exercise}.sml
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
$ poly
|
|
55
|
+
> use "{exercise}.sml";
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Note:** You have to replace {exercise}.
|
|
59
|
+
|
|
60
|
+
## Running the tests
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
$ poly -q --use test.sml
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Feedback, Issues, Pull Requests
|
|
67
|
+
|
|
68
|
+
The [exercism/sml](https://github.com/exercism/sml) repository on
|
|
69
|
+
GitHub is the home for all of the Standard ML exercises.
|
|
70
|
+
|
|
71
|
+
If you have feedback about an exercise, or want to help implementing a new
|
|
72
|
+
one, head over there and create an issue. We'll do our best to help you!
|
|
73
|
+
|
|
74
|
+
## Source
|
|
75
|
+
|
|
76
|
+
The Roman Numeral Kata [http://codingdojo.org/cgi-bin/index.pl?KataRomanNumerals](http://codingdojo.org/cgi-bin/index.pl?KataRomanNumerals)
|
|
77
|
+
|
|
78
|
+
## Submitting Incomplete Solutions
|
|
79
|
+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|