Skip to content

Commit

Permalink
feat: solve day 20 part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
Flashky committed Dec 20, 2024
1 parent b98e20a commit 1804c02
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 10 deletions.
175 changes: 175 additions & 0 deletions src/main/java/com/adventofcode/flashk/day20/RaceCondition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package com.adventofcode.flashk.day20;

import com.adventofcode.flashk.common.Vector2;
import com.adventofcode.flashk.day18.ByteCell;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;

public class RaceCondition {

private Set<Vector2> directions = Set.of(Vector2.right(), Vector2.left(), Vector2.up(), Vector2.down());

private int rows;
private int cols;

private RaceTile[][] map;

private int baseTime = -1; // So we don't count Start tile
private RaceTile start;
private RaceTile end;

private Set<RaceTile> cheatableWalls = new HashSet<>();
Map<Long, Long> numberOfCheatsPerSavedPs = new HashMap<>();

public RaceCondition(char[][] inputs) {
rows = inputs.length;
cols = inputs[0].length;

map = new RaceTile[rows][cols];

for(int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
RaceTile tile = new RaceTile(new Vector2(col, row), inputs[row][col]);
if (tile.isEmpty()) {
baseTime++;
}

if (tile.isStart()) {
start = tile;
} else if (tile.isEnd()) {
end = tile;
}

map[row][col] = tile;

}
}

for(int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
if(isCheatable(map[row][col])) {
cheatableWalls.add(map[row][col]);
}
}
}
}

private boolean isCheatable(RaceTile tile) {

if(tile.isEmpty()) {
return false;
}

Vector2 pos = tile.getPosition();

// Cannot cheat on border tiles
if (pos.getX() == 0 || pos.getX() == cols - 1 || pos.getY() == 0 || pos.getY() == rows - 1) {
return false;
}

Vector2 left = Vector2.transform(pos, Vector2.left());
Vector2 right = Vector2.transform(pos, Vector2.right());

if (map[left.getY()][left.getX()].isEmpty() && map[right.getY()][right.getX()].isEmpty()) {
return true;
}

Vector2 up = Vector2.transform(pos, Vector2.down());
Vector2 down = Vector2.transform(pos, Vector2.up());

return map[up.getY()][up.getX()].isEmpty() && map[down.getY()][down.getX()].isEmpty();
}

public void runRaces() {

// Step 1 - Buscar todos los muros que SI se puedan saltar
// Un muro se puede saltar en dos casos:
// - Tiene tanto a izquierda como a derecha un espacio vacío.
// - Tiene tanto arriba como abajo un espacio vacío.

// Step 2 - Recorrer todos los muros que se puedan saltar:
// - Se aplica dijkstra
// - Se anota el coste del camino hasta la meta
// - Se calcula el ahorro: ahorro = baseTime - coste end tile y se guarda en un mapa de resultados
// - Se resetea el mapa entero

for(RaceTile wall : cheatableWalls) {
wall.cheat();
dijkstra(start);
long distance = end.getDistance();
long savedTime = baseTime - distance;
long savedTimeCount = numberOfCheatsPerSavedPs.getOrDefault(savedTime,0L);
numberOfCheatsPerSavedPs.put(savedTime, savedTimeCount+1);
reset();
}

}

public long solveA(int savedPicoseconds) {

return numberOfCheatsPerSavedPs.entrySet().stream()
.filter(entry -> entry.getKey() >= savedPicoseconds)
.mapToLong(entry -> entry.getValue())
.sum();

}



private void dijkstra(RaceTile start) {
start.setDistance(0);

PriorityQueue<RaceTile> queue = new PriorityQueue<>();
queue.add(start);

while(!queue.isEmpty()) {
RaceTile parent = queue.poll();
parent.setVisited(true);
Set<RaceTile> adjacents = getAdjacents(parent);

for(RaceTile adjacent : adjacents) {
if(adjacent.getDistance() > parent.getDistance()+1) {
adjacent.setDistance(parent.getDistance()+1);
adjacent.setParent(parent);
queue.add(adjacent);
}
}
}
}

private Set<RaceTile> getAdjacents(RaceTile cell) {

Set<RaceTile> adjacents = new HashSet<>();
for(Vector2 dir : directions) {
Vector2 newPos = Vector2.transform(cell.getPosition(), dir);

if(isInbounds(newPos.getY(), newPos.getX())) {
RaceTile newCell = map[newPos.getY()][newPos.getX()];

if (!newCell.isWall() && !newCell.isVisited()) {
adjacents.add(newCell);
}
}
}

return adjacents;

}

private boolean isInbounds(int row, int col) {
return row >= 0 && row < rows && col >= 0 && col < cols;
}

private void reset() {
for(int row = 0; row < rows; row++) {
for(int col = 0; col < cols; col++) {
map[row][col].reset();
}
}
}

}
69 changes: 69 additions & 0 deletions src/main/java/com/adventofcode/flashk/day20/RaceTile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.adventofcode.flashk.day20;

import com.adventofcode.flashk.common.Vector2;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;

@Getter
@Setter
@EqualsAndHashCode
public class RaceTile implements Comparable<RaceTile> {

private static final char WALL = '#';
private static final char EMPTY = '.';
private static final char START = 'S';
private static final char END = 'E';

private Vector2 position;
private long value;

private boolean visited = false;
private long distance = Long.MAX_VALUE;
private boolean cheat = false;
private RaceTile parent;

public RaceTile(Vector2 position, char value) {
this.position = position;
this.value = value;
}

public void cheat() {
value = EMPTY;
cheat = true;
}

public void reset(){
visited = false;
distance = Long.MAX_VALUE;
value = cheat ? WALL : value;
cheat = false;
parent = null;
}

public boolean isStart() {
return value == START;
}

public boolean isEnd() {
return value == END;
}

public boolean isEmpty() {
return !isWall();
}

public boolean isWall() {
return value == WALL;
}

public void paint() {
System.out.print(value);
}

@Override
public int compareTo(@NotNull RaceTile o) {
return Long.compare(distance, o.distance);
}
}
35 changes: 26 additions & 9 deletions src/test/java/com/adventofcode/flashk/day20/Day20Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,25 @@ public class Day20Test extends PuzzleTest {
public void testSolvePart1Sample() {

// Read input file
List<String> inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE_SAMPLE);

assertEquals(0L,0L);
char[][] inputs = Input.read2DCharArray(INPUT_FOLDER, TestFilename.INPUT_FILE_SAMPLE);

RaceCondition raceCondition = new RaceCondition(inputs);
raceCondition.runRaces();
assertEquals(0L,raceCondition.solveA(100));
assertEquals(1L,raceCondition.solveA(64));
assertEquals(2L,raceCondition.solveA(40));
assertEquals(3L,raceCondition.solveA(38));
assertEquals(4L,raceCondition.solveA(36));
assertEquals(5L,raceCondition.solveA(20));
assertEquals(8L,raceCondition.solveA(12));
assertEquals(10L,raceCondition.solveA(10));
assertEquals(14L,raceCondition.solveA(8));
assertEquals(16,raceCondition.solveA(6));
assertEquals(30,raceCondition.solveA(4));
assertEquals(44,raceCondition.solveA(2));

// TODO cuidado, que igual si ejecuto dijkstra cada vez entonces el mapa se modifique
//...
}

@Test
Expand All @@ -47,10 +63,11 @@ public void testSolvePart1Sample() {
public void testSolvePart1Input() {

// Read input file
List<String> inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE);

System.out.println("Solution: ");
assertEquals(0L,0L);
char[][] inputs = Input.read2DCharArray(INPUT_FOLDER, TestFilename.INPUT_FILE);
RaceCondition raceCondition = new RaceCondition(inputs);
raceCondition.runRaces();

assertEquals(1463L,raceCondition.solveA(100));

}

Expand All @@ -62,7 +79,7 @@ public void testSolvePart1Input() {
public void testSolvePart2Sample() {

// Read input file
List<String> inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE_SAMPLE);
char[][] inputs = Input.read2DCharArray(INPUT_FOLDER, TestFilename.INPUT_FILE_SAMPLE);

assertEquals(0L,0L);
}
Expand All @@ -75,7 +92,7 @@ public void testSolvePart2Sample() {
public void testSolvePart2Input() {

// Read input file
List<String> inputs = Input.readStringLines(INPUT_FOLDER, TestFilename.INPUT_FILE);
char[][] inputs = Input.read2DCharArray(INPUT_FOLDER, TestFilename.INPUT_FILE);

System.out.println("Solution: ");
assertEquals(0L,0L);
Expand Down
2 changes: 1 addition & 1 deletion src/test/resources/inputs

0 comments on commit 1804c02

Please sign in to comment.