diff --git a/config.json b/config.json index 7e4095a1..99938799 100644 --- a/config.json +++ b/config.json @@ -788,6 +788,14 @@ ], "difficulty": 4 }, + { + "slug": "clock", + "name": "Clock", + "uuid": "7bde92b9-94dc-4655-a71f-4ba138266de5", + "practices": [], + "prerequisites": [], + "difficulty": 4 + }, { "slug": "crypto-square", "name": "Crypto Square", diff --git a/exercises/practice/clock/.docs/instructions.md b/exercises/practice/clock/.docs/instructions.md new file mode 100644 index 00000000..a1efc789 --- /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 00000000..d3dc1ad8 --- /dev/null +++ b/exercises/practice/clock/.meta/config.json @@ -0,0 +1,18 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "clock.zig" + ], + "test": [ + "test_clock.zig" + ], + "example": [ + ".meta/example.zig" + ] + }, + "blurb": "Implement a clock that handles times without dates.", + "source": "Pairing session with Erin Drummond" +} diff --git a/exercises/practice/clock/.meta/example.zig b/exercises/practice/clock/.meta/example.zig new file mode 100644 index 00000000..7e95a40c --- /dev/null +++ b/exercises/practice/clock/.meta/example.zig @@ -0,0 +1,43 @@ +fn normalize(raw_minutes: i32) i32 { + const minutes_per_day = 24 * 60; + return @mod(raw_minutes, minutes_per_day); +} + +pub const Clock = struct { + total_minutes: i32, + + /// Initializes a Clock, normalizing the given hour and minute. + pub fn init(hour: i32, minute: i32) Clock { + return .{ + .total_minutes = normalize(hour * 60 + minute), + }; + } + + /// Moves the clock forwards by `minutes` minutes. + pub fn advance(self: *Clock, minutes: i32) void { + self.total_minutes = normalize(self.total_minutes + minutes); + } + + /// Moves the clock backwards by `minutes` minutes. + pub fn rewind(self: *Clock, minutes: i32) void { + self.total_minutes = normalize(self.total_minutes - minutes); + } + + /// Renders the time as "hh:mm". + pub fn toString(self: Clock) [5]u8 { + const h: u8 = @intCast(@divFloor(self.total_minutes, 60)); + const m: u8 = @intCast(@mod(self.total_minutes, 60)); + return .{ + h / 10 + '0', + h % 10 + '0', + ':', + m / 10 + '0', + m % 10 + '0', + }; + } + + /// Returns whether two clocks show the same time. + pub fn eql(self: Clock, other: Clock) bool { + return self.total_minutes == other.total_minutes; + } +}; diff --git a/exercises/practice/clock/.meta/tests.toml b/exercises/practice/clock/.meta/tests.toml new file mode 100644 index 00000000..712c87bc --- /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.zig b/exercises/practice/clock/clock.zig new file mode 100644 index 00000000..b0a9c0a4 --- /dev/null +++ b/exercises/practice/clock/clock.zig @@ -0,0 +1,37 @@ +pub const Clock = struct { + // This struct, as well as its fields and methods, needs to be implemented. + + /// Initializes a Clock, normalizing the given hour and minute. + pub fn init(hour: i32, minute: i32) Clock { + _ = hour; + _ = minute; + @compileError("please implement the init function"); + } + + /// Moves the clock forwards by `minutes` minutes. + pub fn advance(self: *Clock, minutes: i32) void { + _ = self; + _ = minutes; + @compileError("please implement the advance function"); + } + + /// Moves the clock backwards by `minutes` minutes. + pub fn rewind(self: *Clock, minutes: i32) void { + _ = self; + _ = minutes; + @compileError("please implement the rewind function"); + } + + /// Renders the time as "hh:mm". + pub fn toString(self: Clock) [5]u8 { + _ = self; + @compileError("please implement the toString function"); + } + + /// Returns whether two clocks show the same time. + pub fn eql(self: Clock, other: Clock) bool { + _ = self; + _ = other; + @compileError("please implement the eql function"); + } +}; diff --git a/exercises/practice/clock/test_clock.zig b/exercises/practice/clock/test_clock.zig new file mode 100644 index 00000000..703e262b --- /dev/null +++ b/exercises/practice/clock/test_clock.zig @@ -0,0 +1,332 @@ +const std = @import("std"); +const testing = std.testing; + +const Clock = @import("clock.zig").Clock; + +test "Create a new clock with an initial time-on the hour" { + const clock = Clock.init(8, 0); + const actual = clock.toString(); + try testing.expectEqualStrings("08:00", &actual); +} + +test "Create a new clock with an initial time-past the hour" { + const clock = Clock.init(11, 9); + const actual = clock.toString(); + try testing.expectEqualStrings("11:09", &actual); +} + +test "Create a new clock with an initial time-midnight is zero hours" { + const clock = Clock.init(24, 0); + const actual = clock.toString(); + try testing.expectEqualStrings("00:00", &actual); +} + +test "Create a new clock with an initial time-hour rolls over" { + const clock = Clock.init(25, 0); + const actual = clock.toString(); + try testing.expectEqualStrings("01:00", &actual); +} + +test "Create a new clock with an initial time-hour rolls over continuously" { + const clock = Clock.init(100, 0); + const actual = clock.toString(); + try testing.expectEqualStrings("04:00", &actual); +} + +test "Create a new clock with an initial time-sixty minutes is next hour" { + const clock = Clock.init(1, 60); + const actual = clock.toString(); + try testing.expectEqualStrings("02:00", &actual); +} + +test "Create a new clock with an initial time-minutes roll over" { + const clock = Clock.init(0, 160); + const actual = clock.toString(); + try testing.expectEqualStrings("02:40", &actual); +} + +test "Create a new clock with an initial time-minutes roll over continuously" { + const clock = Clock.init(0, 1723); + const actual = clock.toString(); + try testing.expectEqualStrings("04:43", &actual); +} + +test "Create a new clock with an initial time-hour and minutes roll over" { + const clock = Clock.init(25, 160); + const actual = clock.toString(); + try testing.expectEqualStrings("03:40", &actual); +} + +test "Create a new clock with an initial time-hour and minutes roll over continuously" { + const clock = Clock.init(201, 3001); + const actual = clock.toString(); + try testing.expectEqualStrings("11:01", &actual); +} + +test "Create a new clock with an initial time-hour and minutes roll over to exactly midnight" { + const clock = Clock.init(72, 8640); + const actual = clock.toString(); + try testing.expectEqualStrings("00:00", &actual); +} + +test "Create a new clock with an initial time-negative hour" { + const clock = Clock.init(-1, 15); + const actual = clock.toString(); + try testing.expectEqualStrings("23:15", &actual); +} + +test "Create a new clock with an initial time-negative hour rolls over" { + const clock = Clock.init(-25, 0); + const actual = clock.toString(); + try testing.expectEqualStrings("23:00", &actual); +} + +test "Create a new clock with an initial time-negative hour rolls over continuously" { + const clock = Clock.init(-91, 0); + const actual = clock.toString(); + try testing.expectEqualStrings("05:00", &actual); +} + +test "Create a new clock with an initial time-negative minutes" { + const clock = Clock.init(1, -40); + const actual = clock.toString(); + try testing.expectEqualStrings("00:20", &actual); +} + +test "Create a new clock with an initial time-negative minutes roll over" { + const clock = Clock.init(1, -160); + const actual = clock.toString(); + try testing.expectEqualStrings("22:20", &actual); +} + +test "Create a new clock with an initial time-negative minutes roll over continuously" { + const clock = Clock.init(1, -4820); + const actual = clock.toString(); + try testing.expectEqualStrings("16:40", &actual); +} + +test "Create a new clock with an initial time-negative sixty minutes is previous hour" { + const clock = Clock.init(2, -60); + const actual = clock.toString(); + try testing.expectEqualStrings("01:00", &actual); +} + +test "Create a new clock with an initial time-negative hour and minutes both roll over" { + const clock = Clock.init(-25, -160); + const actual = clock.toString(); + try testing.expectEqualStrings("20:20", &actual); +} + +test "Create a new clock with an initial time-negative hour and minutes both roll over continuously" { + const clock = Clock.init(-121, -5810); + const actual = clock.toString(); + try testing.expectEqualStrings("22:10", &actual); +} + +test "Add minutes-add minutes" { + var clock = Clock.init(10, 0); + clock.advance(3); + const actual = clock.toString(); + try testing.expectEqualStrings("10:03", &actual); +} + +test "Add minutes-add no minutes" { + var clock = Clock.init(6, 41); + clock.advance(0); + const actual = clock.toString(); + try testing.expectEqualStrings("06:41", &actual); +} + +test "Add minutes-add to next hour" { + var clock = Clock.init(0, 45); + clock.advance(40); + const actual = clock.toString(); + try testing.expectEqualStrings("01:25", &actual); +} + +test "Add minutes-add more than one hour" { + var clock = Clock.init(10, 0); + clock.advance(61); + const actual = clock.toString(); + try testing.expectEqualStrings("11:01", &actual); +} + +test "Add minutes-add more than two hours with carry" { + var clock = Clock.init(0, 45); + clock.advance(160); + const actual = clock.toString(); + try testing.expectEqualStrings("03:25", &actual); +} + +test "Add minutes-add across midnight" { + var clock = Clock.init(23, 59); + clock.advance(2); + const actual = clock.toString(); + try testing.expectEqualStrings("00:01", &actual); +} + +test "Add minutes-add more than one day (1500 min = 25 hrs)" { + var clock = Clock.init(5, 32); + clock.advance(1500); + const actual = clock.toString(); + try testing.expectEqualStrings("06:32", &actual); +} + +test "Add minutes-add more than two days" { + var clock = Clock.init(1, 1); + clock.advance(3500); + const actual = clock.toString(); + try testing.expectEqualStrings("11:21", &actual); +} + +test "Subtract minutes-subtract minutes" { + var clock = Clock.init(10, 3); + clock.rewind(3); + const actual = clock.toString(); + try testing.expectEqualStrings("10:00", &actual); +} + +test "Subtract minutes-subtract to previous hour" { + var clock = Clock.init(10, 3); + clock.rewind(30); + const actual = clock.toString(); + try testing.expectEqualStrings("09:33", &actual); +} + +test "Subtract minutes-subtract more than an hour" { + var clock = Clock.init(10, 3); + clock.rewind(70); + const actual = clock.toString(); + try testing.expectEqualStrings("08:53", &actual); +} + +test "Subtract minutes-subtract across midnight" { + var clock = Clock.init(0, 3); + clock.rewind(4); + const actual = clock.toString(); + try testing.expectEqualStrings("23:59", &actual); +} + +test "Subtract minutes-subtract more than two hours" { + var clock = Clock.init(0, 0); + clock.rewind(160); + const actual = clock.toString(); + try testing.expectEqualStrings("21:20", &actual); +} + +test "Subtract minutes-subtract more than two hours with borrow" { + var clock = Clock.init(6, 15); + clock.rewind(160); + const actual = clock.toString(); + try testing.expectEqualStrings("03:35", &actual); +} + +test "Subtract minutes-subtract more than one day (1500 min = 25 hrs)" { + var clock = Clock.init(5, 32); + clock.rewind(1500); + const actual = clock.toString(); + try testing.expectEqualStrings("04:32", &actual); +} + +test "Subtract minutes-subtract more than two days" { + var clock = Clock.init(2, 20); + clock.rewind(3000); + const actual = clock.toString(); + try testing.expectEqualStrings("00:20", &actual); +} + +test "Compare two clocks for equality-clocks with same time" { + const clock1 = Clock.init(15, 37); + const clock2 = Clock.init(15, 37); + try testing.expect(clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks a minute apart" { + const clock1 = Clock.init(15, 36); + const clock2 = Clock.init(15, 37); + try testing.expect(!clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks an hour apart" { + const clock1 = Clock.init(14, 37); + const clock2 = Clock.init(15, 37); + try testing.expect(!clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks with hour overflow" { + const clock1 = Clock.init(10, 37); + const clock2 = Clock.init(34, 37); + try testing.expect(clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks with hour overflow by several days" { + const clock1 = Clock.init(3, 11); + const clock2 = Clock.init(99, 11); + try testing.expect(clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks with negative hour" { + const clock1 = Clock.init(22, 40); + const clock2 = Clock.init(-2, 40); + try testing.expect(clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks with negative hour that wraps" { + const clock1 = Clock.init(17, 3); + const clock2 = Clock.init(-31, 3); + try testing.expect(clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks with negative hour that wraps multiple times" { + const clock1 = Clock.init(13, 49); + const clock2 = Clock.init(-83, 49); + try testing.expect(clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks with minute overflow" { + const clock1 = Clock.init(0, 1); + const clock2 = Clock.init(0, 1441); + try testing.expect(clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks with minute overflow by several days" { + const clock1 = Clock.init(2, 2); + const clock2 = Clock.init(2, 4322); + try testing.expect(clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks with negative minute" { + const clock1 = Clock.init(2, 40); + const clock2 = Clock.init(3, -20); + try testing.expect(clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks with negative minute that wraps" { + const clock1 = Clock.init(4, 10); + const clock2 = Clock.init(5, -1490); + try testing.expect(clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks with negative minute that wraps multiple times" { + const clock1 = Clock.init(6, 15); + const clock2 = Clock.init(6, -4305); + try testing.expect(clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks with negative hours and minutes" { + const clock1 = Clock.init(7, 32); + const clock2 = Clock.init(-12, -268); + try testing.expect(clock1.eql(clock2)); +} + +test "Compare two clocks for equality-clocks with negative hours and minutes that wrap" { + const clock1 = Clock.init(18, 7); + const clock2 = Clock.init(-54, -11513); + try testing.expect(clock1.eql(clock2)); +} + +test "Compare two clocks for equality-full clock and zeroed clock" { + const clock1 = Clock.init(24, 0); + const clock2 = Clock.init(0, 0); + try testing.expect(clock1.eql(clock2)); +} diff --git a/generators/exercises/clock.py b/generators/exercises/clock.py new file mode 100644 index 00000000..235fffa2 --- /dev/null +++ b/generators/exercises/clock.py @@ -0,0 +1,28 @@ +IMPORT_SELF = False +HEADER = 'const Clock = @import("clock.zig").Clock;' + + +def gen_case(case): + inp = case["input"] + exp = case["expected"] + prop = case["property"] + + if prop == "equal": + clock1 = inp["clock1"] + clock2 = inp["clock2"] + bang = "" if exp else "!" + return ( + f"const clock1 = Clock.init({clock1['hour']}, {clock1['minute']});\n" + f"const clock2 = Clock.init({clock2['hour']}, {clock2['minute']});\n" + f"try testing.expect({bang}clock1.eql(clock2));\n" + ) + + if prop == "create": + out = f"const clock = Clock.init({inp['hour']}, {inp['minute']});\n" + else: + method = "advance" if prop == "add" else "rewind" + out = f"var clock = Clock.init({inp['hour']}, {inp['minute']});\n" + out += f"clock.{method}({inp['value']});\n" + out += "const actual = clock.toString();\n" + out += f'try testing.expectEqualStrings("{exp}", &actual);\n' + return out