1
+ package com .macasaet ;
2
+
3
+ import java .util .*;
4
+ import java .util .stream .Collectors ;
5
+ import java .util .stream .Stream ;
6
+ import java .util .stream .StreamSupport ;
7
+
8
+ import org .junit .jupiter .api .Test ;
9
+
10
+ /**
11
+ * --- Day 15: Chiton ---
12
+ */
13
+ public class Day15 {
14
+
15
+ protected Stream <String > getInput () {
16
+ return StreamSupport
17
+ .stream (new LineSpliterator ("day-15.txt" ),
18
+ false );
19
+ }
20
+
21
+ protected int [][] getGrid () {
22
+ final var list = getInput ().collect (Collectors .toList ());
23
+ final int [][] grid = new int [list .size ()][];
24
+ for (int i = 0 ; i < grid .length ; i ++) {
25
+ final var chars = list .get (i ).toCharArray ();
26
+ final var row = new int [chars .length ];
27
+ for (int j = chars .length ; --j >= 0 ; row [j ] = chars [j ] - '0' ) ;
28
+ grid [i ] = row ;
29
+ }
30
+ return grid ;
31
+ }
32
+
33
+ public record Point (int x , int y ) {
34
+
35
+ public int risk (int [][] risks ) {
36
+ return risks [x ][y ];
37
+ }
38
+
39
+ }
40
+
41
+ public record Cavern (int [][] grid ) {
42
+
43
+ public Set <Point > predecessors (final Point source ) {
44
+ final var result = new HashSet <Point >();
45
+ if (source .x () > 0 ) {
46
+ result .add (new Point (source .x () - 1 , source .y ()));
47
+ }
48
+ if (source .y () > 0 ) {
49
+ result .add (new Point (source .x (), source .y () - 1 ));
50
+ }
51
+ return Collections .unmodifiableSet (result );
52
+ }
53
+
54
+ public Set <Point > successors (final Point source ) {
55
+ final var result = new HashSet <Point >();
56
+ if (source .x () < grid ().length - 1 ) {
57
+ result .add (new Point (source .x () + 1 , source .y ()));
58
+ }
59
+ if (source .y () < grid ()[source .x ()].length - 1 ) {
60
+ result .add (new Point<
10000
/span>(source .x (), source .y () + 1 ));
61
+ }
62
+ return Collections .unmodifiableSet (result );
63
+ }
64
+
65
+ public Cavern explode () {
66
+ final int [][] largeGrid = new int [grid .length * 5 ][];
67
+ for (int i = largeGrid .length ; --i >= 0 ; ) {
68
+ largeGrid [i ] = new int [grid .length * 5 ];
69
+ }
70
+ for (int i = grid .length ; --i >= 0 ; ) {
71
+ for (int j = grid .length ; --j >= 0 ; ) {
72
+ largeGrid [i ][j ] = grid [i ][j ];
73
+ }
74
+ }
75
+ for (int tileRow = 0 ; tileRow < 5 ; tileRow ++) {
76
+ for (int tileColumn = 0 ; tileColumn < 5 ; tileColumn ++) {
77
+ if (tileRow > 0 ) {
78
+ for (int i = grid .length ; --i >= 0 ; ) {
79
+ for (int j = grid .length ; --j >= 0 ; ) {
80
+ // copy from row above
81
+ int value = largeGrid [(tileRow - 1 ) * grid .length + i ][tileColumn * grid .length + j ] + 1 ;
82
+ if (value == 10 ) {
83
+ value = 1 ;
84
+ }
85
+ largeGrid [tileRow * grid .length + i ][tileColumn * grid .length + j ] = value ;
86
+ }
87
+ }
88
+ } else if (tileColumn > 0 ) {
89
+ for (int i = grid .length ; --i >= 0 ; ) {
90
+ for (int j = grid .length ; --j >= 0 ; ) {
91
+ // copy from column to the left
92
+ int value = largeGrid [tileRow * grid .length + i ][(tileColumn - 1 ) * grid .length + j ] + 1 ;
93
+ if (value == 10 ) {
94
+ value = 1 ;
95
+ }
96
+ largeGrid [tileRow * grid .length + i ][tileColumn * grid .length + j ] = value ;
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ return new Cavern (largeGrid );
103
+ }
104
+
105
+ public long [][] calculateCumulativeRisk () {
106
+ final var cumulative = new long [grid ().length ][];
107
+ for (int i = cumulative .length ; --i >= 0 ; cumulative [i ] = new long [grid ()[i ].length ]) ;
108
+ final var visited = new HashSet <Point >();
109
+ final var queue = new LinkedList <Point >();
110
+ final var destination = new Point (grid ().length - 1 , grid ()[grid ().length - 1 ].length - 1 );
111
+ queue .add (destination );
112
+ visited .add (destination );
113
+
114
+ while (!queue .isEmpty ()) {
115
+ final var node = queue .remove ();
116
+ final var successors = successors (node );
117
+ if (successors .isEmpty ()) {
118
+ // destination
119
+ cumulative [node .x ][node .y ] = node .risk (grid ());
120
+ } else {
121
+ var minSuccessorRisk = Long .MAX_VALUE ;
122
+ for (final var successor : successors ) {
123
+ if (!visited .contains (successor )) {
124
+ throw new IllegalStateException ("Successor has not been visited" );
125
+ }
126
+ minSuccessorRisk = Math .min (minSuccessorRisk , cumulative [successor .x ][successor .y ]);
127
+ }
128
+ cumulative [node .x ][node .y ] = node .risk (grid ()) + minSuccessorRisk ;
129
+ }
130
+
131
+ for (final var predecessor : predecessors (node )) {
132
+ if (!visited .contains (predecessor )) {
133
+ queue .add (predecessor );
134
+ visited .add (predecessor );
135
+ }
136
+ }
137
+ }
138
+ return cumulative ;
139
+ }
140
+
141
+ /**
142
+ * @return the risk level associated with the path through the cavern that avoids the most chitons
143
+ */
144
+ public int lowestRiskThroughTheCavern () {
145
+ // the lowest known risk from origin to a given node
146
+ final var lowestRiskToNode = new HashMap <Point , Integer >();
147
+ // the estimated risk from origin to destination if it goes through a given node
148
+ final var estimatedRiskThroughNode = new HashMap <Point , Integer >();
149
+ final var openSet = new PriorityQueue <Point >(Comparator .comparing (estimatedRiskThroughNode ::get ));
150
+
151
+ for (int i = grid ().length ; --i >= 0 ; ) {
152
+ final var row = grid ()[i ];
153
+ for (int j = row .length ; --j >= 0 ; ) {
154
+ final var point = new Point (i , j );
155
+ if (i == 0 && j == 0 ) {
156
+ lowestRiskToNode .put (point , 0 );
157
+ estimatedRiskThroughNode .put (point , manhattanDistance (point ));
158
+ openSet .add (point );
159
+ } else {
160
+ lowestRiskToNode .put (point , Integer .MAX_VALUE );
161
+ estimatedRiskThroughNode .put (point , Integer .MAX_VALUE );
162
+ }
163
+ }
164
+ }
165
+
166
+ while (!openSet .isEmpty ()) {
167
+ final var <
10000
span class=pl-s1>current = openSet .poll ();
168
+ if (current .x () == grid ().length - 1 && current .y () == grid ()[grid ().length - 1 ].length - 1 ) {
169
+ return lowestRiskToNode .get (current );
170
+ }
171
+ final var lowestRiskToCurrent = lowestRiskToNode .get (current );
172
+ for (final var neighbour : neighbours (current )) {
173
+ final var tentativeRisk = lowestRiskToCurrent + neighbour .risk (grid ());
174
+ if (tentativeRisk < lowestRiskToNode .get (neighbour )) {
175
+ lowestRiskToNode .put (neighbour , tentativeRisk );
176
+ estimatedRiskThroughNode .put (neighbour , tentativeRisk + manhattanDistance (neighbour ));
177
+ if (!openSet .contains (neighbour )) {
178
+ openSet .add (neighbour );
179
+ }
180
+ }
181
+ }
182
+ }
183
+ throw new IllegalStateException ("No path out of the cavern!" );
184
+ }
185
+
186
+ /**
187
+ * @param point
188
+ * @return
189
+ */
190
+ protected int manhattanDistance (Point point ) {
191
+ return Math .abs (point .x () - (grid ().length - 1 ))
192
+ + Math .abs (point .y () - (grid ()[grid ().length - 1 ].length - 1 ));
193
+ }
194
+
195
+ public Set <Point > neighbours (final Point point ) {
196
+ final var result = new HashSet <Point >();
197
+ if (point .x () > 0 ) {
198
+ result .add (new Point (point .x () - 1 , point .y ()));
199
+ }
200
+ if (point .x () < grid ().length - 1 ) {
201
+ result .add (new Point (point .x () + 1 , point .y ()));
202
+ }
203
+ if (point .y () > 0 ) {
204
+ result .add (new Point (point .x (), point .y () - 1 ));
205
+ }
206
+ if (point .y () < grid ()[point .x ()].length - 1 ) {
207
+ result .add (new Point (point .x (), point .y () + 1 ));
208
+ }
209
+ return Collections .unmodifiableSet (result );
210
+ }
211
+
212
+ }
213
+
214
+ @ Test
215
+ public final void part1 () {
216
+ final var grid = getGrid ();
217
+ final var cavern = new Cavern (grid );
218
+ System .out .println ("Part 1: " + cavern .lowestRiskThroughTheCavern ());
219
+ }
220
+
221
+ @ Test
222
+ public final void part2 () {
223
+ final var grid = getGrid ();
224
+ final var cavern = new Cavern (grid ).explode ();
225
+ System .out .println ("Part 2: " + cavern .lowestRiskThroughTheCavern ());
226
+ }
227
+
228
+ }
0 commit comments