If you are in the programming space, I'm sure you know what Advent of Code is.
Recently I finished all and every one of the 115 Ziglings exercises and I was looking into what could be the next thing. A graphics engine is surely to come, but I really need to interiorize Zig's syntax first. All those years of C# and C++ are not helping.
Sharing my incredible feat with a friend of mine, they reminded me of the existence of the Advent, which I have known about for quite some time but never really looked into it. After solving the first puzzle from 2015, I think this could be the thing! It is definitely easier than studying chinese, but contrary to my expectations, it took more time than I was expecting.
Simple enough! Btw, puzzle is just the input string as *const []u8.
pub fn main(_: std.process.Init) !void {
std.debug.print("part one: {d}\n", .{part_one()});
std.debug.print("part two: {d}\n", .{part_two()});
}
fn part_one() i16 {
var f: i16 = 0;
for (puzzle) |c|
switch (c) {
'(' => f += 1,
')' => f -= 1,
else => unreachable,
};
return f;
}
fn part_two() i16 {
var f: i16 = 0;
for (puzzle, 1..) |c, i| {
switch (c) {
'(' => f += 1,
')' => f -= 1,
else => unreachable,
}
if (f == -1)
return @intCast(i);
}
return puzzle.len - 1;
}Simple maths are harder the older you are, and I'm dying on this hill.
To make tokenization easier I took the liberty of replacing ever 'x' for a ',' on the input string. If you are using the Helix IDE or NeoVim, straighten the line with 999[shift]C[del], and then replace the 'x' with xrz[del],[enter]. I'm sure there are better ways to do this but I'm still too noob with modal editors!
Anyway, this was fun! I got to use vectors (which proved incredibly convenient) and remember some basic math. What else can I ask from a puzzle?
const std = @import("std");
const parseInt = std.fmt.parseInt;
const puzzle = "29,13,26...";
const by2: @Vector(3, u16) = @splat(2);
pub fn main(_: std.process.Init) !void {
var it = std.mem.tokenizeAny(u8, puzzle, ",");
var sum_wrap: u32 = 0;
var sum_ribbon: u32 = 0;
while (it.next()) |n| {
const l = try parseInt(u16, n, 10);
const w = try parseInt(u16, it.next() orelse "1", 10);
const h = try parseInt(u16, it.next() orelse "1", 10);
const areas = @Vector(3, u16){ l * w, w * h, l * h };
const smallest_area = @reduce(.Min, areas);
const perimeters = @Vector(3, u16){ 2 * (l + w), 2 * (w + h), 2 * (l + h) };
const smallest_perimeter = @reduce(.Min, perimeters);
sum_wrap += @reduce(.Add, areas * by2) + smallest_area;
sum_ribbon += smallest_perimeter + l * w * h;
}
std.debug.print("Paper: {d}ft\n", .{sum_wrap});
std.debug.print("Ribbon: {d}ft\n", .{sum_ribbon});
}Got to use packed unions!
I usually take using allocators and std as cheating for some reason, but I was kinda lazy as to making my own thing. And the packed union just got me a free i32 hash so...
const std = @import("std");
const print = std.debug.print;
const puzzle = "^><^>>>...";
const Coord = packed union(i32) {
value: i32,
pos: packed struct(i32) {
x: i16,
y: i16,
},
};
pub fn main(_: std.process.Init) !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const sand = arena.allocator();
print("part one: {d}\n", .{try part_one(sand)});
print("part two: {d}\n", .{try part_two(sand)});
}
pub fn part_one(sand: std.mem.Allocator) !u32 {
var houses = std.AutoHashMap(i32, void).init(sand);
var addr: Coord = .{ .pos = .{ .x = 0, .y = 0 } };
try houses.put(addr.value, {});
for (puzzle) |c| {
switch (c) {
'^' => addr.pos.y += 1,
'v' => addr.pos.y -= 1,
'>' => addr.pos.x += 1,
'<' => addr.pos.x -= 1,
else => unreachable,
}
try houses.put(addr.value, {});
}
return houses.count();
}
pub fn part_two(sand: std.mem.Allocator) !u32 {
var houses = std.AutoHashMap(i32, void).init(sand);
var addr_snt: Coord = .{ .pos = .{ .x = 0, .y = 0 } };
var addr_bot: Coord = .{ .pos = .{ .x = 0, .y = 0 } };
try houses.put(addr_snt.value, {});
for (puzzle, 0..) |c, i| {
var coord: *Coord = if (i % 2 == 0) &addr_snt else &addr_bot;
switch (c) {
'^' => coord.pos.y += 1,
'v' => coord.pos.y -= 1,
'>' => coord.pos.x += 1,
'<' => coord.pos.x -= 1,
else => unreachable,
}
try houses.put(coord.value, {});
}
return houses.count();
}Just the answer to the first part since the second is trivial given this solution.
const std = @import("std");
const print = std.debug.print;
const puzzle = "ckczppom";
const zeros = "00000";
pub fn main(_: std.process.Init) !void {
var in: [32]u8 = undefined;
var out: [32]u8 = undefined;
var n: u32 = 0;
while (!std.mem.eql(u8, out[0..5], zeros[0..5])) : (n += 1) {
const slice = try std.fmt.bufPrint(&in, "{s}{d}", .{ puzzle, n });
try hash(slice, &out);
}
print("{s}\n", .{in});
print("{s}\n", .{out});
}
fn hash(in: []u8, out: *[32]u8) !void {
var hasher = std.crypto.hash.Md5.init(.{});
hasher.update(in);
var final: [16]u8 = undefined;
hasher.final(&final);
_ = try std.fmt.bufPrint(out, "{x}", .{final});
}const std = @import("std");
const print = std.debug.print;
const eql = std.mem.eql;
const puzzle = "uxcplgxnkwbdwhrp...";
pub fn main(_: std.process.Init) !void {
var it = std.mem.tokenizeAny(u8, puzzle, ",");
var count: i32 = 0;
var total: i32 = 0;
while (it.next()) |str| : (total += 1) {
if (!isNaughty_partOne(str))
count += 1;
}
print("\nPart one {d}/{d}\n", .{ count, total });
count = 0;
total = 0;
it.reset();
while (it.next()) |str| : (total += 1) {
if (!isNaughty_partTwo(str))
count += 1;
}
print("Part two {d}/{d}\n", .{ count, total });
}
fn isNaughty_partOne(str: []const u8) bool {
var cond_vowels: u8 = 0;
var cond_repeat = false;
return for (str, 0..) |c, i| {
if (c == 'a' or c == 'e' or c == 'i' or c == 'o' or c == 'u')
cond_vowels += 1;
if (i == str.len - 1)
continue;
const pair: []const u8 = str[i .. i + 2];
if (eql(u8, pair, "ab") or eql(u8, pair, "cd") or eql(u8, pair, "pq") or eql(u8, pair, "xy"))
return true;
if (pair[0] == pair[1])
cond_repeat = true;
} else cond_vowels < 3 or !cond_repeat;
}
fn isNaughty_partTwo(str: []const u8) bool {
var size: usize = 2;
var bound = str.len - size + 1;
const cond_pairs = blk: {
for (0..bound) |i| for (0..bound) |j| {
const w1 = str[i .. i + size];
const w2 = str[j .. j + size];
if (j != i -% 1 and j != i and j != i + 1)
if (eql(u8, w1, w2))
break :blk true;
};
break :blk false;
};
size = 3;
bound = str.len - size + 1;
const cond_skip = for (0..bound) |i| {
const w1 = str[i .. i + size];
if (w1[0] == w1[2])
break true;
} else false;
if (cond_pairs and cond_skip)
return false;
return true;
}I changed the input so the instructions are separated by a | instead of a line, and I also removed the spaces in turn on and turn off.
When I started researching Zig I saw that one of the main selling points it's advocates defend (the official documentation) is it's high maintainability and reusability. I even saw the phrase "write once, reuse everywhere" which I though overblown at the time.
While in this blog I'm providing the solution to both parts of the advent, I could have very easily have solved the second part with just a couple changes, which I don't think possible in any most other languages I know. I find this very appealing, since it feels like I could just write any pattern once then just reuse parts of it when necessary.
The saturation additions and subtractions themselves are something that I would have had to handle in a very precarious way in other languages. I found them by accident by the way, I knew about the overflow +%= operators and while searching for their syntax I found the saturation ones.
const std = @import("std");
const print = std.debug.print;
const tkn = std.mem.tokenizeAny;
const eql = std.mem.eql;
const parseInt = std.fmt.parseInt;
const puzzle = "turnon 489,959 through 759,...
const Instruction = packed struct(u64) {
action: enum(u4) { On, Off, Tgl },
x1: u15,
y1: u15,
x2: u15,
y2: u15,
};
var lights1: [1000][1000]bool = std.mem.zeroes([1000][1000]bool);
var lights2: [1000][1000]u8 = std.mem.zeroes([1000][1000]u8);
pub fn main(_: std.process.Init) !void {
var line = tkn(u8, puzzle, "|");
while (line.next()) |l| {
var word = tkn(u8, l, " ");
const w1 = word.next().?;
const w2 = word.next().?;
_ = word.next().?;
const w4 = word.next().?;
var min = tkn(u8, w2, ",");
var max = tkn(u8, w4, ",");
var instruction: Instruction = undefined;
instruction.action = if (eql(u8, w1, "turnon")) .On else if (eql(u8, w1, "turnoff")) .Off else .Tgl;
instruction.x1 = try parseInt(u15, min.next().?, 10);
instruction.y1 = try parseInt(u15, min.next().?, 10);
instruction.x2 = try parseInt(u15, max.next().?, 10);
instruction.y2 = try parseInt(u15, max.next().?, 10);
do_partOne(instruction);
do_partTwo(instruction);
}
var count_partOne: u32 = 0;
for (0..lights1.len) |x| for (0..lights1.len) |y| {
count_partOne += if (lights1[x][y]) 1 else 0;
};
var count_partTwo: u32 = 0;
for (0..lights2.len) |x| for (0..lights2.len) |y| {
count_partTwo += lights2[x][y];
};
print("{d}\n", .{count_partOne});
print("{d}\n", .{count_partTwo});
}
fn do_partOne(todo: Instruction) void {
for (todo.x1..todo.x2 + 1) |x| for (todo.y1..todo.y2 + 1) |y| {
lights1[x][y] = switch (todo.action) {
.On => true,
.Off => false,
.Tgl => !lights1[x][y],
};
};
}
fn do_partTwo(todo: Instruction) void {
for (todo.x1..todo.x2 + 1) |x| for (todo.y1..todo.y2 + 1) |y| {
if (todo.action == .Tgl)
lights2[x][y] +|= 2
else if (todo.action == .On)
lights2[x][y] +|= 1
else
lights2[x][y] -|= 1;
};
}
This is the first day that I consider a real challenge. At first I got confused since up until now you could just go line by line and compute along the way, while on this exercise you first lay the network and then probe a cable.
In fact, the way I debugged it was by probing cables with very few connections, to which I knew the final voltage.
Pretty fun!
PS: To solve the second part I just ran it again changing the input value of b.
const std = @import("std");
const log = std.debug.print;
const tknAny = std.mem.tokenizeAny;
const tknSeq = std.mem.tokenizeSequence;
const eql = std.mem.eql;
const prs = std.fmt.parseInt;
const puzzle = "bn RSHIFT 2 -> bo|...";
const Source = union(enum) {
id: [2]u8,
val: u16,
};
const Wire = struct {
id: [2]u8 = .{ ' ', ' ' },
val: ?u16 = null,
gate: enum(u8) { RShift, LShift, And, Or, Not, Value } = .Value,
lSrc: Source = .{ .val = 0 },
rSrc: Source = .{ .val = 0 },
pub fn probe(self: *Wire) u16 {
if (self.val) |val|
return val;
self.val = switch (self.gate) {
.RShift => probe(getWire(&self.lSrc.id)) >> @as(u4, @intCast(self.rSrc.val)),
.LShift => probe(getWire(&self.lSrc.id)) <<| @as(u4, @intCast(self.rSrc.val)),
.And => switch (self.lSrc) {
.id => |id| probe(getWire(&id)),
.val => |v| v,
} & switch (self.rSrc) {
.id => |id| probe(getWire(&id)),
.val => |v| v,
},
.Or => switch (self.lSrc) {
.id => |id| probe(getWire(&id)),
.val => |v| v,
} | switch (self.rSrc) {
.id => |id| probe(getWire(&id)),
.val => |v| v,
},
.Not => ~probe(getWire(&self.lSrc.id)),
.Value => switch (self.lSrc) {
.id => |id| probe(getWire(&id)),
.val => |v| v,
},
};
return self.val.?;
}
};
var wires: [512]Wire = [_]Wire{.{}} ** 512;
var wires_count: u16 = 0;
pub fn main(_: std.process.Init) !void {
const ask = "a";
try createNetwork(puzzle);
log("wire '{s}' has value {d}\n", .{ ask, getWire(ask).probe() });
}
fn createNetwork(input: []const u8) !void {
var lines = tknAny(u8, input, "|");
while (lines.next()) |line| {
var split = tknSeq(u8, line, " -> ");
const left = split.next().?;
const right = split.next().?;
const wire = getWire(right);
var words = tknAny(u8, left, " ");
const w1 = words.next();
const w2 = words.next();
const w3 = words.next();
if (w1 != null and w2 == null and w3 == null) {
const s1 = w1.?;
if (prs(u16, s1, 10)) |num| {
wire.*.gate = .Value;
wire.*.lSrc = .{ .val = num };
// log("{s}\n", .{line});
// log(" wire -> {s: <4} {s: <10} -> {d:>8}\n", .{ wire.*.id, @tagName(wire.*.gate), num });
} else |_| {
wire.*.gate = .Value;
wire.*.lSrc = .{ .id = getWire(s1).*.id };
}
} else if (w1 != null and w2 != null and w3 == null) {
const s2 = w2.?;
wire.*.gate = .Not;
wire.*.lSrc = .{ .id = getWire(s2).*.id };
} else {
const s1 = w1.?;
const s2 = w2.?;
const s3 = w3.?;
const l: Source =
if (prs(u16, s1, 10)) |num|
.{ .val = num }
else |_|
.{ .id = getWire(s1).*.id };
const r: Source =
if (prs(u16, s3, 10)) |num|
.{ .val = num }
else |_|
.{ .id = getWire(s3).*.id };
if (eql(u8, s2, "RSHIFT"))
wire.*.gate = .RShift
else if (eql(u8, s2, "LSHIFT"))
wire.*.gate = .LShift
else if (eql(u8, s2, "AND"))
wire.*.gate = .And
else if (eql(u8, s2, "OR"))
wire.*.gate = .Or;
wire.*.lSrc = l;
wire.*.rSrc = r;
}
}
}
fn getWire(id: []const u8) *Wire {
const coolID: [2]u8 = if (id.len == 1) .{ id[0], '!' } else .{ id[0], id[1] };
return for (wires, 0..) |w, i| {
if (eql(u8, w.id[0..2], coolID[0..2]))
return &wires[@intCast(i)];
} else {
@memcpy(wires[wires_count].id[0..2], coolID[0..2]);
defer wires_count += 1;
return &wires[wires_count];
};
}
I skipped coding and decoding stuff and just added or subtracted :D
const std = @import("std");
const log = std.debug.print;
pub fn main(init: std.process.Init) !void {
const io = init.io;
const file = try std.Io.Dir.cwd().openFile(io, "15-08", .{});
defer file.close(io);
var buff: [64]u8 = undefined;
var reader = file.reader(io, &buff);
try partOne(&reader);
try reader.seekTo(0);
try partTwo(&reader);
}
fn partOne(reader: *std.Io.File.Reader) !void {
var countAdd: u16 = 0;
var countSub: u16 = 0;
while (try reader.interface.takeDelimiter('\n')) |line| {
countAdd += @truncate(line.len);
countSub += 2;
var n: u16 = 0;
while (n < line.len) : (n += 1) switch (line[n]) {
'\\' => switch (line[n + 1]) {
'x' => {
countSub += 3;
n += 3;
},
else => {
countSub += 1;
n += 1;
},
},
else => {},
};
}
log("code {d: >6}\nmemory {d: >6}\nresult {d: >6}\n\n", .{
countAdd,
countAdd - countSub,
countSub,
});
}
fn partTwo(reader: *std.Io.File.Reader) !void {
var countCode: u16 = 0;
var countAdd: u16 = 0;
while (try reader.interface.takeDelimiter('\n')) |line| {
countCode += @truncate(line.len);
countAdd += 2;
var n: u16 = 0;
while (n < line.len) : (n += 1) switch (line[n]) {
'"' => countAdd += 1,
'\\' => countAdd += 1,
else => {},
};
}
log("encode {d: >6}\ncode {d: >6}\nresult {d: >6}", .{
countCode + countAdd,
countCode,
countAdd,
});
}This one costed me tons of time because I misunderstood that directions are bidirectional, thus I was only getting one solution.
Sad times :c
There are no optimizations given that I'm computing both parts of the problem at the same time. I basically had to delete them to make the second part work, since it asks for the longest route. Even then, the time it takes to compute is minimal.
const std = @import("std");
const log = std.debug.print;
const tknAny = std.mem.tokenizeAny;
const prs = std.fmt.parseInt;
const eql = std.mem.eql;
const puzzle = "Faerun to Norrath = 129|...";
const Direction = struct {
place: ?*Place = null,
distance: u16 = 0,
};
const Place = struct {
id: [16]u8 = @splat(' '),
directions: [8]Direction = @splat(.{}),
visited: bool = false,
pub fn getEmptyDirection(self: *Place) ?*Direction {
for (self.directions, 0..) |direction, i|
if (direction.place == null)
return &self.directions[i];
return null;
}
pub fn getNonVisited(self: *Place) ?*Direction {
for (self.directions, 0..) |direction, i|
if (direction.place != null and direction.place.?.visited == false)
return &self.directions[i];
return null;
}
};
var places: [8]Place = @splat(.{});
var placeCount: u8 = 0;
pub fn main(_: std.process.Init) !void {
try layNetwork();
_ = try findShortest();
}
fn layNetwork() !void {
var lines = tknAny(u8, puzzle, "|");
while (lines.next()) |l| {
var words = tknAny(u8, l, " ");
const from = getNode(words.next().?);
_ = words.next();
const to = getNode(words.next().?);
_ = words.next();
const dist = try prs(u16, words.next().?, 10);
from.getEmptyDirection().?.* = .{ .place = to, .distance = dist };
to.getEmptyDirection().?.* = .{ .place = from, .distance = dist };
}
}
fn findShortest() !u32 {
var min: u32 = 9999;
var max: u32 = 0;
for (0..places.len) |i|
visit(&places[i], 0, &min, &max);
log("min {d} max {d}\n", .{ min, max });
return 0;
}
fn visit(at: *Place, sum: u32, min: *u32, max: *u32) void {
at.visited = true;
if (visitedAll()) {
if (sum < min.*) min.* = sum;
if (sum > max.*) max.* = sum;
}
for (at.directions) |next|
if (next.place) |maybe| {
const maybeSum = sum + next.distance;
if (!maybe.*.visited) {
visit(next.place.?, maybeSum, min, max);
}
} else continue;
at.visited = false;
}
fn visitedAll() bool {
for (places) |p|
if (!p.visited)
return false;
return true;
}
fn getNode(id: []const u8) *Place {
return for (places, 0..) |w, i| {
if (eql(u8, w.id[0..id.len], id))
return &places[@intCast(i)];
} else {
@memcpy(places[placeCount].id[0..id.len], id);
defer placeCount += 1;
return &places[placeCount];
};
}Even after watching the Jhon Conways video, I don't think there is a way to optimize this further without implementing the 92 elements.
The input itself is element Bismuth 3113322113 so we know it can not be split and thus cheated. Later down the line I'm sure we can find other elements but again, without developing them, saving them, then trying to find them on the strings and so on, I don't think this can be done faster than O(n)=log(2^n).
You can tho make this problem recursive, but I don't really like recursiveness when it involves strings.
const std = @import("std");
const toBuf = std.fmt.bufPrint;
const log = std.debug.print;
const solve = "3113322113";
const sove_test = "1";
pub fn main(_: std.process.Init) !void {
const toIter = solve;
const iterations = 50;
var iterNum: u32 = 0;
var buffPblm: [1024 * 1024 * 5]u8 = @splat(' ');
var pCount: u32 = 1;
var buffSltn: [1024 * 1024 * 5]u8 = @splat(' ');
var sCount: u32 = 0;
@memcpy(buffPblm[0..toIter.len], toIter);
var prev: u8 = buffPblm[0];
while (iterNum < iterations) : (iterNum += 1) {
for (buffPblm[1..]) |c| {
if (c == ' ' and prev == ' ') {
break;
} else if (c == prev) {
pCount += 1;
} else {
_ = try toBuf(buffSltn[sCount .. sCount + 2], "{d}{c}", .{ pCount, prev });
sCount += 2;
pCount = 1;
prev = c;
}
}
@memcpy(buffPblm[0..buffSltn.len], &buffSltn);
prev = buffPblm[0];
log("{d} ", .{sCount});
pCount = 1;
sCount = 0;
}
}
For some reason, I started optimizing this thing. I just can't stand non responsive programs...
I don't think the code is all that relevant, so here are the results:
// Debug
countElementsInIter -> +254 ms
countElementsInIterNoMemcpy -> +247 ms
countElementsInIterNoMemcpyNoFmt -> +16 ms
// ReleaseSmall
countElementsInIter -> +42 ms
countElementsInIterNoMemcpy -> +32 ms
countElementsInIterNoMemcpyNoFmt -> +2 ms
// RelaseFast
countElementsInIter -> +25 ms
countElementsInIterNoMemcpy -> +21 ms
countElementsInIterNoMemcpyNoFmt -> +2 ms
NoMemcpy: meaning that I removed the @memcpy(buffPblm[0..buffSltn.len], &buffSltn); by making the function swap arrays, one being the "iteration" buffer an the other the "solution" buffer. This resulted in a consistently linear ~7ms reduction in time from the original function. I am kinda surprised by this I don't really understand why this happens. If we save time at all with this change, we should be seeing more time saved by iteration number, which does not happen.
NoFmt: meaning I removed the std.fmt.bufPrint function use and just use math to convert the number into a character. Thank god Zig strings are just u8! This had a massive impact on performance since we don't activate the formatter at any point.
That was fun!
Same as day 5 honestly.
const std = @import("std");
const log = std.debug.print;
var puzzle = [8]u8{ 'v', 'z', 'b', 'x', 'k', 'g', 'h', 'b' };
pub fn main(_: std.process.Init) !void {
while (!validate(&puzzle))
nextPass(&puzzle);
log("success validating -> {s}\n", .{puzzle});
}
fn validate(pass: []u8) bool {
for (pass) |c| if (c == 'i' or c == 'o' or c == 'l')
return false;
var window = std.mem.window(u8, pass, 3, 1);
const progression = while (window.next()) |w| {
if (w[0] + 1 == w[1] and w[0] + 2 == w[2])
break true;
} else false;
window = std.mem.window(u8, pass, 2, 1);
var contiguousCount: u8 = 0;
const contiguous = while (window.next()) |w| {
if (w[0] != w[1])
continue;
contiguousCount += 1;
if (contiguousCount >= 2)
break true;
_ = window.next();
} else false;
return progression and contiguous;
}
fn nextPass(pass: []u8) void {
var i = pass.len - 1;
while (true) : (i -= 1) switch (pass[i]) {
'z' => pass[i] = 'a',
else => {
pass[i] += 1;
return;
},
};
}This one was pretty neat because I was having some trouble with the reader and went to the ziggit.dev forum to ask about it. People there were really helpful and they clearly know much more about computers and systems than me.
Not to be surprised tho, I know how to program from pure wiliness, given that my degree is for design and my masters only covers C++ in Unreal engine.
const std = @import("std");
const log = std.debug.print;
const tokenize = std.mem.tokenizeAny;
pub fn main(init: std.process.Init) !void {
const io = init.io;
const file = try std.Io.Dir.cwd().openFile(io, "15-12", .{});
defer file.close(io);
var buff: [1024 * 64]u8 = @splat(' ');
var stream = file.readerStreaming(io, &.{});
const reader = &stream.interface;
const slice = buff[0..try reader.readSliceShort(&buff)];
log("part one > {d}\n", .{try sum(slice)});
sanitize(slice);
log("part one > {d}\n", .{try sum(slice)});
}
fn sum(slice: []u8) !i64 {
var n: i64 = 0;
var it = tokenize(u8, slice, " :;{}[],.+\"\'qwertyuiopñlkjhgfdsazxcvbnm");
while (it.next()) |val|
n += try std.fmt.parseInt(i64, val, 10);
return n;
}
fn sanitize(slice: []u8) void {
var window = std.mem.window(u8, slice, 3, 1);
while (window.next()) |val| {
if (!std.mem.eql(u8, val, "red"))
continue;
var l: usize = window.index.? - 1;
var r: usize = l + 2;
containerBounds(slice, &l, &r);
if (slice[l] == '{' and slice[r] == '}')
@memset(slice[l .. r + 1], ' ');
}
}
fn containerBounds(slice: []u8, l: *usize, r: *usize) void {
var lCount: usize = 1;
var rCount: usize = 1;
while (lCount > 0 or rCount > 0) {
if (lCount > 0) {
l.* -= 1;
switch (slice[l.*]) {
'{', '[' => lCount -= 1,
'}', ']' => lCount += 1,
else => continue,
}
}
if (rCount > 0) {
r.* += 1;
switch (slice[r.*]) {
'{', '[' => rCount += 1,
'}', ']' => rCount -= 1,
else => continue,
}
}
}
}
test {
const buff = "oh~no";
var it = std.mem.tokenizeAny(u8, buff, "ñ");
while (it.next()) |s|
log("[{s}]", .{s});
// [oh][�no]~
}It's been a while since I have done puzzle since I have been busy learning PurrNet for my current job.
In any case, this problem has been really interesting because it involves permutations, which I ended up making a whole library for! I posted it on ziggit.dev too and they were really really helpful yet again.
Not only I learned a bunch of the Zig testing suit and methodology but I also learned a bunch about memory layout. This is because I wanted to make a function that allows you to know in advance how much memory the iterator is gonna allocate, so I had to be very careful about the alignment of my types.
const Relation = struct { self: u32, other: u32, gain: i32 };
pub fn main(init: std.process.Init) !void {
var buffer: [128]u8 = undefined;
var relations: [128]Relation = undefined;
var humans: [64]u32 = undefined;
var humanCount: u32 = 0;
const io = init.io;
const relationsSlice, const humanSlice = try populateRelations(io, &buffer, &relations, &humans, &humanCount);
var gpa = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer gpa.deinit();
const alloc = gpa.allocator();
var it = try pmt.permutate(u32, alloc, humanSlice);
defer it.deinit(alloc);
var maxHappy: i32 = 0;
while (it.next()) {
const currHappy: i32 = calculateHappy(relationsSlice, humanSlice);
if (currHappy > maxHappy)
maxHappy = currHappy;
}
log("Part one: {d}\n", .{maxHappy});
humans[humanCount] = 0;
humanCount += 1;
var relationsCount = relationsSlice.len;
for (humans) |h| {
relations[relationsCount] = Relation{ .self = 0, .other = h, .gain = 0 };
relationsCount += 1;
}
maxHappy = 0;
it.reset();
while (it.next()) {
const currHappy: i32 = calculateHappy(relations[0..relationsCount], humans[0..humanCount]);
if (currHappy > maxHappy)
maxHappy = currHappy;
}
log("Part one: {d}\n", .{maxHappy});
}
fn populateRelations(
io: std.Io,
buffer: []u8,
relations: []Relation,
humans: []u32,
humanCount: *u32,
) !struct { []Relation, []u32 } {
const file = try std.Io.Dir.cwd().openFile(io, "15-13.txt", .{});
defer file.close(io);
var stream = file.reader(io, buffer);
const reader = &stream.interface;
var relationsCount: u32 = 0;
var n: u32 = 0;
while (try reader.takeDelimiter('\n')) |line| : (n += 1) {
var word = tnk(u8, line, " .");
const self = hashHuman(word.next().?);
_ = word.next();
const sign: i32 = if (std.mem.eql(u8, "gain", word.next().?)) 1 else -1;
const gain: i32 = try prs(i32, word.next().?, 10);
for (0..6) |_| _ = word.next();
const other = hashHuman(word.next().?);
relations[relationsCount] = .{
.self = self,
.other = other,
.gain = gain * sign,
};
relationsCount += 1;
tryRegisterHuman(self, humans, humanCount);
tryRegisterHuman(other, humans, humanCount);
}
return .{ relations[0..n], humans[0..humanCount.*] };
}
fn calculateHappy(relations: []Relation, humans: []u32) i32 {
var happy: i32 = 0;
for (humans, 0..) |self, i| {
const l = if (i == 0) humans[humans.len - 1] else humans[i - 1];
const r = if (i == humans.len - 1) humans[0] else humans[i + 1];
for (relations) |rel| {
if (rel.self != self) continue;
if (rel.other != l and rel.other != r) continue;
happy += rel.gain;
}
}
return happy;
}
fn hashHuman(name: []const u8) u32 {
var hasher = blk.init(.{});
hasher.update(name);
var out: [4]u8 = undefined;
hasher.final(&out);
return @bitCast(out);
}
fn tryRegisterHuman(human: u32, humans: []u32, humanCount: *u32) void {
for (humans[0..humanCount.*]) |h| if (h == human) return;
humans[humanCount.*] = human;
humanCount.* += 1;
}
const std = @import("std");
const pmt = @import("Permutation.zig");
const log = std.debug.print;
const blk = std.crypto.hash.Blake3;
const tnk = std.mem.tokenizeAny;
const prs = std.fmt.parseInt;Quick and easy one.
I might have abused the array iterations a bit to much in the second part and I know I could have used Zig vectors, but I was too sleepy for that. Maybe in the next one. On the other hand, I'm trying to start using allocators more and more because I noticed a severe lack of skill when I actually need them.
const Reindeer = struct {
name: [8]u8 = @splat(' '),
speed: i32 = 0,
running: i32 = 0,
resting: i32 = 0,
score: i32 = 0,
};
pub fn main(init: std.process.Init) !void {
const io = init.io;
const file = try std.Io.Dir.cwd().openFile(io, "15-14.txt", .{});
defer file.close(io);
var buff: [128]u8 = undefined;
var stream = file.reader(io, &buff);
const reader = &stream.interface;
var gpa = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer gpa.deinit();
const alloc = gpa.allocator();
var all = try std.ArrayList(Reindeer).initCapacity(alloc, 9);
defer all.deinit(alloc);
while (try reader.takeDelimiter('\n')) |line| {
var word = tnk(u8, line, " .,");
var one: *Reindeer = try all.addOne(alloc);
one.* = .{};
@memmove(@as([*]u8, &one.name), word.next().?);
for (0..2) |_| _ = word.next();
one.speed = try prs(i32, word.next().?, 10);
for (0..2) |_| _ = word.next();
one.running = try prs(i32, word.next().?, 10);
for (0..6) |_| _ = word.next();
one.resting = try prs(i32, word.next().?, 10);
continue;
}
std.debug.print("\nPart one:\n", .{});
for (all.items) |one| {
const dis = try distAtTime(one, 2503);
std.debug.print("{s}: {d} km\n", .{ one.name, dis });
}
std.debug.print("\nPart two:\n", .{});
for (1..2503) |i| {
var max: i32 = 0;
for (all.items) |one|
max = @max(max, try distAtTime(one, @intCast(i)));
for (all.items) |*one| {
const dist = try distAtTime(one.*, @intCast(i));
one.score += @intFromBool(dist == max);
}
}
for (all.items) |one|
std.debug.print("{s}: {d} points\n", .{ one.name, one.score });
}
pub fn distAtTime(r: Reindeer, t: i32) !i32 {
const chunk = r.running + r.resting;
const dis = try std.math.divFloor(i32, t, chunk) * r.speed * r.running;
const rest = @min(r.running, @mod(t, chunk)) * r.speed;
return dis + rest;
}
const std = @import("std");
const tnk = std.mem.tokenizeAny;
const prs = std.fmt.parseInt;