Skip to content

Commit 6c7d688

Browse files
committed
zig version. unfortunately the bitreader doesn't count bits read so it's a bit hacky because it has to work around that. also reducers is a bit hacky just because I wanted a clean switch statement. also this took far longer than expected
1 parent acea1dc commit 6c7d688

File tree

2 files changed

+221
-1
lines changed

2 files changed

+221
-1
lines changed

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ node_modules
22
yarn-error.log
33
input.txt
44
out.out
5-
cookie.txt
5+
cookie.txt
6+
zig-cache/
7+
zig-out/

2021/solutions/day16/day16.zig

+218
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
const std = @import("std");
2+
3+
const endian: std.builtin.Endian = .Big;
4+
5+
const PacketType = enum(u3) {
6+
literal = 4,
7+
8+
sum = 0,
9+
product = 1,
10+
minimum = 2,
11+
maximum = 3,
12+
greater_than = 5,
13+
less_than = 6,
14+
equal_to = 7,
15+
};
16+
17+
const LengthMode = enum(u1) {
18+
bits = 0,
19+
packets = 1,
20+
};
21+
22+
const Length = union(LengthMode) {
23+
bits: u15,
24+
packets: u11,
25+
};
26+
27+
const PacketResult = u64;
28+
29+
const PacketIter = struct {
30+
remaining: Length,
31+
fbs: *std.io.FixedBufferStream([]const u8),
32+
bits: *std.io.BitReader(endian, std.io.FixedBufferStream([]const u8).Reader),
33+
34+
pub fn next(pi: *PacketIter) error{InvalidPacket}!?PacketResult {
35+
switch(pi.remaining) {
36+
.bits => |*bits| {
37+
// unfortunately super messy because BitReader doesn't count bits
38+
if(bits.* == 0) return null;
39+
40+
const pos_start = pi.fbs.pos;
41+
const bit_start = 8 - @as(u4, pi.bits.bit_count);
42+
43+
const result = try eval(pi.fbs, pi.bits);
44+
45+
const pos_end = pi.fbs.pos;
46+
const bit_end = 8 - @as(u4, pi.bits.bit_count);
47+
48+
const total_bits = (pos_end * 8 + bit_end) - (pos_start * 8 + bit_start);
49+
50+
if(total_bits > std.math.maxInt(u15)) return error.InvalidPacket;
51+
if(bits.* < total_bits) return error.InvalidPacket;
52+
bits.* -= @intCast(u15, total_bits);
53+
54+
return result;
55+
},
56+
.packets => |*pc| {
57+
if(pc.* == 0) return null;
58+
pc.* -= 1;
59+
return try eval(pi.fbs, pi.bits);
60+
},
61+
}
62+
}
63+
64+
pub fn reduce(
65+
pi: *PacketIter,
66+
comptime Reducer: type,
67+
) !@TypeOf(Reducer.initial) {
68+
var result = Reducer.initial;
69+
while(try pi.next()) |value| {
70+
result = Reducer.cb(result, value);
71+
}
72+
return result;
73+
}
74+
};
75+
76+
const Reducers = struct {
77+
const Sum = struct {
78+
const initial: PacketResult = 0;
79+
pub fn cb(t: PacketResult, a: PacketResult) PacketResult {
80+
return t + a;
81+
}
82+
};
83+
const Mul = struct {
84+
const initial: PacketResult = 1;
85+
pub fn cb(t: PacketResult, a: PacketResult) PacketResult {
86+
return t * a;
87+
}
88+
};
89+
const Min = struct {
90+
const initial: PacketResult = std.math.maxInt(u64);
91+
pub fn cb(t: PacketResult, a: PacketResult) PacketResult {
92+
return std.math.min(t, a);
93+
}
94+
};
95+
const Max = struct {
96+
const initial: PacketResult = std.math.minInt(u64);
97+
pub fn cb(t: PacketResult, a: PacketResult) PacketResult {
98+
return std.math.max(t, a);
99+
}
100+
};
101+
};
102+
103+
pub fn eval(
104+
fbs: *std.io.FixedBufferStream([]const u8),
105+
bits: *std.io.BitReader(endian, std.io.FixedBufferStream([]const u8).Reader),
106+
) error{InvalidPacket}!PacketResult {
107+
const version = bits.readBitsNoEof(u3, 3) catch return error.InvalidPacket;
108+
_ = version; // no one cares about you
109+
const packet_type_raw = bits.readBitsNoEof(u3, 3) catch return error.InvalidPacket;
110+
const packet_type = std.meta.intToEnum(PacketType, packet_type_raw) catch return error.InvalidPacket;
111+
112+
if(packet_type == .literal) {
113+
var res: u64 = 0;
114+
while(true) {
115+
const cont = 1 == (bits.readBitsNoEof(u1, 1) catch return error.InvalidPacket);
116+
res <<= 4;
117+
res |= bits.readBitsNoEof(u4, 4) catch return error.InvalidPacket;
118+
if(!cont) break;
119+
}
120+
return res;
121+
}
122+
123+
const len_kind = @intToEnum(LengthMode, bits.readBitsNoEof(u1, 1) catch return error.InvalidPacket);
124+
125+
const len: Length = switch(len_kind) {
126+
.bits => .{.bits = bits.readBitsNoEof(u15, 15) catch return error.InvalidPacket},
127+
.packets => .{.packets = bits.readBitsNoEof(u11, 11) catch return error.InvalidPacket},
128+
};
129+
130+
var iter = PacketIter{
131+
.remaining = len,
132+
.fbs = fbs,
133+
.bits = bits,
134+
};
135+
136+
return switch(packet_type) {
137+
.literal => unreachable,
138+
139+
.sum => iter.reduce(Reducers.Sum),
140+
.product => iter.reduce(Reducers.Mul),
141+
.minimum => iter.reduce(Reducers.Min),
142+
.maximum => iter.reduce(Reducers.Max),
143+
.greater_than, .less_than, .equal_to => {
144+
const a = (iter.next() catch return error.InvalidPacket) orelse return error.InvalidPacket;
145+
const b = (iter.next() catch return error.InvalidPacket) orelse return error.InvalidPacket;
146+
if((iter.next() catch return error.InvalidPacket) != null) return error.InvalidPacket;
147+
return switch(packet_type) {
148+
.greater_than => @boolToInt(a > b),
149+
.less_than => @boolToInt(a < b),
150+
.equal_to => @boolToInt(a == b),
151+
else => unreachable,
152+
};
153+
},
154+
};
155+
}
156+
157+
pub fn evalFromInput(raw_input: []const u8, alloc: std.mem.Allocator) !usize {
158+
const input = std.mem.trim(u8, raw_input, "\r\n ");
159+
160+
var data = try std.ArrayList(u8).initCapacity(
161+
alloc,
162+
std.math.divCeil(usize, input.len, 2) catch unreachable,
163+
);
164+
defer data.deinit();
165+
166+
var writer = std.io.bitWriter(endian, data.writer());
167+
for(input) |char| {
168+
writer.writeBits(
169+
@intCast(u4, try std.fmt.charToDigit(char, 16)),
170+
4,
171+
) catch unreachable;
172+
}
173+
174+
var fbs = std.io.fixedBufferStream(@as([]const u8, data.items));
175+
var reader = std.io.bitReader(endian, fbs.reader());
176+
177+
return try eval(&fbs, &reader);
178+
}
179+
180+
pub fn main() !void {
181+
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
182+
defer arena.deinit();
183+
const alloc = arena.allocator();
184+
185+
const input = @embedFile("day16.txt");
186+
187+
std.log.info("result: {d}", .{try evalFromInput(input, alloc)});
188+
}
189+
190+
191+
fn testCase(input: []const u8, result: usize) !void {
192+
try std.testing.expectEqual(result, try evalFromInput(input, std.testing.allocator));
193+
}
194+
fn testError(input: []const u8) !void {
195+
try std.testing.expectError(try evalFromInput(input, std.testing.allocator));
196+
}
197+
test "cases" {
198+
// C200B40A82 finds the sum of 1 and 2, resulting in the value 3.
199+
// 04005AC33890 finds the product of 6 and 9, resulting in the value 54.
200+
// 880086C3E88112 finds the minimum of 7, 8, and 9, resulting in the value 7.
201+
// CE00C43D881120 finds the maximum of 7, 8, and 9, resulting in the value 9.
202+
// D8005AC2A8F0 produces 1, because 5 is less than 15.
203+
// F600BC2D8F produces 0, because 5 is not greater than 15.
204+
// 9C005AC2F8F0 produces 0, because 5 is not equal to 15.
205+
// 9C0141080250320F1802104A08 produces 1, because 1 + 3 = 2 * 2.
206+
207+
try testCase("C200B40A82", 3);
208+
try testCase("04005AC33890", 54);
209+
try testCase("880086C3E88112", 7);
210+
try testCase("CE00C43D881120", 9);
211+
try testCase("D8005AC2A8F0", 1);
212+
try testCase("F600BC2D8F", 0);
213+
try testCase("9C005AC2F8F0", 0);
214+
try testCase("9C0141080250320F1802104A08", 1);
215+
// ^ gh copilot converted those into test cases for me, thanks
216+
217+
try testCase(@embedFile("day16.txt"), 1015320896946);
218+
}

0 commit comments

Comments
 (0)