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