Skip to content

Commit 84d347b

Browse files
committed
Day 9
1 parent 721dc53 commit 84d347b

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

src/test/java/com/macasaet/Day09.java

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package com.macasaet;
2+
3+
import java.util.*;
4+
import java.util.function.Function;
5+
import java.util.stream.Collectors;
6+
import java.util.stream.IntStream;
7+
import java.util.stream.Stream;
8+
import java.util.stream.StreamSupport;
9+
10+
import org.junit.jupiter.api.Test;
11+
12+
/**
13+
* --- Day 9: Smoke Basin ---
14+
*/
15+
public class Day09 {
16+
17+
protected Stream<String> getInput() {
18+
return StreamSupport
19+
.stream(new LineSpliterator("day-09.txt"),
20+
false);
21+
}
22+
23+
public HeightMap getHeightMap() {
24+
final var list = getInput().map(line -> {
25+
final var chars = line.toCharArray();
26+
final var ints = new int[chars.length];
27+
for (int i = chars.length; --i >= 0; ints[i] = chars[i] - '0') ;
28+
return ints;
29+
}).collect(Collectors.toList());
30+
final int[][] grid = new int[list.size()][];
31+
for (int i = list.size(); --i >= 0; grid[i] = list.get(i)) ;
32+
return new HeightMap(grid);
33+
}
34+
35+
/**
36+
* A height map of the floor of the nearby caves generated by the submarine
37+
*/
38+
public record HeightMap(int[][] grid) { // FIXME use bytes
39+
40+
public Stream<Point> points() {
41+
return IntStream.range(0, grid().length)
42+
.boxed()
43+
.flatMap(i -> IntStream.range(0, grid()[i].length)
44+
.mapToObj(j -> new Point(i, j)));
45+
}
46+
47+
/**
48+
* A location on the floor of a nearby cave
49+
*/
50+
public class Point {
51+
final int x;
52+
final int y;
53+
54+
public Point(final int x, final int y) {
55+
this.x = x;
56+
this.y = y;
57+
}
58+
59+
public int x() {
60+
return this.x;
61+
}
62+
63+
public int y() {
64+
return this.y;
65+
}
66+
67+
public int getBasinSize() {
68+
return getBasinPoints().size();
69+
}
70+
71+
/**
72+
* Identify all the higher points that are also part of the same basin, assuming this location is part of a
73+
* basin.
74+
*
75+
* @return all the higher points, if any, that are part of the same basin.
76+
*/
77+
public Set<Point> getBasinPoints() {
78+
if (getHeight() >= 9) {
79+
return Collections.emptySet();
80+
}
81+
final var result = new HashSet<Point>();
82+
result.add(this);
83+
final Function<Point, Stream<Point>> basinPointRetriever = neighbour -> {
84+
if (neighbour.getHeight() >= 9 || neighbour.getHeight() <= getHeight() || result.contains(neighbour)) {
85+
return Stream.empty();
86+
}
87+
return neighbour.getBasinPoints().stream();
88+
};
89+
above().stream().flatMap(basinPointRetriever).forEach(result::add);
90+
below().stream().flatMap(basinPointRetriever).forEach(result::add);
91+
left().stream().flatMap(basinPointRetriever).forEach(result::add);
92+
right().stream().flatMap(basinPointRetriever).forEach(result::add);
93+
return Collections.unmodifiableSet(result);
94+
}
95+
96+
/**
97+
* @return true if and only if this location is lower than all of its adjacent locations (up to four,
98+
* diagonals do not count)
99+
*/
100+
public boolean isLowPoint() {
101+
final var compareTo = new ArrayList<Point>(4);
102+
above().ifPresent(compareTo::add);
103+
below().ifPresent(compareTo::add);
104+
left().ifPresent(compareTo::add);
105+
right().ifPresent(compareTo::add);
106+
return compareTo.stream().allMatch(neighbour -> neighbour.getHeight() > getHeight());
107+
}
108+
109+
/**
110+
* @return an assessment of the risk from smoke flowing through the cave
111+
*/
112+
public int getRiskLevel() {
113+
return getHeight() + 1;
114+
}
115+
116+
/**
117+
* @return the height of this particular location, from 0-9
118+
*/
119+
public int getHeight() {
120+
return grid()[x()][y()];
121+
}
122+
123+
public Optional<Point> above() {
124+
return x() > 0 ? Optional.of(new Point(x() - 1, y())) : Optional.empty();
125+
}
126+
127+
public Optional<Point> below() {
128+
return x() < grid().length - 1 ? Optional.of(new Point(x() + 1, y())) : Optional.empty();
129+
}
130+
131+
public Optional<Point> left() {
132+
return y() > 0 ? Optional.of(new Point(x(), y() - 1)) : Optional.empty();
133+
}
134+
135+
public Optional<Point> right() {
136+
return y() < grid()[x()].length - 1 ? Optional.of(new Point(x(), y() + 1)) : Optional.empty();
137+
}
138+
139+
public int hashCode() {
140+
return Objects.hash(x(), y());
141+
}
142+
143+
public boolean equals(final Object o) {
144+
try {
145+
final Point other = (Point) o;
146+
return this.x() == other.x() && this.y() == other.y();
147+
} catch (final ClassCastException cce) {
148+
return false;
149+
}
150+
}
151+
}
152+
153+
}
154+
155+
@Test
156+
public final void part1() {
157+
final var map = getHeightMap();
158+
final int sum = map.points()
159+
.filter(HeightMap.Point::isLowPoint)
160+
.mapToInt(HeightMap.Point::getRiskLevel)
161+
.sum();
162+
System.out.println("Part 1: " + sum);
163+
}
164+
165+
@Test
166+
public final void part2() {
167+
final var map = getHeightMap();
168+
final var basinSizes = map.points()
169+
.filter(HeightMap.Point::isLowPoint)
170+
.mapToInt(HeightMap.Point::getBasinSize)
171+
.collect(() -> new TreeSet<Integer>(Comparator.reverseOrder()), SortedSet::add, SortedSet::addAll);
172+
final var iterator = basinSizes.iterator();
173+
final var result = iterator.next() * iterator.next() * iterator.next();
174+
System.out.println("Part 2: " + result);
175+
}
176+
177+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2199943210
2+
3987894921
3+
9856789892
4+
8767896789
5+
9899965678

0 commit comments

Comments
 (0)