1
+ package com .macasaet ;
2
+
3
+ import static org .junit .jupiter .api .Assertions .assertEquals ;
4
+ import static org .junit .jupiter .api .Assertions .assertTrue ;
5
+
6
+ import java .util .*;
7
+ import java .util .stream .Collectors ;
8
+ import java .util .stream .Stream ;
9
+ import java .util .stream .StreamSupport ;
10
+
11
+ import org .junit .jupiter .api .Nested ;
12
+ import org .junit .jupiter .api .Test ;
13
+
14
+ /**
15
+ * --- Day 16: Packet Decoder ---
16
+ */
17
+ public class Day16 {
18
+
19
+ protected Stream <String > getInput () {
20
+ return StreamSupport
21
+ .stream (new LineSpliterator ("day-16.txt" ),
22
+ false );
23
+ }
24
+
25
+ public interface Packet {
26
+ long version ();
27
+
28
+ void accept (PacketVisitor packetVisitor );
29
+
30
+ long evaluate ();
31
+ }
32
+
33
+ public record Literal (long version , long value ) implements Packet {
34
+
35
+ public void accept (PacketVisitor packetVisitor ) {
36
+ packetVisitor .visit (this );
37
+ }
38
+
39
+ public long evaluate () {
40
+ return value ();
41
+ }
42
+ }
43
+
44
+ public enum OperatorType {
45
+ SUM {
46
+ public long evaluate (List <? extends Packet > operands ) {
47
+ return operands .stream ().mapToLong (Packet ::evaluate ).sum ();
48
+ }
49
+ },
50
+ PRODUCT {
51
+ public long evaluate (List <? extends Packet > operands ) {
52
+ return operands .stream ().mapToLong (Packet ::evaluate ).reduce (1 , (x , y ) -> x * y );
53
+ }
54
+ },
55
+ MINIMUM {
56
+ public long evaluate (List <? extends Packet > operands ) {
57
+ return operands .stream ().mapToLong (Packet ::evaluate ).min ().orElseThrow ();
58
+ }
59
+ },
60
+ MAXIMUM {
61
+ public long evaluate (List <? extends Packet > operands ) {
62
+ return operands .stream ().mapToLong (Packet ::evaluate ).max ().orElseThrow ();
63
+ }
64
+ },
65
+ GREATER_THAN {
66
+ public long evaluate (List <? extends Packet > operands ) {
67
+ if (operands .size () != 2 ) {
68
+ throw new IllegalArgumentException ("Invalid operand list for \" greater than\" operator: " + operands );
69
+ }
70
+ final var x = operands .get (0 ).evaluate ();
71
+ final var y = operands .get (1 ).evaluate ();
72
+ return x > y ? 1 : 0 ;
73
+ }
74
+ },
75
+ LESS_THAN {
76
+ public long evaluate (List <? extends Packet > operands ) {
77
+ if (operands .size () != 2 ) {
78
+ throw new IllegalStateException ("Invalid operand list for \" less than\" operator: " + operands );
79
+ }
80
+ final var x = operands .get (0 ).evaluate ();
81
+ final var y = operands .get (1 ).evaluate ();
82
+ return x < y ? 1 : 0 ;
83
+ }
84
+ },
85
+ EQUAL_TO {
86
+ public long evaluate (List <? extends Packet > operands ) {
87
+ if (operands .size () != 2 ) {
88
+ throw new IllegalStateException ("Invalid operand list for \" equal to\" operator: " + operands );
89
+ }
90
+ final var x = operands .get (0 ).evaluate ();
91
+ final var y = operands .get (1 ).evaluate ();
92
+ return x == y ? 1 : 0 ;
93
+ }
94
+ };
95
+
96
+ public abstract long evaluate (List <? extends Packet > operands );
97
+
98
+ public static OperatorType forId (final int typeId ) {
99
+ return switch (typeId ) {
100
+ case 0 -> SUM ;
101
+ case 1 -> PRODUCT ;
102
+ case 2 -> MINIMUM ;
103
+ case 3 -> MAXIMUM ;
104
+ case 5 -> GREATER_THAN ;
105
+ case 6 -> LESS_THAN ;
106
+ case 7 -> EQUAL_TO ;
107
+ default -> throw new IllegalArgumentException ("Invalid operator type ID: " + typeId );
108
+ };
109
+ }
110
+ }
111
+
112
+ public record Operator (long version , OperatorType operatorType , List <Packet > operands ) implements Packet {
113
+
114
+ public void accept (PacketVisitor packetVisitor ) {
115
+ packetVisitor .enter (this );
116
+ for (final var subPacket : operands ()) {
117
+ subPacket .accept (packetVisitor );
118
+ }
119
+ packetVisitor .exit (this );
120
+ }
121
+
122
+ public long evaluate () {
123
+ return operatorType ().evaluate (operands ());
124
+ }
125
+ }
126
+
127
+ public interface PacketVisitor {
128
+ void visit (Literal literal );
129
+
130
+ void enter (Operator operator );
131
+
132
+ void exit (Operator operator );
133
+ }
134
+
135
+ public static class PacketBuilder {
136
+
137
+ private long version ;
138
+ private long typeId ;
139
+ private OptionalLong literalValue = OptionalLong .empty ();
140
+ private final List <Packet > subPackets = new ArrayList <>();
141
+
142
+ public Packet readHex (final String hexString ) {
143
+ final var hexDigits = hexString .toCharArray ();
144
+ final var bits = hexToBits (hexDigits );
145
+ read (bits , 0 );
146
+ return toPacket ();
147
+ }
148
+
149
+ public int read (final List <Byte > bits , int transmissionCursor ) {
150
+ final var versionBits = bits .subList (transmissionCursor , transmissionCursor + 3 );
151
+ transmissionCursor += 3 ;
152
+ this .version = toLong (versionBits );
153
+
154
+ final var typeBits = bits .subList (transmissionCursor , transmissionCursor + 3 );
155
+ transmissionCursor += 3 ;
156
+ this .typeId = toLong (typeBits );
157
+
158
+ // TODO consider adding methods to parse each type specifically
159
+ if (this .typeId == 4 ) {
160
+ boolean finalGroup = false ;
161
+ final var literalBits = new ArrayList <Byte >();
162
+ while (!finalGroup ) {
163
+ final var groupBits = bits .subList (transmissionCursor , transmissionCursor + 5 );
164
+ transmissionCursor += 5 ;
165
+ finalGroup = groupBits .get (0 ) == 0 ;
166
+ literalBits .addAll (groupBits .subList (1 , 5 ));
167
+ }
168
+ if (literalBits .size () > 63 ) {
169
+ throw new IllegalArgumentException ("Literal is too large for an long: " + literalBits .size ());
170
+ }
171
+ literalValue = OptionalLong .of (toLong (literalBits ));
172
+ return transmissionCursor ;
173
+ } else {
174
+ final var lengthTypeIdBits = bits .subList (transmissionCursor , transmissionCursor + 1 );
175
+ transmissionCursor += 1 ;
176
+ final var lengthTypeId = toLong (lengthTypeIdBits );
177
+ if (lengthTypeId == 0 ) {
178
+ final var lengthOfSubPacketsBits = bits .subList (transmissionCursor , transmissionCursor + 15 );
179
+ transmissionCursor += 15 ;
180
+ final var lengthOfSubPackets = toLong (lengthOfSubPacketsBits );
181
+ int bitsRead = 0 ;
182
+ while (bitsRead < lengthOfSubPackets ) {
183
+ final var subPacketBuilder = new PacketBuilder ();
184
+ final var newCursor = subPacketBuilder .read (bits , transmissionCursor );
185
+ final var subPacketSize = newCursor - transmissionCursor ; // size of sub-packet in bits
186
+ transmissionCursor = newCursor ;
187
+
188
+ subPackets .add (subPacketBuilder .toPacket ());
189
+ bitsRead += subPacketSize ;
190
+ }
191
+ return transmissionCursor ;
192
+ } else if (lengthTypeId == 1 ) {
193
+ final var numSubPacketsBits = bits .subList (transmissionCursor , transmissionCursor + 11 );
194
+ transmissionCursor += 11 ;
195
+ final var numSubPackets = toLong (numSubPacketsBits );
196
+ for (int packetsRead = 0 ; packetsRead < numSubPackets ; packetsRead ++) {
197
+ final var subPacketBuilder = new PacketBuilder ();
198
+ transmissionCursor = subPacketBuilder .read (bits , transmissionCursor );
199
+ subPackets .add (subPacketBuilder .toPacket ());
200
+ }
201
+ return transmissionCursor ;
202
+ } else {
203
+ throw new IllegalArgumentException ("Invalid length type ID: " + lengthTypeId );
204
+ }
205
+ }
206
+ }
207
+
208
+ public Packet toPacket () {
209
+ if (typeId == 4 ) {
210
+ return new Literal (version , literalValue .orElseThrow ());
211
+ } else {
212
+ return new Operator (version , OperatorType .forId ((int ) typeId ), subPackets );
213
+ }
214
+ }
215
+
216
+ protected long toLong (final List <Byte > bits ) {
217
+ long result = 0 ;
218
+ for (int i = 0 ; i < bits .size (); i ++) {
219
+ final var bit = bits .get (i );
220
+ if (bit == 1 ) {
221
+ final long shiftDistance = bits .size () - i - 1 ;
222
+ result |= 1L << shiftDistance ;
223
+ } else if (bit != 0 ) {
224
+ throw new IllegalArgumentException ("Invalid bit representation of an integer: " + bits );
225
+ }
226
+ }
227
+ return result ;
228
+ }
229
+
230
+ protected List <Byte > hexToBits (final char [] hexDigits ) {
231
+ final var result = new ArrayList <Byte >(hexDigits .length * 4 );
232
+ for (final var digit : hexDigits ) {
233
+ final var bits = switch (digit ) {
234
+ case '0' -> Arrays .asList ((byte ) 0 , (byte ) 0 , (byte ) 0 , (byte ) 0 );
235
+ case '1' -> Arrays .asList ((byte ) 0 , (byte ) 0 , (byte ) 0 , (byte ) 1 );
236
+ case '2' -> Arrays .asList ((byte ) 0 , (byte ) 0 , (byte ) 1 , (byte ) 0 );
237
+ case '3' -> Arrays .asList ((byte ) 0 , (byte ) 0 , (byte ) 1 , (byte ) 1 );
238
+ case '4' -> Arrays .asList ((byte ) 0 , (byte ) 1 , (byte ) 0 , (byte ) 0 );
239
+ case '5' -> Arrays .asList ((byte ) 0 , (byte ) 1 , (byte ) 0 , (byte ) 1 );
240
+ case '6' -> Arrays .asList ((byte ) 0 , (byte ) 1 , (byte ) 1 , (byte ) 0 );
241
+ case '7' -> Arrays .asList ((byte ) 0 , (byte ) 1 , (byte ) 1 , (byte ) 1 );
242
+ case '8' -> Arrays .asList ((byte ) 1 , (byte ) 0 , (byte ) 0 , (byte ) 0 );
243
+ case '9' -> Arrays .asList ((byte ) 1 , (byte ) 0 , (byte ) 0 , (byte ) 1 );
244
+ case 'A' , 'a' -> Arrays .asList ((byte ) 1 , (byte ) 0 , (byte ) 1 , (byte ) 0 );
245
+ case 'B' , 'b' -> Arrays .asList ((byte ) 1 , (byte ) 0 , (byte ) 1 , (byte ) 1 );
246
+ case 'C' , 'c' -> Arrays .asList ((byte ) 1 , (byte ) 1 , (byte ) 0 , (byte ) 0 );
247
+ case 'D' , 'd' -> Arrays .asList ((byte ) 1 , (byte ) 1 , (byte ) 0 , (byte ) 1 );
248
+ case 'E' , 'e' -> Arrays .asList ((byte ) 1 , (byte ) 1 , (byte ) 1 , (byte ) 0 );
249
+ case 'F' , 'f' -> Arrays .asList ((byte ) 1 , (byte ) 1 , (byte ) 1 , (byte ) 1 );
250
+ default -> throw new IllegalStateException ("Unexpected value: " + digit );
251
+ };
252
+ result .addAll (bits );
253
+ }
254
+ return Collections .unmodifiableList (result );
255
+ }
256
+ }
257
+
258
+ @ Test
259
+ public final void testParseLiteral () {
260
+ // given
261
+ final var input = "D2FE28" ;
262
+ final var builder = new PacketBuilder ();
263
+
264
+ // when
265
+ final var result = builder .readHex (input );
266
+
267
+ // then
268
+ assertTrue (result instanceof Literal );
269
+ final var literal = (Literal ) result ;
270
+ assertEquals (2021 , literal .value );
271
+ }
272
+
273
+ @ Test
274
+ public final void testOperatorWithTwoSubPackets () {
275
+ // given
276
+ final var input = "38006F45291200" ;
277
+ final var builder = new PacketBuilder ();
278
+
279
+ // when
280
+ final var result = builder .readHex (input );
281
+
282
+ // then
283
+ assertTrue (result instanceof Operator );
284
+ final var operator = (Operator ) result ;
285
+ assertEquals (1 , operator .version ());
286
+ assertEquals (OperatorType .LESS_THAN , operator .operatorType ());
287
+ assertEquals (2 , operator .operands ().size ());
288
+ final var a = (Literal ) operator .operands ().get (0 );
289
+ assertEquals (10 , a .value ());
290
+ final var b = (Literal ) operator .operands ().get (1 );
291
+ assertEquals (20 , b .value ());
292
+ }
293
+
294
+ @ Test
295
+ public final void part1 () {
296
+ final var line = getInput ().collect (Collectors .toList ()).get (0 );
297
+ final var builder = new PacketBuilder ();
298
+ final var packet = builder .readHex (line );
299
+ class VersionSummer implements PacketVisitor {
300
+
301
+ int sum = 0 ;
302
+
303
+ public void visit (Literal literal ) {
304
+ sum += literal .version ();
305
+ }
306
+
307
+ public void enter (Operator operator ) {
308
+ }
309
+
310
+ public void exit (Operator operator ) {
311
+ sum += operator .version ();
312
+ }
313
+ }
314
+ final var summer = new VersionSummer ();
315
+ packet .accept (summer );
316
+
317
+ System .out .println ("Part 1: " + summer .sum );
318
+ }
319
+
320
+ @ Test
321
+ public final void part2 () {
322
+ final var line = getInput ().collect (Collectors .toList ()).get (0 );
323
+ final var builder = new PacketBuilder ();
324
+ final var packet = builder .readHex (line );
325
+ System .out .println ("Part 2: " + packet .evaluate ());
326
+ }
327
+
328
+ @ Nested
329
+ public class PacketBuilderTest {
330
+ @ Test
331
+ public void testToInt () {
332
+ // given
333
+ final var builder = new PacketBuilder ();
334
+ // when
335
+ // then
336
+ assertEquals (2021 , builder .toLong (Arrays .asList ((byte ) 0 , (byte ) 1 , (byte ) 1 , (byte ) 1 , (byte ) 1 , (byte ) 1 , (byte ) 1 , (byte ) 0 , (byte ) 0 , (byte ) 1 , (byte ) 0 , (byte ) 1 )));
337
+ }
338
+
339
+ @ Test
340
+ public final void testMaths () {
341
+ assertEquals (3 , new PacketBuilder ().readHex ("C200B40A82" ).evaluate ());
342
+ assertEquals (54 , new PacketBuilder ().readHex ("04005AC33890" ).evaluate ());
343
+ assertEquals (7 , new PacketBuilder ().readHex ("880086C3E88112" ).evaluate ());
344
+ assertEquals (9 , new PacketBuilder ().readHex ("CE00C43D881120" ).evaluate ());
345
+ assertEquals (1 , new PacketBuilder ().readHex ("D8005AC2A8F0" ).evaluate ());
346
+ assertEquals (0 , new PacketBuilder ().readHex ("F600BC2D8F" ).evaluate ());
347
+ assertEquals (0 , new PacketBuilder ().readHex ("9C005AC2F8F0" ).evaluate ());
348
+ assertEquals (1 , new PacketBuilder ().readHex ("9C0141080250320F1802104A08" ).evaluate ());
349
+ }
350
+ }
351
+
352
+ }
0 commit comments