Skip to content

Commit 128d421

Browse files
committed
Day 15
1 parent 4aa6730 commit 128d421

File tree

2 files changed

+219
-0
lines changed

2 files changed

+219
-0
lines changed

src/test/java/com/macasaet/Day15.java

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
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+
}

src/test/resources/sample/day-15.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Sensor at x=2, y=18: closest beacon is at x=-2, y=15
2+
Sensor at x=9, y=16: closest beacon is at x=10, y=16
3+
Sensor at x=13, y=2: closest beacon is at x=15, y=3
4+
Sensor at x=12, y=14: closest beacon is at x=10, y=16
5+
Sensor at x=10, y=20: closest beacon is at x=10, y=16
6+
Sensor at x=14, y=17: closest beacon is at x=10, y=16
7+
Sensor at x=8, y=7: closest beacon is at x=2, y=10
8+
Sensor at x=2, y=0: closest beacon is at x=2, y=10
9+
Sensor at x=0, y=11: closest beacon is at x=2, y=10
10+
Sensor at x=20, y=14: closest beacon is at x=25, y=17
11+
Sensor at x=17, y=20: closest beacon is at x=21, y=22
12+
Sensor at x=16, y=7: closest beacon is at x=15, y=3
13+
Sensor at x=14, y=3: closest beacon is at x=15, y=3
14+
Sensor at x=20, y=1: closest beacon is at x=15, y=3

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy