From 36abfed74c65762a277b243d26865b02ca8d8e50 Mon Sep 17 00:00:00 2001 From: Jack Jackson Date: Thu, 23 Jan 2025 23:37:39 -0800 Subject: [PATCH] Committing to save Getting a `bus error` when running tests, which googling hasn't really helped me to understand. Ohhh, just realized - it's because some of those `[]const []const u8`s _aren't_ initialized with the `allocator`, so we shouldn't be freeing them. Ugh. --- solutions/21.zig | 202 +++++++++++++++++++++++++++++++++++++++++++++ solutions/util.zig | 2 +- 2 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 solutions/21.zig diff --git a/solutions/21.zig b/solutions/21.zig new file mode 100644 index 0000000..98dcb2a --- /dev/null +++ b/solutions/21.zig @@ -0,0 +1,202 @@ +const std = @import("std"); +const util = @import("util.zig"); +const Point = util.Point; +const expect = std.testing.expect; + +// Use `10` to represent `A` +// If we wanted, we could introduce a `rotate` function to, say, define `1->6` in terms of `7->2` - but I think that's +// over-abstraction. +fn shortestSequencesToMoveNumericFromAToB(a: u8, b: u8, allocator: std.mem.Allocator) []const []const u8 { + if (a == b) { + return allocator.alloc([]u8, 0) catch unreachable; + } + if (a > b) { + const sequencesForBToA = shortestSequencesToMoveNumericFromAToB(b, a, allocator); + var op = std.ArrayList([]u8).init(allocator); + for (sequencesForBToA) |sequence| { + op.append(invertASequence(sequence, allocator)) catch unreachable; + allocator.free(sequence); + } + allocator.free(sequencesForBToA); + return op.toOwnedSlice() catch unreachable; + } + // God bless https://stackoverflow.com/a/77248553 for showing me how to initialize a slice of slices! + return switch (a) { + 0 => switch (b) { + 1 => &.{&.{ '^', '<' }}, + 2 => &.{&.{'^'}}, + 3 => &.{ &.{ '^', '>' }, &.{ '>', '^' } }, + 4 => &.{ &.{ '^', '^', '<' }, &.{ '^', '<', '^' } }, + 5 => &.{&.{ '^', '^' }}, + 6 => &.{ &.{ '^', '^', '>' }, &.{ '^', '>', '^' }, &.{ '>', '^', '^' } }, + 7 => { + const twoToSeven = shortestSequencesToMoveNumericFromAToB(2, 7, allocator); + return prependSequencesWith(twoToSeven, '^', allocator); + }, + 8 => &.{&.{ '^', '^', '^' }}, + 9 => &.{ &.{ '^', '^', '^', '>' }, &.{ '^', '^', '>', '^' }, &.{ '^', '>', '^', '^' }, &.{ '>', '^', '^', '^' } }, + 10 => &.{&.{'>'}}, + else => unreachable, + }, + 1 => switch (b) { + 0 => &.{&.{ '>', 'V' }}, + 2 => &.{&.{'>'}}, + 3 => &.{&.{ '>', '>' }}, + 4 => &.{&.{'^'}}, + 5 => &.{ &.{ '^', '>' }, &.{ '>', '^' } }, + 6 => &.{ &.{ '^', '>', '>' }, &.{ '>', '^', '>' }, &.{ '>', '>', '^' } }, + 7 => &.{&.{ '^', '^' }}, + 8 => &.{ &.{ '^', '^', '>' }, &.{ '^', '>', '^' }, &.{ '>', '^', '^' } }, + 9 => { + const fourToNine = shortestSequencesToMoveNumericFromAToB(4, 9, allocator); + const fourBasedMoves = prependSequencesWith(fourToNine, '^', allocator); + + const twoToNine = shortestSequencesToMoveNumericFromAToB(2, 9, allocator); + const twoBasedMoves = prependSequencesWith(twoToNine, '>', allocator); + return joinSlices(fourBasedMoves, twoBasedMoves, allocator); + }, + 10 => &.{ &.{ '>', '>', 'V' }, &.{ '>', 'V', '>' } }, + else => unreachable, + }, + 2 => switch (b) { + 3 => &.{&.{'>'}}, + 4 => &.{ &.{ '^', '<' }, &.{ '<', '^' } }, + 5 => &.{&.{'^'}}, + 6 => &.{ &.{ '^', '>' }, &.{ '>', '^' } }, + 7 => &.{ &.{ '^', '^', '<' }, &.{ '^', '<', '^' }, &.{ '<', '^', '^' } }, + 8 => &.{&.{ '^', '^' }}, + 9 => &.{ &.{ '^', '^', '>' }, &.{ '^', '>', '^' }, &.{ '>', '^', '^' } }, + 10 => &.{ &.{ '>', 'V' }, &.{ 'V', '>' } }, + else => unreachable, + }, + 3 => switch (b) { + 4 => &.{ &.{ '^', '<', '<' }, &.{ '<', '^', '<' }, &.{ '<', '<', '^' } }, + 5 => &.{ &.{ '^', '<' }, &.{ '<', '^' } }, + 6 => &.{&.{'^'}}, + 7 => { + const sixToSeven = shortestSequencesToMoveNumericFromAToB(6, 7, allocator); + const sixBasedMoves = prependSequencesWith(sixToSeven, '^', allocator); + + const twoToSeven = shortestSequencesToMoveNumericFromAToB(2, 7, allocator); + const twoBasedMoves = prependSequencesWith(twoToSeven, '<', allocator); + return joinSlices(sixBasedMoves, twoBasedMoves, allocator); + }, + 8 => &.{ &.{ '^', '^', '<' }, &.{ '^', '<', '^' }, &.{ '<', '^', '^' } }, + 9 => &.{&.{ '^', '^' }}, + 10 => &.{&.{'V'}}, + else => unreachable, + }, + 8 => switch (b) { + // 10 => &.{&.{'V'}}, + 10 => &.{ &.{ 'V', 'V', 'V', '>' }, &.{ 'V', 'V', '>', 'V' }, &.{ 'V', '>', 'V', 'V' }, &.{ '>', 'V', 'V', 'V' } }, + else => unreachable, + }, + else => unreachable, + }; +} + +// To save having to type out _all_ the options above, only type out the ones where the number is increasing, then +// observe that "moving from A to B" is the same as "moving from B to A in reverse" - that is, taking the sequence of +// moves in reverse, and doing the opposite of each of them +fn invertASequence(sequence: []const u8, allocator: std.mem.Allocator) []u8 { + var op = std.ArrayList(u8).init(allocator); + var i: usize = sequence.len; + while (i > 0) : (i -= 1) { + switch (sequence[i - 1]) { + '>' => op.append('<') catch unreachable, + '^' => op.append('V') catch unreachable, + '<' => op.append('>') catch unreachable, + 'V' => op.append('^') catch unreachable, + else => unreachable, + } + } + return op.toOwnedSlice() catch unreachable; +} + +fn joinSlices(a: []const []const u8, b: []const []const u8, allocator: std.mem.Allocator) []const []const u8 { + var op = std.ArrayList([]const u8).init(allocator); + for (a) |seq| { + op.append(seq) catch unreachable; + } + for (b) |seq| { + op.append(seq) catch unreachable; + } + return op.toOwnedSlice() catch unreachable; +} + +fn prependSequencesWith(seqs: []const []const u8, prefix: u8, allocator: std.mem.Allocator) [][]u8 { + const resp = allocator.alloc([]u8, seqs.len) catch unreachable; + for (seqs, 0..) |seq, i| { + var new_seq = allocator.alloc(u8, seq.len + 1) catch unreachable; + new_seq[0] = prefix; + for (seq, 0..) |c, j| { + new_seq[j + 1] = c; + } + resp[i] = new_seq; + } + return resp; +} + +const NumericRobotError = error{ OutOfBoundsError, GapError }; + +// To test, implement an actual robot! +const NumericRobot = struct { + pos: Point, + pub fn move(self: *NumericRobot, m: u8) void { + switch (m) { + '^' => self.pos.y += 1, + '>' => self.pos.x += 1, + 'V' => self.pos.y -= 1, + '<' => self.pos.x -= 1, + else => unreachable, + } + } + + pub fn process(self: *NumericRobot, moves: []const u8) NumericRobotError!void { + for (moves) |m| { + self.move(m); + if (self.pos.x == 0 and self.pos.y == 0) { + return NumericRobotError.GapError; + } + // No need to check for <0 as that'll be a language error anyway + if (self.pos.x > 2 or self.pos.y > 3) { + return NumericRobotError.OutOfBoundsError; + } + } + return {}; + } +}; + +test "Basic Movement" { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const allocator = gpa.allocator(); + + const seqs = shortestSequencesToMoveNumericFromAToB(0, 7, allocator); + for (seqs) |seq| { + var robot = NumericRobot{ .pos = Point{ .x = 1, .y = 0 } }; + robot.process(seq) catch unreachable; + try expect(std.meta.eql(robot.pos, Point{ .x = 0, .y = 3 })); + allocator.free(seq); // TODO - I could probably do some fancy shenanigans with `errdefer` here (and elsewhere) + } + allocator.free(seqs); + + // I don't know why, but reusing `seqs` leads to Memory Leaks, even when (AFAICT) everything is freed. + const new_seqs = shortestSequencesToMoveNumericFromAToB(7, 0, allocator); + for (new_seqs) |seq| { + var robot = NumericRobot{ .pos = Point{ .x = 0, .y = 3 } }; + robot.process(seq) catch unreachable; + try expect(std.meta.eql(robot.pos, Point{ .x = 1, .y = 0 })); + allocator.free(seq); + } + allocator.free(new_seqs); + + const newer_seqs = shortestSequencesToMoveNumericFromAToB(0, 1, allocator); + for (newer_seqs) |seq| { + // var robot = NumericRobot{ .pos = Point{ .x = 2, .y = 0 } }; + // robot.process(seq) catch unreachable; + // try expect(std.meta.eql(robot.pos, Point{ .x = 1, .y = 3 })); + allocator.free(seq); + } + allocator.free(newer_seqs); +} diff --git a/solutions/util.zig b/solutions/util.zig index 072e95b..925ced5 100644 --- a/solutions/util.zig +++ b/solutions/util.zig @@ -274,7 +274,7 @@ test "Dijkstra" { var result = dijkstra([]u8, Point, &data, neighboursFunc, start, end, false, allocator); defer result.deinit(); const distance = result.get(end).?; - print("Dijkstra result is {}\n", .{distance}); + // print("Dijkstra result is {}\n", .{distance}); try expect(distance == 84); }