Solution to 21-1

Phewf - this was a tough one! As I call out in the comments on 21.zig,
I had cracked _most_ of the shortcutting to this, but hadn't noticed
that choice of order-of-operations was not arbitrary - needed to notice
that doing a < before a ^ ends up more efficient than the other way
around, for instance.

I shudder to think what part 2 is going to be like...

For the record, my attempts at this took 1253 lines, over two files -
considering my total line count so far is 6233, that's over one-fifth.
Wild!
This commit is contained in:
Jack Jackson 2025-01-28 21:26:43 -08:00
parent ba85344b3d
commit 8770497049
2 changed files with 1125 additions and 327 deletions

View File

@ -3,8 +3,169 @@ const util = @import("util.zig");
const Point = util.Point; const Point = util.Point;
const expect = std.testing.expect; const expect = std.testing.expect;
fn shortestDirectionalPushesToEnterNumericSequence(numericSequence: []const u8, allocator: std.mem.Allocator) []const []const u8 { // Logic (after going down a rabbithole in `21_abandoned` that ended up having impractical runtime complexity):
// * Prefer moves that include doubles (because those will lead to shorter sequences on the "next level" because "A" can
// be pushed twice)
// * Other than that, picking moves is arbitrary - in particular, any moves on the numeric keypad can only ever have two
// directions (and they must be orthogonal to one another), which on the next-level directional keypad can only ever
// lead to the same amount of doubles no matter how they are arranged (e.g. `<<^^` will lead to the same number of
// doubles on the next-level keypad as `^^<<`)
// (I'd gotten all of that by myself, but was puzzled by one failing test case. I gave up and checked the internet
// for inspiration, and https://old.reddit.com/r/adventofcode/comments/1hj2odw/2024_day_21_solutions/m6qcv0f/ and
// https://old.reddit.com/r/adventofcode/comments/1hjgyps/2024_day_21_part_2_i_got_greedyish/ helped me realize that, in
// fact, moves _cannot_ be chosen arbitrarily because the lengths of resultant sequences _do_ diverge after repeated
// processing. Except where we have to pick moves to avoid the voids, we should always do moves in this order, <v^>)
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const strings = allocator.dupe([]const u8, &.{ "671A", "826A", "670A", "085A", "283A" }) catch unreachable;
var total: u32 = 0;
for (strings) |string| {
total += findComplexityOfCode(string, allocator);
}
std.debug.print("Solution is {}\n", .{total});
}
fn findComplexityOfCode(code: []const u8, allocator: std.mem.Allocator) u32 {
const length_of_sequence: u32 = @intCast(findLengthOfShortestResultOfLoopingNTimes(code, 3, allocator));
const numeric_part = std.fmt.parseInt(u32, code[0 .. code.len - 1], 10) catch unreachable;
return length_of_sequence * numeric_part;
}
fn findLengthOfShortestResultOfLoopingNTimes(originalNumericSequence: []const u8, times: usize, allocator: std.mem.Allocator) usize {
const sequence = shortestDirectionalPushToEnterNumericSequence(originalNumericSequence, allocator);
// defer allocator.free(sequence);
var cur_sequences = std.StringHashMap(void).init(allocator);
defer cur_sequences.deinit();
cur_sequences.put(sequence, {}) catch unreachable;
var next_sequences = std.StringHashMap(void).init(allocator);
defer next_sequences.deinit();
for (0..times - 1) |i| {
std.debug.print("\nLooping for the {}-th time\n", .{i});
var cur_it = cur_sequences.keyIterator();
while (cur_it.next()) |n| {
const next_level_entry = shortestDirectionalPushToEnterDirectionalSequence(n.*, allocator);
next_sequences.put(next_level_entry, {}) catch unreachable;
}
cur_sequences.clearRetainingCapacity();
var next_it = next_sequences.keyIterator();
while (next_it.next()) |n| {
cur_sequences.put(n.*, {}) catch unreachable;
}
next_sequences.clearRetainingCapacity();
}
var shortest_so_far: usize = std.math.maxInt(u32);
var cur_it = cur_sequences.keyIterator();
while (cur_it.next()) |c| {
shortest_so_far = @min(c.*.len, shortest_so_far);
}
return shortest_so_far;
}
fn shortestDirectionalPushToEnterDirectionalSequence(directionalSequence: []const u8, allocator: std.mem.Allocator) []const u8 {
//std.debug.print("Finding the shortest directional pushes to enter the directional sequence ", .{});
printDirectionSeqAsDirections(directionalSequence);
//std.debug.print("\n", .{});
var cur_loc: u8 = 'A';
var sequences_so_far = std.StringHashMap(void).init(allocator);
defer sequences_so_far.deinit();
sequences_so_far.put(allocator.dupe(u8, &.{}) catch unreachable, {}) catch unreachable;
var new_candidate_sequences = std.StringHashMap(void).init(allocator);
defer new_candidate_sequences.deinit();
for (directionalSequence) |c| {
const move = shortestSequenceToMoveDirectionalFromAToB(cur_loc, c, allocator);
var move_with_enter = allocator.alloc(u8, move.len + 1) catch unreachable;
for (move, 0..) |move_c, i| {
move_with_enter[i] = move_c;
}
move_with_enter[move.len] = 65;
allocator.free(move);
var seq_it = sequences_so_far.keyIterator();
while (seq_it.next()) |seq_so_far| {
new_candidate_sequences.put(util.concatString(seq_so_far.*, move_with_enter) catch unreachable, {}) catch unreachable;
}
allocator.free(move_with_enter);
cur_loc = c;
sequences_so_far.clearRetainingCapacity();
var cands_it = new_candidate_sequences.keyIterator();
while (cands_it.next()) |next| {
sequences_so_far.put(next.*, {}) catch unreachable;
}
new_candidate_sequences.clearRetainingCapacity();
}
expect(sequences_so_far.count() == 1) catch unreachable;
var seq_it = sequences_so_far.keyIterator();
while (seq_it.next()) |next| {
return next.*;
}
unreachable;
}
// Unlike `...toMoveNumeric...`, `u8` here are the literal symbols on the keys
fn shortestSequenceToMoveDirectionalFromAToB(a: u8, b: u8, allocator: std.mem.Allocator) []const u8 {
if (a == b) {
return allocator.alloc(u8, 0) catch unreachable;
}
// Codes:
// < = 60
// > = 62
// A = 65
// ^ = 94
// v = 118
if (a > b) {
const sequenceForBToA = shortestSequenceToMoveDirectionalFromAToB(b, a, allocator);
const response = invertASequence(sequenceForBToA, allocator);
allocator.free(sequenceForBToA);
return response;
}
return switch (a) {
'<' => switch (b) {
'>' => allocator.dupe(u8, &.{ '>', '>' }) catch unreachable,
'A' => allocator.dupe(u8, &.{ '>', '>', '^' }) catch unreachable,
'^' => allocator.dupe(u8, &.{ '>', '^' }) catch unreachable,
'v' => allocator.dupe(u8, &.{'>'}) catch unreachable,
else => unreachable,
},
'>' => switch (b) {
'A' => allocator.dupe(u8, &.{'^'}) catch unreachable,
'^' => allocator.dupe(u8, &.{ '<', '^' }) catch unreachable,
'v' => allocator.dupe(u8, &.{'<'}) catch unreachable,
else => unreachable,
},
'A' => switch (b) {
'^' => allocator.dupe(u8, &.{'<'}) catch unreachable,
'v' => allocator.dupe(u8, &.{ '<', 'v' }) catch unreachable,
else => unreachable,
},
'^' => switch (b) {
'v' => allocator.dupe(u8, &.{'v'}) catch unreachable,
else => unreachable,
},
else => unreachable,
};
}
fn shortestDirectionalPushToEnterNumericSequence(numericSequence: []const u8, allocator: std.mem.Allocator) []const u8 {
var cur_number: u8 = 10; var cur_number: u8 = 10;
// Keeping this as just a StringHashMap in carry-over from `21_abandoned`, even though there will only ever be a
// single string at each stage.
// But this way I don't have to worry about Zig's memory management with the fact that the "string" will change
// size :P
var sequences_so_far = std.StringHashMap(void).init(allocator); var sequences_so_far = std.StringHashMap(void).init(allocator);
defer sequences_so_far.deinit(); defer sequences_so_far.deinit();
sequences_so_far.put(allocator.dupe(u8, &.{}) catch unreachable, {}) catch unreachable; sequences_so_far.put(allocator.dupe(u8, &.{}) catch unreachable, {}) catch unreachable;
@ -13,36 +174,42 @@ fn shortestDirectionalPushesToEnterNumericSequence(numericSequence: []const u8,
defer new_candidate_sequences.deinit(); defer new_candidate_sequences.deinit();
for (numericSequence) |c| { for (numericSequence) |c| {
std.debug.print("Consuming {c} from numericSequence\n", .{c}); //std.debug.print("Consuming {c} from numericSequence\n", .{c});
const numericSequenceCharAsNumber = numericSequenceCharToNumber(c); const numericSequenceCharAsNumber = numericSequenceCharToNumber(c);
const moves = shortestSequencesToMoveNumericFromAToB(cur_number, numericSequenceCharAsNumber, allocator); const move = shortestSequenceToMoveNumericFromAToB(cur_number, numericSequenceCharAsNumber, allocator);
std.debug.print("DEBUG - moves are:\n", .{}); //std.debug.print("DEBUG - moves are:\n", .{});
for (moves) |move| { // for (moves) |move| {
printDirectionSeqAsDirections(move); // printDirectionSeqAsDirections(move);
std.debug.print("\n", .{}); //std.debug.print("\n", .{});
} // }
const movesWithEnter = appendSequencesWith(moves, 65, allocator); var move_with_enter = allocator.alloc(u8, move.len + 1) catch unreachable;
std.debug.print("And with a trailing `A`, they are:\n", .{}); for (move, 0..) |move_c, i| {
for (movesWithEnter) |move| { move_with_enter[i] = move_c;
printDirectionSeqAsDirections(move);
std.debug.print("\n", .{});
} }
move_with_enter[move.len] = 65;
allocator.free(move);
//std.debug.print("And with a trailing `A`, they are:\n", .{});
// for (movesWithEnter) |move| {
// printDirectionSeqAsDirections(move);
//std.debug.print("\n", .{});
// }
var seq_it = sequences_so_far.keyIterator(); var seq_it = sequences_so_far.keyIterator();
while (seq_it.next()) |seq_so_far| { while (seq_it.next()) |seq_so_far| {
for (movesWithEnter) |new_moves| { // for (movesWithEnter) |new_moves| {
std.debug.print("About to concat these moves:", .{}); //std.debug.print("About to concat these moves:", .{});
printDirectionSeqAsDirections(seq_so_far.*); // printDirectionSeqAsDirections(seq_so_far.*);
std.debug.print(", ", .{}); //std.debug.print(", ", .{});
printDirectionSeqAsDirections(new_moves); // printDirectionSeqAsDirections(new_moves);
std.debug.print("\n", .{}); //std.debug.print("\n", .{});
new_candidate_sequences.put(util.concatString(seq_so_far.*, new_moves) catch unreachable, {}) catch unreachable; new_candidate_sequences.put(util.concatString(seq_so_far.*, move_with_enter) catch unreachable, {}) catch unreachable;
} // }
} }
for (movesWithEnter) |new_moves| { // for (movesWithEnter) |new_moves| {
allocator.free(new_moves); // allocator.free(new_moves);
} // }
allocator.free(movesWithEnter); allocator.free(move_with_enter);
// At this point, `new_candidate_sequences` contains all the new aggregated sequences - so, transfer them to // At this point, `new_candidate_sequences` contains all the new aggregated sequences - so, transfer them to
// `sequences_so_far`. // `sequences_so_far`.
@ -57,12 +224,13 @@ fn shortestDirectionalPushesToEnterNumericSequence(numericSequence: []const u8,
new_candidate_sequences.clearRetainingCapacity(); new_candidate_sequences.clearRetainingCapacity();
} }
var response = std.ArrayList([]const u8).init(allocator); expect(sequences_so_far.count() == 1) catch unreachable;
var seq_it = sequences_so_far.keyIterator(); var seq_it = sequences_so_far.keyIterator();
while (seq_it.next()) |next| { while (seq_it.next()) |next| {
response.append(next.*) catch unreachable; return next.*;
} }
return response.toOwnedSlice() catch unreachable; unreachable;
} }
// Translates from "the Unicode number _of_ the number" to "the actual number" (or, from A=>10) // Translates from "the Unicode number _of_ the number" to "the actual number" (or, from A=>10)
@ -77,165 +245,112 @@ fn numericSequenceCharToNumber(numericSequenceChar: u8) u8 {
// Use `10` to represent `A` // 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 // 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. // over-abstraction.
fn shortestSequencesToMoveNumericFromAToB(a: u8, b: u8, allocator: std.mem.Allocator) []const []const u8 { fn shortestSequenceToMoveNumericFromAToB(a: u8, b: u8, allocator: std.mem.Allocator) []const u8 {
if (a == b) { if (a == b) {
return allocator.alloc([]u8, 0) catch unreachable; return allocator.alloc(u8, 0) catch unreachable;
} }
if (a > b) { if (a > b) {
const sequencesForBToA = shortestSequencesToMoveNumericFromAToB(b, a, allocator); const sequenceForBToA = shortestSequenceToMoveNumericFromAToB(b, a, allocator);
var op = std.ArrayList([]u8).init(allocator); const response = invertASequence(sequenceForBToA, allocator);
for (sequencesForBToA) |sequence| { allocator.free(sequenceForBToA);
op.append(invertASequence(sequence, allocator)) catch unreachable; return response;
allocator.free(sequence);
}
allocator.free(sequencesForBToA);
return op.toOwnedSlice() catch unreachable;
} }
// Taking inspiration from https://ziggit.dev/t/how-to-free-or-identify-a-slice-literal/8188/3 // Taking inspiration from https://ziggit.dev/t/how-to-free-or-identify-a-slice-literal/8188/3
return switch (a) { return switch (a) {
0 => switch (b) { 0 => switch (b) {
1 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '<' }) catch unreachable}) catch unreachable, 1 => allocator.dupe(u8, &.{ '^', '<' }) catch unreachable,
2 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable, 2 => allocator.dupe(u8, &.{'^'}) catch unreachable,
3 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^' }) catch unreachable }) catch unreachable, 3 => allocator.dupe(u8, &.{ '^', '>' }) catch unreachable,
4 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '^', '<', '^' }) catch unreachable }) catch unreachable, 4 => allocator.dupe(u8, &.{ '^', '^', '<' }) catch unreachable,
5 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '^' }) catch unreachable}) catch unreachable, 5 => allocator.dupe(u8, &.{ '^', '^' }) catch unreachable,
6 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '^', '>', '^' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^', '^' }) catch unreachable }) catch unreachable, 6 => allocator.dupe(u8, &.{ '^', '^', '>' }) catch unreachable,
7 => { 7 => allocator.dupe(u8, &.{ '^', '^', '^', '<' }) catch unreachable,
const twoToSeven = shortestSequencesToMoveNumericFromAToB(2, 7, allocator); 8 => allocator.dupe(u8, &.{ '^', '^', '^' }) catch unreachable,
return prependSequencesWith(twoToSeven, '^', allocator); 9 => allocator.dupe(u8, &.{ '^', '^', '^', '>' }) catch unreachable,
}, 10 => allocator.dupe(u8, &.{'>'}) catch unreachable,
8 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '^', '^' }) catch unreachable}) catch unreachable,
9 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '^', '^', '>', '^' }) catch unreachable, allocator.dupe(u8, &.{ '^', '>', '^', '^' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^', '^', '^' }) catch unreachable }) catch unreachable,
10 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable,
else => unreachable, else => unreachable,
}, },
1 => switch (b) { 1 => switch (b) {
0 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '>', 'v' }) catch unreachable}) catch unreachable, 2 => allocator.dupe(u8, &.{'>'}) catch unreachable,
2 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable, 3 => allocator.dupe(u8, &.{ '>', '>' }) catch unreachable,
3 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '>', '>' }) catch unreachable}) catch unreachable, 4 => allocator.dupe(u8, &.{'^'}) catch unreachable,
4 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable, 5 => allocator.dupe(u8, &.{ '^', '>' }) catch unreachable,
5 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^' }) catch unreachable }) catch unreachable, 6 => allocator.dupe(u8, &.{ '^', '>', '>' }) catch unreachable,
6 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '>', '>' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '>', '>', '^' }) catch unreachable }) catch unreachable, 7 => allocator.dupe(u8, &.{ '^', '^' }) catch unreachable,
7 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '^' }) catch unreachable}) catch unreachable, 8 => allocator.dupe(u8, &.{ '^', '^', '>' }) catch unreachable,
8 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '^', '>', '^' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^', '^' }) catch unreachable }) catch unreachable, 9 => allocator.dupe(u8, &.{ '^', '^', '>', '>' }) catch unreachable,
9 => { 10 => allocator.dupe(u8, &.{ '>', '>', 'v' }) catch unreachable,
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 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '>', '>', 'v' }) catch unreachable, allocator.dupe(u8, &.{ '>', 'v', '>' }) catch unreachable }) catch unreachable,
else => unreachable, else => unreachable,
}, },
2 => switch (b) { 2 => switch (b) {
3 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable, 3 => allocator.dupe(u8, &.{'>'}) catch unreachable,
4 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^' }) catch unreachable }) catch unreachable, 4 => allocator.dupe(u8, &.{ '<', '^' }) catch unreachable,
5 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable, 5 => allocator.dupe(u8, &.{'^'}) catch unreachable,
6 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^' }) catch unreachable }) catch unreachable, 6 => allocator.dupe(u8, &.{ '^', '>' }) catch unreachable,
7 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '^', '<', '^' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^', '^' }) catch unreachable }) catch unreachable, 7 => allocator.dupe(u8, &.{ '<', '^', '^' }) catch unreachable,
8 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '^' }) catch unreachable}) catch unreachable, 8 => allocator.dupe(u8, &.{ '^', '^' }) catch unreachable,
9 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '^', '>', '^' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^', '^' }) catch unreachable }) catch unreachable, 9 => allocator.dupe(u8, &.{ '^', '^', '>' }) catch unreachable,
10 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '>', 'v' }) catch unreachable, allocator.dupe(u8, &.{ 'v', '>' }) catch unreachable }) catch unreachable, 10 => allocator.dupe(u8, &.{ 'v', '>' }) catch unreachable,
else => unreachable, else => unreachable,
}, },
3 => switch (b) { 3 => switch (b) {
4 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '<', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '<', '^' }) catch unreachable }) catch unreachable, 4 => allocator.dupe(u8, &.{ '<', '<', '^' }) catch unreachable,
5 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^' }) catch unreachable }) catch unreachable, 5 => allocator.dupe(u8, &.{ '<', '^' }) catch unreachable,
6 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable, 6 => allocator.dupe(u8, &.{'^'}) catch unreachable,
7 => { 7 => allocator.dupe(u8, &.{
const sixToSeven = shortestSequencesToMoveNumericFromAToB(6, 7, allocator); '<',
const sixBasedMoves = prependSequencesWith(sixToSeven, '^', allocator); '<',
'^',
const twoToSeven = shortestSequencesToMoveNumericFromAToB(2, 7, allocator); '^',
const twoBasedMoves = prependSequencesWith(twoToSeven, '<', allocator); }) catch unreachable,
return joinSlices(sixBasedMoves, twoBasedMoves, allocator); 8 => allocator.dupe(u8, &.{ '<', '^', '^' }) catch unreachable,
}, 9 => allocator.dupe(u8, &.{ '^', '^' }) catch unreachable,
8 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '^', '<', '^' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^', '^' }) catch unreachable }) catch unreachable, 10 => allocator.dupe(u8, &.{'v'}) catch unreachable,
9 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '^' }) catch unreachable}) catch unreachable,
10 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'v'}) catch unreachable}) catch unreachable,
else => unreachable, else => unreachable,
}, },
4 => switch (b) { 4 => switch (b) {
5 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable, 5 => allocator.dupe(u8, &.{'>'}) catch unreachable,
6 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '>', '>' }) catch unreachable}) catch unreachable, 6 => allocator.dupe(u8, &.{ '>', '>' }) catch unreachable,
7 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable, 7 => allocator.dupe(u8, &.{'^'}) catch unreachable,
8 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '>', '^' }) catch unreachable, allocator.dupe(u8, &.{ '^', '>' }) catch unreachable }) catch unreachable, 8 => allocator.dupe(u8, &.{ '>', '^' }) catch unreachable,
9 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '>', '>' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '^', '>', '>' }) catch unreachable }) catch unreachable, 9 => allocator.dupe(u8, &.{ '^', '>', '>' }) catch unreachable,
10 => { 10 => allocator.dupe(u8, &.{ '>', '>', 'v', 'v' }) catch unreachable,
const fiveToA = shortestSequencesToMoveNumericFromAToB(5, 10, allocator);
const fiveBasedMoves = prependSequencesWith(fiveToA, '>', allocator);
const oneToA = shortestSequencesToMoveNumericFromAToB(1, 10, allocator);
const oneBasedMoves = prependSequencesWith(oneToA, 'v', allocator);
return joinSlices(fiveBasedMoves, oneBasedMoves, allocator);
},
else => unreachable, else => unreachable,
}, },
5 => switch (b) { 5 => switch (b) {
6 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable, 6 => allocator.dupe(u8, &.{'>'}) catch unreachable,
7 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^' }) catch unreachable }) catch unreachable, 7 => allocator.dupe(u8, &.{ '^', '<' }) catch unreachable,
8 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable, 8 => allocator.dupe(u8, &.{'^'}) catch unreachable,
9 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '>' }) catch unreachable}) catch unreachable, 9 => allocator.dupe(u8, &.{ '^', '>' }) catch unreachable,
10 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ 'v', 'v', '>' }) catch unreachable, allocator.dupe(u8, &.{ 'v', '>', 'v' }) catch unreachable, allocator.dupe(u8, &.{ '>', 'v', 'v' }) catch unreachable }) catch unreachable, 10 => allocator.dupe(u8, &.{ 'v', 'v', '>' }) catch unreachable,
else => unreachable, else => unreachable,
}, },
6 => switch (b) { 6 => switch (b) {
7 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '<', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '^', '<', '<' }) catch unreachable }) catch unreachable, 7 => allocator.dupe(u8, &.{ '<', '<', '^' }) catch unreachable,
8 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^' }) catch unreachable }) catch unreachable, 8 => allocator.dupe(u8, &.{ '<', '^' }) catch unreachable,
9 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable, 9 => allocator.dupe(u8, &.{'^'}) catch unreachable,
10 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ 'v', 'v' }) catch unreachable}) catch unreachable, 10 => allocator.dupe(u8, &.{ 'v', 'v' }) catch unreachable,
else => unreachable, else => unreachable,
}, },
7 => switch (b) { 7 => switch (b) {
8 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable, 8 => allocator.dupe(u8, &.{'>'}) catch unreachable,
9 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '>', '>' }) catch unreachable}) catch unreachable, 9 => allocator.dupe(u8, &.{ '>', '>' }) catch unreachable,
10 => { 10 => allocator.dupe(u8, &.{ '>', '>', 'v', 'v', 'v' }) catch unreachable,
const eightToA = shortestSequencesToMoveNumericFromAToB(8, 10, allocator);
const eightBasedMoves = prependSequencesWith(eightToA, '>', allocator);
const fourToA = shortestSequencesToMoveNumericFromAToB(4, 10, allocator);
const fourBasedMoves = prependSequencesWith(fourToA, 'v', allocator);
return joinSlices(eightBasedMoves, fourBasedMoves, allocator);
},
else => unreachable, else => unreachable,
}, },
8 => switch (b) { 8 => switch (b) {
9 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable, 9 => allocator.dupe(u8, &.{'>'}) catch unreachable,
10 => { 10 => allocator.dupe(u8, &.{ 'v', 'v', 'v', '>' }) catch unreachable,
const nineToA = shortestSequencesToMoveNumericFromAToB(9, 10, allocator);
const nineBasedMoves = prependSequencesWith(nineToA, '>', allocator);
const fiveToA = shortestSequencesToMoveNumericFromAToB(5, 10, allocator);
const fiveBasedMoves = prependSequencesWith(fiveToA, 'v', allocator);
return joinSlices(nineBasedMoves, fiveBasedMoves, allocator);
},
else => unreachable, else => unreachable,
}, },
9 => switch (b) { 9 => switch (b) {
10 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ 'v', 'v', 'v' }) catch unreachable}) catch unreachable, 10 => allocator.dupe(u8, &.{ 'v', 'v', 'v' }) catch unreachable,
else => unreachable, else => unreachable,
}, },
else => unreachable, else => unreachable,
}; };
} }
// Need to make these via a separate function rather than using the `&.{&.{...}}` syntax, because the latter doesn't use
// an allocator and so attempts to `free` them will give bus errors.
// This language is _really_ awkward sometimes...
fn makeSliceOfSlices(strings: []const []const u8, allocator: std.mem.Allocator) [][]u8 {
var op = std.ArrayList([]u8).init(allocator);
for (strings) |string| {
var slice = allocator.alloc(u8, string.len) catch unreachable;
for (string, 0..) |c, i| {
slice[i] = c;
}
op.append(slice) catch unreachable;
}
return op.toOwnedSlice() catch unreachable;
}
// To save having to type out _all_ the options above, only type out the ones where the number is increasing, then // 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 // 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 // moves in reverse, and doing the opposite of each of them
@ -254,202 +369,43 @@ fn invertASequence(sequence: []const u8, allocator: std.mem.Allocator) []u8 {
return op.toOwnedSlice() catch 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;
}
allocator.free(a);
allocator.free(b);
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;
allocator.free(seq);
}
allocator.free(seqs);
return resp;
}
fn appendSequencesWith(seqs: []const []const u8, suffix: 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;
for (seq, 0..) |c, j| {
new_seq[j] = c;
}
new_seq[seq.len] = suffix;
resp[i] = new_seq;
allocator.free(seq);
}
allocator.free(seqs);
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 => {
std.debug.print("Encountered unparsable move {}\n", .{m});
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 {};
}
pub fn pointForNumber(number: u8) Point {
return switch (number) {
0 => Point{ .x = 1, .y = 0 },
1...9 => {
const x = (number - 1) % 3;
const y = @divFloor(number - 1, 3) + 1;
return Point{ .x = x, .y = y };
},
10 => Point{ .x = 2, .y = 0 },
else => unreachable,
};
}
pub fn numberForPoint(point: Point) u8 {
if (point.y > 0 and point.y < 4 and point.x >= 0 and point.x < 3) {
return 3 * (point.y - 1) + point.x + 1;
}
if (point.y == 0) {
switch (point.x) {
1 => return 0,
2 => return 10,
else => unreachable,
}
}
unreachable;
}
};
fn makeZeroToOneSequenceDirectly(allocator: std.mem.Allocator) [][]const u8 {
var op = std.ArrayList([]const u8).init(allocator);
op.append("hello") catch unreachable;
return op.toOwnedSlice() catch unreachable;
}
fn printDirectionSeqAsDirections(seq: []const u8) void { fn printDirectionSeqAsDirections(seq: []const u8) void {
for (seq) |c| { for (seq) |c| {
std.debug.print("{c}", .{c}); std.debug.print("{c}", .{c});
} }
} }
test "Basic Movement" { test "Shortest sequence for 0-loops" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit(); defer _ = gpa.deinit();
const allocator = gpa.allocator(); const allocator = gpa.allocator();
var seqs = shortestSequencesToMoveNumericFromAToB(0, 7, allocator); const shortest_sequence = shortestDirectionalPushToEnterNumericSequence("029A", allocator);
for (seqs) |seq| { printDirectionSeqAsDirections(shortest_sequence);
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 this is necessary!? I'm re-allocating to it on the next line :shrug:
seqs = shortestSequencesToMoveNumericFromAToB(7, 0, allocator);
for (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(seqs);
seqs = shortestSequencesToMoveNumericFromAToB(0, 1, allocator);
for (seqs) |seq| {
var robot = NumericRobot{ .pos = Point{ .x = 1, .y = 0 } };
robot.process(seq) catch unreachable;
const expected_point = Point{ .x = 0, .y = 1 };
try expect(std.meta.eql(robot.pos, expected_point));
allocator.free(seq);
}
allocator.free(seqs);
} }
test "Exhaustive Movement test" { test "Looping 3 times" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit(); defer _ = gpa.deinit();
const allocator = gpa.allocator(); const allocator = gpa.allocator();
for (0..11) |i| { const min_length = findLengthOfShortestResultOfLoopingNTimes("029A", 3, allocator);
for (0..11) |j| { std.debug.print("min_length is {}\n", .{min_length});
const i_as_u8: u8 = @intCast(i); try expect(min_length == 68);
const j_as_u8: u8 = @intCast(j);
const seqs = shortestSequencesToMoveNumericFromAToB(i_as_u8, j_as_u8, allocator);
for (seqs) |seq| {
var robot = NumericRobot{ .pos = NumericRobot.pointForNumber(i_as_u8) };
robot.process(seq) catch unreachable;
try expect(std.meta.eql(robot.pos, NumericRobot.pointForNumber(j_as_u8)));
allocator.free(seq);
}
allocator.free(seqs);
}
}
}
test "Shortest sequences for first level of indirection" { const min_length_1 = findLengthOfShortestResultOfLoopingNTimes("980A", 3, allocator);
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; std.debug.print("min_length of 980A is {}\n", .{min_length_1});
defer _ = gpa.deinit(); try expect(min_length_1 == 60);
const allocator = gpa.allocator();
const sequences = shortestDirectionalPushesToEnterNumericSequence("029A", allocator); const min_length_2 = findLengthOfShortestResultOfLoopingNTimes("179A", 3, allocator);
defer allocator.free(sequences); std.debug.print("min_length of 179A is {}\n", .{min_length_2});
// for readable output, uncomment the lines below try expect(min_length_2 == 68);
// ---------
// for (sequences) |seq| { const min_length_3 = findLengthOfShortestResultOfLoopingNTimes("456A", 3, allocator);
// printDirectionSeqAsDirections(seq); std.debug.print("min_length of 456A is {}\n", .{min_length_3});
// std.debug.print("\n", .{}); try expect(min_length_3 == 64);
// // Not sure why we don't need to `allocator.free(seq)` here :shrug:
// } const min_length_4 = findLengthOfShortestResultOfLoopingNTimes("379A", 3, allocator);
// --------- std.debug.print("min_length of 379A is {}\n", .{min_length_4});
const targetSequence = allocator.dupe(u8, &.{ '<', 'A', '^', 'A', '>', '^', '^', 'A', 'v', 'v', 'v', 'A' }) catch unreachable; try expect(min_length_4 == 64);
defer allocator.free(targetSequence);
var foundTargetSequence = false;
for (sequences) |seq| {
if (std.mem.eql(u8, seq, targetSequence)) {
foundTargetSequence = true;
break;
} else {
std.debug.print("Found the following sequence: ", .{});
printDirectionSeqAsDirections(seq);
}
}
try expect(foundTargetSequence);
} }

842
solutions/21_abandoned.zig Normal file
View File

@ -0,0 +1,842 @@
const std = @import("std");
const util = @import("util.zig");
const Point = util.Point;
const expect = std.testing.expect;
// This seems like a deceptively simple problem.
// At first glance it'd seem like you could take some shortcuts - instead of gathering _all_ possible routes from one
// button to another, and iterating on them, just pick _one_ route. That way, the number of strings being processed
// doesn't grow exponentially.
// Unfortunately I don't think it's that simple - "<<^" and "<^<" will result in a different length of "next-level"
// instructions, because repeated pushes of the same button result in shorter instructions (just repush the "A" button
// at the next layer up)
// But we can't just bias for repetition within a single move, because there might be repetition across moves:
// `v< | <^< | <v`, for instance, has two "doublings-up", across a move-boundary, even though no move contains repeated
// pushes.
// Although...actually, all moves are going to be suffixed by `A` anyway, so maybe that's not actually accurate and I
// should just try biasing for repetition?
// I've already written too much in this file to really be able to refactor it - I'm going to rename this to
// `21_abandoned` and start afresh.
fn findLengthOfShortestResultOfLoopingNTimes(originalNumericSequence: []const u8, times: usize, allocator: std.mem.Allocator) usize {
const sequences = shortestDirectionalPushesToEnterNumericSequence(originalNumericSequence, allocator);
defer allocator.free(sequences);
var cur_sequences = std.StringHashMap(void).init(allocator);
defer cur_sequences.deinit();
for (sequences) |seq| {
cur_sequences.put(seq, {}) catch unreachable;
}
var next_sequences = std.StringHashMap(void).init(allocator);
defer next_sequences.deinit();
for (0..times) |i| {
std.debug.print("Looping for the {}-th time\n", .{i});
var cur_it = cur_sequences.keyIterator();
while (cur_it.next()) |n| {
const next_level_entries = shortestDirectionalPushesToEnterDirectionalSequence(n.*, allocator);
for (next_level_entries) |nle| {
next_sequences.put(nle, {}) catch unreachable;
}
}
cur_sequences.clearRetainingCapacity();
var next_it = next_sequences.keyIterator();
while (next_it.next()) |n| {
cur_sequences.put(n.*, {}) catch unreachable;
}
next_sequences.clearRetainingCapacity();
}
var shortest_so_far: usize = std.math.maxInt(u32);
var cur_it = cur_sequences.keyIterator();
while (cur_it.next()) |c| {
shortest_so_far = @min(c.*.len, shortest_so_far);
}
return shortest_so_far;
}
fn shortestDirectionalPushesToEnterDirectionalSequence(directionalSequence: []const u8, allocator: std.mem.Allocator) []const []const u8 {
//std.debug.print("Finding the shortest directional pushes to enter the directional sequence ", .{});
printDirectionSeqAsDirections(directionalSequence);
//std.debug.print("\n", .{});
var cur_loc: u8 = 'A';
var sequences_so_far = std.StringHashMap(void).init(allocator);
defer sequences_so_far.deinit();
sequences_so_far.put(allocator.dupe(u8, &.{}) catch unreachable, {}) catch unreachable;
var new_candidate_sequences = std.StringHashMap(void).init(allocator);
defer new_candidate_sequences.deinit();
for (directionalSequence) |c| {
const moves = shortestSequencesToMoveDirectionalFromAToB(cur_loc, c, allocator);
//std.debug.print("For a move from {c} to {c}, found options:\n", .{ cur_loc, c });
for (moves) |move| {
printDirectionSeqAsDirections(move);
//std.debug.print("\n", .{});
}
const movesWithEnter = if (moves.len == 0)
allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'A'}) catch unreachable}) catch unreachable
else
appendSequencesWith(moves, 65, allocator);
//std.debug.print("And after adding enter, they are:\n", .{});
for (movesWithEnter) |move| {
printDirectionSeqAsDirections(move);
//std.debug.print("\n", .{});
}
var seq_it = sequences_so_far.keyIterator();
while (seq_it.next()) |seq_so_far| {
for (movesWithEnter) |new_moves| {
new_candidate_sequences.put(util.concatString(seq_so_far.*, new_moves) catch unreachable, {}) catch unreachable;
}
}
for (movesWithEnter) |new_moves| {
allocator.free(new_moves);
}
allocator.free(movesWithEnter);
cur_loc = c;
sequences_so_far.clearRetainingCapacity();
var cands_it = new_candidate_sequences.keyIterator();
while (cands_it.next()) |next| {
sequences_so_far.put(next.*, {}) catch unreachable;
}
new_candidate_sequences.clearRetainingCapacity();
}
var response = std.ArrayList([]const u8).init(allocator);
var seq_it = sequences_so_far.keyIterator();
while (seq_it.next()) |next| {
response.append(next.*) catch unreachable;
}
return response.toOwnedSlice() catch unreachable;
}
// Unlike `...toMoveNumeric...`, `u8` here are the literal symbols on the keys
fn shortestSequencesToMoveDirectionalFromAToB(a: u8, b: u8, allocator: std.mem.Allocator) []const []const u8 {
if (a == b) {
return allocator.alloc([]u8, 0) catch unreachable;
}
// Codes:
// < = 60
// > = 62
// A = 65
// ^ = 94
// v = 118
if (a > b) {
const sequencesForBToA = shortestSequencesToMoveDirectionalFromAToB(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;
}
return switch (a) {
'<' => switch (b) {
'>' => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '>', '>' }) catch unreachable}) catch unreachable,
'A' => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '>', '>', '^' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^', '>' }) catch unreachable }) catch unreachable,
'^' => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '>', '^' }) catch unreachable}) catch unreachable,
'v' => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable,
else => unreachable,
},
'>' => switch (b) {
'A' => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable,
'^' => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '<', '^' }) catch unreachable, allocator.dupe(u8, &.{ '^', '<' }) catch unreachable }) catch unreachable,
'v' => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'<'}) catch unreachable}) catch unreachable,
else => unreachable,
},
'A' => switch (b) {
'^' => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'<'}) catch unreachable}) catch unreachable,
'v' => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ 'v', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', 'v' }) catch unreachable }) catch unreachable,
else => unreachable,
},
'^' => switch (b) {
'v' => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'v'}) catch unreachable}) catch unreachable,
else => unreachable,
},
else => unreachable,
};
}
fn shortestDirectionalPushesToEnterNumericSequence(numericSequence: []const u8, allocator: std.mem.Allocator) []const []const u8 {
var cur_number: u8 = 10;
var sequences_so_far = std.StringHashMap(void).init(allocator);
defer sequences_so_far.deinit();
sequences_so_far.put(allocator.dupe(u8, &.{}) catch unreachable, {}) catch unreachable;
var new_candidate_sequences = std.StringHashMap(void).init(allocator);
defer new_candidate_sequences.deinit();
for (numericSequence) |c| {
//std.debug.print("Consuming {c} from numericSequence\n", .{c});
const numericSequenceCharAsNumber = numericSequenceCharToNumber(c);
const moves = shortestSequencesToMoveNumericFromAToB(cur_number, numericSequenceCharAsNumber, allocator);
//std.debug.print("DEBUG - moves are:\n", .{});
for (moves) |move| {
printDirectionSeqAsDirections(move);
//std.debug.print("\n", .{});
}
const movesWithEnter = appendSequencesWith(moves, 65, allocator);
//std.debug.print("And with a trailing `A`, they are:\n", .{});
for (movesWithEnter) |move| {
printDirectionSeqAsDirections(move);
//std.debug.print("\n", .{});
}
var seq_it = sequences_so_far.keyIterator();
while (seq_it.next()) |seq_so_far| {
for (movesWithEnter) |new_moves| {
//std.debug.print("About to concat these moves:", .{});
printDirectionSeqAsDirections(seq_so_far.*);
//std.debug.print(", ", .{});
printDirectionSeqAsDirections(new_moves);
//std.debug.print("\n", .{});
new_candidate_sequences.put(util.concatString(seq_so_far.*, new_moves) catch unreachable, {}) catch unreachable;
}
}
for (movesWithEnter) |new_moves| {
allocator.free(new_moves);
}
allocator.free(movesWithEnter);
// At this point, `new_candidate_sequences` contains all the new aggregated sequences - so, transfer them to
// `sequences_so_far`.
// TODO - not sure if I should be trimming to shortest _here_, or only at the end.
cur_number = numericSequenceCharAsNumber;
sequences_so_far.clearRetainingCapacity();
var cands_it = new_candidate_sequences.keyIterator();
while (cands_it.next()) |next| {
sequences_so_far.put(next.*, {}) catch unreachable;
}
new_candidate_sequences.clearRetainingCapacity();
}
var response = std.ArrayList([]const u8).init(allocator);
var seq_it = sequences_so_far.keyIterator();
while (seq_it.next()) |next| {
response.append(next.*) catch unreachable;
}
return response.toOwnedSlice() catch unreachable;
}
// Translates from "the Unicode number _of_ the number" to "the actual number" (or, from A=>10)
fn numericSequenceCharToNumber(numericSequenceChar: u8) u8 {
return switch (numericSequenceChar) {
48...57 => numericSequenceChar - 48,
65 => 10,
else => unreachable,
};
}
// 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;
}
// Taking inspiration from https://ziggit.dev/t/how-to-free-or-identify-a-slice-literal/8188/3
return switch (a) {
0 => switch (b) {
1 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '<' }) catch unreachable}) catch unreachable,
2 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable,
3 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^' }) catch unreachable }) catch unreachable,
4 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '^', '<', '^' }) catch unreachable }) catch unreachable,
5 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '^' }) catch unreachable}) catch unreachable,
6 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '^', '>', '^' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^', '^' }) catch unreachable }) catch unreachable,
7 => {
const twoToSeven = shortestSequencesToMoveNumericFromAToB(2, 7, allocator);
return prependSequencesWith(twoToSeven, '^', allocator);
},
8 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '^', '^' }) catch unreachable}) catch unreachable,
9 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '^', '^', '>', '^' }) catch unreachable, allocator.dupe(u8, &.{ '^', '>', '^', '^' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^', '^', '^' }) catch unreachable }) catch unreachable,
10 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable,
else => unreachable,
},
1 => switch (b) {
0 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '>', 'v' }) catch unreachable}) catch unreachable,
2 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable,
3 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '>', '>' }) catch unreachable}) catch unreachable,
4 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable,
5 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^' }) catch unreachable }) catch unreachable,
6 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '>', '>' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '>', '>', '^' }) catch unreachable }) catch unreachable,
7 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '^' }) catch unreachable}) catch unreachable,
8 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '^', '>', '^' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^', '^' }) catch unreachable }) catch unreachable,
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 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '>', '>', 'v' }) catch unreachable, allocator.dupe(u8, &.{ '>', 'v', '>' }) catch unreachable }) catch unreachable,
else => unreachable,
},
2 => switch (b) {
3 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable,
4 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^' }) catch unreachable }) catch unreachable,
5 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable,
6 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^' }) catch unreachable }) catch unreachable,
7 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '^', '<', '^' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^', '^' }) catch unreachable }) catch unreachable,
8 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '^' }) catch unreachable}) catch unreachable,
9 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '^', '>', '^' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^', '^' }) catch unreachable }) catch unreachable,
10 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '>', 'v' }) catch unreachable, allocator.dupe(u8, &.{ 'v', '>' }) catch unreachable }) catch unreachable,
else => unreachable,
},
3 => switch (b) {
4 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '<', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '<', '^' }) catch unreachable }) catch unreachable,
5 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^' }) catch unreachable }) catch unreachable,
6 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable,
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 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '^', '<', '^' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^', '^' }) catch unreachable }) catch unreachable,
9 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '^' }) catch unreachable}) catch unreachable,
10 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'v'}) catch unreachable}) catch unreachable,
else => unreachable,
},
4 => switch (b) {
5 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable,
6 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '>', '>' }) catch unreachable}) catch unreachable,
7 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable,
8 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '>', '^' }) catch unreachable, allocator.dupe(u8, &.{ '^', '>' }) catch unreachable }) catch unreachable,
9 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '>', '>' }) catch unreachable, allocator.dupe(u8, &.{ '>', '^', '>' }) catch unreachable, allocator.dupe(u8, &.{ '^', '>', '>' }) catch unreachable }) catch unreachable,
10 => {
const fiveToA = shortestSequencesToMoveNumericFromAToB(5, 10, allocator);
const fiveBasedMoves = prependSequencesWith(fiveToA, '>', allocator);
const oneToA = shortestSequencesToMoveNumericFromAToB(1, 10, allocator);
const oneBasedMoves = prependSequencesWith(oneToA, 'v', allocator);
return joinSlices(fiveBasedMoves, oneBasedMoves, allocator);
},
else => unreachable,
},
5 => switch (b) {
6 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable,
7 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^' }) catch unreachable }) catch unreachable,
8 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable,
9 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '^', '>' }) catch unreachable}) catch unreachable,
10 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ 'v', 'v', '>' }) catch unreachable, allocator.dupe(u8, &.{ 'v', '>', 'v' }) catch unreachable, allocator.dupe(u8, &.{ '>', 'v', 'v' }) catch unreachable }) catch unreachable,
else => unreachable,
},
6 => switch (b) {
7 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '<', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '^', '<', '<' }) catch unreachable }) catch unreachable,
8 => allocator.dupe([]u8, &.{ allocator.dupe(u8, &.{ '^', '<' }) catch unreachable, allocator.dupe(u8, &.{ '<', '^' }) catch unreachable }) catch unreachable,
9 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'^'}) catch unreachable}) catch unreachable,
10 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ 'v', 'v' }) catch unreachable}) catch unreachable,
else => unreachable,
},
7 => switch (b) {
8 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable,
9 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ '>', '>' }) catch unreachable}) catch unreachable,
10 => {
const eightToA = shortestSequencesToMoveNumericFromAToB(8, 10, allocator);
const eightBasedMoves = prependSequencesWith(eightToA, '>', allocator);
const fourToA = shortestSequencesToMoveNumericFromAToB(4, 10, allocator);
const fourBasedMoves = prependSequencesWith(fourToA, 'v', allocator);
return joinSlices(eightBasedMoves, fourBasedMoves, allocator);
},
else => unreachable,
},
8 => switch (b) {
9 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{'>'}) catch unreachable}) catch unreachable,
10 => {
const nineToA = shortestSequencesToMoveNumericFromAToB(9, 10, allocator);
const nineBasedMoves = prependSequencesWith(nineToA, '>', allocator);
const fiveToA = shortestSequencesToMoveNumericFromAToB(5, 10, allocator);
const fiveBasedMoves = prependSequencesWith(fiveToA, 'v', allocator);
return joinSlices(nineBasedMoves, fiveBasedMoves, allocator);
},
else => unreachable,
},
9 => switch (b) {
10 => allocator.dupe([]u8, &.{allocator.dupe(u8, &.{ 'v', 'v', 'v' }) catch unreachable}) catch unreachable,
else => unreachable,
},
else => unreachable,
};
}
// Need to make these via a separate function rather than using the `&.{&.{...}}` syntax, because the latter doesn't use
// an allocator and so attempts to `free` them will give bus errors.
// This language is _really_ awkward sometimes...
fn makeSliceOfSlices(strings: []const []const u8, allocator: std.mem.Allocator) [][]u8 {
var op = std.ArrayList([]u8).init(allocator);
for (strings) |string| {
var slice = allocator.alloc(u8, string.len) catch unreachable;
for (string, 0..) |c, i| {
slice[i] = c;
}
op.append(slice) catch unreachable;
}
return op.toOwnedSlice() catch 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;
}
allocator.free(a);
allocator.free(b);
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;
allocator.free(seq);
}
allocator.free(seqs);
return resp;
}
fn appendSequencesWith(seqs: []const []const u8, suffix: 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;
for (seq, 0..) |c, j| {
new_seq[j] = c;
}
new_seq[seq.len] = suffix;
resp[i] = new_seq;
allocator.free(seq);
}
allocator.free(seqs);
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 => {
//std.debug.print("Encountered unparsable move {}\n", .{m});
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 {};
}
pub fn pointForNumber(number: u8) Point {
return switch (number) {
0 => Point{ .x = 1, .y = 0 },
1...9 => {
const x = (number - 1) % 3;
const y = @divFloor(number - 1, 3) + 1;
return Point{ .x = x, .y = y };
},
10 => Point{ .x = 2, .y = 0 },
else => unreachable,
};
}
pub fn numberForPoint(point: Point) u8 {
if (point.y > 0 and point.y < 4 and point.x >= 0 and point.x < 3) {
return 3 * (point.y - 1) + point.x + 1;
}
if (point.y == 0) {
switch (point.x) {
1 => return 0,
2 => return 10,
else => unreachable,
}
}
unreachable;
}
};
const DirectionalRobotError = error{ OutOfBoundsError, GapError };
// TODO - a lot of this could probably be extracted from here and NumericRobot (I don't know if Zig has subclassing)
const DirectionalRobot = struct {
pos: Point,
pub fn move(self: *DirectionalRobot, m: u8) void {
switch (m) {
'^' => self.pos.y += 1,
'>' => self.pos.x += 1,
'v' => self.pos.y -= 1,
'<' => self.pos.x -= 1,
else => {
//std.debug.print("Dencountered unparsable move {}\n", .{m});
unreachable;
},
}
}
pub fn process(self: *DirectionalRobot, moves: []const u8) DirectionalRobotError!void {
for (moves) |m| {
self.move(m);
if (self.pos.x == 0 and self.pos.y == 1) {
return DirectionalRobotError.GapError;
}
if (self.pos.x > 2 or self.pos.y > 1) {
return DirectionalRobotError.OutOfBoundsError;
}
}
return {};
}
pub fn pointForSymbol(symbol: u8) Point {
return switch (symbol) {
'^' => Point{ .x = 1, .y = 1 },
'<' => Point{ .x = 0, .y = 0 },
'v' => Point{ .x = 1, .y = 0 },
'>' => Point{ .x = 2, .y = 0 },
'A' => Point{ .x = 2, .y = 1 },
else => unreachable,
};
}
pub fn symbolForPoint(point: Point) u8 {
return switch (point.x) {
0 => {
if (point.y == 0) {
return '<';
} else {
unreachable;
}
},
1 => switch (point.y) {
0 => 'v',
1 => '^',
else => unreachable,
},
2 => switch (point.y) {
0 => '>',
1 => 'A',
else => unreachable,
},
else => unreachable,
};
}
};
fn makeZeroToOneSequenceDirectly(allocator: std.mem.Allocator) [][]const u8 {
var op = std.ArrayList([]const u8).init(allocator);
op.append("hello") catch unreachable;
return op.toOwnedSlice() catch unreachable;
}
fn printDirectionSeqAsDirections(_: []const u8) void {
// for (seq) |c| {
// //std.debug.print("{c}", .{c});
// }
}
test "Basic Movement" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var 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 this is necessary!? I'm re-allocating to it on the next line :shrug:
seqs = shortestSequencesToMoveNumericFromAToB(7, 0, allocator);
for (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(seqs);
seqs = shortestSequencesToMoveNumericFromAToB(0, 1, allocator);
for (seqs) |seq| {
var robot = NumericRobot{ .pos = Point{ .x = 1, .y = 0 } };
robot.process(seq) catch unreachable;
const expected_point = Point{ .x = 0, .y = 1 };
try expect(std.meta.eql(robot.pos, expected_point));
allocator.free(seq);
}
allocator.free(seqs);
}
test "Exhaustive Movement test" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
for (0..11) |i| {
for (0..11) |j| {
const i_as_u8: u8 = @intCast(i);
const j_as_u8: u8 = @intCast(j);
const seqs = shortestSequencesToMoveNumericFromAToB(i_as_u8, j_as_u8, allocator);
for (seqs) |seq| {
var robot = NumericRobot{ .pos = NumericRobot.pointForNumber(i_as_u8) };
robot.process(seq) catch unreachable;
try expect(std.meta.eql(robot.pos, NumericRobot.pointForNumber(j_as_u8)));
allocator.free(seq);
}
allocator.free(seqs);
}
}
}
test "Shortest sequences for first level of indirection" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const sequences = shortestDirectionalPushesToEnterNumericSequence("029A", allocator);
defer allocator.free(sequences);
// for readable output, uncomment the lines below
// ---------
// for (sequences) |seq| {
// printDirectionSeqAsDirections(seq);
// //std.debug.print("\n", .{});
// // Not sure why we don't need to `allocator.free(seq)` here :shrug:
// }
// ---------
const targetSequence = allocator.dupe(u8, &.{ '<', 'A', '^', 'A', '>', '^', '^', 'A', 'v', 'v', 'v', 'A' }) catch unreachable;
defer allocator.free(targetSequence);
var foundTargetSequence = false;
for (sequences) |seq| {
if (std.mem.eql(u8, seq, targetSequence)) {
foundTargetSequence = true;
break;
} else {
//std.debug.print("Found the following sequence: ", .{});
printDirectionSeqAsDirections(seq);
//std.debug.print("\n", .{});
}
}
try expect(foundTargetSequence);
}
test "Basic Navigation of Directional Keypad" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var robot = DirectionalRobot{ .pos = Point{ .x = 2, .y = 1 } };
const sequencesToLeft = shortestSequencesToMoveDirectionalFromAToB('A', '<', allocator);
try expect(sequencesToLeft.len > 0);
robot.process(sequencesToLeft[0]) catch unreachable;
for (sequencesToLeft) |seq| {
allocator.free(seq);
}
allocator.free(sequencesToLeft);
try expect(std.meta.eql(robot.pos, Point{ .x = 0, .y = 0 }));
const sequencesToUp = shortestSequencesToMoveDirectionalFromAToB('<', '^', allocator);
try expect(sequencesToUp.len > 0);
robot.process(sequencesToUp[0]) catch unreachable;
for (sequencesToUp) |seq| {
allocator.free(seq);
}
allocator.free(sequencesToUp);
try expect(std.meta.eql(robot.pos, Point{ .x = 1, .y = 1 }));
const sequencesToRight = shortestSequencesToMoveDirectionalFromAToB('^', '>', allocator);
try expect(sequencesToRight.len > 0);
robot.process(sequencesToRight[0]) catch unreachable;
for (sequencesToRight) |seq| {
allocator.free(seq);
}
allocator.free(sequencesToRight);
try expect(std.meta.eql(robot.pos, Point{ .x = 2, .y = 0 }));
}
test "Direct second-level operation" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const second_level_sequences = shortestDirectionalPushesToEnterDirectionalSequence("<A^A>^^AvvvA", allocator);
//std.debug.print("Found {} second-level sequences\n", .{second_level_sequences.len});
var shortest_so_far: usize = std.math.maxInt(usize);
var shortest_sequences = std.ArrayList([]const u8).init(allocator);
for (second_level_sequences) |second_level_sequence| {
//std.debug.print("Processing this second_level sequence: {any}\n", .{second_level_sequence});
const len = second_level_sequence.len;
if (len > shortest_so_far) {
continue;
}
if (len == shortest_so_far) {
shortest_sequences.append(second_level_sequence) catch unreachable;
}
if (len < shortest_so_far) {
shortest_so_far = len;
shortest_sequences.clearRetainingCapacity();
shortest_sequences.append(second_level_sequence) catch unreachable;
}
// allocator.free(second_level_sequence); -- again, don't know why this isn't necessary
}
allocator.free(second_level_sequences);
var foundSequence = false;
//std.debug.print("We found {} shortest sequences\n", .{shortest_sequences.items.len});
for (shortest_sequences.items) |shortest| {
//std.debug.print("Found this shortest-sequence:", .{});
printDirectionSeqAsDirections(shortest);
//std.debug.print("\n", .{});
if (std.mem.eql(u8, shortest, "v<<A>>^A<A>AvA<^AA>A<vAAA>^A")) {
foundSequence = true;
// Apparently it's not necessary to...
// allocator.free(shortest);
// ... so we can just...
break;
}
}
shortest_sequences.deinit();
try expect(foundSequence);
}
test "One Pass Through" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const sequences = shortestDirectionalPushesToEnterNumericSequence("029A", allocator);
defer allocator.free(sequences);
// I have no idea why this (and the other commented-out example) free isn't required :shrug:
// defer {
// for (sequences) |seq| {
// allocator.free(seq);
// }
// }
//std.debug.print("Found {} first-level sequences\n", .{sequences.len});
var second_level_sequences_list = std.ArrayList([]const u8).init(allocator);
for (sequences) |seq| {
const seconds = shortestDirectionalPushesToEnterDirectionalSequence(seq, allocator);
for (seconds) |sec| {
second_level_sequences_list.append(sec) catch unreachable;
// allocator.free(sec);
}
allocator.free(seconds);
}
const second_level_sequences = second_level_sequences_list.toOwnedSlice() catch unreachable;
defer allocator.free(second_level_sequences);
// defer {
// for (second_level_sequences) |sls| {
// allocator.free(sls);
// }
// }
//std.debug.print("Found {} second-level sequences\n", .{second_level_sequences_list.items.len});
var shortest_so_far: usize = std.math.maxInt(usize);
var shortest_sequences = std.ArrayList([]const u8).init(allocator);
for (second_level_sequences) |second_level_sequence| {
//std.debug.print("Processing this second_level sequence: {any}\n", .{second_level_sequence});
const len = second_level_sequence.len;
if (len > shortest_so_far) {
continue;
}
if (len == shortest_so_far) {
shortest_sequences.append(second_level_sequence) catch unreachable;
}
if (len < shortest_so_far) {
shortest_so_far = len;
shortest_sequences.clearRetainingCapacity();
shortest_sequences.append(second_level_sequence) catch unreachable;
}
}
var foundSequence = false;
//std.debug.print("We found {} shortest sequences\n", .{shortest_sequences.items.len});
for (shortest_sequences.items) |shortest| {
//std.debug.print("Found this shortest-sequence:", .{});
printDirectionSeqAsDirections(shortest);
//std.debug.print("\n", .{});
if (std.mem.eql(u8, shortest, "v<<A>>^A<A>AvA<^AA>A<vAAA>^A")) {
foundSequence = true;
// Apparently it's not necessary to...
// allocator.free(shortest);
// ... so we can just...
break;
}
}
shortest_sequences.deinit();
try expect(foundSequence);
}
test "Min Length of 3-times loop" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const min_length = findLengthOfShortestResultOfLoopingNTimes("029A", 3, allocator);
try expect(min_length == 69);
}