1
+ package com .macasaet ;
2
+
3
+ import org .junit .jupiter .api .Test ;
4
+
5
+ import java .util .ArrayList ;
6
+ import java .util .Collections ;
7
+ import java .util .HashMap ;
8
+ import java .util .List ;
9
+ import java .util .Objects ;
10
+ import java .util .PriorityQueue ;
11
+ import java .util .stream .StreamSupport ;
12
+
13
+ /**
14
+ * --- Day 12: Hill Climbing Algorithm ---
15
+ * <a href="https://adventofcode.com/2022/day/12">https://adventofcode.com/2022/day/12</a>
16
+ */
17
+ public class Day12 {
18
+
19
+ record Coordinate (int x , int y ) {
20
+ }
21
+
22
+ public record HeightMap (int [][] grid , Coordinate start , Coordinate end ) {
23
+
24
+ public int lengthOfShortestPath () {
25
+ return this .lengthOfShortestPath (this .start );
26
+ }
27
+
28
+ public int lengthOfShortestPath (final Coordinate startingPoint ) {
29
+ final var cheapestCostToNode = new HashMap <Coordinate , Integer >();
30
+ cheapestCostToNode .put (startingPoint , 0 );
31
+ final var estimatedCostToFinish = new HashMap <Coordinate , Integer >();
32
+ estimatedCostToFinish .put (startingPoint , estimateDistance (startingPoint , this .end ));
33
+ final var openSet = new PriorityQueue <Coordinate >((x , y ) -> {
34
+ final var xScore = estimatedCostToFinish .getOrDefault (x , Integer .MAX_VALUE );
35
+ final var yScore = estimatedCostToFinish .getOrDefault (y , Integer .MAX_VALUE );
36
+ return xScore .compareTo (yScore );
37
+ });
38
+ openSet .add (startingPoint );
39
+ while (!openSet .isEmpty ()) {
40
+ final var current = openSet .remove ();
41
+ if (current .equals (this .end )) {
42
+ return cheapestCostToNode .get (current );
43
+ }
44
+ for (final var neighbour : neighbours (current )) {
45
+ final var tentativeGScore = cheapestCostToNode .get (current ) + 1 ;
46
+ if (tentativeGScore < cheapestCostToNode .getOrDefault (neighbour , Integer .MAX_VALUE )) {
47
+ cheapestCostToNode .put (neighbour , tentativeGScore );
48
+ estimatedCostToFinish .put (neighbour , tentativeGScore + estimateDistance (neighbour , this .end ));
49
+ if (!openSet .contains (neighbour )) {
50
+ openSet .add (neighbour );
51
+ }
52
+ }
53
+ }
54
+ }
55
+ return Integer .MAX_VALUE ;
56
+ }
57
+
58
+ public List <Coordinate > getPotentialTrailHeads () {
59
+ final var list = new ArrayList <Coordinate >();
60
+ for (int i = this .grid ().length ; --i >= 0 ; ) {
61
+ final var row = this .grid ()[i ];
62
+ for (int j = row .length ; --j >= 0 ; ) {
63
+ if (row [j ] == 0 ) {
64
+ list .add (new Coordinate (i , j ));
65
+ }
66
+ }
67
+ }
68
+ return Collections .unmodifiableList (list );
69
+ }
70
+
71
+ int height (final Coordinate coordinate ) {
72
+ return this .grid [coordinate .x ()][coordinate .y ()];
73
+ }
74
+
75
+ List <Coordinate > neighbours (final Coordinate coordinate ) {
76
+ final var list = new ArrayList <Coordinate >(4 );
77
+ if (coordinate .x () > 0 ) {
78
+ final var up = new Coordinate (coordinate .x () - 1 , coordinate .y ());
79
+ if (height (coordinate ) + 1 >= height (up )) {
80
+ list .add (up );
81
+ }
82
+ }
83
+ if (coordinate .x () < this .grid .length - 1 ) {
84
+ final var down = new Coordinate (coordinate .x () + 1 , coordinate .y ());
85
+ if (height (coordinate ) + 1 >= height (down )) {
86
+ list .add (down );
87
+ }
88
+ }
89
+ if (coordinate .y () > 0 ) {
90
+ final var left = new Coordinate (coordinate .x (), coordinate .y () - 1 );
91
+ if (height (coordinate ) + 1 >= height (left )) {
92
+ list .add (left );
93
+ }
94
+ }
95
+ final var row = this .grid [coordinate .x ()];
96
+ if (coordinate .y () < row .length - 1 ) {
97
+ final var right = new Coordinate (coordinate .x (), coordinate .y () + 1 );
98
+ if (height (coordinate ) + 1 >= height (right )) {
99
+ list .add (right );
100
+ }
101
+ }
102
+ return Collections .unmodifiableList (list );
103
+ }
104
+
105
+ int estimateDistance (final Coordinate from , final Coordinate to ) {
106
+ return (int ) Math .sqrt (Math .pow (from .x () - to .x (), 2.0 ) + Math .pow (from .y () - to .y (), 2.0 ));
107
+ }
108
+
109
+ }
110
+
111
+ protected HeightMap getInput () {
112
+ final var charGrid = StreamSupport .stream (new LineSpliterator ("day-12.txt" ), false ).map (line -> {
113
+ final var list = new ArrayList <Character >(line .length ());
114
+ for (final var c : line .toCharArray ()) {
115
+ list .add (c );
116
+ }
117
+ return list ;
118
+ }).toList ();
119
+ Coordinate origin = null ;
120
+ Coordinate destination = null ;
121
+ int [][] grid = new int [charGrid .size ()][];
122
+ for (int i = charGrid .size (); --i >= 0 ; ) {
123
+ final var row = charGrid .get (i );
124
+ grid [i ] = new int [row .size ()];
125
+ for (int j = row .size (); --j >= 0 ; ) {
126
+ final char c = row .get (j );
127
+ if (c == 'S' ) {
128
+ origin = new Coordinate (i , j );
129
+ grid [i ][j ] = 0 ;
130
+ } else if (c == 'E' ) {
131
+ destination = new Coordinate (i , j );
132
+ grid [i ][j ] = 'z' - 'a' ;
133
+ } else {
134
+ grid [i ][j ] = c - 'a' ;
135
+ }
136
+ }
137
+ }
138
+ Objects .requireNonNull (origin );
139
+ Objects .requireNonNull (destination );
140
+ return new HeightMap (grid , origin , destination );
141
+ }
142
+
143
+ @ Test
144
+ public final void part1 () {
145
+ final var map = getInput ();
146
+ final var result = map .lengthOfShortestPath ();
147
+ System .out .println ("Part 1: " + result );
148
+ }
149
+
150
+ @ Test
151
+ public final void part2 () {
152
+ final var map = getInput ();
153
+ var result = Integer .MAX_VALUE ;
154
+ for (final var candidate : map .getPotentialTrailHeads ()) {
155
+ final var length = map .lengthOfShortestPath (candidate );
156
+ if (length < result ) {
157
+ result = length ;
158
+ }
159
+ }
160
+
161
+ System .out .println ("Part 2: " + result );
162
+ }
163
+
164
+ }
0 commit comments