Skip to content

Commit

Permalink
Add matrix exercise (#268)
Browse files Browse the repository at this point in the history
  • Loading branch information
keiravillekode authored Jan 20, 2024
1 parent 2f1c814 commit c78e2c1
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 0 deletions.
11 changes: 11 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,17 @@
"stacks"
]
},
{
"slug": "matrix",
"name": "Matrix",
"uuid": "892922b8-1616-41f5-9faa-11eb0f776cce",
"practices": [],
"prerequisites": [],
"difficulty": 4,
"topics": [
"lists"
]
},
{
"slug": "square-root",
"name": "Square Root",
Expand Down
38 changes: 38 additions & 0 deletions exercises/practice/matrix/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Instructions

Given a string representing a matrix of numbers, return the rows and columns of that matrix.

So given a string with embedded newlines like:

```text
9 8 7
5 3 2
6 6 7
```

representing this matrix:

```text
1 2 3
|---------
1 | 9 8 7
2 | 5 3 2
3 | 6 6 7
```

your code should be able to spit out:

- A list of the rows, reading each row left-to-right while moving top-to-bottom across the rows,
- A list of the columns, reading each column top-to-bottom while moving from left-to-right.

The rows for our example matrix:

- 9, 8, 7
- 5, 3, 2
- 6, 6, 7

And its columns:

- 9, 5, 6
- 8, 3, 6
- 7, 2, 7
19 changes: 19 additions & 0 deletions exercises/practice/matrix/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"keiravillekode"
],
"files": {
"solution": [
"matrix.sml"
],
"test": [
"test.sml"
],
"example": [
".meta/example.sml"
]
},
"blurb": "Given a string representing a matrix of numbers, return the rows and columns of that matrix.",
"source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.",
"source_url": "https://turing.edu"
}
17 changes: 17 additions & 0 deletions exercises/practice/matrix/.meta/example.sml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
local
val lines: string -> string list =
String.tokens (fn (c: char) => c = #"\n")

val cells: string -> string list =
String.tokens (fn (c: char) => c = #" ")

fun extract (index: int) (l: string list): string =
List.nth (l, index - 1)
in
fun row (s: string, index: int): int list =
map (valOf o Int.fromString) ((cells o extract index o lines) s)

fun column (s: string, index: int): int list =
map (valOf o Int.fromString o extract index o cells) (lines s)
end

34 changes: 34 additions & 0 deletions exercises/practice/matrix/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 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.

[ca733dab-9d85-4065-9ef6-a880a951dafd]
description = "extract row from one number matrix"

[5c93ec93-80e1-4268-9fc2-63bc7d23385c]
description = "can extract row"

[2f1aad89-ad0f-4bd2-9919-99a8bff0305a]
description = "extract row where numbers have different widths"

[68f7f6ba-57e2-4e87-82d0-ad09889b5204]
description = "can extract row from non-square matrix with no corresponding column"

[e8c74391-c93b-4aed-8bfe-f3c9beb89ebb]
description = "extract column from one number matrix"

[7136bdbd-b3dc-48c4-a10c-8230976d3727]
description = "can extract column"

[ad64f8d7-bba6-4182-8adf-0c14de3d0eca]
description = "can extract column from non-square matrix with no corresponding row"

[9eddfa5c-8474-440e-ae0a-f018c2a0dd89]
description = "extract column where numbers have different widths"
6 changes: 6 additions & 0 deletions exercises/practice/matrix/matrix.sml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fun row (s: string, index: int): int list =
raise Fail "'row' is not implemented"

fun column (s: string, index: int): int list =
raise Fail "'column' is not implemented"

68 changes: 68 additions & 0 deletions exercises/practice/matrix/test.sml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
(* version 1.0.0 *)

use "testlib.sml";
use "matrix.sml";

infixr |>
fun x |> f = f x

val testsuite =
describe "matrix" [
test "extract row from one number matrix"
(fn _ => let
val s = "1"
in
row (s, 1) |> Expect.equalTo [1]
end),

test "can extract row"
(fn _ => let
val s = "1 2\n3 4"
in
row (s, 2) |> Expect.equalTo [3, 4]
end),

test "extract row where numbers have different widths"
(fn _ => let
val s = "1 2\n10 20"
in
row (s, 2) |> Expect.equalTo [10, 20]
end),

test "can extract row from non-square matrix with no corresponding column"
(fn _ => let
val s = "1 2 3\n4 5 6\n7 8 9\n8 7 6"
in
row (s, 4) |> Expect.equalTo [8, 7, 6]
end),

test "extract column from one number matrix"
(fn _ => let
val s = "1"
in
column (s, 1) |> Expect.equalTo [1]
end),

test "can extract column"
(fn _ => let
val s = "1 2 3\n4 5 6\n7 8 9"
in
column (s, 3) |> Expect.equalTo [3, 6, 9]
end),

test "can extract column from non-square matrix with no corresponding row"
(fn _ => let
val s = "1 2 3 4\n5 6 7 8\n9 8 7 6"
in
column (s, 4) |> Expect.equalTo [4, 8, 6]
end),

test "extract column where numbers have different widths"
(fn _ => let
val s = "89 1903 3\n18 3 1\n9 4 800"
in
column (s, 2) |> Expect.equalTo [1903, 3, 4]
end)
]

val _ = Test.run testsuite
160 changes: 160 additions & 0 deletions exercises/practice/matrix/testlib.sml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
structure Expect =
struct
datatype expectation = Pass | Fail of string * string

local
fun failEq b a =
Fail ("Expected: " ^ b, "Got: " ^ a)

fun failExn b a =
Fail ("Expected: " ^ b, "Raised: " ^ a)

fun exnName (e: exn): string = General.exnName e
in
fun truthy a =
if a
then Pass
else failEq "true" "false"

fun falsy a =
if a
then failEq "false" "true"
else Pass

fun equalTo b a =
if a = b
then Pass
else failEq (PolyML.makestring b) (PolyML.makestring a)

fun nearTo delta b a =
if Real.abs (a - b) <= delta * Real.abs a orelse
Real.abs (a - b) <= delta * Real.abs b
then Pass
else failEq (Real.toString b ^ " +/- " ^ Real.toString delta) (Real.toString a)

fun anyError f =
(
f ();
failExn "an exception" "Nothing"
) handle _ => Pass

fun error e f =
(
f ();
failExn (exnName e) "Nothing"
) handle e' => if exnMessage e' = exnMessage e
then Pass
else failExn (exnMessage e) (exnMessage e')
end
end

structure TermColor =
struct
datatype color = Red | Green | Yellow | Normal

fun f Red = "\027[31m"
| f Green = "\027[32m"
| f Yellow = "\027[33m"
| f Normal = "\027[0m"

fun colorize color s = (f color) ^ s ^ (f Normal)

val redit = colorize Red

val greenit = colorize Green

val yellowit = colorize Yellow
end

structure Test =
struct
datatype testnode = TestGroup of string * testnode list
| Test of string * (unit -> Expect.expectation)

local
datatype evaluation = Success of string
| Failure of string * string * string
| Error of string * string

fun indent n s = (implode (List.tabulate (n, fn _ => #" "))) ^ s

fun fmt indentlvl ev =
let
val check = TermColor.greenit "\226\156\148 " (**)
val cross = TermColor.redit "\226\156\150 " (**)
val indentlvl = indentlvl * 2
in
case ev of
Success descr => indent indentlvl (check ^ descr)
| Failure (descr, exp, got) =>
String.concatWith "\n" [indent indentlvl (cross ^ descr),
indent (indentlvl + 2) exp,
indent (indentlvl + 2) got]
| Error (descr, reason) =>
String.concatWith "\n" [indent indentlvl (cross ^ descr),
indent (indentlvl + 2) (TermColor.redit reason)]
end

fun eval (TestGroup _) = raise Fail "Only a 'Test' can be evaluated"
| eval (Test (descr, thunk)) =
(
case thunk () of
Expect.Pass => ((1, 0, 0), Success descr)
| Expect.Fail (s, s') => ((0, 1, 0), Failure (descr, s, s'))
)
handle e => ((0, 0, 1), Error (descr, "Unexpected error: " ^ exnMessage e))

fun flatten depth testnode =
let
fun sum (x, y, z) (a, b, c) = (x + a, y + b, z + c)

fun aux (t, (counter, acc)) =
let
val (counter', texts) = flatten (depth + 1) t
in
(sum counter' counter, texts :: acc)
end
in
case testnode of
TestGroup (descr, ts) =>
let
val (counter, texts) = foldr aux ((0, 0, 0), []) ts
in
(counter, (indent (depth * 2) descr) :: List.concat texts)
end
| Test _ =>
let
val (counter, evaluation) = eval testnode
in
(counter, [fmt depth evaluation])
end
end

fun println s = print (s ^ "\n")
in
fun run suite =
let
val ((succeeded, failed, errored), texts) = flatten 0 suite

val summary = String.concatWith ", " [
TermColor.greenit ((Int.toString succeeded) ^ " passed"),
TermColor.redit ((Int.toString failed) ^ " failed"),
TermColor.redit ((Int.toString errored) ^ " errored"),
(Int.toString (succeeded + failed + errored)) ^ " total"
]

val status = if failed = 0 andalso errored = 0
then OS.Process.success
else OS.Process.failure

in
List.app println texts;
println "";
println ("Tests: " ^ summary);
OS.Process.exit status
end
end
end

fun describe description tests = Test.TestGroup (description, tests)
fun test description thunk = Test.Test (description, thunk)

0 comments on commit c78e2c1

Please sign in to comment.