Skip to content

Commit

Permalink
Solution for 2024, Day 18
Browse files Browse the repository at this point in the history
  • Loading branch information
zodac committed Dec 19, 2024
1 parent d67be35 commit c75ddea
Show file tree
Hide file tree
Showing 9 changed files with 390 additions and 19 deletions.
3 changes: 2 additions & 1 deletion 2024/src/main/java/me/zodac/advent/Day10.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import me.zodac.advent.grid.Grid;
import me.zodac.advent.pojo.Point;
import me.zodac.advent.pojo.tuple.Pair;
import me.zodac.advent.search.PathFinder;

/**
* Solution for 2024, Day 10.
Expand Down Expand Up @@ -86,7 +87,7 @@ private static Pair<Long, Set<Point>> calculateValueAndNextPoints(final Grid<Int
for (final Point nextPoint : nextPoints) {
if (integerGrid.at(nextPoint) == END_POINT_VALUE) {
if (calculateRating) {
final List<List<Point>> allPaths = integerGrid.findAllPaths(startPoint, nextPoint, AdjacentDirection.CARDINAL,
final List<List<Point>> allPaths = PathFinder.all(integerGrid, startPoint, nextPoint, AdjacentDirection.CARDINAL,
(currentPoint, potentialNextPoint) -> integerGrid.at(currentPoint) + 1 == integerGrid.at(potentialNextPoint)
);
value += allPaths.size();
Expand Down
110 changes: 110 additions & 0 deletions 2024/src/main/java/me/zodac/advent/Day18.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* BSD Zero Clause License
*
* Copyright (c) 2021-2024 zodac.me
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

package me.zodac.advent;

import java.util.List;
import java.util.Set;
import java.util.function.BiPredicate;
import me.zodac.advent.grid.AdjacentDirection;
import me.zodac.advent.grid.Grid;
import me.zodac.advent.grid.GridFactory;
import me.zodac.advent.pojo.Point;
import me.zodac.advent.search.PathFinder;

/**
* Solution for 2024, Day 18.
*
* @see <a href="https://adventofcode.com/2024/day/18">[2024: 18] RAM Run</a>
*/
public final class Day18 {

private static final char OBSTACLE_SYMBOL = '#';

private Day18() {

}

/**
* Creates a {@link Grid} populated with obstacles defined by the input {@link Point}s. While many {@link Point}s are provided, only
* {@code pointsToCheck} {@link Point}s should be considered.
*
* <p>
* Once the obstacles have been populated, find the shortest path between the top-left {@link Point} to the bottom-right {@link Point}. Return
* the number of steps required (size of path - <b>1</b>).
*
* @param obstaclePoints the input {@link Point}s defining the obstacles on the {@link Grid}
* @param gridSize the size of the {@link Grid}
* @param pointsToCheck the number of obstacle {@link Point}s to consider
* @return the smallest number of steps between the start and end {@link Point}
*/
public static long smallestStepsBetweenStartAndEnd(final List<Point> obstaclePoints, final int gridSize, final int pointsToCheck) {
final Point startPoint = Point.atOrigin();
final Point endPoint = Point.of(gridSize - 1, gridSize - 1);
final Grid<Character> grid = createPopulatedGrid(obstaclePoints, gridSize, pointsToCheck);

// Ignore first point, since we're looking for number of steps
final BiPredicate<Point, Point> filter = (_, nextPoint) -> grid.at(nextPoint) != OBSTACLE_SYMBOL;
return PathFinder.shortest(grid, startPoint, endPoint, AdjacentDirection.CARDINAL, filter).size() - 1;
}

/**
* Having previously found the shortest path from start {@link Point} to end {@link Point}, we continue adding obstacles from the input
* {@link Point}s supplied. We can start from a later index, defined by {@code previouslyCheckedPoints} to speed up our analysis.
*
* <p>
* We keep adding new {@link Point}s until we find one that prevents any valid path between the start {@link Point} and the end {@link Point}. We
* then return this {@link Point} as a {@link String} in the format:
* <pre>
* x,y
* </pre>
*
* @param obstaclePoints the input {@link Point}s defining the obstacles on the {@link Grid}
* @param gridSize the size of the {@link Grid}
* @param previouslyCheckedPoints the starting value to continue checking subsequent {@link Point}s
* @return the {@link Point} that blocks the start {@link Point} from the end {@link Point}, as a {@link String}
*/
public static String findPointWhichBlocksPath(final List<Point> obstaclePoints, final int gridSize, final int previouslyCheckedPoints) {
final Point startPoint = Point.atOrigin();
final Point endPoint = Point.of(gridSize - 1, gridSize - 1);
Grid<Character> grid = createPopulatedGrid(obstaclePoints, gridSize, previouslyCheckedPoints);

for (int i = previouslyCheckedPoints; i <= obstaclePoints.size(); i++) {
final Point point = obstaclePoints.get(i);

grid = grid.updateAt(point, OBSTACLE_SYMBOL);
final Grid<Character> updatedGrid = grid; // Stupid workaround for lambda function
final BiPredicate<Point, Point> filter = (_, nextPoint) -> updatedGrid.at(nextPoint) != OBSTACLE_SYMBOL;

final Set<Point> shortestPath = PathFinder.shortest(updatedGrid, startPoint, endPoint, AdjacentDirection.CARDINAL, filter);
if (shortestPath.isEmpty()) {
return String.format("%s,%s", point.x(), point.y());
}
}
throw new IllegalStateException("Unable to find any Point to block the path");
}

private static Grid<Character> createPopulatedGrid(final List<Point> values, final int gridSize, final int bytesToCheck) {
Grid<Character> grid = GridFactory.ofCharactersWithSize(gridSize);

for (int i = 0; i < bytesToCheck; i++) {
final Point point = values.get(i);
grid = grid.updateAt(point, OBSTACLE_SYMBOL);
}
return grid;
}
}
77 changes: 77 additions & 0 deletions 2024/src/test/java/me/zodac/advent/Day18Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* BSD Zero Clause License
*
* Copyright (c) 2021-2024 zodac.me
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

package me.zodac.advent;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;
import me.zodac.advent.input.InputReader;
import me.zodac.advent.pojo.Point;
import org.junit.jupiter.api.Test;

/**
* Tests to verify answers for {@link Day18}.
*/
class Day18Test {

private static final String INPUT_FILENAME = "day18.txt";
private static final int GRID_SIZE_EXAMPLE = 7;
private static final int GRID_SIZE_PUZZLE = 71;
private static final int POINTS_TO_CHECK_EXAMPLE = 12;
private static final int POINTS_TO_CHECK_PUZZLE = 1024;

@Test
void example() {
final List<Point> values = InputReader
.forExample(INPUT_FILENAME)
.as(Point::parse)
.readAllLines();

final long part1Result = Day18.smallestStepsBetweenStartAndEnd(values, GRID_SIZE_EXAMPLE, POINTS_TO_CHECK_EXAMPLE);
assertThat(part1Result)
.isEqualTo(22L);

final String part2Result = Day18.findPointWhichBlocksPath(values, GRID_SIZE_EXAMPLE, POINTS_TO_CHECK_EXAMPLE);
assertThat(part2Result)
.isEqualTo("6,1");
}

@Test
void part1() {
final List<Point> values = InputReader
.forPuzzle(INPUT_FILENAME)
.as(Point::parse)
.readAllLines();

final long part1Result = Day18.smallestStepsBetweenStartAndEnd(values, GRID_SIZE_PUZZLE, POINTS_TO_CHECK_PUZZLE);
assertThat(part1Result)
.isEqualTo(278L);
}

@Test
void part2() {
final List<Point> values = InputReader
.forPuzzle(INPUT_FILENAME)
.as(Point::parse)
.readAllLines();

final String part2Result = Day18.findPointWhichBlocksPath(values, GRID_SIZE_PUZZLE, POINTS_TO_CHECK_PUZZLE);
assertThat(part2Result)
.isEqualTo("43,12");
}
}
25 changes: 25 additions & 0 deletions 2024/src/test/resources/day18.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
5,4
4,2
4,5
3,0
2,1
6,3
2,4
1,5
0,6
3,3
2,6
5,1
1,2
5,5
2,5
6,5
1,4
0,4
6,4
1,1
6,1
1,0
0,5
1,6
2,0
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Advent Of Code: Java Edition

![2024](https://img.shields.io/badge/2024%20⭐-28-yellow)
![2024](https://img.shields.io/badge/2024%20⭐-30-yellow)
![2023](https://img.shields.io/badge/2023%20⭐-32-yellow)
![2022](https://img.shields.io/badge/2022%20⭐-28-yellow)
![2021](https://img.shields.io/badge/2021%20⭐-19-orange)
Expand Down Expand Up @@ -72,6 +72,10 @@ The source code is released under the [BSD Zero Clause License](https://opensour
| [Day 12](https://adventofcode.com/2024/day/12) | 45,080 ⭐ | 35,116 ⭐ |
| [Day 13](https://adventofcode.com/2024/day/13) | 42,105 ⭐ | 37,422 ⭐ |
| [Day 14](https://adventofcode.com/2024/day/14) | 40,754 ⭐ | 37,375 ⭐ |
| [Day 15](https://adventofcode.com/2024/day/15) |||
| [Day 16](https://adventofcode.com/2024/day/16) |||
| [Day 17](https://adventofcode.com/2024/day/17) |||
| [Day 18](https://adventofcode.com/2024/day/18) | 22,587 ⭐ | 21,997 ⭐ |

</details>

Expand Down
2 changes: 1 addition & 1 deletion advent-of-code-inputs
15 changes: 2 additions & 13 deletions common-utils/src/main/java/me/zodac/advent/grid/Grid.java
Original file line number Diff line number Diff line change
Expand Up @@ -538,9 +538,8 @@ private void dfs(final Point current,
if (current.equals(end)) {
paths.add(new ArrayList<>(currentPath));
} else {
getValidAdjacentPoints(current, adjacentDirection, adjacentPointFilter)
.stream()
.filter(neighbour -> !visited.contains(neighbour))
current.getAdjacentPoints(AdjacentPointsSelector.bounded(false, adjacentDirection, gridSize))
.filter(nextPoint -> adjacentPointFilter.test(current, nextPoint) && !visited.contains(nextPoint))
.forEach(neighbour -> dfs(neighbour, end, currentPath, paths, visited, adjacentDirection, adjacentPointFilter));
}

Expand All @@ -549,16 +548,6 @@ private void dfs(final Point current,
visited.remove(current);
}

private List<Point> getValidAdjacentPoints(final Point currentPoint,
final AdjacentDirection adjacentDirection,
final BiPredicate<? super Point, ? super Point> adjacentPointFilter
) {
return currentPoint
.getAdjacentPoints(AdjacentPointsSelector.bounded(false, adjacentDirection, gridSize))
.filter(nextPoint -> adjacentPointFilter.test(currentPoint, nextPoint))
.toList();
}

/**
* Prints the content of the {@link Grid}.
*
Expand Down
21 changes: 18 additions & 3 deletions common-utils/src/main/java/me/zodac/advent/grid/GridFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public static Grid<Boolean> ofBooleans(final List<String> strings, final char sy
* Creates a {@link Boolean} {@link Grid} with the dimensions {@code gridSize}x{@code gridSize}.
*
* @param gridSize the length and width of the {@link Boolean} {@link Grid}
* @return the created {@link Boolean} {@link Grid}
* @return the created {@link Boolean} {@link Grid}, with default value <b>false</b>
* @throws IllegalArgumentException thrown if input size is less than <b>0</b>
*/
public static Grid<Boolean> ofBooleansWithSize(final int gridSize) {
Expand All @@ -65,12 +65,27 @@ public static Grid<Character> ofCharacters(final List<String> strings) {
return Grid.parseGrid(strings, character -> character);
}

/**
* Creates a {@link Character} {@link Grid} with the dimensions {@code gridSize}x{@code gridSize}.
*
* @param gridSize the length and width of the {@link Character} {@link Grid}
* @return the created {@link Character} {@link Grid}, with default value <b>.</b>
* @throws IllegalArgumentException thrown if input size is less than <b>0</b>
*/
public static Grid<Character> ofCharactersWithSize(final int gridSize) {
if (gridSize <= 0) {
throw new IllegalArgumentException("Size must be positive integer, found: " + gridSize);
}

return new Grid<>(gridSize, new Character[gridSize][gridSize], '.');
}

/**
* Converts the {@link List} of {@link String}s to a {@link Integer} {@link Grid}. Note that this expects no blank spaces, and will assume every
* character is a single digit {@link Integer}.
*
* @param strings the input {@link List} of {@link String}s
* @return the {@link Integer} {@link Grid}
* @return the {@link Integer} {@link Grid}, with default value <b>0</b>
*/
public static Grid<Integer> ofIntegers(final List<String> strings) {
return Grid.parseGrid(strings, character -> NumberUtils.toIntOrDefault(character, 0));
Expand All @@ -80,7 +95,7 @@ public static Grid<Integer> ofIntegers(final List<String> strings) {
* Creates a {@link Integer} {@link Grid} with the dimensions {@code gridSize}x{@code gridSize}.
*
* @param gridSize the length and width of the {@link Integer} {@link Grid}
* @return the created {@link Integer} {@link Grid}
* @return the created {@link Integer} {@link Grid}, with default value <b>0</b>
* @throws IllegalArgumentException thrown if input size is less than <b>0</b>
*/
public static Grid<Integer> ofIntegersWithSize(final int gridSize) {
Expand Down
Loading

0 comments on commit c75ddea

Please sign in to comment.