1
+ package com .macasaet ;
2
+
3
+ import org .junit .jupiter .api .Assertions ;
4
+ import org .junit .jupiter .api .Test ;
5
+
6
+ import java .util .Arrays ;
7
+ import java .util .Collections ;
8
+ import java .util .HashMap ;
9
+ import java .util .List ;
10
+ import java .util .Map ;
11
+ import java .util .function .IntPredicate ;
12
+ import java .util .stream .StreamSupport ;
13
+
14
+ /**
15
+ * --- Day 15: Beacon Exclusion Zone ---
16
+ * <a href="https://adventofcode.com/2022/day/15">https://adventofcode.com/2022/day/15</a>
17
+ */
18
+ public class Day15 {
19
+
20
+ record Coordinate (int x , int y ) {
21
+ static Coordinate parse (final String string ) {
22
+ final var components = string .split (", " );
23
+ return new Coordinate (Integer .parseInt (components [1 ].replaceAll ("^y=" , "" )),
24
+ Integer .parseInt (components [0 ].replaceAll ("^x=" , "" )));
25
+ }
26
+
27
+ Map <Integer , Item > getRow (final Map <Integer , Map <Integer , Item >> grid ) {
28
+ return grid .computeIfAbsent (x (), ignored -> new HashMap <>());
29
+ }
30
+
31
+ int distanceTo (final Coordinate other ) {
32
+ return Math .abs (x () - other .x ()) + Math .abs (y () - other .y ());
33
+ }
34
+ }
35
+
36
+ public record Sensor (Coordinate location , Coordinate beaconLocation ) {
37
+ public static Sensor parse (final String line ) {
38
+ final var location = Coordinate .parse (line .replaceAll ("^Sensor at " , "" ).replaceAll (": closest beacon is at .*$" , "" ));
39
+ final var beaconLocation = Coordinate .parse (line .replaceAll ("^.*: closest beacon is at " , "" ));
40
+ return new Sensor (location , beaconLocation );
41
+ }
42
+
43
+ void setSensor (final Map <Integer , Map <Integer , Item >> grid , IntPredicate includeRow , IntPredicate includeColumn ) {
44
+ if (includeRow .test (location ().x ()) && includeColumn .test (location ().y ())) {
45
+ location ().getRow (grid ).put (location ().y (), Item .SENSOR );
46
+ }
47
+ }
48
+
49
+ void setBeacon (final Map <Integer , Map <Integer , Item >> grid , IntPredicate includeRow , IntPredicate includeColumn ) {
50
+ if (includeRow .test (beaconLocation ().x ()) && includeColumn .test (beaconLocation ().y ())) {
51
+ beaconLocation ().getRow (grid ).put (beaconLocation ().y (), Item .BEACON );
52
+ }
53
+ }
54
+
55
+ void setCoverageArea (final Map <Integer , Map <Integer , Item >> grid , IntPredicate includeRow , IntPredicate includeColumn ) {
56
+ final var distance = distanceToBeacon ();
57
+ final var x = location ().x ();
58
+ final var y = location ().y ();
59
+
60
+ for (int i = 0 ; i <= distance ; i ++ ) {
61
+ if (!includeRow .test (x + i ) && !includeRow .test (x - i )) {
62
+ continue ;
63
+ }
64
+ final var lowerRow = includeRow .test (x + i )
65
+ ? grid .computeIfAbsent (x + i , ignored -> new HashMap <>())
66
+ : new HashMap <Integer , Item >();
67
+ final var upperRow = includeRow .test (x - i )
68
+ ? grid .computeIfAbsent (x - i , ignored -> new HashMap <>())
69
+ : new HashMap <Integer , Item >();
70
+ for (int j = 0 ; j <= distance - i ; j ++ ) {
71
+ if (includeColumn .test (y + j )) {
72
+ // SE
73
+ lowerRow .putIfAbsent (y + j , Item .COVERED );
74
+ // NE
75
+ upperRow .putIfAbsent (y + j , Item .COVERED );
76
+ }
77
+ if (includeColumn .test (y - j )) {
78
+ // SW
79
+ lowerRow .putIfAbsent (y - j , Item .COVERED );
80
+
81
+ // NW
82
+ upperRow .putIfAbsent (y - j , Item .COVERED );
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ int distanceToBeacon () {
89
+ return location ().distanceTo (beaconLocation ());
90
+ }
91
+ }
92
+
93
+ enum Item {
94
+ SENSOR ,
95
+ BEACON ,
96
+ COVERED
97
+ }
98
+ public record CaveMap (Map <Integer , Map <Integer , Item >> grid , int minX , int maxX , int minY , int maxY ) {
99
+ public int countCoveredCellsInRow (final int x ) {
100
+ final var row = grid ().getOrDefault (x , Collections .emptyMap ());
101
+ int result = 0 ;
102
+ for (int j = minY (); j <= maxY (); j ++) {
103
+ final var cell = row .get (j );
104
+ if (cell != null && cell != Item .BEACON ) {
105
+ result ++;
106
+ }
107
+ }
108
+ return result ;
109
+ }
110
+
111
+ public static CaveMap fromSensors (final Iterable <? extends Sensor > sensors , IntPredicate includeRow , IntPredicate includeColumn ) {
112
+ int minX = Integer .MAX_VALUE ;
113
+ int maxX = Integer .MIN_VALUE ;
114
+ int minY = Integer .MAX_VALUE ;
115
+ int maxY = Integer .MIN_VALUE ;
116
+ final var grid = new HashMap <Integer , Map <Integer , Item >>();
117
+ for (final var sensor : sensors ) {
118
+ minX = Math .min (minX , sensor .location ().x () - sensor .distanceToBeacon ());
119
+ maxX = Math .max (maxX , sensor .location ().x () + sensor .distanceToBeacon ());
120
+ minY = Math .min (minY , sensor .location ().y () - sensor .distanceToBeacon ());
121
+ maxY = Math .max (maxY , sensor .location ().y () + sensor .distanceToBeacon ());
122
+
123
+ sensor .setCoverageArea (grid , includeRow , includeColumn );
124
+ sensor .setBeacon (grid , includeRow , includeColumn );
125
+ sensor .setSensor (grid , includeRow , includeColumn );
126
+ }
127
+
128
+ return new CaveMap (grid , minX , maxX , minY , maxY );
129
+ }
130
+
131
+ public String toString () {
132
+ final var builder = new StringBuilder ();
133
+ for (int i = minX (); i <= maxX (); i ++) {
134
+ builder .append (i ).append ('\t' );
135
+ final var row = grid ().getOrDefault (i , Collections .emptyMap ());
136
+ for (int j = minY (); j <= maxY (); j ++) {
137
+ final var item = row .get (j );
138
+ final var marker = item == null
139
+ ? '.'
140
+ : item == Item .BEACON
141
+ ? 'B'
142
+ : item == Item .SENSOR
143
+ ? 'S'
144
+ : '#' ;
145
+ builder .append (marker );
146
+ }
147
+ builder .append ('\n' );
148
+ }
149
+ return builder .toString ();
150
+ }
151
+ }
152
+
153
+ protected CaveMap getInput (final IntPredicate includeRow , IntPredicate includeColumn ) {
154
+ final var sensors = getSensors ();
155
+ return CaveMap .fromSensors (sensors , includeRow , includeColumn );
156
+ }
157
+
158
+ protected static List <Sensor > getSensors () {
159
+ return StreamSupport .stream (new LineSpliterator ("day-15.txt" ), false )
160
+ .map (Sensor ::parse )
161
+ .toList ();
162
+ }
163
+
164
+ @ Test
165
+ public final void part1 () {
166
+ final int rowOfInterest = 2_000_000 ;
167
+ final var map = getInput (row -> row == rowOfInterest , _column -> true );
168
+ final var result = map .countCoveredCellsInRow (rowOfInterest );
169
+
170
+ System .out .println ("Part 1: " + result );
171
+ }
172
+
173
+ @ Test
174
+ public final void part2 () {
175
+ final int max = 4_000_000 ;
176
+ final var sensors = getSensors ();
177
+ for (final var sensor : sensors ) {
178
+ final var x = sensor .location ().x ();
179
+ final var y = sensor .location ().y ();
180
+ final var reach = sensor .distanceToBeacon ();
181
+ // Find all the points just outside this sensor's reach
182
+ for (int horizontalOffset = 0 ; horizontalOffset <= reach + 1 ; horizontalOffset ++) {
183
+ final var verticalOffset = reach + 1 - horizontalOffset ;
184
+ Assertions .assertEquals (horizontalOffset + verticalOffset , reach + 1 );
185
+ for (final var candidate : Arrays .asList (new Coordinate (x + verticalOffset , y + horizontalOffset ), // SE
186
+ new Coordinate (x + verticalOffset , y - horizontalOffset ), // SW
187
+ new Coordinate (x - verticalOffset , y + horizontalOffset ), // NE
188
+ new Coordinate (x - verticalOffset , y - horizontalOffset ))) { // NW
189
+ if (candidate .x () < 0 || candidate .y () < 0 || candidate .x () > max || candidate .y () > max ) {
190
+ continue ;
191
+ }
192
+ Assertions .assertTrue (candidate .distanceTo (sensor .location ()) > sensor .distanceToBeacon ());
193
+ // Check if the point is also outside the reach of every other sensor
194
+ if (sensors .stream ().allMatch (other -> candidate .distanceTo (other .location ()) > other .distanceToBeacon ())) {
195
+ final long result = (long )candidate .y () * 4_000_000l + (long )candidate .x ();
196
+ System .out .println ("Part 2: " + result );
197
+ return ;
198
+ }
199
+ }
200
+ }
201
+ }
202
+ throw new IllegalStateException ("No uncovered point found" );
203
+ }
204
+
205
+ }
0 commit comments