1
+ package com .macasaet ;
2
+
3
+ import static org .junit .jupiter .api .Assertions .*;
4
+
5
+ import java .util .*;
6
+ import java .util .stream .Stream ;
7
+ import java .util .stream .StreamSupport ;
8
+
9
+ import org .junit .jupiter .api .Nested ;
10
+ import org .junit .jupiter .api .Test ;
11
+
12
+ /**
13
+ * --- Day 20: Trench Map ---
14
+ */
15
+ public class Day20 {
16
+
17
+ protected Stream <String > getInput () {
18
+ return StreamSupport
19
+ .stream (new LineSpliterator ("day-20.txt" ),
20
+ false );
21
+ }
22
+
23
+ public record ImageEnhancementAlgorithm (boolean [] map ) {
24
+
25
+ public boolean isPixelLit (final int code ) {
26
+ return map ()[code ];
27
+ }
28
+
29
+ public static ImageEnhancementAlgorithm parse (final String line ) {
30
+ final var map = new boolean [line .length ()];
31
+ for (int i = line .length (); --i >= 0 ; map [i ] = line .charAt (i ) == '#' ) ;
32
+ return new ImageEnhancementAlgorithm (map );
33
+ }
34
+ }
35
+
36
+ protected record Coordinate (int x , int y ) {
37
+ }
38
+
39
+ public record Image (SortedMap <Integer , SortedMap <Integer , Boolean >> pixels , int minX , int maxX , int minY ,
40
+ int maxY , boolean isEven ) {
41
+ public Image enhance (final ImageEnhancementAlgorithm algorithm ) {
42
+ final var enhancedPixelMap = new TreeMap <Integer , SortedMap <Integer , Boolean >>();
43
+ int enhancedMinX = minX ;
44
+ int enhancedMaxX = maxX ;
45
+ int enhancedMinY = minY ;
46
+ int enhancedMaxY = maxY ;
47
+ for (int i = minX - 1 ; i <= maxX + 1 ; i ++) {
48
+ final var targetRow = new TreeMap <Integer , Boolean >();
49
+ for (int j = minY - 1 ; j <= maxY + 1 ; j ++) {
50
+ final int replacementId = decode (i , j , !isEven && algorithm .isPixelLit (0 ));
51
+ final var shouldLight = algorithm .isPixelLit (replacementId );
52
+ if (shouldLight ) {
53
+ // save space by only storing an entry when lit
54
+ targetRow .put (j , true );
55
+ enhancedMinY = Math .min (enhancedMinY , j );
56
+ enhancedMaxY = Math .max (enhancedMaxY , j );
57
+ }
58
+ }
59
+ if (!targetRow .isEmpty ()) {
60
+ // save space by only storing a row if at least one cell is lit
61
+ enhancedPixelMap .put (i , Collections .unmodifiableSortedMap (targetRow ));
62
+ enhancedMinX = Math .min (enhancedMinX , i );
63
+ enhancedMaxX = Math .max (enhancedMaxX , i );
64
+ }
65
+ }
66
+ return new Image (Collections .unmodifiableSortedMap (enhancedPixelMap ), enhancedMinX , enhancedMaxX , enhancedMinY , enhancedMaxY , !isEven );
67
+ }
68
+
69
+ int decode (final int x , final int y , boolean voidIsLit ) {
70
+ final var list = getNeighbouringCoordinates (x , y ).stream ()
71
+ .map (coordinate -> isBitSet (coordinate , voidIsLit ))
72
+ .toList ();
73
+ int result = 0 ;
74
+ for (int i = list .size (); --i >= 0 ; ) {
75
+ if (list .get (i )) {
76
+ final int shiftDistance = list .size () - i - 1 ;
77
+ result |= 1 << shiftDistance ;
78
+ }
79
+ }
80
+ if (result < 0 || result > 512 ) {
81
+ throw new IllegalStateException ("Unable to decode pixel at " + x + ", " + y );
82
+ }
83
+ return result ;
84
+ }
85
+
86
+ boolean isBitSet (final Coordinate coordinate , boolean voidIsLit ) {
87
+ final var row = pixels ().get (coordinate .x ());
88
+ if ((coordinate .x () < minX || coordinate .x () > maxX ) && row == null ) {
89
+ return voidIsLit ;
90
+ }
91
+ else if (row == null ) {
92
+ return false ;
93
+ }
94
+ return row .getOrDefault (coordinate .y (), (coordinate .y () < minY || coordinate .y () > maxY ) && voidIsLit );
95
+ }
96
+
97
+ List <Coordinate > getNeighbouringCoordinates (int x , int y ) {
98
+ return Arrays .asList (
99
+ new Coordinate (x - 1 , y - 1 ),
100
+ new Coordinate (x - 1 , y ),
101
+ new Coordinate (x - 1 , y + 1 ),
102
+ new Coordinate (x , y - 1 ),
103
+ new Coordinate (x , y ),
104
+ new Coordinate (x , y + 1 ),
105
+ new Coordinate (x + 1 , y - 1 ),
106
+ new Coordinate (x + 1 , y ),
107
+ new Coordinate (x + 1 , y + 1 )
108
+ );
109
+ }
110
+
111
+ public long countLitPixels () {
112
+ return pixels ().values ()
113
+ .stream ()
114
+ .flatMap (row -> row .values ().stream ())
115
+ .filter (isLit -> isLit )
116
+ .count ();
117
+ }
118
+
119
+ public int width () {
120
+ return maxX - minX + 1 ;
121
+ }
122
+
123
+ public int height () {
124
+ return maxY - minY + 1 ;
125
+ }
126
+
127
+ public String toString () {
128
+ final var builder = new StringBuilder ();
129
+ builder .append (width ()).append ('x' ).append (height ()).append ('\n' );
130
+ for (int i = minX ; i <= maxX ; i ++) {
131
+ final var row = pixels .getOrDefault (i , Collections .emptySortedMap ());
132
+ for (int j = minY ; j <= maxY ; j ++) {
133
+ final var value = row .getOrDefault (j , false );
134
+ builder .append (value ? '#' : '.' );
135
+ }
136
+ builder .append ('\n' );
137
+ }
138
+ return builder .toString ();
139
+ }
140
+
141
+ public static Image parse (final List <String > lines ) {
142
+ final var pixels = new TreeMap <Integer , SortedMap <Integer , Boolean >>();
143
+ final int minX = 0 ;
144
+ final int minY = 0 ;
145
+ int maxX = 0 ;
146
+ int maxY = 0 ;
147
+ for (int i = lines .size (); --i >= 0 ; ) {
148
+ final var line = lines .get (i );
149
+ final var row = new TreeMap <Integer , Boolean >();
150
+ for (int j = line .length (); --j >= 0 ; ) {
151
+ final var pixel = line .charAt (j );
152
+ row .put (j , pixel == '#' );
153
+ if (pixel == '#' ) {
154
+ row .put (j , true );
155
+ maxY = Math .max (maxY , j );
156
+ }
157
+ }
158
+ if (!row .isEmpty ()) {
159
+ maxX = Math .max (maxX , i );
160
+ pixels .put (i , Collections .unmodifiableSortedMap (row ));
161
+ }
162
+ }
163
+ return new Image (Collections .unmodifiableSortedMap (pixels ), minX , maxX , minY , maxY , true );
164
+ }
165
+
166
+ }
167
+
168
+ @ Nested
169
+ public class ImageTest {
170
+ @ Test
171
+ public final void testToInt () {
172
+ final var string = """
173
+ #..#.
174
+ #....
175
+ ##..#
176
+ ..#..
177
+ ..###
178
+ """ ;
179
+ final var image = Image .parse (Arrays .asList (string .split ("\n " )));
180
+ assertEquals (34 , image .decode (2 , 2 , false ));
181
+ }
182
+
183
+ @ Test
184
+ public final void flipAllOn () {
185
+ final var template = "#........" ;
186
+ final var imageString = """
187
+ ...
188
+ ...
189
+ ...
190
+ """ ;
191
+ final var image = Image .parse (Arrays .asList (imageString .split ("\n " )));
192
+ final var result = image .enhance (ImageEnhancementAlgorithm .parse (template ));
193
+ assertTrue (result .pixels ().get (1 ).get (1 ));
194
+ }
195
+
196
+ @ Test
197
+ public final void turnOffPixel () {
198
+ final var templateBuilder = new StringBuilder ();
199
+ for (int i = 511 ; --i >= 0 ; templateBuilder .append ('#' )) ;
200
+ templateBuilder .append ('.' );
201
+ final var template = templateBuilder .toString ();
202
+ final var imageString = """
203
+ ###
204
+ ###
205
+ ###
206
+ """ ;
207
+ final var image = Image .parse (Arrays .asList (imageString .split ("\n " )));
208
+ final var result = image .enhance (ImageEnhancementAlgorithm .parse (template ));
209
+ final var middleRow = result .pixels ().get (1 );
210
+ assertFalse (middleRow .containsKey (1 ));
211
+ }
212
+ }
213
+
214
+ @ Test
215
+ public final void part1 () {
216
+ final var list = getInput ().toList ();
217
+ final var algorithm = ImageEnhancementAlgorithm .parse (list .get (0 ));
218
+ final var image = Image .parse (list .subList (2 , list .size ()))
219
+ .enhance (algorithm )
220
+ .enhance (algorithm );
221
+ System .out .println ("Part 1: " + image .countLitPixels ());
222
+ }
223
+
224
+ @ Test
225
+ public final void part2 () {
226
+ final var list = getInput ().toList ();
227
+ final var algorithm = ImageEnhancementAlgorithm .parse (list .get (0 ));
228
+ var image = Image .parse (list .subList (2 , list .size ()));
229
+ for (int _i = 50 ; --_i >= 0 ; image = image .enhance (algorithm ));
230
+ System .out .println ("Part 2: " + image .countLitPixels ());
231
+ }
232
+
233
+ }
0 commit comments