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
+ }
0 commit comments