Skip to content

Commit 5fb0b6d

Browse files
authored
Add new practice exercise split-second-stopwatch (#749)
* Add new practice exercise `split-second-stopwatch` * use aSTate notation * use strings instead of types in test
1 parent 6170a4f commit 5fb0b6d

File tree

9 files changed

+773
-0
lines changed

9 files changed

+773
-0
lines changed

config.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,6 +1558,24 @@
15581558
"comparison"
15591559
],
15601560
"difficulty": 7
1561+
},
1562+
{
1563+
"slug": "split-second-stopwatch",
1564+
"name": "Split-Second Stopwatch",
1565+
"uuid": "0945769a-6e67-409d-9f11-102112fb8be9",
1566+
"practices": [
1567+
"opaque-types"
1568+
],
1569+
"prerequisites": [
1570+
"opaque-types",
1571+
"custom-types",
1572+
"pattern-matching",
1573+
"strings",
1574+
"lists",
1575+
"result",
1576+
"records"
1577+
],
1578+
"difficulty": 4
15611579
}
15621580
],
15631581
"foregone": [
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Instructions
2+
3+
Your task is to build a stopwatch to keep precise track of lap times.
4+
5+
The stopwatch uses four commands (start, stop, lap, and reset) to keep track of:
6+
7+
1. The current lap's tracked time
8+
2. Previously recorded lap times
9+
10+
What commands can be used depends on which state the stopwatch is in:
11+
12+
1. Ready: initial state
13+
2. Running: tracking time
14+
3. Stopped: not tracking time
15+
16+
| Command | Begin state | End state | Effect |
17+
| ------- | ----------- | --------- | -------------------------------------------------------- |
18+
| Start | Ready | Running | Start tracking time |
19+
| Start | Stopped | Running | Resume tracking time |
20+
| Stop | Running | Stopped | Stop tracking time |
21+
| Lap | Running | Running | Add current lap to previous laps, then reset current lap |
22+
| Reset | Stopped | Ready | Reset current lap and clear previous laps |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Introduction
2+
3+
You've always run for the thrill of it — no schedules, no timers, just the sound of your feet on the pavement.
4+
But now that you've joined a competitive running crew, things are getting serious.
5+
Training sessions are timed to the second, and every split second counts.
6+
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.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"authors": [
3+
"jiegillet"
4+
],
5+
"files": {
6+
"solution": [
7+
"src/SplitSecondStopwatch.elm"
8+
],
9+
"test": [
10+
"tests/Tests.elm"
11+
],
12+
"example": [
13+
".meta/src/SplitSecondStopwatch.example.elm"
14+
]
15+
},
16+
"blurb": "Keep track of time through a digital stopwatch.",
17+
"source": "Erik Schierboom",
18+
"source_url": "https://github.com/exercism/problem-specifications/pull/2547"
19+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
module SplitSecondStopwatch exposing
2+
( State(..)
3+
, Stopwatch
4+
, advanceTime
5+
, currentLap
6+
, lap
7+
, new
8+
, previousLaps
9+
, reset
10+
, start
11+
, state
12+
, stop
13+
, total
14+
)
15+
16+
17+
type State
18+
= Ready
19+
| Running
20+
| Stopped
21+
22+
23+
type Stopwatch
24+
= Stopwatch
25+
{ aState : State
26+
, aCurrentLap : Int
27+
, aPreviousLaps : List Int
28+
}
29+
30+
31+
new : Stopwatch
32+
new =
33+
Stopwatch { aState = Ready, aCurrentLap = 0, aPreviousLaps = [] }
34+
35+
36+
state : Stopwatch -> State
37+
state (Stopwatch { aState }) =
38+
aState
39+
40+
41+
currentLap : Stopwatch -> String
42+
currentLap (Stopwatch { aCurrentLap }) =
43+
formatTime aCurrentLap
44+
45+
46+
previousLaps : Stopwatch -> List String
47+
previousLaps (Stopwatch { aPreviousLaps }) =
48+
aPreviousLaps
49+
|> List.reverse
50+
|> List.map formatTime
51+
52+
53+
advanceTime : String -> Stopwatch -> Stopwatch
54+
advanceTime time (Stopwatch ({ aState, aCurrentLap } as stopwatch)) =
55+
case aState of
56+
Running ->
57+
Stopwatch { stopwatch | aCurrentLap = aCurrentLap + parseTime time }
58+
59+
_ ->
60+
Stopwatch stopwatch
61+
62+
63+
total : Stopwatch -> String
64+
total (Stopwatch { aCurrentLap, aPreviousLaps }) =
65+
(aCurrentLap :: aPreviousLaps)
66+
|> List.sum
67+
|> formatTime
68+
69+
70+
start : Stopwatch -> Result String Stopwatch
71+
start (Stopwatch ({ aState } as stopwatch)) =
72+
case aState of
73+
Running ->
74+
Err "cannot start an already running stopwatch"
75+
76+
_ ->
77+
Ok (Stopwatch { stopwatch | aState = Running })
78+
79+
80+
stop : Stopwatch -> Result String Stopwatch
81+
stop (Stopwatch ({ aState } as stopwatch)) =
82+
case aState of
83+
Running ->
84+
Ok (Stopwatch { stopwatch | aState = Stopped })
85+
86+
_ ->
87+
Err "cannot stop a stopwatch that is not running"
88+
89+
90+
lap : Stopwatch -> Result String Stopwatch
91+
lap (Stopwatch ({ aState, aCurrentLap, aPreviousLaps } as stopwatch)) =
92+
case aState of
93+
Running ->
94+
Ok (Stopwatch { stopwatch | aCurrentLap = 0, aPreviousLaps = aCurrentLap :: aPreviousLaps })
95+
96+
_ ->
97+
Err "cannot lap a stopwatch that is not running"
98+
99+
100+
reset : Stopwatch -> Result String Stopwatch
101+
reset (Stopwatch { aState }) =
102+
case aState of
103+
Stopped ->
104+
Ok new
105+
106+
_ ->
107+
Err "cannot reset a stopwatch that is not stopped"
108+
109+
110+
parseTime : String -> Int
111+
parseTime time =
112+
let
113+
timeValues =
114+
time
115+
|> String.split ":"
116+
|> List.filterMap String.toInt
117+
in
118+
case timeValues of
119+
[ hours, minutes, seconds ] ->
120+
hours * 3600 + minutes * 60 + seconds
121+
122+
_ ->
123+
0
124+
125+
126+
formatTime : Int -> String
127+
formatTime time =
128+
let
129+
hours =
130+
time // 3600
131+
132+
minutes =
133+
modBy 3600 time // 60
134+
135+
seconds =
136+
modBy 60 time
137+
in
138+
[ hours, minutes, seconds ]
139+
|> List.map String.fromInt
140+
|> List.map (String.padLeft 2 '0')
141+
|> String.join ":"
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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+
[ddb238ea-99d4-4eaa-a81d-3c917a525a23]
13+
description = "new stopwatch starts in ready state"
14+
15+
[b19635d4-08ad-4ac3-b87f-aca10e844071]
16+
description = "new stopwatch's current lap has no elapsed time"
17+
18+
[492eb532-268d-43ea-8a19-2a032067d335]
19+
description = "new stopwatch's total has no elapsed time"
20+
21+
[8a892c1e-9ef7-4690-894e-e155a1fe4484]
22+
description = "new stopwatch does not have previous laps"
23+
24+
[5b2705b6-a584-4042-ba3a-4ab8d0ab0281]
25+
description = "start from ready state changes state to running"
26+
27+
[748235ce-1109-440b-9898-0a431ea179b6]
28+
description = "start does not change previous laps"
29+
30+
[491487b1-593d-423e-a075-aa78d449ff1f]
31+
description = "start initiates time tracking for current lap"
32+
33+
[a0a7ba2c-8db6-412c-b1b6-cb890e9b72ed]
34+
description = "start initiates time tracking for total"
35+
36+
[7f558a17-ef6d-4a5b-803a-f313af7c41d3]
37+
description = "start cannot be called from running state"
38+
39+
[32466eef-b2be-4d60-a927-e24fce52dab9]
40+
description = "stop from running state changes state to stopped"
41+
42+
[621eac4c-8f43-4d99-919c-4cad776d93df]
43+
description = "stop pauses time tracking for current lap"
44+
45+
[465bcc82-7643-41f2-97ff-5e817cef8db4]
46+
description = "stop pauses time tracking for total"
47+
48+
[b1ba7454-d627-41ee-a078-891b2ed266fc]
49+
description = "stop cannot be called from ready state"
50+
51+
[5c041078-0898-44dc-9d5b-8ebb5352626c]
52+
description = "stop cannot be called from stopped state"
53+
54+
[3f32171d-8fbf-46b6-bc2b-0810e1ec53b7]
55+
description = "start from stopped state changes state to running"
56+
57+
[626997cb-78d5-4fe8-b501-29fdef804799]
58+
description = "start from stopped state resumes time tracking for current lap"
59+
60+
[58487c53-ab26-471c-a171-807ef6363319]
61+
description = "start from stopped state resumes time tracking for total"
62+
63+
[091966e3-ed25-4397-908b-8bb0330118f8]
64+
description = "lap adds current lap to previous laps"
65+
66+
[1aa4c5ee-a7d5-4d59-9679-419deef3c88f]
67+
description = "lap resets current lap and resumes time tracking"
68+
69+
[4b46b92e-1b3f-46f6-97d2-0082caf56e80]
70+
description = "lap continues time tracking for total"
71+
72+
[ea75d36e-63eb-4f34-97ce-8c70e620bdba]
73+
description = "lap cannot be called from ready state"
74+
75+
[63731154-a23a-412d-a13f-c562f208eb1e]
76+
description = "lap cannot be called from stopped state"
77+
78+
[e585ee15-3b3f-4785-976b-dd96e7cc978b]
79+
description = "stop does not change previous laps"
80+
81+
[fc3645e2-86cf-4d11-97c6-489f031103f6]
82+
description = "reset from stopped state changes state to ready"
83+
84+
[20fbfbf7-68ad-4310-975a-f5f132886c4e]
85+
description = "reset resets current lap"
86+
87+
[00a8f7bb-dd5c-43e5-8705-3ef124007662]
88+
description = "reset clears previous laps"
89+
90+
[76cea936-6214-4e95-b6d1-4d4edcf90499]
91+
description = "reset cannot be called from ready state"
92+
93+
[ba4d8e69-f200-4721-b59e-90d8cf615153]
94+
description = "reset cannot be called from running state"
95+
96+
[0b01751a-cb57-493f-bb86-409de6e84306]
97+
description = "supports very long laps"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"type": "application",
3+
"source-directories": [
4+
"src"
5+
],
6+
"elm-version": "0.19.1",
7+
"dependencies": {
8+
"direct": {
9+
"elm/core": "1.0.5",
10+
"elm/json": "1.1.3",
11+
"elm/parser": "1.1.0",
12+
"elm/random": "1.0.0",
13+
"elm/regex": "1.0.0",
14+
"elm/time": "1.0.0",
15+
"elm/html": "1.0.0"
16+
},
17+
"indirect": {}
18+
},
19+
"test-dependencies": {
20+
"direct": {
21+
"elm-explorations/test": "2.1.0",
22+
"rtfeldman/elm-iso8601-date-strings": "1.1.4"
23+
},
24+
"indirect": {
25+
"elm/bytes": "1.0.8",
26+
"elm/virtual-dom": "1.0.3"
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)