Skip to content

Commit

Permalink
Add pythagorean-triplet exercise
Browse files Browse the repository at this point in the history
  • Loading branch information
keiravillekode committed Dec 19, 2023
1 parent bcc6f8f commit 8f45875
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 0 deletions.
11 changes: 11 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,17 @@
"difficulty": 1,
"topics": []
},
{
"slug": "pythagorean-triplet",
"name": "Pythagorean Triplet",
"uuid": "203470a2-f80c-4d6c-8ea4-7d09d7bfb509",
"practices": [],
"prerequisites": [],
"difficulty": 5,
"topics": [
"math"
]
},
{
"slug": "roman-numerals",
"name": "Roman Numerals",
Expand Down
23 changes: 23 additions & 0 deletions exercises/practice/pythagorean-triplet/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Instructions

A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for which,

```text
a² + b² = c²
```

and such that,

```text
a < b < c
```

For example,

```text
3² + 4² = 5².
```

Given an input integer N, find all Pythagorean triplets for which `a + b + c = N`.

For example, with N = 1000, there is exactly one Pythagorean triplet for which `a + b + c = 1000`: `{200, 375, 425}`.
19 changes: 19 additions & 0 deletions exercises/practice/pythagorean-triplet/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"keiravillekode"
],
"files": {
"solution": [
"pythagorean-triplet.sml"
],
"test": [
"test.sml"
],
"example": [
".meta/example.sml"
]
},
"blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the triplet.",
"source": "Problem 9 at Project Euler",
"source_url": "https://projecteuler.net/problem=9"
}
21 changes: 21 additions & 0 deletions exercises/practice/pythagorean-triplet/.meta/example.sml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
(* a*a + b*b = c*c, a + b + c = n
* a*a + b*b = (n - a - b) * (n - a - b)
* 0 = n*n - 2*n*a - 2*n*b + 2*a*b
* (2*n - 2*a) b = (n*n - 2*n*a)
*)
fun tripletsWithSum (n: int): int list list =
let
fun recurse (a: int): int list list =
let
val numerator = n * (n - 2 * a)
val denominator = 2 * (n - a)
val b = numerator div denominator
in
if b <= a then nil
else if numerator mod denominator <> 0 then recurse (a + 1)
else [a, b, n - a - b] :: recurse (a + 1)
end
in
if n < 2 then nil
else recurse 1
end
31 changes: 31 additions & 0 deletions exercises/practice/pythagorean-triplet/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 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.

[a19de65d-35b8-4480-b1af-371d9541e706]
description = "triplets whose sum is 12"

[48b21332-0a3d-43b2-9a52-90b2a6e5c9f5]
description = "triplets whose sum is 108"

[dffc1266-418e-4daa-81af-54c3e95c3bb5]
description = "triplets whose sum is 1000"

[5f86a2d4-6383-4cce-93a5-e4489e79b186]
description = "no matching triplets for 1001"

[bf17ba80-1596-409a-bb13-343bdb3b2904]
description = "returns all matching triplets"

[9d8fb5d5-6c6f-42df-9f95-d3165963ac57]
description = "several matching triplets"

[f5be5734-8aa0-4bd1-99a2-02adcc4402b4]
description = "triplets for large number"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fun tripletsWithSum (n: int): int list list =
raise Fail "'tripletsWithSum' is not implemented"
33 changes: 33 additions & 0 deletions exercises/practice/pythagorean-triplet/test.sml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
(* version 1.0.0 *)

use "testlib.sml";
use "pythagorean-triplet.sml";

infixr |>
fun x |> f = f x

val testsuite =
describe "pythagorean-triplet" [
test "triplets whose sum is 12"
(fn _ => tripletsWithSum 12 |> Expect.equalTo [[3, 4, 5]]),

test "triplets whose sum is 108"
(fn _ => tripletsWithSum 108 |> Expect.equalTo [[27, 36, 45]]),

test "triplets whose sum is 1000"
(fn _ => tripletsWithSum 1000 |> Expect.equalTo [[200, 375, 425]]),

test "no matching triplets for 1001"
(fn _ => tripletsWithSum 1001 |> Expect.equalTo []),

test "returns all matching triplets"
(fn _ => tripletsWithSum 90 |> Expect.equalTo [[9, 40, 41], [15, 36, 39]]),

test "several matching triplets"
(fn _ => tripletsWithSum 840 |> Expect.equalTo [[40, 399, 401], [56, 390, 394], [105, 360, 375], [120, 350, 370], [140, 336, 364], [168, 315, 357], [210, 280, 350], [240, 252, 348]]),

test "triplets for large number"
(fn _ => tripletsWithSum 30000 |> Expect.equalTo [[1200, 14375, 14425], [1875, 14000, 14125], [5000, 12000, 13000], [6000, 11250, 12750], [7500, 10000, 12500]])
]

val _ = Test.run testsuite
160 changes: 160 additions & 0 deletions exercises/practice/pythagorean-triplet/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 8f45875

Please sign in to comment.