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