From ba657236d4d2e6d8aa4edf1e15fae659daa050bf Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Sun, 11 May 2025 11:52:49 +0900 Subject: [PATCH 1/3] Add new practice exercise `split-second-stopwatch` --- config.json | 18 + .../.docs/instructions.md | 22 ++ .../.docs/introduction.md | 6 + .../split-second-stopwatch/.meta/config.json | 19 + .../src/SplitSecondStopwatch.example.elm | 141 +++++++ .../split-second-stopwatch/.meta/tests.toml | 97 +++++ .../practice/split-second-stopwatch/elm.json | 29 ++ .../src/SplitSecondStopwatch.elm | 74 ++++ .../split-second-stopwatch/tests/Tests.elm | 373 ++++++++++++++++++ 9 files changed, 779 insertions(+) create mode 100644 exercises/practice/split-second-stopwatch/.docs/instructions.md create mode 100644 exercises/practice/split-second-stopwatch/.docs/introduction.md create mode 100644 exercises/practice/split-second-stopwatch/.meta/config.json create mode 100644 exercises/practice/split-second-stopwatch/.meta/src/SplitSecondStopwatch.example.elm create mode 100644 exercises/practice/split-second-stopwatch/.meta/tests.toml create mode 100644 exercises/practice/split-second-stopwatch/elm.json create mode 100644 exercises/practice/split-second-stopwatch/src/SplitSecondStopwatch.elm create mode 100644 exercises/practice/split-second-stopwatch/tests/Tests.elm diff --git a/config.json b/config.json index 163da4e2..42516efc 100644 --- a/config.json +++ b/config.json @@ -1543,6 +1543,24 @@ "comparison" ], "difficulty": 7 + }, + { + "slug": "split-second-stopwatch", + "name": "Split-Second Stopwatch", + "uuid": "0945769a-6e67-409d-9f11-102112fb8be9", + "practices": [ + "opaque-types" + ], + "prerequisites": [ + "opaque-types", + "custom-types", + "pattern-matching", + "strings", + "lists", + "result", + "records" + ], + "difficulty": 4 } ], "foregone": [ diff --git a/exercises/practice/split-second-stopwatch/.docs/instructions.md b/exercises/practice/split-second-stopwatch/.docs/instructions.md new file mode 100644 index 00000000..30bdc988 --- /dev/null +++ b/exercises/practice/split-second-stopwatch/.docs/instructions.md @@ -0,0 +1,22 @@ +# Instructions + +Your task is to build a stopwatch to keep precise track of lap times. + +The stopwatch uses four commands (start, stop, lap, and reset) to keep track of: + +1. The current lap's tracked time +2. Previously recorded lap times + +What commands can be used depends on which state the stopwatch is in: + +1. Ready: initial state +2. Running: tracking time +3. Stopped: not tracking time + +| Command | Begin state | End state | Effect | +| ------- | ----------- | --------- | -------------------------------------------------------- | +| Start | Ready | Running | Start tracking time | +| Start | Stopped | Running | Resume tracking time | +| Stop | Running | Stopped | Stop tracking time | +| Lap | Running | Running | Add current lap to previous laps, then reset current lap | +| Reset | Stopped | Ready | Reset current lap and clear previous laps | diff --git a/exercises/practice/split-second-stopwatch/.docs/introduction.md b/exercises/practice/split-second-stopwatch/.docs/introduction.md new file mode 100644 index 00000000..a8432247 --- /dev/null +++ b/exercises/practice/split-second-stopwatch/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +You've always run for the thrill of it — no schedules, no timers, just the sound of your feet on the pavement. +But now that you've joined a competitive running crew, things are getting serious. +Training sessions are timed to the second, and every split second counts. +To keep pace, you've picked up the _Split-Second Stopwatch_ — a sleek, high-tech gadget that's about to become your new best friend. diff --git a/exercises/practice/split-second-stopwatch/.meta/config.json b/exercises/practice/split-second-stopwatch/.meta/config.json new file mode 100644 index 00000000..aa355e3c --- /dev/null +++ b/exercises/practice/split-second-stopwatch/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "jiegillet" + ], + "files": { + "solution": [ + "src/SplitSecondStopwatch.elm" + ], + "test": [ + "tests/Tests.elm" + ], + "example": [ + ".meta/src/SplitSecondStopwatch.example.elm" + ] + }, + "blurb": "Keep track of time through a digital stopwatch.", + "source": "Erik Schierboom", + "source_url": "https://github.com/exercism/problem-specifications/pull/2547" +} diff --git a/exercises/practice/split-second-stopwatch/.meta/src/SplitSecondStopwatch.example.elm b/exercises/practice/split-second-stopwatch/.meta/src/SplitSecondStopwatch.example.elm new file mode 100644 index 00000000..e73787e3 --- /dev/null +++ b/exercises/practice/split-second-stopwatch/.meta/src/SplitSecondStopwatch.example.elm @@ -0,0 +1,141 @@ +module SplitSecondStopwatch exposing + ( State(..) + , Stopwatch + , advanceTime + , currentLap + , lap + , new + , previousLaps + , reset + , start + , state + , stop + , total + ) + + +type State + = Ready + | Running + | Stopped + + +type Stopwatch + = Stopwatch + { state_ : State + , currentLap_ : Int + , previousLaps_ : List Int + } + + +new : Stopwatch +new = + Stopwatch { state_ = Ready, currentLap_ = 0, previousLaps_ = [] } + + +state : Stopwatch -> State +state (Stopwatch { state_ }) = + state_ + + +currentLap : Stopwatch -> String +currentLap (Stopwatch { currentLap_ }) = + formatTime currentLap_ + + +previousLaps : Stopwatch -> List String +previousLaps (Stopwatch { previousLaps_ }) = + previousLaps_ + |> List.reverse + |> List.map formatTime + + +advanceTime : String -> Stopwatch -> Stopwatch +advanceTime time (Stopwatch ({ state_, currentLap_ } as stopwatch)) = + case state_ of + Running -> + Stopwatch { stopwatch | currentLap_ = currentLap_ + parseTime time } + + _ -> + Stopwatch stopwatch + + +total : Stopwatch -> String +total (Stopwatch { currentLap_, previousLaps_ }) = + (currentLap_ :: previousLaps_) + |> List.sum + |> formatTime + + +start : Stopwatch -> Result String Stopwatch +start (Stopwatch ({ state_ } as stopwatch)) = + case state_ of + Running -> + Err "cannot start an already running stopwatch" + + _ -> + Ok (Stopwatch { stopwatch | state_ = Running }) + + +stop : Stopwatch -> Result String Stopwatch +stop (Stopwatch ({ state_ } as stopwatch)) = + case state_ of + Running -> + Ok (Stopwatch { stopwatch | state_ = Stopped }) + + _ -> + Err "cannot stop a stopwatch that is not running" + + +lap : Stopwatch -> Result String Stopwatch +lap (Stopwatch ({ state_, currentLap_, previousLaps_ } as stopwatch)) = + case state_ of + Running -> + Ok (Stopwatch { stopwatch | currentLap_ = 0, previousLaps_ = currentLap_ :: previousLaps_ }) + + _ -> + Err "cannot lap a stopwatch that is not running" + + +reset : Stopwatch -> Result String Stopwatch +reset (Stopwatch { state_ }) = + case state_ of + Stopped -> + Ok new + + _ -> + Err "cannot reset a stopwatch that is not stopped" + + +parseTime : String -> Int +parseTime time = + let + timeValues = + time + |> String.split ":" + |> List.filterMap String.toInt + in + case timeValues of + [ hours, minutes, seconds ] -> + hours * 3600 + minutes * 60 + seconds + + _ -> + 0 + + +formatTime : Int -> String +formatTime time = + let + hours = + time // 3600 + + minutes = + modBy 3600 time // 60 + + seconds = + modBy 60 time + in + [ hours, minutes, seconds ] + |> List.map String.fromInt + |> List.map (String.padLeft 2 '0') + |> String.join ":" diff --git a/exercises/practice/split-second-stopwatch/.meta/tests.toml b/exercises/practice/split-second-stopwatch/.meta/tests.toml new file mode 100644 index 00000000..323cb7ae --- /dev/null +++ b/exercises/practice/split-second-stopwatch/.meta/tests.toml @@ -0,0 +1,97 @@ +# 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. + +[ddb238ea-99d4-4eaa-a81d-3c917a525a23] +description = "new stopwatch starts in ready state" + +[b19635d4-08ad-4ac3-b87f-aca10e844071] +description = "new stopwatch's current lap has no elapsed time" + +[492eb532-268d-43ea-8a19-2a032067d335] +description = "new stopwatch's total has no elapsed time" + +[8a892c1e-9ef7-4690-894e-e155a1fe4484] +description = "new stopwatch does not have previous laps" + +[5b2705b6-a584-4042-ba3a-4ab8d0ab0281] +description = "start from ready state changes state to running" + +[748235ce-1109-440b-9898-0a431ea179b6] +description = "start does not change previous laps" + +[491487b1-593d-423e-a075-aa78d449ff1f] +description = "start initiates time tracking for current lap" + +[a0a7ba2c-8db6-412c-b1b6-cb890e9b72ed] +description = "start initiates time tracking for total" + +[7f558a17-ef6d-4a5b-803a-f313af7c41d3] +description = "start cannot be called from running state" + +[32466eef-b2be-4d60-a927-e24fce52dab9] +description = "stop from running state changes state to stopped" + +[621eac4c-8f43-4d99-919c-4cad776d93df] +description = "stop pauses time tracking for current lap" + +[465bcc82-7643-41f2-97ff-5e817cef8db4] +description = "stop pauses time tracking for total" + +[b1ba7454-d627-41ee-a078-891b2ed266fc] +description = "stop cannot be called from ready state" + +[5c041078-0898-44dc-9d5b-8ebb5352626c] +description = "stop cannot be called from stopped state" + +[3f32171d-8fbf-46b6-bc2b-0810e1ec53b7] +description = "start from stopped state changes state to running" + +[626997cb-78d5-4fe8-b501-29fdef804799] +description = "start from stopped state resumes time tracking for current lap" + +[58487c53-ab26-471c-a171-807ef6363319] +description = "start from stopped state resumes time tracking for total" + +[091966e3-ed25-4397-908b-8bb0330118f8] +description = "lap adds current lap to previous laps" + +[1aa4c5ee-a7d5-4d59-9679-419deef3c88f] +description = "lap resets current lap and resumes time tracking" + +[4b46b92e-1b3f-46f6-97d2-0082caf56e80] +description = "lap continues time tracking for total" + +[ea75d36e-63eb-4f34-97ce-8c70e620bdba] +description = "lap cannot be called from ready state" + +[63731154-a23a-412d-a13f-c562f208eb1e] +description = "lap cannot be called from stopped state" + +[e585ee15-3b3f-4785-976b-dd96e7cc978b] +description = "stop does not change previous laps" + +[fc3645e2-86cf-4d11-97c6-489f031103f6] +description = "reset from stopped state changes state to ready" + +[20fbfbf7-68ad-4310-975a-f5f132886c4e] +description = "reset resets current lap" + +[00a8f7bb-dd5c-43e5-8705-3ef124007662] +description = "reset clears previous laps" + +[76cea936-6214-4e95-b6d1-4d4edcf90499] +description = "reset cannot be called from ready state" + +[ba4d8e69-f200-4721-b59e-90d8cf615153] +description = "reset cannot be called from running state" + +[0b01751a-cb57-493f-bb86-409de6e84306] +description = "supports very long laps" diff --git a/exercises/practice/split-second-stopwatch/elm.json b/exercises/practice/split-second-stopwatch/elm.json new file mode 100644 index 00000000..ee68ab84 --- /dev/null +++ b/exercises/practice/split-second-stopwatch/elm.json @@ -0,0 +1,29 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "elm/core": "1.0.5", + "elm/json": "1.1.3", + "elm/parser": "1.1.0", + "elm/random": "1.0.0", + "elm/regex": "1.0.0", + "elm/time": "1.0.0", + "elm/html": "1.0.0" + }, + "indirect": {} + }, + "test-dependencies": { + "direct": { + "elm-explorations/test": "2.1.0", + "rtfeldman/elm-iso8601-date-strings": "1.1.4" + }, + "indirect": { + "elm/bytes": "1.0.8", + "elm/virtual-dom": "1.0.3" + } + } +} \ No newline at end of file diff --git a/exercises/practice/split-second-stopwatch/src/SplitSecondStopwatch.elm b/exercises/practice/split-second-stopwatch/src/SplitSecondStopwatch.elm new file mode 100644 index 00000000..c8f433b2 --- /dev/null +++ b/exercises/practice/split-second-stopwatch/src/SplitSecondStopwatch.elm @@ -0,0 +1,74 @@ +module SplitSecondStopwatch exposing + ( State(..) + , Stopwatch + , advanceTime + , currentLap + , lap + , new + , previousLaps + , reset + , start + , state + , stop + , total + ) + + +type State + = Ready + | Running + | Stopped + + +type Stopwatch + = TODO String + + +new : Stopwatch +new = + TODO "Please implement new" + + +state : Stopwatch -> State +state stopwatch = + Debug.todo "Please implement state" + + +currentLap : Stopwatch -> String +currentLap stopwatch = + Debug.todo "Please implement currentLap" + + +previousLaps : Stopwatch -> List String +previousLaps stopwatch = + Debug.todo "Please implement previousLaps" + + +advanceTime : String -> Stopwatch -> Stopwatch +advanceTime time stopwatch = + Debug.todo "Please implement advanceTime" + + +total : Stopwatch -> String +total stopwatch = + Debug.todo "Please implement total" + + +start : Stopwatch -> Result String Stopwatch +start stopwatch = + Debug.todo "Please implement start" + + +stop : Stopwatch -> Result String Stopwatch +stop stopwatch = + Debug.todo "Please implement stop" + + +lap : Stopwatch -> Result String Stopwatch +lap stopwatch = + Debug.todo "Please implement lap" + + +reset : Stopwatch -> Result String Stopwatch +reset stopwatch = + Debug.todo "Please implement reset" diff --git a/exercises/practice/split-second-stopwatch/tests/Tests.elm b/exercises/practice/split-second-stopwatch/tests/Tests.elm new file mode 100644 index 00000000..ea622aa2 --- /dev/null +++ b/exercises/practice/split-second-stopwatch/tests/Tests.elm @@ -0,0 +1,373 @@ +module Tests exposing (tests) + +import Expect +import SplitSecondStopwatch exposing (State(..), Stopwatch) +import Test exposing (Test, describe, skip, test) + + +type Output + = CurrentLap String + | PreviousLaps (List String) + | Total String + + +tests : Test +tests = + describe "SplitSecondStopwatch" + [ -- skip <| + test "new stopwatch starts in ready state" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.state + |> Expect.equal Ready + , skip <| + test "new stopwatch's current lap has no elapsed time" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.currentLap + |> Expect.equal "00:00:00" + , skip <| + test "new stopwatch's total has no elapsed time" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.total + |> Expect.equal "00:00:00" + , skip <| + test "new stopwatch does not have previous laps" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.previousLaps + |> Expect.equal [] + , skip <| + test "start from ready state changes state to running" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map SplitSecondStopwatch.state + |> Expect.equal (Ok Running) + , skip <| + test "start does not change previous laps" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map SplitSecondStopwatch.previousLaps + |> Expect.equal (Ok []) + , skip <| + test "start initiates time tracking for current lap" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:05") + |> Result.map SplitSecondStopwatch.currentLap + |> Expect.equal (Ok "00:00:05") + , skip <| + test "start initiates time tracking for total" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:23") + |> Result.map SplitSecondStopwatch.total + |> Expect.equal (Ok "00:00:23") + , skip <| + test "start cannot be called from running state" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.andThen SplitSecondStopwatch.start + |> Expect.equal (Err "cannot start an already running stopwatch") + , skip <| + test "stop from running state changes state to stopped" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.andThen SplitSecondStopwatch.stop + |> Result.map SplitSecondStopwatch.state + |> Expect.equal (Ok Stopped) + , skip <| + test "stop pauses time tracking for current lap" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:05") + |> Result.andThen SplitSecondStopwatch.stop + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:08") + |> Result.map SplitSecondStopwatch.currentLap + |> Expect.equal (Ok "00:00:05") + , skip <| + test "stop pauses time tracking for total" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:13") + |> Result.andThen SplitSecondStopwatch.stop + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:44") + |> Result.map SplitSecondStopwatch.total + |> Expect.equal (Ok "00:00:13") + , skip <| + test "stop cannot be called from ready state" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.stop + |> Expect.equal (Err "cannot stop a stopwatch that is not running") + , skip <| + test "stop cannot be called from stopped state" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.andThen SplitSecondStopwatch.stop + |> Result.andThen SplitSecondStopwatch.stop + |> Expect.equal (Err "cannot stop a stopwatch that is not running") + , skip <| + test "start from stopped state changes state to running" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.andThen SplitSecondStopwatch.stop + |> Result.andThen SplitSecondStopwatch.start + |> Result.map SplitSecondStopwatch.state + |> Expect.equal (Ok Running) + , skip <| + test "start from stopped state resumes time tracking for current lap" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:01:20") + |> Result.andThen SplitSecondStopwatch.stop + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:20") + |> Result.andThen SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:08") + |> Result.map SplitSecondStopwatch.currentLap + |> Expect.equal (Ok "00:01:28") + , skip <| + test "start from stopped state resumes time tracking for total" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:23") + |> Result.andThen SplitSecondStopwatch.stop + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:44") + |> Result.andThen SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:09") + |> Result.map SplitSecondStopwatch.total + |> Expect.equal (Ok "00:00:32") + , skip <| + test "lap adds current lap to previous laps" <| + \() -> + let + stopwatch1 : Result String Stopwatch + stopwatch1 = + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:01:38") + |> Result.andThen SplitSecondStopwatch.lap + + stopwatch2 : Result String Stopwatch + stopwatch2 = + stopwatch1 + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:44") + |> Result.andThen SplitSecondStopwatch.lap + in + [ Result.map SplitSecondStopwatch.previousLaps stopwatch1 + , Result.map SplitSecondStopwatch.previousLaps stopwatch2 + ] + |> Expect.equalLists + [ Ok [ "00:01:38" ] + , Ok [ "00:01:38", "00:00:44" ] + ] + , skip <| + test "lap resets current lap and resumes time tracking" <| + \() -> + let + stopwatch1 : Result String Stopwatch + stopwatch1 = + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:08:22") + |> Result.andThen SplitSecondStopwatch.lap + + stopwatch2 : Result String Stopwatch + stopwatch2 = + Result.map (SplitSecondStopwatch.advanceTime "00:00:15") stopwatch1 + in + [ Result.map SplitSecondStopwatch.currentLap stopwatch1 + , Result.map SplitSecondStopwatch.currentLap stopwatch2 + ] + |> Expect.equalLists + [ Ok "00:00:00" + , Ok "00:00:15" + ] + , skip <| + test "lap continues time tracking for total" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:22") + |> Result.andThen SplitSecondStopwatch.lap + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:33") + |> Result.map SplitSecondStopwatch.total + |> Expect.equal (Ok "00:00:55") + , skip <| + test "lap cannot be called from ready state" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.lap + |> Expect.equal (Err "cannot lap a stopwatch that is not running") + , skip <| + test "lap cannot be called from stopped state" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.andThen SplitSecondStopwatch.stop + |> Result.andThen SplitSecondStopwatch.lap + |> Expect.equal (Err "cannot lap a stopwatch that is not running") + , skip <| + test "stop does not change previous laps" <| + \() -> + let + stopwatch1 : Result String Stopwatch + stopwatch1 = + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:11:22") + |> Result.andThen SplitSecondStopwatch.lap + + stopwatch2 : Result String Stopwatch + stopwatch2 = + stopwatch1 + |> Result.andThen SplitSecondStopwatch.stop + in + [ Result.map SplitSecondStopwatch.previousLaps stopwatch1 + , Result.map SplitSecondStopwatch.previousLaps stopwatch2 + ] + |> Expect.equalLists + [ Ok [ "00:11:22" ] + , Ok [ "00:11:22" ] + ] + , skip <| + test "reset from stopped state changes state to ready" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.andThen SplitSecondStopwatch.stop + |> Result.andThen SplitSecondStopwatch.reset + |> Result.map SplitSecondStopwatch.state + |> Expect.equal (Ok Ready) + , skip <| + test "reset resets current lap" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:10") + |> Result.andThen SplitSecondStopwatch.stop + |> Result.andThen SplitSecondStopwatch.reset + |> Result.map SplitSecondStopwatch.currentLap + |> Expect.equal (Ok "00:00:00") + , skip <| + test "reset clears previous laps" <| + \() -> + let + stopwatch1 : Result String Stopwatch + stopwatch1 = + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:10") + |> Result.andThen SplitSecondStopwatch.lap + |> Result.map (SplitSecondStopwatch.advanceTime "00:00:20") + |> Result.andThen SplitSecondStopwatch.lap + + stopwatch2 = + stopwatch1 + |> Result.andThen SplitSecondStopwatch.stop + |> Result.andThen SplitSecondStopwatch.reset + in + [ Result.map SplitSecondStopwatch.previousLaps stopwatch1 + , Result.map SplitSecondStopwatch.previousLaps stopwatch2 + ] + |> Expect.equalLists + [ Ok [ "00:00:10", "00:00:20" ] + , Ok [] + ] + , skip <| + test "reset cannot be called from ready state" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.reset + |> Expect.equal (Err "cannot reset a stopwatch that is not stopped") + , skip <| + test "reset cannot be called from running state" <| + \() -> + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.andThen SplitSecondStopwatch.reset + |> Expect.equal (Err "cannot reset a stopwatch that is not stopped") + , skip <| + test "supports very long laps" <| + \() -> + let + stopwatch1 : Result String Stopwatch + stopwatch1 = + SplitSecondStopwatch.new + |> SplitSecondStopwatch.start + |> Result.map (SplitSecondStopwatch.advanceTime "01:23:45") + + stopwatch2 : Result String Stopwatch + stopwatch2 = + stopwatch1 + |> Result.andThen SplitSecondStopwatch.lap + + stopwatch3 : Result String Stopwatch + stopwatch3 = + stopwatch2 + |> Result.map (SplitSecondStopwatch.advanceTime "04:01:40") + + stopwatch4 : Result String Stopwatch + stopwatch4 = + stopwatch3 + |> Result.andThen SplitSecondStopwatch.lap + + stopwatch5 : Result String Stopwatch + stopwatch5 = + stopwatch4 + |> Result.map (SplitSecondStopwatch.advanceTime "08:43:05") + + stopwatch6 : Result String Stopwatch + stopwatch6 = + stopwatch5 + |> Result.andThen SplitSecondStopwatch.lap + in + [ stopwatch1 + |> Result.map SplitSecondStopwatch.currentLap + |> Result.map CurrentLap + , stopwatch2 + |> Result.map SplitSecondStopwatch.previousLaps + |> Result.map PreviousLaps + , stopwatch3 + |> Result.map SplitSecondStopwatch.currentLap + |> Result.map CurrentLap + , stopwatch3 + |> Result.map SplitSecondStopwatch.total + |> Result.map Total + , stopwatch4 + |> Result.map SplitSecondStopwatch.previousLaps + |> Result.map PreviousLaps + , stopwatch5 + |> Result.map SplitSecondStopwatch.currentLap + |> Result.map CurrentLap + , stopwatch5 + |> Result.map SplitSecondStopwatch.total + |> Result.map Total + , stopwatch6 + |> Result.map SplitSecondStopwatch.previousLaps + |> Result.map PreviousLaps + ] + |> Expect.equalLists + [ Ok (CurrentLap "01:23:45") + , Ok (PreviousLaps [ "01:23:45" ]) + , Ok (CurrentLap "04:01:40") + , Ok (Total "05:25:25") + , Ok (PreviousLaps [ "01:23:45", "04:01:40" ]) + , Ok (CurrentLap "08:43:05") + , Ok (Total "14:08:30") + , Ok (PreviousLaps [ "01:23:45", "04:01:40", "08:43:05" ]) + ] + ] From 80757887cff918cf53dfe5fea402e021e458d54b Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Fri, 16 May 2025 09:22:50 +0900 Subject: [PATCH 2/3] use aSTate notation --- .../src/SplitSecondStopwatch.example.elm | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/exercises/practice/split-second-stopwatch/.meta/src/SplitSecondStopwatch.example.elm b/exercises/practice/split-second-stopwatch/.meta/src/SplitSecondStopwatch.example.elm index e73787e3..dde1843a 100644 --- a/exercises/practice/split-second-stopwatch/.meta/src/SplitSecondStopwatch.example.elm +++ b/exercises/practice/split-second-stopwatch/.meta/src/SplitSecondStopwatch.example.elm @@ -22,84 +22,84 @@ type State type Stopwatch = Stopwatch - { state_ : State - , currentLap_ : Int - , previousLaps_ : List Int + { aState : State + , aCurrentLap : Int + , aPreviousLaps : List Int } new : Stopwatch new = - Stopwatch { state_ = Ready, currentLap_ = 0, previousLaps_ = [] } + Stopwatch { aState = Ready, aCurrentLap = 0, aPreviousLaps = [] } state : Stopwatch -> State -state (Stopwatch { state_ }) = - state_ +state (Stopwatch { aState }) = + aState currentLap : Stopwatch -> String -currentLap (Stopwatch { currentLap_ }) = - formatTime currentLap_ +currentLap (Stopwatch { aCurrentLap }) = + formatTime aCurrentLap previousLaps : Stopwatch -> List String -previousLaps (Stopwatch { previousLaps_ }) = - previousLaps_ +previousLaps (Stopwatch { aPreviousLaps }) = + aPreviousLaps |> List.reverse |> List.map formatTime advanceTime : String -> Stopwatch -> Stopwatch -advanceTime time (Stopwatch ({ state_, currentLap_ } as stopwatch)) = - case state_ of +advanceTime time (Stopwatch ({ aState, aCurrentLap } as stopwatch)) = + case aState of Running -> - Stopwatch { stopwatch | currentLap_ = currentLap_ + parseTime time } + Stopwatch { stopwatch | aCurrentLap = aCurrentLap + parseTime time } _ -> Stopwatch stopwatch total : Stopwatch -> String -total (Stopwatch { currentLap_, previousLaps_ }) = - (currentLap_ :: previousLaps_) +total (Stopwatch { aCurrentLap, aPreviousLaps }) = + (aCurrentLap :: aPreviousLaps) |> List.sum |> formatTime start : Stopwatch -> Result String Stopwatch -start (Stopwatch ({ state_ } as stopwatch)) = - case state_ of +start (Stopwatch ({ aState } as stopwatch)) = + case aState of Running -> Err "cannot start an already running stopwatch" _ -> - Ok (Stopwatch { stopwatch | state_ = Running }) + Ok (Stopwatch { stopwatch | aState = Running }) stop : Stopwatch -> Result String Stopwatch -stop (Stopwatch ({ state_ } as stopwatch)) = - case state_ of +stop (Stopwatch ({ aState } as stopwatch)) = + case aState of Running -> - Ok (Stopwatch { stopwatch | state_ = Stopped }) + Ok (Stopwatch { stopwatch | aState = Stopped }) _ -> Err "cannot stop a stopwatch that is not running" lap : Stopwatch -> Result String Stopwatch -lap (Stopwatch ({ state_, currentLap_, previousLaps_ } as stopwatch)) = - case state_ of +lap (Stopwatch ({ aState, aCurrentLap, aPreviousLaps } as stopwatch)) = + case aState of Running -> - Ok (Stopwatch { stopwatch | currentLap_ = 0, previousLaps_ = currentLap_ :: previousLaps_ }) + Ok (Stopwatch { stopwatch | aCurrentLap = 0, aPreviousLaps = aCurrentLap :: aPreviousLaps }) _ -> Err "cannot lap a stopwatch that is not running" reset : Stopwatch -> Result String Stopwatch -reset (Stopwatch { state_ }) = - case state_ of +reset (Stopwatch { aState }) = + case aState of Stopped -> Ok new From 2f65f6a1070bf21f6dc9f29b2b53cfd845db219b Mon Sep 17 00:00:00 2001 From: Jeremie Gillet Date: Fri, 16 May 2025 09:30:32 +0900 Subject: [PATCH 3/3] use strings instead of types in test --- .../split-second-stopwatch/tests/Tests.elm | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/exercises/practice/split-second-stopwatch/tests/Tests.elm b/exercises/practice/split-second-stopwatch/tests/Tests.elm index ea622aa2..0768948a 100644 --- a/exercises/practice/split-second-stopwatch/tests/Tests.elm +++ b/exercises/practice/split-second-stopwatch/tests/Tests.elm @@ -5,12 +5,6 @@ import SplitSecondStopwatch exposing (State(..), Stopwatch) import Test exposing (Test, describe, skip, test) -type Output - = CurrentLap String - | PreviousLaps (List String) - | Total String - - tests : Test tests = describe "SplitSecondStopwatch" @@ -337,37 +331,37 @@ tests = in [ stopwatch1 |> Result.map SplitSecondStopwatch.currentLap - |> Result.map CurrentLap + |> Result.map (\lap -> "currentLap: " ++ lap) , stopwatch2 |> Result.map SplitSecondStopwatch.previousLaps - |> Result.map PreviousLaps + |> Result.map (\laps -> "previousLaps: " ++ String.join ", " laps) , stopwatch3 |> Result.map SplitSecondStopwatch.currentLap - |> Result.map CurrentLap + |> Result.map (\lap -> "currentLap: " ++ lap) , stopwatch3 |> Result.map SplitSecondStopwatch.total - |> Result.map Total + |> Result.map (\total -> "total: " ++ total) , stopwatch4 |> Result.map SplitSecondStopwatch.previousLaps - |> Result.map PreviousLaps + |> Result.map (\laps -> "previousLaps: " ++ String.join ", " laps) , stopwatch5 |> Result.map SplitSecondStopwatch.currentLap - |> Result.map CurrentLap + |> Result.map (\lap -> "currentLap: " ++ lap) , stopwatch5 |> Result.map SplitSecondStopwatch.total - |> Result.map Total + |> Result.map (\total -> "total: " ++ total) , stopwatch6 |> Result.map SplitSecondStopwatch.previousLaps - |> Result.map PreviousLaps + |> Result.map (\laps -> "previousLaps: " ++ String.join ", " laps) ] |> Expect.equalLists - [ Ok (CurrentLap "01:23:45") - , Ok (PreviousLaps [ "01:23:45" ]) - , Ok (CurrentLap "04:01:40") - , Ok (Total "05:25:25") - , Ok (PreviousLaps [ "01:23:45", "04:01:40" ]) - , Ok (CurrentLap "08:43:05") - , Ok (Total "14:08:30") - , Ok (PreviousLaps [ "01:23:45", "04:01:40", "08:43:05" ]) + [ Ok "currentLap: 01:23:45" + , Ok "previousLaps: 01:23:45" + , Ok "currentLap: 04:01:40" + , Ok "total: 05:25:25" + , Ok "previousLaps: 01:23:45, 04:01:40" + , Ok "currentLap: 08:43:05" + , Ok "total: 14:08:30" + , Ok "previousLaps: 01:23:45, 04:01:40, 08:43:05" ] ]