66import java .awt .geom .Point2D ;
77import java .util .ArrayDeque ;
88import java .util .ArrayList ;
9+ import java .util .Arrays ;
910import java .util .Collection ;
10- import java .util .Collections ;
1111import java .util .HashMap ;
1212import java .util .List ;
1313import java .util .Map ;
14+ import java .util .Objects ;
15+ import java .util .Set ;
1416import java .util .regex .Matcher ;
1517import java .util .regex .Pattern ;
1618import java .util .stream .Collector ;
@@ -56,7 +58,7 @@ protected String part2(List<String> input) {
5658 placedTiles .put (new Point2D .Double (0 , 0 ), tiles .get (firstTile ));
5759 System .out .println (tiles .get (firstTile ).raw .stream ().collect (Collectors .joining ("\n " )));
5860 System .out .println ();
59- for (Integer hash : tiles .get (firstTile ).hashCircle ) {
61+ for (Integer hash : tiles .get (firstTile ).getAllHashes () ) {
6062 List <Tile > matchingTiles = tilesByHash .get (hash );
6163 if (matchingTiles .size () > 1 ) {
6264 tiles .get (firstTile ).setOrientation (Direction .RIGHT , hash );
@@ -94,7 +96,7 @@ protected String part2(List<String> input) {
9496 done = false ;
9597 int row = 0 ;
9698 while (!done ) {
97- int hash = lastPlacedTile .getNextHash (Direction .UP );
99+ int hash = lastPlacedTile .getNextHash (Direction .DOWN );
98100 final long lastId = lastPlacedTile .id ;
99101 List <Tile > matchingTiles = tilesByHash .get (hash );
100102 if (matchingTiles .size () == 1 ) {
@@ -103,7 +105,7 @@ protected String part2(List<String> input) {
103105 else {
104106 row ++;
105107 Tile nextTile = matchingTiles .stream ().filter (t -> t .id != lastId ).findAny ().get ();
106- nextTile .setOrientation (Direction .DOWN , hash );
108+ nextTile .setOrientation (Direction .UP , hash );
107109 placedTiles .put (new Point2D .Double (column , row ), nextTile );
108110 lastPlacedTile = nextTile ;
109111 }
@@ -112,7 +114,7 @@ protected String part2(List<String> input) {
112114 // Set the bottom edge pieces
113115 done = false ;
114116 while (!done ) {
115- int hash = lastPlacedTile .getNextHash (Direction .RIGHT );
117+ int hash = lastPlacedTile .getNextHash (Direction .LEFT );
116118 final long lastId = lastPlacedTile .id ;
117119 List <Tile > matchingTiles = tilesByHash .get (hash );
118120 if (matchingTiles .size () == 1 ) {
@@ -121,7 +123,7 @@ protected String part2(List<String> input) {
121123 else {
122124 column --;
123125 Tile nextTile = matchingTiles .stream ().filter (t -> t .id != lastId ).findAny ().get ();
124- nextTile .setOrientation (Direction .LEFT , hash );
126+ nextTile .setOrientation (Direction .RIGHT , hash );
125127 placedTiles .put (new Point2D .Double (column , row ), nextTile );
126128 lastPlacedTile = nextTile ;
127129 }
@@ -182,13 +184,9 @@ private Map<Integer, List<Tile>> buildTilesByHash(Collection<Tile> tiles) {
182184 tiles .stream ().forEach (Tile ::calculateHashes );
183185 Map <Integer , List <Tile >> tilesByHash = new HashMap <>();
184186 for (Tile t : tiles ) {
185- for (Integer h : t .hashCircle ) {
186- tilesByHash .putIfAbsent (h , new ArrayList <>());
187- tilesByHash .get (h ).add (t );
188- }
189- for (Integer h : t .hashCircleFlipped ) {
190- tilesByHash .putIfAbsent (h , new ArrayList <>());
191- tilesByHash .get (h ).add (t );
187+ for (Integer hash : t .getAllHashes ()) {
188+ tilesByHash .putIfAbsent (hash , new ArrayList <>());
189+ tilesByHash .get (hash ).add (t );
192190 }
193191 }
194192 return tilesByHash ;
@@ -211,11 +209,10 @@ private Map<Tile.Location, List<Long>> classifyTiles(Collection<Tile> tiles, Map
211209 public static class Tile {
212210 public enum Location {CORNER , EDGE , MIDDLE }
213211 List <String > raw ;
214- List <String > rawRotatedCW ;
215212 List <String > finalOrientation ;
213+ Map <Direction , Integer > finalHashCircle ;
216214 long id ;
217- List <Integer > hashCircle ;
218- List <Integer > hashCircleFlipped ;
215+ List <Hash > hashCircle ;
219216 boolean usingFlippedHashCircle ;
220217 Location location ;
221218
@@ -224,7 +221,6 @@ public Tile(long id) {
224221 this .raw = new ArrayList <>();
225222 this .location = Location .MIDDLE ;
226223 this .hashCircle = new ArrayList <>();
227- this .hashCircleFlipped = new ArrayList <>();
228224 this .usingFlippedHashCircle = false ;
229225 this .finalOrientation = null ;
230226 }
@@ -245,39 +241,30 @@ public Location getLocation() {
245241 return location ;
246242 }
247243
248- public void setOrientation (Direction d , int hash ) {
249- List <Integer > hashes ;
250- if (hashCircleFlipped .contains (hash )) {
251- usingFlippedHashCircle = true ;
252- hashes = hashCircleFlipped ;
253- }
254- else {
255- usingFlippedHashCircle = false ;
256- hashes = hashCircle ;
257- }
258- int hashPosition = hashes .indexOf (hash );
259- switch (d ) {
260- case LEFT -> {
261- Collections .rotate (hashes , -hashPosition );
262- setFinalOrientation (-hashPosition );
263- }
264- case UP -> {
265- Collections .rotate (hashes , -hashPosition + 1 );
266- setFinalOrientation (-hashPosition + 1 );
267- }
268- case RIGHT -> {
269- Collections .rotate (hashes , -hashPosition + 2 );
270- setFinalOrientation (-hashPosition + 2 );
271- }
272- case DOWN -> {
273- Collections .rotate (hashes , -hashPosition + 3 );
274- setFinalOrientation (-hashPosition + 3 );
275- }
244+ public void calculateHashes () {
245+ List <String > rotatedCW = rotateTileCW (raw );
246+ hashCircle .add (new Hash (rotatedCW .get (0 ), Direction .LEFT ));
247+ hashCircle .add (new Hash (raw .get (0 ), Direction .UP ));
248+ hashCircle .add (new Hash (rotatedCW .get (rotatedCW .size () - 1 ), Direction .RIGHT ));
249+ hashCircle .add (new Hash (raw .get (raw .size () - 1 ), Direction .DOWN ));
250+ for (int i = 0 ; i < 4 ; i ++) {
251+ hashCircle .get (i ).setRightNeighbor (hashCircle .get ((i + 1 ) % 4 ));
276252 }
277253 }
278254
279- private void setFinalOrientation (int rotationDistance ) {
280- if (usingFlippedHashCircle ) {
255+ public Set <Integer > getAllHashes () {
256+ return Stream .concat (hashCircle .stream ().map (h -> h .hash ), hashCircle .stream ().map (h -> h .flippedHash )).collect (Collectors .toSet ());
257+ }
258+
259+ public void setOrientation (Direction d , int hash ) {
260+ Hash matchingHash = hashCircle .stream ().filter (h -> h .containsHash (hash )).findAny ().get ();
261+ int rotationDistance = matchingHash .rotateTo (d , d );
262+ setFinalOrientation (rotationDistance , matchingHash .flippedFromRaw );
263+ }
264+
265+ private void setFinalOrientation (int rotationDistance , boolean flippedFromRaw ) {
266+ List <String > rawRotatedCW = rotateTileCW (raw );
267+ if (flippedFromRaw ) {
281268 finalOrientation = switch (rotationDistance ) {
282269 case 0 , 4 , -4 -> raw .stream ().map (Tile ::reverseString ).collect (Collectors .toList ());
283270 case 1 , -3 -> reverseStream (rawRotatedCW .stream ()).collect (Collectors .toList ());
@@ -309,45 +296,14 @@ private static <T> Stream<T> reverseStream(Stream<T> s) {
309296 }
310297
311298 public int getNextHash (Direction d ) {
312- List <Integer > hashes ;
313- if (usingFlippedHashCircle ) {
314- hashes = hashCircleFlipped ;
315- }
316- else {
317- hashes = hashCircle ;
318- }
319- return switch (d ) {
320- case LEFT -> hashes .get (0 );
321- case UP -> hashes .get (1 );
322- case RIGHT -> hashes .get (2 );
323- case DOWN -> hashes .get (3 );
324- default -> hashes .get (0 );
325- };
299+ Hash matchingHash = hashCircle .stream ().filter (h -> h .getDirection () == d ).findAny ().get ();
300+ return matchingHash .getHash ();
326301 }
327302
328- public void calculateHashes () {
329- rawRotatedCW = rotateTileCW ();
330- hashCircle .add (rawRotatedCW .get (0 ).hashCode ());
331- hashCircle .add (raw .get (0 ).hashCode ());
332- hashCircle .add (reverseString (rawRotatedCW .get (rawRotatedCW .size () - 1 )).hashCode ());
333- hashCircle .add (reverseString (raw .get (raw .size () - 1 )).hashCode ());
334- hashCircleFlipped .add (rawRotatedCW .get (rawRotatedCW .size () - 1 ).hashCode ());
335- hashCircleFlipped .add (reverseString (raw .get (0 )).hashCode ());
336- hashCircleFlipped .add (reverseString (rawRotatedCW .get (0 )).hashCode ());
337- hashCircleFlipped .add (raw .get (raw .size () - 1 ).hashCode ());
338- if (Stream .concat (hashCircle .stream (), hashCircleFlipped .stream ()).distinct ().count () < 8L ) {
339- System .out .println (this .id + " has duplicate edge hashes!" );
340- }
341- }
342-
343- private static String reverseString (String s ) {
344- return new StringBuilder (s ).reverse ().toString ();
345- }
346-
347- private List <String > rotateTileCW () {
348- char [][] mat = new char [raw .size ()][];
349- for (int i = 0 ; i < raw .size (); i ++) {
350- mat [i ] = raw .get (i ).toCharArray ();
303+ private List <String > rotateTileCW (List <String > image ) {
304+ char [][] mat = new char [image .size ()][];
305+ for (int i = 0 ; i < image .size (); i ++) {
306+ mat [i ] = image .get (i ).toCharArray ();
351307 }
352308 //https://stackoverflow.com/a/2800033
353309 final int M = mat .length ;
@@ -358,11 +314,11 @@ private List<String> rotateTileCW() {
358314 ret [c ][M -1 -r ] = mat [r ][c ];
359315 }
360316 }
361- List < String > rotatedTile = new ArrayList <>( );
362- for ( int i = 0 ; i < ret . length ; i ++) {
363- rotatedTile . add ( new String ( ret [ i ]));
364- }
365- return rotatedTile ;
317+ return Arrays . stream ( ret ). map ( String :: new ). collect ( Collectors . toList () );
318+ }
319+
320+ private static String reverseString ( String s ) {
321+ return new StringBuilder ( s ). reverse (). toString () ;
366322 }
367323
368324 @ Override
@@ -374,5 +330,108 @@ public String toString() {
374330 return finalOrientation .stream ().map (s -> " " + s + " " ).collect (Collectors .joining ("\n " ));
375331 }
376332 }
333+
334+ static class Hash {
335+ int hash ;
336+ int flippedHash ;
337+ Direction direction ;
338+ boolean flipped ;
339+ boolean flippedFromRaw ;
340+ private static final Set <Direction > TOP_LEFT = Set .of (Direction .UP , Direction .LEFT );
341+ private static final Set <Direction > BOTTOM_RIGHT = Set .of (Direction .DOWN , Direction .RIGHT );
342+ Hash rightNeighbor ;
343+
344+ public Hash (String edge , Direction direction ) {
345+ this .direction = direction ;
346+ if (direction == Direction .DOWN || direction == Direction .RIGHT ) {
347+ this .flippedHash = edge .hashCode ();
348+ this .hash = reverseString (edge ).hashCode ();
349+ this .flipped = true ;
350+ }
351+ else {
352+ this .hash = edge .hashCode ();
353+ this .flippedHash = reverseString (edge ).hashCode ();
354+ this .flipped = false ;
355+ }
356+ }
357+
358+ public void setRightNeighbor (Hash rightNeighbor ) {
359+ this .rightNeighbor = rightNeighbor ;
360+ }
361+
362+ public int getHash () {
363+ return flipped ? flippedHash : hash ;
364+ }
365+
366+ public Direction getDirection () {
367+ return direction ;
368+ }
369+
370+ public boolean containsHash (int hashcode ) {
371+ return hash == hashcode || flippedHash == hashcode ;
372+ }
373+
374+ public boolean isFlippedFromRaw () {
375+ return flippedFromRaw ;
376+ }
377+
378+ /**
379+ *
380+ * @param originalNewSide
381+ * @param newSide
382+ * @return rotationDistance, +CW -CCW
383+ */
384+ public int rotateTo (Direction originalNewSide , Direction newSide ) {
385+ int rotationDistance = direction .rotation90Distance (newSide );
386+ if (rotationDistance == 0 ) {
387+ return 0 ;
388+ }
389+ if (TOP_LEFT .contains (direction ) && BOTTOM_RIGHT .contains (newSide ) ||
390+ BOTTOM_RIGHT .contains (direction ) && TOP_LEFT .contains (newSide )) {
391+ flipped = !flipped ;
392+ flippedFromRaw = true ;
393+ }
394+ direction = newSide ;
395+ Direction neighborsNewSide = newSide .rotateRight90 ();
396+ if (neighborsNewSide != originalNewSide ) {
397+ rightNeighbor .rotateTo (originalNewSide , neighborsNewSide );
398+ }
399+ return rotationDistance ;
400+ }
401+
402+ @ Override
403+ public int hashCode () {
404+ int h = 7 ;
405+ h = 41 * h + this .hash ;
406+ h = 41 * h + this .flippedHash ;
407+ h = 41 * h + Objects .hashCode (this .direction );
408+ h = 41 * h + (this .flipped ? 1 : 0 );
409+ return h ;
410+ }
411+
412+ @ Override
413+ public boolean equals (Object obj ) {
414+ if (this == obj ) {
415+ return true ;
416+ }
417+ if (obj == null ) {
418+ return false ;
419+ }
420+ if (getClass () != obj .getClass ()) {
421+ return false ;
422+ }
423+ final Hash other = (Hash ) obj ;
424+ if (this .hash != other .hash ) {
425+ return false ;
426+ }
427+ if (this .flippedHash != other .flippedHash ) {
428+ return false ;
429+ }
430+ if (this .flipped != other .flipped ) {
431+ return false ;
432+ }
433+ return this .direction == other .direction ;
434+ }
435+ }
377436 }
378437}
0 commit comments