diff --git a/config.json b/config.json index fef42c2..9036e7b 100644 --- a/config.json +++ b/config.json @@ -105,6 +105,14 @@ "prerequisites": [], "difficulty": 2 }, + { + "slug": "clock", + "name": "Clock", + "uuid": "1adaf7d8-7154-4ab9-8db3-7c120f52338c", + "practices": [], + "prerequisites": [], + "difficulty": 4 + }, { "slug": "collatz-conjecture", "name": "Collatz Conjecture", diff --git a/exercises/practice/clock/.docs/instructions-append.md b/exercises/practice/clock/.docs/instructions-append.md new file mode 100644 index 0000000..051babd --- /dev/null +++ b/exercises/practice/clock/.docs/instructions-append.md @@ -0,0 +1,13 @@ +# Instructions append + +The tests for this exercise expect that your clock will be implemented using a `data declaration` in Pyret. +If you are unfamiliar with data declarations, [the official documentation][data-declaration] is a good place to start. + +As part of the exercise, you'll be comparing values constructed by a `Clock` data variant. +Because a clock's hours and minutes can roll over in either direction, the following combinations are the same on a clock: 1 hour 1 minute, 0 hours 61 minutes, 25 hours 1 minutes, and 2 hours -59 minutes. +Pyret's `is` testing operator uses structural equality, which will result in none of these combinations being equal by default. +To that end, you'll need to define your own equality method for comparing time between values from a Clock data variant. +If you're unfamiliar with how Pyret handles equality, review the official docs on [equality]. + +[data-declaration]: https://pyret.org/docs/latest/s_declarations.html#%28part._s~3adata-decl%29 +[equality]: https://pyret.org/docs/latest/equality.html diff --git a/exercises/practice/clock/.docs/instructions.md b/exercises/practice/clock/.docs/instructions.md new file mode 100644 index 0000000..a1efc78 --- /dev/null +++ b/exercises/practice/clock/.docs/instructions.md @@ -0,0 +1,7 @@ +# Instructions + +Implement a clock that handles times without dates. + +You should be able to add and subtract minutes to it. + +Two clocks that represent the same time should be equal to each other. diff --git a/exercises/practice/clock/.meta/config.json b/exercises/practice/clock/.meta/config.json new file mode 100644 index 0000000..17ac696 --- /dev/null +++ b/exercises/practice/clock/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "BNAndras" + ], + "files": { + "solution": [ + "clock.arr" + ], + "test": [ + "clock-test.arr" + ], + "example": [ + ".meta/example.arr" + ] + }, + "blurb": "Implement a clock that handles times without dates.", + "source": "Pairing session with Erin Drummond", + "source_url": "https://twitter.com/ebdrummond" +} diff --git a/exercises/practice/clock/.meta/example.arr b/exercises/practice/clock/.meta/example.arr new file mode 100644 index 0000000..7f11412 --- /dev/null +++ b/exercises/practice/clock/.meta/example.arr @@ -0,0 +1,61 @@ +use context essentials2020 + +provide-types * + +import equality as E + +data Clock: + | clock(hours :: Number, minutes :: Number) +sharing: + method add(self, minutes :: Number) -> Clock: + new-clock = clock(self.hours, self.minutes + minutes) + + new-clock.normalize() + end, + method subtract(self, minutes :: Number) -> Clock: + new-clock = clock(self.hours, self.minutes - minutes) + + new-clock.normalize() + end, + method normalize(self) -> Clock: + is-in-range = lam(): + ((self.minutes >= 0) and (self.minutes < 60)) + and ((self.hours >= 0) and (self.hours < 24)) + end + + ask: + | not(is-in-range()) then: + additional-hours = num-floor(self.minutes / 60) + hours = num-modulo((self.hours + additional-hours), 24) + minutes = num-modulo(self.minutes, 60) + + clock(hours, minutes) + | otherwise: + self + end + end, + method _equals(self, other :: Clock, _) -> E.EqualityResult: + left = self.normalize() + right = other.normalize() + + if (left.hours == right.hours) and (left.minutes == right.minutes): + E.Equal + else: + E.NotEqual("Clocks represent different periods in time", self, other) + end + end, + method to-string(self) -> String: + to-two-digits = lam(n): + stringified = num-to-string(n) + if string-length(stringified) == 1: + "0" + stringified + else: + stringified + end + end + + normalized = self.normalize() + + to-two-digits(normalized.hours) + ":" + to-two-digits(normalized.minutes) + end +end \ No newline at end of file diff --git a/exercises/practice/clock/.meta/tests.toml b/exercises/practice/clock/.meta/tests.toml new file mode 100644 index 0000000..712c87b --- /dev/null +++ b/exercises/practice/clock/.meta/tests.toml @@ -0,0 +1,166 @@ +# 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. + +[a577bacc-106b-496e-9792-b3083ea8705e] +description = "Create a new clock with an initial time -> on the hour" + +[b5d0c360-3b88-489b-8e84-68a1c7a4fa23] +description = "Create a new clock with an initial time -> past the hour" + +[473223f4-65f3-46ff-a9f7-7663c7e59440] +description = "Create a new clock with an initial time -> midnight is zero hours" + +[ca95d24a-5924-447d-9a96-b91c8334725c] +description = "Create a new clock with an initial time -> hour rolls over" + +[f3826de0-0925-4d69-8ac8-89aea7e52b78] +description = "Create a new clock with an initial time -> hour rolls over continuously" + +[a02f7edf-dfd4-4b11-b21a-86de3cc6a95c] +description = "Create a new clock with an initial time -> sixty minutes is next hour" + +[8f520df6-b816-444d-b90f-8a477789beb5] +description = "Create a new clock with an initial time -> minutes roll over" + +[c75c091b-47ac-4655-8d40-643767fc4eed] +description = "Create a new clock with an initial time -> minutes roll over continuously" + +[06343ecb-cf39-419d-a3f5-dcbae0cc4c57] +description = "Create a new clock with an initial time -> hour and minutes roll over" + +[be60810e-f5d9-4b58-9351-a9d1e90e660c] +description = "Create a new clock with an initial time -> hour and minutes roll over continuously" + +[1689107b-0b5c-4bea-aad3-65ec9859368a] +description = "Create a new clock with an initial time -> hour and minutes roll over to exactly midnight" + +[d3088ee8-91b7-4446-9e9d-5e2ad6219d91] +description = "Create a new clock with an initial time -> negative hour" + +[77ef6921-f120-4d29-bade-80d54aa43b54] +description = "Create a new clock with an initial time -> negative hour rolls over" + +[359294b5-972f-4546-bb9a-a85559065234] +description = "Create a new clock with an initial time -> negative hour rolls over continuously" + +[509db8b7-ac19-47cc-bd3a-a9d2f30b03c0] +description = "Create a new clock with an initial time -> negative minutes" + +[5d6bb225-130f-4084-84fd-9e0df8996f2a] +description = "Create a new clock with an initial time -> negative minutes roll over" + +[d483ceef-b520-4f0c-b94a-8d2d58cf0484] +description = "Create a new clock with an initial time -> negative minutes roll over continuously" + +[1cd19447-19c6-44bf-9d04-9f8305ccb9ea] +description = "Create a new clock with an initial time -> negative sixty minutes is previous hour" + +[9d3053aa-4f47-4afc-bd45-d67a72cef4dc] +description = "Create a new clock with an initial time -> negative hour and minutes both roll over" + +[51d41fcf-491e-4ca0-9cae-2aa4f0163ad4] +description = "Create a new clock with an initial time -> negative hour and minutes both roll over continuously" + +[d098e723-ad29-4ef9-997a-2693c4c9d89a] +description = "Add minutes -> add minutes" + +[b6ec8f38-e53e-4b22-92a7-60dab1f485f4] +description = "Add minutes -> add no minutes" + +[efd349dd-0785-453e-9ff8-d7452a8e7269] +description = "Add minutes -> add to next hour" + +[749890f7-aba9-4702-acce-87becf4ef9fe] +description = "Add minutes -> add more than one hour" + +[da63e4c1-1584-46e3-8d18-c9dc802c1713] +description = "Add minutes -> add more than two hours with carry" + +[be167a32-3d33-4cec-a8bc-accd47ddbb71] +description = "Add minutes -> add across midnight" + +[6672541e-cdae-46e4-8be7-a820cc3be2a8] +description = "Add minutes -> add more than one day (1500 min = 25 hrs)" + +[1918050d-c79b-4cb7-b707-b607e2745c7e] +description = "Add minutes -> add more than two days" + +[37336cac-5ede-43a5-9026-d426cbe40354] +description = "Subtract minutes -> subtract minutes" + +[0aafa4d0-3b5f-4b12-b3af-e3a9e09c047b] +description = "Subtract minutes -> subtract to previous hour" + +[9b4e809c-612f-4b15-aae0-1df0acb801b9] +description = "Subtract minutes -> subtract more than an hour" + +[8b04bb6a-3d33-4e6c-8de9-f5de6d2c70d6] +description = "Subtract minutes -> subtract across midnight" + +[07c3bbf7-ce4d-4658-86e8-4a77b7a5ccd9] +description = "Subtract minutes -> subtract more than two hours" + +[90ac8a1b-761c-4342-9c9c-cdc3ed5db097] +description = "Subtract minutes -> subtract more than two hours with borrow" + +[2149f985-7136-44ad-9b29-ec023a97a2b7] +description = "Subtract minutes -> subtract more than one day (1500 min = 25 hrs)" + +[ba11dbf0-ac27-4acb-ada9-3b853ec08c97] +description = "Subtract minutes -> subtract more than two days" + +[f2fdad51-499f-4c9b-a791-b28c9282e311] +description = "Compare two clocks for equality -> clocks with same time" + +[5d409d4b-f862-4960-901e-ec430160b768] +description = "Compare two clocks for equality -> clocks a minute apart" + +[a6045fcf-2b52-4a47-8bb2-ef10a064cba5] +description = "Compare two clocks for equality -> clocks an hour apart" + +[66b12758-0be5-448b-a13c-6a44bce83527] +description = "Compare two clocks for equality -> clocks with hour overflow" + +[2b19960c-212e-4a71-9aac-c581592f8111] +description = "Compare two clocks for equality -> clocks with hour overflow by several days" + +[6f8c6541-afac-4a92-b0c2-b10d4e50269f] +description = "Compare two clocks for equality -> clocks with negative hour" + +[bb9d5a68-e324-4bf5-a75e-0e9b1f97a90d] +description = "Compare two clocks for equality -> clocks with negative hour that wraps" + +[56c0326d-565b-4d19-a26f-63b3205778b7] +description = "Compare two clocks for equality -> clocks with negative hour that wraps multiple times" + +[c90b9de8-ddff-4ffe-9858-da44a40fdbc2] +description = "Compare two clocks for equality -> clocks with minute overflow" + +[533a3dc5-59a7-491b-b728-a7a34fe325de] +description = "Compare two clocks for equality -> clocks with minute overflow by several days" + +[fff49e15-f7b7-4692-a204-0f6052d62636] +description = "Compare two clocks for equality -> clocks with negative minute" + +[605c65bb-21bd-43eb-8f04-878edf508366] +description = "Compare two clocks for equality -> clocks with negative minute that wraps" + +[b87e64ed-212a-4335-91fd-56da8421d077] +description = "Compare two clocks for equality -> clocks with negative minute that wraps multiple times" + +[822fbf26-1f3b-4b13-b9bf-c914816b53dd] +description = "Compare two clocks for equality -> clocks with negative hours and minutes" + +[e787bccd-cf58-4a1d-841c-ff80eaaccfaa] +description = "Compare two clocks for equality -> clocks with negative hours and minutes that wrap" + +[96969ca8-875a-48a1-86ae-257a528c44f5] +description = "Compare two clocks for equality -> full clock and zeroed clock" diff --git a/exercises/practice/clock/clock-test.arr b/exercises/practice/clock/clock-test.arr new file mode 100644 index 0000000..e310965 --- /dev/null +++ b/exercises/practice/clock/clock-test.arr @@ -0,0 +1,383 @@ +use context essentials2020 + +include file("clock.arr") + +#| + When working offline, all tests except the first one are skipped by default. + Once you get the first test running, unskip the next one until all tests pass locally. + Check the block comment below for further details. +|# + +fun create-clock-on-hour(): + check "Create a new clock with an initial time -> on the hour": + clock(8, 0).normalize().to-string() is "08:00" + end +end + +fun create-clock-past-hour(): + check "Create a new clock with an initial time -> past the hour": + clock(11, 09).normalize().to-string() is "11:09" + end +end + +fun create-clock-midnight(): + check "Create a new clock with an initial time -> midnight is zero hours": + clock(24, 0).normalize().to-string() is "00:00" + end +end + +fun create-clock-hours-rollover(): + check "Create a new clock with an initial time -> hour rolls over": + clock(25, 0).normalize().to-string() is "01:00" + end +end + +fun create-clock-hours-rollover-multiple(): + check "Create a new clock with an initial time -> hour rolls over continuously": + clock(100, 0).normalize().to-string() is "04:00" + end +end + +fun create-clock-sixty-minutes(): + check "Create a new clock with an initial time -> sixty minutes is next hour": + clock(1, 60).normalize().to-string() is "02:00" + end +end + +fun create-clock-minutes-rollover(): + check "Create a new clock with an initial time -> minutes roll over": + clock(0, 160).normalize().to-string() is "02:40" + end +end + +fun create-clock-minutes-rollover-multiple(): + check "Create a new clock with an initial time -> minutes roll over continuously": + clock(0, 1723).normalize().to-string() is "04:43" + end +end + +fun create-clock-rollover(): + check "Create a new clock with an initial time -> hour and minutes roll over": + clock(25, 160).normalize().to-string() is "03:40" + end +end + +fun create-clock-rollover-multiple(): + check "Create a new clock with an initial time -> hour and minutes roll over continuously": + clock(201, 3001).normalize().to-string() is "11:01" + end +end + +fun create-clock-rollover-to-midnight(): + check "Create a new clock with an initial time -> hour and minutes roll over to exactly midnight": + clock(72, 8640).normalize().to-string() is "00:00" + end +end + +fun create-clock-negative-hours(): + check "Create a new clock with an initial time -> negative hour": + clock(-1, 15).normalize().to-string() is "23:15" + end +end + +fun create-clock-negative-hours-rollover(): + check "Create a new clock with an initial time -> negative hour rolls over": + clock(-25, 0).normalize().to-string() is "23:00" + end +end + +fun create-clock-negative-hours-rollover-multiple(): + check "Create a new clock with an initial time -> negative hour rolls over continuously": + clock(-91, 0).normalize().to-string() is "05:00" + end +end + +fun create-clock-negative-minutes(): + check "Create a new clock with an initial time -> negative minutes": + clock(1, -40).normalize().to-string() is "00:20" + end +end + +fun create-clock-negative-minutes-rollover(): + check "Create a new clock with an initial time -> negative minutes roll over": + clock(1, -160).normalize().to-string() is "22:20" + end +end + +fun create-clock-negative-minutes-rollover-multiple(): + check "Create a new clock with an initial time -> negative minutes roll over continuously": + clock(1, -4820).normalize().to-string() is "16:40" + end +end + +fun create-clock-negative-sixty-minutes(): + check "Create a new clock with an initial time -> negative sixty minutes is previous hour": + clock(2, -60).normalize().to-string() is "01:00" + end +end + +fun create-clock-negative-rollover(): + check "Create a new clock with an initial time -> negative hour and minutes both roll over": + clock(-25, -160).normalize().to-string() is "20:20" + end +end + +fun create-clock-negative-rollover-multiple(): + check "Create a new clock with an initial time -> negative hour and minutes both roll over continuously": + clock(-121, -5810).normalize().to-string() is "22:10" + end +end + +fun add-minutes(): + check "Add minutes -> add minutes": + clock(10, 0).add(3).to-string() is "10:03" + end +end + +fun add-no-minutes(): + check "Add minutes -> add no minutes": + clock(6, 41).add(0).to-string() is "06:41" + end +end + +fun add-to-hours(): + check "Add minutes -> add to next hour": + clock(0, 45).add(40).to-string() is "01:25" + end +end + +fun add-to-hours-multiple(): + check "Add minutes -> add more than one hour": + clock(10, 0).add(61).to-string() is "11:01" + end +end + +fun add-to-hours-carryover(): + check "Add minutes -> add more than two hours with carry": + clock(0, 45).add(160).to-string() is "03:25" + end +end + +fun add-across-midnight(): + check "Add minutes -> add across midnight": + clock(23, 59).add(2).to-string() is "00:01" + end +end + +fun add-more-than-one-day(): + check "Add minutes -> add more than one day (1500 min = 25 hrs)": + clock(5, 32).add(1500).to-string() is "06:32" + end +end + +fun add-more-than-one-day-multiple(): + check "Add minutes -> add more than two days": + clock(1, 1).add(3500).to-string() is "11:21" + end +end + +fun subtract-minutes(): + check "Subtract minutes -> subtract minutes": + clock(10, 3).subtract(3).to-string() is "10:00" + end +end + +fun subtract-rollover(): + check "Subtract minutes -> subtract to previous hour": + clock(10, 3).subtract(30).to-string() is "09:33" + end +end + +fun subtract-rollover-multiple(): + check "Subtract minutes -> subtract more than an hour": + clock(10, 3).subtract(70).to-string() is "08:53" + end +end + +fun subtract-across-midnight(): + check "Subtract minutes -> subtract across midnight": + clock(0, 3).subtract(4).to-string() is "23:59" + end +end + +fun subtract-more-than-two-hours(): + check "Subtract minutes -> subtract more than two hours": + clock(0, 0).subtract(160).to-string() is "21:20" + end +end + +fun subtract-rollover-borrow(): + check "Subtract minutes -> subtract more than two hours with borrow": + clock(6, 15).subtract(160).to-string() is "03:35" + end +end + +fun subtract-more-than-a-day(): + check "Subtract minutes -> subtract more than one day (1500 min = 25 hrs)": + clock(5, 32).subtract(1500).to-string() is "04:32" + end +end + +fun subtract-more-than-two-days(): + check "Subtract minutes -> subtract more than two days": + clock(2, 20).subtract(3000).to-string() is "00:20" + end +end + +fun equality-same-time(): + check "Compare two clocks for equality -> clocks with same time": + clock(15, 37) is clock(15, 37) + end +end + +fun equality-a-minute-apart(): + check "Compare two clocks for equality -> clocks a minute apart": + clock(15, 36) is-not clock(15, 37) + end +end + +fun equality-an-hour-apart(): + check "Compare two clocks for equality -> clocks an hour apart": + clock(14, 37) is-not clock(15, 37) + end +end + +fun equality-hour-rollover(): + check "Compare two clocks for equality -> clocks with hour overflow": + clock(10, 37) is clock(34, 37) + end +end + +fun equality-hour-overflow-multiple(): + check "Compare two clocks for equality -> clocks with hour overflow by several days": + clock(3, 11) is clock(99, 11) + end +end + +fun equality-negative-hour(): + check "Compare two clocks for equality -> clocks with negative hour": + clock(22, 40) is clock(-2, 40) + end +end + +fun equality-negative-hour-rollover(): + check "Compare two clocks for equality -> clocks with negative hour that wraps": + clock(17, 3) is clock(-31, 3) + end +end + +fun equality-negative-hour-rollover-multiple(): + check "Compare two clocks for equality -> clocks with negative hour that wraps multiple times": + clock(13, 49) is clock(-83, 49) + end +end + +fun equality-minute-rollover(): + check "Compare two clocks for equality -> clocks with minute overflow": + clock(0, 1) is clock(0, 1441) + end +end + +fun equality-rollover-multiple(): + check "Compare two clocks for equality -> clocks with minute overflow by several days": + clock(2, 2) is clock(2, 4322) + end +end + +fun equality-negative-minute(): + check "Compare two clocks for equality -> clocks with negative minute": + clock(2, 40) is clock(3, -20) + end +end + +fun equality-negative-minute-rollover(): + check "Compare two clocks for equality -> clocks with negative minute that wraps": + clock(4, 10) is clock(5, -1490) + end +end + +fun equality-negative-minute-rollover-multiple(): + check "Compare two clocks for equality -> clocks with negative minute that wraps multiple times": + clock(6, 15) is clock(6, -4305) + end +end + +fun equality-negative-hour-and-minute(): + check "Compare two clocks for equality -> clocks with negative hours and minutes": + clock(7, 32) is clock(-12, -268) + end +end + +fun equality-negative-hours-and-minutes-rollover(): + check "Compare two clocks for equality -> clocks with negative hours and minutes that wrap": + clock(18, 7) is clock(-54, -11513) + end +end + +fun equality-full-clock-empty-clock(): + check "Compare two clocks for equality -> full clock and zeroed clock": + clock(24, 0) is clock(0, 0) + end +end + +#| + Code to run each test. Each line corresponds to a test above and whether it should be run. + To mark a test to be run, replace `false` with `true` on that same line after the comma. + test(test-a, true) will be run. test(test-a, false) will be skipped. +|# + +data TestRun: test(run, active) end + +[list: + test(create-clock-on-hour, true), + test(create-clock-past-hour, false), + test(create-clock-midnight, false), + test(create-clock-hours-rollover, false), + test(create-clock-hours-rollover-multiple, false), + test(create-clock-sixty-minutes, false), + test(create-clock-minutes-rollover, false), + test(create-clock-minutes-rollover-multiple, false), + test(create-clock-rollover, false), + test(create-clock-rollover-multiple, false), + test(create-clock-rollover-to-midnight, false), + test(create-clock-negative-hours, false), + test(create-clock-negative-hours-rollover, false), + test(create-clock-negative-hours-rollover-multiple, false), + test(create-clock-negative-minutes, false), + test(create-clock-negative-minutes-rollover-multiple, false), + test(create-clock-negative-sixty-minutes, false), + test(create-clock-negative-rollover, false), + test(create-clock-negative-rollover-multiple, false), + test(add-minutes, false), + test(add-no-minutes, false), + test(add-to-hours, false), + test(add-to-hours-multiple, false), + test(add-to-hours-carryover, false), + test(add-across-midnight, false), + test(add-more-than-one-day, false), + test(add-more-than-one-day-multiple, false), + test(subtract-minutes, false), + test(subtract-rollover, false), + test(subtract-rollover-multiple, false), + test(subtract-across-midnight, false), + test(subtract-more-than-two-hours, false), + test(subtract-rollover-borrow, false), + test(subtract-more-than-a-day, false), + test(subtract-more-than-two-days, false), + test(equality-same-time, false), + test(equality-a-minute-apart, false), + test(equality-an-hour-apart, false), + test(equality-hour-rollover, false), + test(equality-hour-overflow-multiple, false), + test(equality-negative-hour, false), + test(equality-negative-hour-rollover, false), + test(equality-negative-hour-rollover-multiple, false), + test(equality-minute-rollover, false), + test(equality-rollover-multiple, false), + test(equality-negative-minute, false), + test(equality-negative-minute-rollover, false), + test(equality-negative-minute-rollover-multiple, false), + test(equality-negative-hour-and-minute, false), + test(equality-negative-hours-and-minutes-rollover, false), + test(equality-full-clock-empty-clock, false) +].each(lam(t): when t.active: t.run() end end) \ No newline at end of file diff --git a/exercises/practice/clock/clock.arr b/exercises/practice/clock/clock.arr new file mode 100644 index 0000000..e1df6bb --- /dev/null +++ b/exercises/practice/clock/clock.arr @@ -0,0 +1,28 @@ +use context essentials2020 + +provide-types * + +import equality as E + +# Replace the ... with your code to pass the tests. Good luck! + +data Clock: + | clock(hours :: NumInteger, minutes :: NumInteger) +sharing: + method add(self, minutes :: NumInteger) -> Clock: + ... + end, + method subtract(self, minutes :: NumInteger) -> Clock: + ... + end, + method normalize(self) -> Clock: + ... + end, + method _equals(self, other :: Clock, _) -> E.EqualityResult: + # The callback argument after other is ignored for this exercise + ... + end, + method to-string(self) -> String: + ... + end +end