diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a8c1a94..007257b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,18 +10,18 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-java@v2
+ - uses: actions/checkout@v3
+ - uses: actions/setup-java@v3
with:
distribution: 'temurin'
- java-version: '17'
+ java-version: '19'
check-latest: true
- - uses: actions/cache@v2
+ - uses: actions/cache@v3
with:
path: ~/.m2
- key: m2-${{ runner.os }}-${{ matrix.java-version }}-${{ hashFiles('**/pom.xml') }}
+ key: m2-${{ runner.os }}-19-${{ hashFiles('**/pom.xml') }}
restore-keys: |
- m2-${{ runner.os }}-${{ matrix.java-version }}
+ m2-${{ runner.os }}-19
m2-${{ runner.os }}
m2
- run: mvn clean install
diff --git a/.gitignore b/.gitignore
index db33628..4d131ae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ target/
src/test/resources/config.properties
src/test/resources/input
src/test/resources/2020
+.DS_Store
diff --git a/.java-version b/.java-version
index 98d9bcb..6ea9a3b 100644
--- a/.java-version
+++ b/.java-version
@@ -1 +1 @@
-17
+19.0
diff --git a/README.md b/README.md
index 8235f91..29e9c19 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,12 @@
-# Advent of Code 2021 (Java)
+# Advent of Code 2022 (Java)
This is the code I used to solve the
-[Advent of Code](https://adventofcode.com/2021) puzzles. The main branch
+[Advent of Code](https://adventofcode.com/2022) puzzles. The main branch
contains the most recent year in which I participated. Other years have
their own branch.
## Other Editions
-* 2020 Advent of Code ([Java](https://github.com/l0s/advent-of-code-java/tree/2020) | [Rust](https://github.com/l0s/advent-of-code-rust))
+* 2020 Advent of Code ([Java](https://github.com/l0s/advent-of-code-java/tree/2020) | [Rust](https://github.com/l0s/advent-of-code-rust/releases/tag/y2020))
+* 2021 Advent of Code ([Java](https://github.com/l0s/advent-of-code-java/releases/tag/2021))
+* 2022 Advent of Code ([Rust](https://github.com/l0s/advent-of-code-rust))
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 7099d59..fc13d64 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,21 +6,21 @@
com.macasaet
advent-of-code
- 0.2021.0-SNAPSHOT
+ 0.2022.0-SNAPSHOT
- Advent of Code 2021
+ Advent of Code 2022
UTF-8
- 17
- 17
+ 19
+ 19
org.junit.jupiter
junit-jupiter
- 5.8.2
+ 5.9.0
diff --git a/src/test/java/com/macasaet/Day01.java b/src/test/java/com/macasaet/Day01.java
index 0485635..d70e9ed 100644
--- a/src/test/java/com/macasaet/Day01.java
+++ b/src/test/java/com/macasaet/Day01.java
@@ -1,65 +1,76 @@
package com.macasaet;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
import java.util.ArrayList;
-import java.util.LinkedList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
import java.util.List;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 1: Sonar Sweep ---
+ * --- Day 1: Calorie Counting ---
*/
public class Day01 {
- /**
- * Perform a sonar sweep of the nearby sea floor.
- *
- * @return measurements of the sea floor depth further and further away from the submarine
- */
- protected List getInput() {
+ protected Iterator getInput() {
return StreamSupport
.stream(new LineSpliterator("day-01.txt"),
false)
- .mapToInt(Integer::parseInt)
- .collect(ArrayList::new, List::add, List::addAll);
+ .iterator();
+ }
+
+ protected List getElves() {
+ var calories = new ArrayList();
+ final var elves = new ArrayList();
+ for (final var i = getInput(); i.hasNext(); ) {
+ final var line = i.next();
+ if (line.isBlank()) {
+ elves.add(new Elf(Collections.unmodifiableList(calories)));
+ calories = new ArrayList<>();
+ } else {
+ calories.add(new BigInteger(line.strip()));
+ }
+ }
+ if (!calories.isEmpty()) {
+ elves.add(new Elf(Collections.unmodifiableList(calories)));
+ }
+ return Collections.unmodifiableList(elves);
}
@Test
public final void part1() {
- final var list = getInput();
- int increases = countIncreases(list);
- System.out.println("Part 1: " + increases);
+ final var elves = getElves();
+ final var elf = elves.stream()
+ .max(Comparator.comparing(Elf::totalCaloriesCarried))
+ .get();
+
+ System.out.println("Part 1: " + elf.totalCaloriesCarried());
}
@Test
public final void part2() {
- final var list = getInput();
- final var windows = new LinkedList();
- for (int i = 2; i < list.size(); i++) {
- windows.add(list.get(i) + list.get(i - 1) + list.get(i - 2));
- }
- final int increases = countIncreases(windows);
- System.out.println("Part 2: " + increases);
+ final var elves = getElves();
+ final var list = elves.stream()
+ .sorted(Comparator.comparing(Elf::totalCaloriesCarried).reversed())
+ .toList();
+
+ System.out.println("Part 2: " + (list.get(0).totalCaloriesCarried().add(list.get(1).totalCaloriesCarried()).add(list.get(2).totalCaloriesCarried())));
}
/**
- * Determine how quickly the depth increases.
+ * An elf who collects food for the reindeer.
*
- * @param list progressively further measurements of the sea floor depth
- * @return the number of times a depth measurement increase from the previous measurement
+ * @param itemCalories The number of calories of each item carried by the elf
*/
- protected int countIncreases(final List extends Integer> list) {
- int previous = list.get(0);
- int increases = 0;
- for (int i = 1; i < list.size(); i++) {
- final var current = list.get(i);
- if (current > previous) {
- increases++;
- }
- previous = current;
+ public record Elf(List itemCalories) {
+ public BigInteger totalCaloriesCarried() {
+ return itemCalories().stream()
+ .reduce(BigInteger::add)
+ .get();
}
- return increases;
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day02.java b/src/test/java/com/macasaet/Day02.java
index f5a2e9f..424b241 100644
--- a/src/test/java/com/macasaet/Day02.java
+++ b/src/test/java/com/macasaet/Day02.java
@@ -1,110 +1,175 @@
package com.macasaet;
-import java.util.Locale;
+import org.junit.jupiter.api.Test;
+
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 2: Dive! ---
+ * --- Day 2: Rock Paper Scissors ---
+ * https://adventofcode.com/2022/day/2
*/
public class Day02 {
- public enum Operation {
- FORWARD {
- public Position adjust(Position position, int magnitude) {
- return new Position(position.horizontalPosition() + magnitude, position.depth());
+ protected Stream getInput() {
+ return StreamSupport
+ .stream(new LineSpliterator("day-02.txt"),
+ false)
+ .map(line -> {
+ final var components = line.strip().split(" ");
+ return new Round(Shape.forChar(components[0].charAt(0)),
+ Shape.forChar(components[1].charAt(0)),
+ ResponseStrategy.forChar(components[1].charAt(0)));
+ });
+ }
+
+ @Test
+ public final void part1() {
+ final var result = getInput().mapToInt(Round::naiveScore).sum();
+
+ System.out.println("Part 1: " + result);
+ }
+
+ @Test
+ public final void part2() {
+ final var result = getInput().mapToInt(Round::score).sum();
+
+ System.out.println("Part 2: " + result);
+ }
+
+ /**
+ * A shape that a contestant can play in a round
+ */
+ public enum Shape {
+ Rock {
+ public int score() {
+ return 1;
+ }
+
+ public Shape beatenBy() {
+ return Paper;
}
- public OrientedPosition adjust(OrientedPosition position, int magnitude) {
- return new OrientedPosition(position.horizontalPosition() + magnitude,
- position.depth() + (position.aim() * magnitude),
- position.aim());
+ public Shape beats() {
+ return Scissors;
}
},
- DOWN {
- public Position adjust(Position position, int magnitude) {
- return new Position(position.horizontalPosition(), position.depth() + magnitude);
+ Paper {
+ public int score() {
+ return 2;
}
- public OrientedPosition adjust(OrientedPosition position, int magnitude) {
- return new OrientedPosition(position.horizontalPosition(),
- position.depth(),
- position.aim() + magnitude);
+ public Shape beatenBy() {
+ return Scissors;
+ }
+
+ public Shape beats() {
+ return Rock;
}
},
- UP {
- public Position adjust(Position position, int magnitude) {
- return new Position(position.horizontalPosition(), position.depth() - magnitude);
+ Scissors {
+ public int score() {
+ return 3;
}
- public OrientedPosition adjust(OrientedPosition position, int magnitude) {
- return new OrientedPosition(position.horizontalPosition(),
- position.depth(),
- position.aim() - magnitude);
+ @Override
+ public Shape beatenBy() {
+ return Rock;
}
- };
- public abstract Position adjust(Position position, int magnitude);
-
- public abstract OrientedPosition adjust(OrientedPosition position, int magnitude);
- }
+ public Shape beats() {
+ return Paper;
+ }
+ };
- public record Command(Operation operation, int magnitude) {
- public static Command parse(final String string) {
- final String[] components = string.split(" ");
- final var operation = Operation.valueOf(components[0].toUpperCase(Locale.US));
- final int magnitude = Integer.parseInt(components[1]);
- return new Command(operation, magnitude);
+ public static Shape forChar(final int c) {
+ return switch (c) {
+ case 'X':
+ case 'A':
+ yield Shape.Rock;
+ case 'Y':
+ case 'B':
+ yield Shape.Paper;
+ case 'Z':
+ case 'C':
+ yield Shape.Scissors;
+ default:
+ throw new IllegalArgumentException("Invalid shape: " + c);
+ };
}
- public Position adjust(final Position position) {
- return operation().adjust(position, magnitude());
- }
+ /**
+ * @return the inherent value of this shape
+ */
+ public abstract int score();
- public OrientedPosition adjust(final OrientedPosition position) {
- return operation().adjust(position, magnitude());
- }
- }
+ /**
+ * @return the shape that beats this one
+ */
+ public abstract Shape beatenBy();
- public record Position(int horizontalPosition, int depth) {
- public int result() {
- return horizontalPosition() * depth();
- }
+ /**
+ * @return the shape this one beats
+ */
+ public abstract Shape beats();
}
- public record OrientedPosition(int horizontalPosition, int depth, int aim) {
- public int result() {
- return horizontalPosition() * depth();
+ /**
+ * An approach to responding to the shape played by the opponent
+ */
+ public enum ResponseStrategy {
+ Lose {
+ public Shape respond(Shape opponent) {
+ return opponent.beats();
+ }
+ },
+ Draw {
+ public Shape respond(Shape opponent) {
+ return opponent;
+ }
+ },
+ Win {
+ public Shape respond(Shape opponent) {
+ return opponent.beatenBy();
+ }
+ };
+
+ public static ResponseStrategy forChar(final char c) {
+ return switch (c) {
+ case 'X' -> Lose;
+ case 'Y' -> Draw;
+ case 'Z' -> Win;
+ default -> throw new IllegalArgumentException("Invalid strategy: " + c);
+ };
}
- }
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-02.txt"),
- false)
- .map(Command::parse);
+ public abstract Shape respond(final Shape opponent);
}
- @Test
- public final void part1() {
- var position = new Position(0, 0);
- for (final var i = getInput().iterator(); i.hasNext(); ) {
- final var operation = i.next();
- position = operation.adjust(position);
+ /**
+ * A single round of the game
+ *
+ * @param opponent The shape played by the opponent
+ * @param player The shape chosen based on the original (incorrect) interpretation of the responseStrategy guide
+ * @param responseStrategy The responseStrategy for responding to the opponent according to the responseStrategy guide
+ */
+ public record Round(Shape opponent, Shape player, ResponseStrategy responseStrategy) {
+ /**
+ * @return the score based on the simple (incorrect) interpretation of the strategy guide
+ */
+ public int naiveScore() {
+ final var outcome = opponent() == player() ? 3 : opponent().beatenBy() == player() ? 6 : 0;
+ return outcome + player().score();
}
- System.out.println("Part 1: " + position.result());
- }
- @Test
- public final void part2() {
- var position = new OrientedPosition(0, 0, 0);
- for (final var i = getInput().iterator(); i.hasNext(); ) {
- final var operation = i.next();
- position = operation.adjust(position);
+ /**
+ * @return the score based on following the strategy guide
+ */
+ public int score() {
+ final var response = responseStrategy().respond(opponent());
+ final var outcome = opponent() == response ? 3 : opponent().beatenBy() == response ? 6 : 0;
+ return outcome + response.score();
}
- System.out.println("Part 2: " + position.result());
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day03.java b/src/test/java/com/macasaet/Day03.java
index 47f6ec5..af6e98a 100644
--- a/src/test/java/com/macasaet/Day03.java
+++ b/src/test/java/com/macasaet/Day03.java
@@ -1,120 +1,117 @@
package com.macasaet;
+import org.junit.jupiter.api.Test;
+
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Set;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 3: Binary Diagnostic ---
+ * --- Day 3: Rucksack Reörganisation ---
+ * https://adventofcode.com/2022/day/3
*/
public class Day03 {
- /**
- * @return "a list of binary numbers which, when decoded properly, can tell you many useful things about the
- * conditions of the submarine"
- */
- protected Stream getDiagnosticReport() {
+ protected static int priority(final char c) {
+ if (c >= 'a' && c <= 'z') {
+ return c - 'a' + 1;
+ }
+ return c - 'A' + 27;
+ }
+
+ protected Stream getInput() {
return StreamSupport
.stream(new LineSpliterator("day-03.txt"),
false)
- .map(string -> {
- final var chars = string.toCharArray();
- final var bits = new byte[chars.length];
- for (int i = chars.length; --i >= 0; bits[i] = chars[i] == '0' ? (byte) 0 : (byte) 1) ;
- return bits;
- });
+ .map(Rucksack::parse);
}
- protected int toUnsignedInt(final byte[] bits) {
- int result = 0;
- for (int i = bits.length; --i >= 0; result += bits[i] * Math.pow(2, bits.length - i - 1)) ;
- return result;
+ @Test
+ public final void part1() {
+ final var result = getInput().mapToInt(Rucksack::priority).sum();
+
+ System.out.println("Part 1: " + result);
}
@Test
- public final void part1() {
- final var list = getDiagnosticReport().collect(Collectors.toList());
- final int width = list.get(0).length;
- final int[] zeroCounts = new int[width];
- for (int i = zeroCounts.length; --i >= 0; zeroCounts[i] = 0) ;
- final int[] oneCounts = new int[width];
- for (int i = oneCounts.length; --i >= 0; oneCounts[i] = 0) ;
- for (final var array : list) {
- for (int j = 0; j < width; j++) {
- if (array[j] == 0) {
- zeroCounts[j] += 1;
- } else {
- oneCounts[j] += 1;
- }
+ public final void part2() {
+ final var groups = new ArrayList>();
+ var currentGroup = new ArrayList(3);
+
+ for (final var i = getInput().iterator(); i.hasNext(); ) {
+ final var rucksack = i.next();
+ if (currentGroup.size() == 3) {
+ groups.add(Collections.unmodifiableList(currentGroup));
+ currentGroup = new ArrayList<>(3);
}
+ currentGroup.add(rucksack);
}
- final byte[] gammaArray = new byte[width];
- final byte[] epsilonArray = new byte[width];
- for (int i = gammaArray.length; --i >= 0; ) {
- gammaArray[i] = zeroCounts[i] > oneCounts[i] ? (byte) 0 : (byte) 1;
- epsilonArray[i] = zeroCounts[i] > oneCounts[i] ? (byte) 1 : (byte) 0;
+ if (currentGroup.size() == 3) {
+ groups.add(Collections.unmodifiableList(currentGroup));
}
+ final var result = groups.stream().map(this::getBadge).mapToInt(Day03::priority).sum();
- final int gammaRate = toUnsignedInt(gammaArray);
- final int epsilonRate = toUnsignedInt(epsilonArray);
- System.out.println("Part 1: " + (gammaRate * epsilonRate));
+ System.out.println("Part 2: " + result);
}
- @Test
- public final void part2() {
- final var list = getDiagnosticReport().collect(Collectors.toList());
- final int width = list.get(0).length;
- List oxygenCandidates = new ArrayList<>(list);
- for (int i = 0; i < width && oxygenCandidates.size() > 1; i++) {
- int zeros = 0;
- int ones = 0;
- for (final var value : oxygenCandidates) {
- if (value[i] == 0) {
- zeros++;
- } else {
- ones++;
- }
- }
- final int index = i;
- if (ones >= zeros) {
- oxygenCandidates = oxygenCandidates.stream().filter(value -> value[index] == 1).collect(Collectors.toList());
- } else {
- oxygenCandidates = oxygenCandidates.stream().filter(value -> value[index] == 0).collect(Collectors.toList());
+ protected char getBadge(final List extends Rucksack> group) {
+ final var first = group.get(0);
+ for (final var item : first.allItems()) {
+ if (group.get(1).allItems().contains(item) && group.get(2).allItems().contains(item)) {
+ return item;
}
}
- if (oxygenCandidates.size() > 1) {
- throw new IllegalStateException("Too many oxygen candidates");
+ throw new IllegalStateException();
+ }
+
+ /**
+ * An Elf's container of supplies for a jungle journey. "Each rucksack has two large compartments. All items of a
+ * given type are meant to go into exactly one of the two compartments."
+ *
+ * @param firstCompartment All the items in one compartment
+ * @param secondCompartment All the items in one compartment
+ * @param allItems All the items
+ */
+ public record Rucksack(Set firstCompartment, Set secondCompartment, Set allItems) {
+
+ public static Rucksack parse(final String line) {
+ final var chars = line.toCharArray();
+ if (chars.length % 2 != 0) {
+ throw new IllegalArgumentException();
+ }
+ final var firstCompartment = new HashSet(chars.length / 2);
+ final var secondCompartment = new HashSet(chars.length / 2);
+ for (int i = 0; i < chars.length / 2; i++) {
+ firstCompartment.add(chars[i]);
+ }
+ for (int i = chars.length / 2; i < chars.length; i++) {
+ secondCompartment.add(chars[i]);
+ }
+ final var union = new HashSet(chars.length);
+ union.addAll(firstCompartment);
+ union.addAll(secondCompartment);
+ return new Rucksack(Collections.unmodifiableSet(firstCompartment),
+ Collections.unmodifiableSet(secondCompartment),
+ Collections.unmodifiableSet(union));
}
- List co2Candidates = new ArrayList<>(list);
- for (int i = 0; i < width && co2Candidates.size() > 1; i++) {
- int zeros = 0;
- int ones = 0;
- for (final var value : co2Candidates) {
- if (value[i] == 0) {
- zeros++;
- } else {
- ones++;
+
+ public int priority() {
+ final var intersection = new HashSet();
+ for (final char c : firstCompartment) {
+ if (secondCompartment.contains(c)) {
+ intersection.add(c);
}
}
- final int index = i;
- if (zeros <= ones) {
- co2Candidates = co2Candidates.stream().filter(value -> value[index] == 0).collect(Collectors.toList());
- } else {
- co2Candidates = co2Candidates.stream().filter(value -> value[index] == 1).collect(Collectors.toList());
+ if (intersection.size() != 1) {
+ throw new IllegalStateException("There should only be one common item between compartments");
}
+ return Day03.priority(intersection.iterator().next());
}
- if (co2Candidates.size() > 1) {
- throw new IllegalStateException("Too many CO2 candidates");
- }
- final byte[] oxyArray = oxygenCandidates.get(0);
- final byte[] co2Array = co2Candidates.get(0);
- final int oxygenGeneratorRating = toUnsignedInt(oxyArray);
- final int co2ScrubberRating = toUnsignedInt(co2Array);
- System.out.println("Part 2: " + (oxygenGeneratorRating * co2ScrubberRating));
- }
+
+ }
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day04.java b/src/test/java/com/macasaet/Day04.java
index d69541d..8b3d13d 100644
--- a/src/test/java/com/macasaet/Day04.java
+++ b/src/test/java/com/macasaet/Day04.java
@@ -1,181 +1,69 @@
package com.macasaet;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import org.junit.jupiter.api.Test;
+
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 4: Giant Squid ---
+ * --- Day 4: Camp Cleanup ---
+ * https://adventofcode.com/2022/day/4
*/
public class Day04 {
/**
- * The number of rows and columns on each square Bingo card
+ * One crew member responsible for cleaning up the camp. They are responsible for a contiguous range of sections.
+ *
+ * @param sectionMin the lowest section ID for which this Elf is responsible (inclusive)
+ * @param sectionMax the highest section ID for which this Elf is responsible (inclusive).
*/
- static final int EDGE_LENGTH = 5;
-
- public static class Board {
- private final int[][] grid;
- private final boolean[][] marked;
-
- protected Board(final int[][] grid, final boolean[][] marked) {
- this.grid = grid;
- this.marked = marked;
- }
+ public record Elf(int sectionMin, int sectionMax) {
- public Board(final int[][] grid) {
- this(grid, new boolean[grid.length][]);
- for (int i = grid.length; --i >= 0; this.marked[i] = new boolean[grid.length]) ;
+ public boolean fullyContains(final Elf other) {
+ return sectionMin() <= other.sectionMin() && sectionMax() >= other.sectionMax();
}
- public boolean isWinner() {
- // check rows
- for (int i = marked.length; --i >= 0; ) {
- final var row = marked[i];
- boolean complete = true;
- for (int j = row.length; --j >= 0 && complete; complete = row[j]) ;
- if (complete) {
- return true;
- }
- }
- // check columns
- for (int j = marked.length; --j >= 0; ) {
- boolean complete = true;
- for (int i = marked.length; --i >= 0 && complete; complete = marked[i][j]) ;
- if (complete) {
- return true;
- }
- }
- return false;
- }
-
- public int score(final int lastDrawn) {
- int sum = 0;
- for (int i = grid.length; --i >= 0; ) {
- final var row = grid[i];
- for (int j = row.length; --j >= 0; ) {
- if (!marked[i][j]) {
- sum += row[j];
- }
- }
- }
- return sum * lastDrawn;
- }
-
- public void mark(final int drawn) {
- for (int i = grid.length; --i >= 0; ) {
- final var row = grid[i];
- for (int j = row.length; --j >= 0; ) {
- if (row[j] == drawn) {
- marked[i][j] = true;
- }
- }
- }
+ public static Elf parse(final String string) {
+ final var components = string.split("-");
+ return new Elf(Integer.parseInt(components[0]), Integer.parseInt(components[1]));
}
}
- public record Game(List boards, List numbers) {
-
- public int countBoards() {
- return boards().size();
+ /**
+ * Two elves (of a larger crew) assigned to clean up the camp.
+ */
+ public record Pair(Elf left, Elf right) {
+ public boolean oneFullyContainsTheOther() {
+ return left.fullyContains(right) || right.fullyContains(left);
}
-
- public void removeBoard(final int index) {
- this.boards().remove(index);
+ public boolean hasOverlap() {
+ return (left.sectionMin() <= right.sectionMin() && left.sectionMax() >= right.sectionMin())
+ || (right.sectionMin() <= left.sectionMin() && right.sectionMax() >= left.sectionMin());
+ }
+ public static Pair parse(final String line) {
+ final var components = line.split(",");
+ return new Pair(Elf.parse(components[0]), Elf.parse(components[1]));
}
-
}
-
- protected Stream getInput() {
+ protected Stream getInput() {
return StreamSupport
.stream(new LineSpliterator("day-04.txt"),
- false);
- }
-
- protected Game getGame() {
- final var input = getInput().iterator();
- final var moves = input.next();
-
- List boards = new ArrayList<>();
- int[][] grid = null;
- int gridIndex = -1;
- while (input.hasNext()) {
- final var line = input.next();
- if (line.isBlank()) {
- if (grid != null) {
- boards.add(new Board(grid));
- }
- grid = new int[EDGE_LENGTH][];
- for (int i = EDGE_LENGTH; --i >= 0; grid[i] = new int[EDGE_LENGTH]) ;
- gridIndex = 0;
- continue;
- }
- final var cells = line.split("\\s");
- if (cells.length > 0) {
- final var values = Arrays.stream(cells)
- .filter(candidate -> !candidate.isBlank())
- .mapToInt(Integer::parseInt)
- .toArray();
- if (values.length > 0) {
- grid[gridIndex++] = values;
- }
- }
- }
- if (grid != null) {
- boards.add(new Board(grid));
- }
- final var moveArray = Arrays.stream(moves.split(","))
- .mapToInt(Integer::parseInt)
- .collect(ArrayList::new, List::add, List::addAll);
- return new Game(boards, moveArray);
+ false)
+ .map(Pair::parse);
}
@Test
public final void part1() {
- final var game = getGame();
- for (final var number : game.numbers()) {
- for (final var board : game.boards()) {
- board.mark(number);
- if (board.isWinner()) {
- final int score = board.score(number);
- System.out.println("Part 1: " + score);
- return;
- }
- }
- }
- throw new IllegalStateException("No winners");
+ final var result = getInput().filter(Pair::oneFullyContainsTheOther).count();
+
+ System.out.println("Part 1: " + result);
}
@Test
public final void part2() {
- final var game = getGame();
- for (final var number : game.numbers()) {
- if (game.countBoards() == 1) {
- final var lastWinner = game.boards().get(0);
- lastWinner.mark(number);
- if (!lastWinner.isWinner()) {
- continue;
- }
- System.out.println("Part 2: " + lastWinner.score(number));
- return;
- }
- final List idsToRemove = new ArrayList<>();
- for (int i = game.boards().size(); --i >= 0; ) {
- final var board = game.boards().get(i);
- board.mark(number);
- if (board.isWinner()) {
- idsToRemove.add(i);
- }
- }
- for (final var id : idsToRemove) {
- game.removeBoard(id);
- }
- }
- throw new IllegalStateException("Tie for last place");
+ final var result = getInput().filter(Pair::hasOverlap).count();
+
+ System.out.println("Part 2: " + result);
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day05.java b/src/test/java/com/macasaet/Day05.java
index bb23695..97c43c4 100644
--- a/src/test/java/com/macasaet/Day05.java
+++ b/src/test/java/com/macasaet/Day05.java
@@ -1,188 +1,139 @@
package com.macasaet;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
+import org.junit.jupiter.api.Test;
+
+import java.util.Deque;
+import java.util.LinkedList;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 5: Hydrothermal Venture ---
+ * --- Day 5: Supply Stacks ---
+ * https://adventofcode.com/2022/day/5
*/
public class Day05 {
- /**
- * A point on the ocean floor
- */
- public static record Point(int x, int y) {
- public static Point parse(final String string) {
- final var components = string.split(",");
- return new Point(Integer.parseInt(components[0]), Integer.parseInt(components[1]));
+ public record CrateMover9000Instruction(int count, int from, int to) {
+ public static CrateMover9000Instruction parse(final String line) {
+ final var components = line.split(" ");
+ final var count = Integer.parseInt(components[1]);
+ final var from = Integer.parseInt(components[3]) - 1;
+ final var to = Integer.parseInt(components[5]) - 1;
+ return new CrateMover9000Instruction(count, from, to);
}
-
- }
-
- /**
- * A portion of the ocean floor on which there are hydrothermal vents, which produce large, opaque clouds
- */
- public static record LineSegment(Point start, Point end) {
- public static LineSegment parse(final String string) {
- final var components = string.split(" -> ");
- return new LineSegment(Point.parse(components[0]), Point.parse(components[1]));
- }
-
- /**
- * Highlight the location of this line segment on the diagram. Each cell that this segment covers will have its
- * value incremented. The higher the number, the more vents cover the cell.
- *
- * @param diagram A map of the ocean floor which will be updated by this call.
- */
- public void update(final int[][] diagram) {
- /*
- "Because of the limits of the hydrothermal vent mapping system, the lines in your list will only ever be
- horizontal, vertical, or a diagonal line at exactly 45 degrees."
- */
- final int horizontalStep = start().x() == end().x()
- ? 0
- : start().x() < end().x()
- ? 1
- : -1;
- final int verticalStep = start().y() == end().y()
- ? 0
- : start().y() < end().y()
- ? 1
- : -1;
- final Predicate xTester = start().x() == end().x()
- ? x -> true
- : start().x() < end().x()
- ? x -> x <= end().x()
- : x -> x >= end().x();
- final Predicate yTester = start().y() == end().y()
- ? y -> true
- : start().y() < end().y()
- ? y -> y <= end().y()
- : y -> y >= end().y();
-
- for (int i = start().x(), j = start().y();
- xTester.test(i) && yTester.test(j);
- i += horizontalStep, j += verticalStep) {
- diagram[i][j]++;
+ public void execute(final Deque[] columns) {
+ final Deque from = columns[from()];
+ final Deque to = columns[to()];
+ for(int i = count(); --i >= 0; ) {
+ to.push(from.pop());
}
}
+ }
- public int lowestX() {
- return Math.min(start().x(), end().x());
- }
-
- public int highestX() {
- return Math.max(start().x(), end().x());
- }
-
- public int lowestY() {
- return Math.min(start().y(), end().y());
- }
-
- public int highestY() {
- return Math.max(start().y(), end().y());
+ public record CrateMover9001Instruction(int count, int from, int to) {
+ public static CrateMover9001Instruction parse(final String line) {
+ final var components = line.split(" ");
+ final var count = Integer.parseInt(components[1]);
+ final var from = Integer.parseInt(components[3]) - 1;
+ final var to = Integer.parseInt(components[5]) - 1;
+ return new CrateMover9001Instruction(count, from, to);
}
-
- public String toString() {
- return start() + " -> " + end();
+ public void execute(final Deque[] columns) {
+ final Deque from = columns[from()];
+ final Deque to = columns[to()];
+ final var buffer = new LinkedList();
+ for(int i = count(); --i >= 0; ) {
+ buffer.push(from.pop());
+ }
+ while(!buffer.isEmpty()) {
+ to.push(buffer.pop());
+ }
}
}
-
- protected Stream getInput() {
+ protected Stream getInput() {
return StreamSupport
.stream(new LineSpliterator("day-05.txt"),
- false)
- .map(LineSegment::parse);
- }
-
- protected record Extremes(int lowestX, int lowestY, int highestX, int highestY) {
- public Extremes combine(final LineSegment segment) {
- return new Extremes(Math.min(lowestX(), segment.lowestX()),
- Math.min(lowestY(), segment.lowestY()),
- Math.max(highestX(), segment.highestX()),
- Math.max(highestY(), segment.highestY()));
- }
-
- public Extremes combine(final Extremes other) {
- return new Extremes(Math.min(lowestX(), other.lowestX()),
- Math.min(lowestY(), other.lowestY()),
- Math.max(highestX(), other.highestX()),
- Math.max(highestY(), other.highestY()));
- }
-
- public int[][] createBlankDiagram() {
- final int[][] result = new int[highestX() + 1][];
- for (int i = result.length; --i >= 0; result[i] = new int[highestY() + 1]) ;
- return result;
- }
+ false);
}
@Test
public final void part1() {
- final var segments = getInput()
- // "For now, only consider horizontal and vertical lines: lines where either x1 = x2 or y1 = y2."
- .filter(segment -> segment.start().x() == segment.end().x() || segment.start().y() == segment.end().y())
- .collect(Collectors.toList());
-
- final var extremes = segments
- .stream()
- .reduce(new Extremes(0, 0, 0, 0),
- Extremes::combine,
- Extremes::combine);
- // there are no negative values
- // Note, we could save a little bit of space and time by using a smaller map since none of the line segments
- // need point 0,0. However, the savings are likely negligible.
- final int[][] diagram = extremes.createBlankDiagram();
-
- for (final var segment : segments) {
- segment.update(diagram);
- }
- int sum = 0;
- for (int i = diagram.length; --i >= 0; ) {
- final var row = diagram[i];
- for (int j = row.length; --j >= 0; ) {
- if (row[j] >= 2) {
- sum++;
+ int mode = 0;
+ final Deque[] columns = new Deque[9];
+ for(int i = columns.length; --i >= 0; columns[i] = new LinkedList<>());
+ for(final var line : getInput().toList()) {
+ if(line.isBlank()) {
+ mode = 1;
+ }
+ if( mode == 0 ) {
+ final var chars = line.toCharArray();
+ int index = -1;
+ for(int i = 0; i < chars.length; i++) {
+ if(chars[i] == '[') {
+ index = i / 4;
+ } else if(index >= 0) {
+ columns[index].addLast(chars[i]);
+ index = -1;
+ }
+ }
+ } else {
+ if(line.isBlank()) {
+ continue;
}
+ final var instruction = CrateMover9000Instruction.parse(line);
+ instruction.execute(columns);
}
}
- System.out.println("Part 1: " + sum);
+ final var builder = new StringBuilder();
+ for(final var column : columns) {
+ if(!column.isEmpty()) {
+ builder.append(column.getFirst());
+ }
+ }
+ final var result = builder.toString();
+
+ System.out.println("Part 1: " + result);
}
@Test
public final void part2() {
- /*
- "Unfortunately, considering only horizontal and vertical lines doesn't give you the full picture; you need to
- also consider diagonal lines."
- */
- final var segments = getInput()
- .collect(Collectors.toList());
- final var extremes = segments
- .stream()
- .reduce(new Extremes(0, 0, 0, 0),
- Extremes::combine,
- Extremes::combine);
- // there are no negative values
- // Note, we could save a little bit of space and time by using a smaller map since none of the line segments
- // need point 0,0. However, the savings are likely negligible.
- final int[][] diagram = extremes.createBlankDiagram();
- for (final var segment : segments) {
- segment.update(diagram);
- }
- int sum = 0;
- for (int i = diagram.length; --i >= 0; ) {
- final var row = diagram[i];
- for (int j = row.length; --j >= 0; ) {
- if (row[j] >= 2) {
- sum++;
+ int mode = 0;
+ final Deque[] columns = new Deque[9];
+ for(int i = columns.length; --i >= 0; columns[i] = new LinkedList<>());
+ for(final var line : getInput().toList()) {
+ if(line.isBlank()) {
+ mode = 1;
+ }
+ if( mode == 0 ) {
+ final var chars = line.toCharArray();
+ int index = -1;
+ for(int i = 0; i < chars.length; i++) {
+ if(chars[i] == '[') {
+ index = i / 4;
+ } else if(index >= 0) {
+// System.err.println("column[ " + index + " ].addLast( " + chars[ i ] + " )" );
+ columns[index].addLast(chars[i]);
+ index = -1;
+ }
}
+ } else {
+ if(line.isBlank()) {
+ continue;
+ }
+ final var instruction = CrateMover9001Instruction.parse(line);
+ instruction.execute(columns);
+ }
+ }
+ final var builder = new StringBuilder();
+ for(final var column : columns) {
+ if(!column.isEmpty()) {
+ builder.append(column.getFirst());
}
}
- System.out.println("Part 2: " + sum);
+ final var result = builder.toString();
+
+ System.out.println("Part 2: " + result);
+
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day06.java b/src/test/java/com/macasaet/Day06.java
index 10a59b3..769fb1b 100644
--- a/src/test/java/com/macasaet/Day06.java
+++ b/src/test/java/com/macasaet/Day06.java
@@ -1,120 +1,55 @@
package com.macasaet;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashSet;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 6: Lanternfish ---
+ * --- Day 6: ---
+ * https://adventofcode.com/2022/day/6
*/
public class Day06 {
- /**
- * A glowing fish that spawns very quickly. Their population grows exponentially.
- */
- public static class Lanternfish {
-
- private int daysToSpawn;
-
- /**
- * @param daysToSpawn the number of days until it creates a new {@link Lanternfish}
- */
- public Lanternfish(final int daysToSpawn) {
- setDaysToSpawn(daysToSpawn);
- }
-
- /**
- * Simulate the passage of one day
- *
- * @return either a new Lanternfish or nothing depending on whether the fish spawned
- */
- public Optional tick() {
- final var timer = getDaysToSpawn() - 1;
- if (timer < 0) {
- setDaysToSpawn(6);
- return Optional.of(new Lanternfish(8));
- } else {
- setDaysToSpawn(timer);
- return Optional.empty();
- }
- }
-
- /**
- * @return the number of days until the fish spawns
- */
- public int getDaysToSpawn() {
- return this.daysToSpawn;
- }
-
- /**
- * Update this fish's days to spawn
- *
- * @param daysToSpawn the number of days until the fish spawns, must be non-negative
- */
- protected void setDaysToSpawn(final int daysToSpawn) {
- if (daysToSpawn < 0) {
- throw new IllegalArgumentException("\"days to spawn\" must be non-negative");
- }
- this.daysToSpawn = daysToSpawn;
- }
- }
-
protected Stream getInput() {
return StreamSupport
.stream(new LineSpliterator("day-06.txt"),
false);
}
- public List parseInput() {
- final var list = getInput().toList();
- final var line = list.get(0);
- final var components = line.split(",");
- return Arrays.stream(components)
- .mapToInt(Integer::parseInt)
- .mapToObj(Lanternfish::new)
- .collect(Collectors.toList());
- }
-
@Test
public final void part1() {
- var population = parseInput();
- for (int _i = 80; --_i >= 0; ) {
- final List list = new ArrayList<>(population);
- for (final var fish : population) {
- final var result = fish.tick();
- result.ifPresent(list::add);
+ final var input = getInput().findFirst().get();
+ for(int i = 4; i < input.length(); i++) {
+ final var set = new HashSet(4);
+ for(int j = 0; j < 4; j++) {
+ set.add(input.charAt(i - 4 + j));
+ }
+ if(set.size() >= 4) {
+ final var result = i;
+ System.out.println("Part 1: " + result);
+ return;
}
- population = list;
}
- System.out.println("Part 1: " + population.size());
+ throw new IllegalStateException();
}
@Test
public final void part2() {
- final var initial = parseInput();
- var map = new long[9];
- for (final var fish : initial) {
- map[fish.getDaysToSpawn()]++;
- }
- for (int _i = 256; --_i >= 0; ) {
- final var temp = new long[map.length];
- for (int daysToSpawn = map.length; --daysToSpawn >= 0; ) {
- final var count = map[daysToSpawn];
- final var prototype = new Lanternfish(daysToSpawn);
- final var result = prototype.tick();
- temp[prototype.getDaysToSpawn()] += count;
- result.ifPresent(spawn -> temp[spawn.getDaysToSpawn()] = temp[spawn.getDaysToSpawn()] + count);
+ final var input = getInput().findFirst().get();
+ for(int i = 14; i < input.length(); i++) {
+ final var set = new HashSet(14);
+ for(int j = 0; j < 14; j++) {
+ set.add(input.charAt(i - 14 + j));
+ }
+ if(set.size() >= 14) {
+ final var result = i;
+ System.out.println("Part 2: " + result);
+ return;
}
- map = temp;
}
- final var result = Arrays.stream(map).reduce(0L, Long::sum);
- System.out.println("Part 2: " + result);
+ throw new IllegalStateException();
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day07.java b/src/test/java/com/macasaet/Day07.java
index 4393d46..5aee1bc 100644
--- a/src/test/java/com/macasaet/Day07.java
+++ b/src/test/java/com/macasaet/Day07.java
@@ -1,112 +1,240 @@
package com.macasaet;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 7: The Treachery of Whales ---
+ * --- Day 7: ---
+ * https://adventofcode.com/2022/day/7
*/
public class Day07 {
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-07.txt"),
- false);
+ static class Session {
+ private final Directory root = new Directory("/", new HashMap<>());
+ private Directory workingDirectory = root;
+ private final Map parentMap = new HashMap<>();
+ }
+
+ static abstract class File {
+ abstract int size();
+ }
+
+ static class Directory extends File {
+ private final String name;
+ private final Map files;
+
+ public Directory(String name, Map files) {
+ this.name = name;
+ this.files = files;
+ }
+
+ int size() {
+ int result = 0;
+ for(final var file : files.values()) {
+ result += file.size();
+ }
+ return result;
+ }
+
+ public String toString() {
+ return "Directory{" +
+ "name='" + name + '\'' +
+ '}';
+ }
+
+ final Set findDirectoriesSmallerThan(final int maxSize) {
+ final var result = new HashSet();
+ for(final var file : files.values()) {
+ try {
+ final var directory = (Directory)file;
+ result.addAll(directory.findDirectoriesSmallerThan(maxSize));
+ } catch(final ClassCastException ignored) {
+ }
+ }
+ if(size() < maxSize) { // FIXME duplicated traversal
+ result.add(this);
+ }
+ return Collections.unmodifiableSet(result);
+ }
+ final Set findDirectoriesLargerThan(final int minSize) {
+ final var result = new HashSet();
+ if(size() >= minSize) {
+ for (final var file : files.values()) { // FIXME duplicated traversal
+ try {
+ final var directory = (Directory) file;
+ result.addAll(directory.findDirectoriesLargerThan(minSize));
+ } catch (final ClassCastException ignored) {
+ }
+ }
+ result.add(this);
+ }
+ return Collections.unmodifiableSet(result);
+ }
+ }
+
+ static class Leaf extends File {
+ private final String name;
+ private final int size;
+
+ public Leaf(String name, int size) {
+ this.name = name;
+ this.size = size;
+ }
+
+ int size() {
+ return size;
+ }
+
+ @Override
+ public String toString() {
+ return "Leaf{" +
+ "name='" + name + '\'' +
+ ", size=" + size +
+ '}';
+ }
+ }
+
+ static abstract class Line {
+ static Line parse(final String line) {
+ if(line.startsWith("$")) {
+ return Command.parse(line);
+ }
+ return Output.parse(line);
+ }
+ abstract void execute(Session session);
+ }
+
+ static abstract class Command extends Line {
+ static Command parse(final String line) {
+ if(line.startsWith("$ cd")) {
+ return ChangeDirectory.parse(line);
+ }
+ return ListContents.parse(line);
+ }
}
- /**
- * @return the horizontal position of each crab submarine in the swarm
- */
- protected List getCrabPositions() {
- final var list = getInput().collect(Collectors.toList());
- final var line = list.get(0);
- return Arrays.stream(line.split(","))
- .mapToInt(Integer::parseInt)
- .collect(ArrayList::new, List::add, List::addAll);
+ static class ListContents extends Command {
+ void execute(final Session session) {
+ }
+ static Command parse(final String ignored) {
+ return new ListContents();
+ }
}
- /**
- * Assuming a constant fuel consumption rate, calculate the fuel required for the swarm to reach alignmentPoint.
- *
- * @param positions the starting position of each crab submarine
- * @param alignmentPoint a potential point for the crabs to gather in order to blast a hole in the ocean floor
- * @return the fuel required to reach the alignment point
- */
- protected int calculateConstantFuel(final Iterable extends Integer> positions, final int alignmentPoint) {
- int sum = 0;
- for (final var position : positions) {
- sum += Math.abs(alignmentPoint - position);
- }
- return sum;
+ static class ChangeDirectory extends Command {
+ private final String argument;
+
+ public ChangeDirectory(String argument) {
+ this.argument = argument;
+ }
+
+ void execute(Session session) {
+ if("..".equals(argument)) {
+ final var parent = session.parentMap.get(session.workingDirectory);
+ if(parent == null) {
+ throw new IllegalArgumentException("Working directory has no parent: " + session.workingDirectory);
+ }
+ session.workingDirectory = parent;
+ } else if( "/".equals(argument)) {
+ session.workingDirectory = session.root;
+ } else {
+ final var target = (Directory) session.workingDirectory.files.get(argument);
+ if(target == null) {
+ throw new IllegalArgumentException("No directory named \"" + argument + "\" inside \"" + session.workingDirectory + "\"");
+ }
+ session.workingDirectory = target;
+ }
+ }
+
+ static ChangeDirectory parse(final String line) {
+ return new ChangeDirectory(line.split(" ")[2]);
+ }
}
- /**
- * Calculate the fuel required for the swarm to reach alignmentPoint
- *
- * @param positions the starting position for each crab submarine
- * @param alignmentPoint a potential point for the crabs to gather in order to blast a hole in the ocean floor
- * @return the fuel required to reach the alignment point
- */
- protected int calculateFuel(final Iterable extends Integer> positions, final int alignmentPoint) {
- int sum = 0;
- for (final var position : positions) {
- sum += calculateFuel(position, alignmentPoint);
- }
- return sum;
+ static abstract class Output extends Line {
+ static Output parse(final String line) {
+ if(line.startsWith("dir")) {
+ return DirectoryListing.parse(line);
+ }
+ return LeafListing.parse(line);
+ }
}
- /**
- * Calculate the fuel required for a single crab submarine to travel from one horizontal position to the next.
- *
- * @param start the starting position (inclusive)
- * @param end the ending position (inclusive)
- * @return the amount of fuel consumed in the journey
- */
- protected int calculateFuel(final int start, final int end) {
- final int target = Math.abs(end - start);
- int sum = 0;
- for (int i = target; --i >= 0; ) {
- sum += i + 1;
- }
- return sum;
+ static class DirectoryListing extends Output {
+ private final String name;
+
+ public DirectoryListing(String name) {
+ this.name = name;
+ }
+
+ void execute(Session session) {
+ final var directory = new Directory(name, new HashMap<>());
+ session.parentMap.put(directory, session.workingDirectory); // TODO method on Session
+ session.workingDirectory.files.put(name, directory);
+ }
+
+ static DirectoryListing parse(final String line) {
+ final var components = line.split(" ");
+ return new DirectoryListing(components[1]);
+ }
+ }
+
+ static class LeafListing extends Output {
+ private final int size;
+ private final String name;
+
+ public LeafListing(int size, String name) {
+ this.size = size;
+ this.name = name;
+ }
+
+ void execute(Session session) {
+ session.workingDirectory.files.put(name, new Leaf(name, size));
+ }
+
+ static LeafListing parse(final String line) {
+ final var components = line.split(" ");
+ final var size = Integer.parseInt(components[0]);
+ final var name = components[1];
+ return new LeafListing(size, name);
+ }
+ }
+
+ protected Stream getInput() {
+ return StreamSupport
+ .stream(new LineSpliterator("day-07.txt"),
+ false)
+ .map(Line::parse);
}
@Test
public final void part1() {
- int min = Integer.MAX_VALUE;
- int max = Integer.MIN_VALUE;
- final var positions = getCrabPositions();
- for (final var position : positions) {
- min = Math.min(min, position);
- max = Math.max(max, position);
- }
- final int result = IntStream.range(min, max)
- .map(alignmentPoint -> calculateConstantFuel(positions, alignmentPoint))
- .min()
- .getAsInt();
+ final var session = new Session();
+ getInput().forEach(line -> line.execute(session));
+ final var result = session.root.findDirectoriesSmallerThan(100_000).stream().mapToInt(Directory::size).sum();
System.out.println("Part 1: " + result);
}
@Test
public final void part2() {
- int min = Integer.MAX_VALUE;
- int max = Integer.MIN_VALUE;
- final var positions = getCrabPositions();
- for (final var position : positions) {
- min = Math.min(min, position);
- max = Math.max(max, position);
- }
- final int result = IntStream.range(min, max)
- .map(alignmentPoint -> calculateFuel(positions, alignmentPoint))
- .min()
- .getAsInt();
+ final var session = new Session();
+ getInput().forEach(line -> line.execute(session));
+ final var consumed = session.root.size();
+ final var unused = 70_000_000 - consumed;
+ final var required = 30_000_000 - unused;
+ final var result = session.root.findDirectoriesLargerThan(required)
+ .stream()
+ .min(Comparator.comparing(Directory::size))
+ .get()
+ .size();
System.out.println("Part 2: " + result);
}
diff --git a/src/test/java/com/macasaet/Day08.java b/src/test/java/com/macasaet/Day08.java
index 7ac8b58..98c498e 100644
--- a/src/test/java/com/macasaet/Day08.java
+++ b/src/test/java/com/macasaet/Day08.java
@@ -1,224 +1,160 @@
package com.macasaet;
-import java.util.*;
+import org.junit.jupiter.api.Test;
+
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 8: Seven Segment Search ---
+ * --- Day 8: Treetop Tree House ---
+ * https://adventofcode.com/2022/day/8
*/
public class Day08 {
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-08.txt"),
- false);
- }
-
- public record Digit(List segments) {
- public Digit decode(final Map map) {
- return new Digit(segments().stream()
- .map(map::get)
- .collect(Collectors.toUnmodifiableList()));
- }
-
- public int asInt() {
- if (segments().size() == 6 && hasSegments('a', 'b', 'c', 'e', 'f', 'g')) { // FIXME use on/off mechanism
- return 0;
- } else if (segments().size() == 2 && hasSegments('c', 'f')) {
- return 1;
- } else if (segments().size() == 5 && hasSegments('a', 'c', 'd', 'e', 'g')) {
- return 2;
- } else if (segments().size() == 5 && hasSegments('a', 'c', 'd', 'f', 'g')) {
- return 3;
- } else if (segments().size() == 4 && hasSegments('b', 'c', 'd', 'f')) {
- return 4;
- } else if (segments().size() == 5 && hasSegments('a', 'b', 'd', 'f', 'g')) {
- return 5;
- } else if (segments().size() == 6 && hasSegments('a', 'b', 'd', 'e', 'f', 'g')) {
- return 6;
- } else if (segments().size() == 3 && hasSegments('a', 'c', 'f')) {
- return 7;
- } else if (segments().size() == 7 && hasSegments('a', 'b', 'c', 'd', 'e', 'f', 'g')) {
- return 8;
- } else if (segments().size() == 6 && hasSegments('a', 'b', 'c', 'd', 'f', 'g')) {
- return 9;
+ record Forest(int[][] grid) {
+ public int countVisible() {
+ int result = 0;
+ for(int i = grid().length; --i >= 0; ) {
+ final var row = grid()[i];
+ for(int j = row.length; --j >= 0; ) {
+ if(isVisible(i, j)) {
+ result++;
+ }
+ }
}
- throw new IllegalStateException("Invalid Digit: " + this);
+ return result;
}
- public boolean hasSegments(final char... segments) {
- for (final var segment : segments) {
- if (!hasSegment(segment)) {
- return false;
+ public int scenicScore(final int x, final int y) {
+ final int treeHeight = grid()[x][y];
+ int northScore = 0;
+ for(int i = x; --i >= 0; ) {
+ final var height = grid()[i][y];
+ northScore += 1;
+ if(height >= treeHeight) {
+ break;
}
}
- return true;
- }
-
- public boolean hasSegment(final char segment) {
- return segments().contains(segment);
- }
-
- public static Digit parse(final String string) {
- final var array = string.toCharArray();
- final var list = new ArrayList(array.length);
- for (final var c : array) {
- list.add(c);
+ int southScore = 0;
+ for(int i = x + 1; i < grid().length; i++) {
+ final var height = grid()[i][y];
+ southScore += 1;
+ if(height >= treeHeight) {
+ break;
+ }
}
- return new Digit(Collections.unmodifiableList(list));
- }
- }
-
- public static record Entry(List uniqueSignalPatterns, List outputValue) {
-
- public int decodeOutput() {
- final var map = buildDecodingMap();
- final StringBuilder builder = new StringBuilder();
- for (final var outputDigit : outputValue()) {
- final var decodedDigit = outputDigit.decode(map);
- final int digit = decodedDigit.asInt();
- builder.append(digit);
+ int westScore = 0;
+ for(int j = y; --j >= 0; ) {
+ final var height = grid()[x][j];
+ westScore += 1;
+ if(height >= treeHeight) {
+ break;
+ }
}
- final String stringInt = builder.toString();
- return Integer.parseInt(stringInt);
- }
-
- protected SortedSet getDigitSegmentsWithCount(final int n) {
- return uniqueSignalPatterns().stream()
- .filter(digit -> digit.segments().size() == n)
- .findFirst()
- .get()
- .segments()
- .stream()
- .collect(TreeSet::new, SortedSet::add, SortedSet::addAll);
- }
-
- protected Set getDigitsWithCount(final int n) { // TODO return stream
- return uniqueSignalPatterns()
- .stream()
- .filter(digit -> digit.segments().size() == n).collect(Collectors.toUnmodifiableSet());
- }
-
- public Map buildDecodingMap() {
- final var encodingMap = buildEncodingMap();
- final var result = new HashMap();
- for(final var entry : encodingMap.entrySet()) {
- result.put(entry.getValue(), entry.getKey());
+ int eastScore = 0;
+ for(int j = y + 1; j < grid()[x].length; j++) {
+ final var height = grid()[x][j];
+ eastScore += 1;
+ if(height >= treeHeight) {
+ break;
+ }
}
- return Collections.unmodifiableMap(result);
+ return northScore * eastScore * southScore * westScore;
}
- public Map buildEncodingMap() {
- final var map = new HashMap();
- final var oneSegments = getDigitSegmentsWithCount(2);
- final var sevenSegments = getDigitSegmentsWithCount(3);
- final var fourSegments = getDigitSegmentsWithCount(4);
- final var eightSegments = getDigitSegmentsWithCount(7);
- final var aMapping = sevenSegments.stream().filter(c -> !oneSegments.contains(c)).findFirst().get();
- map.put('a', aMapping);
-
- final var zeroSixNine = getDigitsWithCount(6);
- var zsnSegments = zeroSixNine.stream().flatMap(digit -> digit.segments().stream()).collect(Collectors.toList());
- zsnSegments.removeIf(sevenSegments::contains);
- zsnSegments.removeIf(fourSegments::contains);
- final var sssMap = new HashMap();
- for (final var c : zsnSegments) {
- sssMap.compute(c, (_key, old) -> old == null ? 1 : old + 1);
+ boolean isVisible(final int x, final int y) {
+ if(x == 0 || x == grid().length || y == 0 || y == grid()[x].length) {
+ // trees on the edge
+ return true;
}
- if(sssMap.size() != 2) {
- throw new IllegalStateException("More segments for 0, 6, 9 encountered: " + sssMap);
+ final int treeHeight = grid()[x][y];
+ if (!isObstructedFromTheNorth(x, y, treeHeight)) {
+ return true;
}
- for (final var entry : sssMap.entrySet()) {
- if (entry.getValue() == 3) {
- map.put('g', entry.getKey());
- } else if (entry.getValue() == 2) {
- map.put('e', entry.getKey());
- } else {
- throw new IllegalStateException();
- }
+ if (!isObstructedFromTheSouth(x, y, treeHeight)) {
+ return true;
}
-
- final var twoFiveThree = getDigitsWithCount(5);
- var tftSegments = twoFiveThree.stream().flatMap(digit -> digit.segments.stream()).collect(Collectors.toList());
- tftSegments.removeIf(sevenSegments::contains);
- tftSegments.removeIf(candidate -> candidate.equals(map.get('e')));
- tftSegments.removeIf(candidate -> candidate.equals(map.get('g')));
- final var tftCounts = new HashMap();
- for(final var c : tftSegments) {
- tftCounts.compute(c, (_key, old) -> old == null ? 1 : old + 1);
+ if (!isObstructedFromTheWest(x, y, treeHeight)) {
+ return true;
}
- for(final var entry : tftCounts.entrySet()) {
- if(entry.getValue() == 3) {
- map.put('d', entry.getKey());
- } else if(entry.getValue() == 1) {
- map.put('b', entry.getKey());
- } else {
- throw new IllegalStateException();
- }
+ if (!isObstructedFromTheEast(x, y, treeHeight)) {
+ return true;
}
+ return false;
+ }
- zsnSegments = zeroSixNine.stream().flatMap(digit -> digit.segments().stream()).collect(Collectors.toList());
- zsnSegments.removeIf(candidate -> candidate.equals(map.get('a')));
- zsnSegments.removeIf(candidate -> candidate.equals(map.get('b')));
- zsnSegments.removeIf(candidate -> candidate.equals(map.get('d')));
- zsnSegments.removeIf(candidate -> candidate.equals(map.get('e')));
- zsnSegments.removeIf(candidate -> candidate.equals(map.get('g')));
- final var zsnCounts = new HashMap();
- for(final var c : zsnSegments) {
- zsnCounts.compute(c, (_key, old) -> old == null ? 1 : old + 1);
- }
- for(final var entry : zsnCounts.entrySet()) {
- if(entry.getValue() == 2) {
- map.put('c', entry.getKey());
- } else if( entry.getValue() == 3) {
- map.put('f', entry.getKey());
- } else {
- throw new IllegalStateException();
+ private boolean isObstructedFromTheEast(int x, int y, int treeHeight) {
+ for(int j = grid()[x].length; --j > y; ) {
+ if(grid()[x][j] >= treeHeight) {
+ return true;
}
}
+ return false;
+ }
- return map;
+ private boolean isObstructedFromTheWest(int x, int y, int treeHeight) {
+ for(int j = y; --j >= 0; ) {
+ if(grid()[x][j] >= treeHeight) {
+ return true;
+ }
+ }
+ return false;
}
- public static Entry parse(final String string) {
- final var components = string.split(" \\| ");
- final var uniqueSignalPatterns = components[0].split(" ");
- final var outputValue = components[1].split(" ");
+ private boolean isObstructedFromTheSouth(int x, int y, int treeHeight) {
+ for(int i = grid().length; --i > x; ) {
+ if(grid()[i][y] >= treeHeight) {
+ return true;
+ }
+ }
+ return false;
+ }
- return new Entry(Arrays.stream(uniqueSignalPatterns)
- .map(Digit::parse)
- .collect(Collectors.toUnmodifiableList()),
- Arrays.stream(outputValue)
- .map(Digit::parse)
- .collect(Collectors.toUnmodifiableList()));
+ private boolean isObstructedFromTheNorth(int x, int y, int treeHeight) {
+ for(int i = x; --i >= 0; ) {
+ if(grid()[i][y] >= treeHeight) {
+ return true;
+ }
+ }
+ return false;
}
+ }
+ protected Forest getInput() {
+ final var list = StreamSupport
+ .stream(new LineSpliterator("day-08.txt"),
+ false)
+ .map(line -> {
+ final var chars = line.toCharArray();
+ final var row = new int[chars.length];
+ for(int i = chars.length; --i >= 0; row[i] = chars[i] - '0');
+ return row;
+ })
+ .collect(Collectors.toList());
+ final var grid = new int[list.size()][];
+ for(int i = list.size(); --i >= 0; grid[i] = list.get(i));
+ return new Forest(grid);
}
@Test
public final void part1() {
- final var result = getInput()
- .map(Entry::parse)
- .flatMap(entry -> entry.outputValue().stream())
- .filter(digit -> {
- final var segments = digit.segments();
- final var numSegments = segments.size();
- return numSegments == 2 || numSegments == 4 || numSegments == 3 || numSegments == 7;
- })
- .count();
+ final var forest = getInput();
+ final var result = forest.countVisible();
System.out.println("Part 1: " + result);
}
@Test
public final void part2() {
- final var result = getInput()
- .map(Entry::parse)
- .mapToInt(Entry::decodeOutput).sum();
-
+ final var forest = getInput();
+ int result = Integer.MIN_VALUE;
+ for(int i = forest.grid().length; --i >= 0; ) {
+ for( int j = forest.grid.length; --j >= 0; ) {
+ final var score = forest.scenicScore(i, j);
+ if(score > result) {
+ result = score;
+ }
+ }
+ }
System.out.println("Part 2: " + result);
}
diff --git a/src/test/java/com/macasaet/Day09.java b/src/test/java/com/macasaet/Day09.java
index abd2f4c..b6bcb00 100644
--- a/src/test/java/com/macasaet/Day09.java
+++ b/src/test/java/com/macasaet/Day09.java
@@ -1,176 +1,168 @@
package com.macasaet;
-import java.util.*;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.TreeSet;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 9: Smoke Basin ---
+ * --- Day 9: Rope Bridge ---
+ * https://adventofcode.com/2022/day/9
*/
public class Day09 {
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-09.txt"),
- false);
- }
-
- public HeightMap getHeightMap() {
- final var list = getInput().map(line -> {
- final var chars = line.toCharArray();
- final var ints = new int[chars.length];
- for (int i = chars.length; --i >= 0; ints[i] = chars[i] - '0') ;
- return ints;
- }).collect(Collectors.toList());
- final int[][] grid = new int[list.size()][];
- for (int i = list.size(); --i >= 0; grid[i] = list.get(i)) ;
- return new HeightMap(grid);
- }
+ record Coordinate(int x, int y) {
- /**
- * A height map of the floor of the nearby caves generated by the submarine
- */
- public record HeightMap(int[][] grid) { // FIXME use bytes
+ public int distance(final Coordinate other) {
+ return (int)Math.sqrt(Math.pow((double)x() - (double)other.x(), 2.0) + Math.pow((double)y() - (double)other.y(), 2.0));
+ }
- public Stream points() {
- return IntStream.range(0, grid().length)
- .boxed()
- .flatMap(i -> IntStream.range(0, grid()[i].length)
- .mapToObj(j -> new Point(i, j)));
+ public Coordinate step(final int xDistance, final int yDistance) {
+ return new Coordinate(x() + xDistance, y + yDistance);
}
+ public Coordinate stepTowards(final Coordinate leader) {
+ final var xDistance = Integer.compare(leader.x(), x());
+ final var yDistance = Integer.compare(leader.y(), y());
+ return step(xDistance, yDistance);
+ }
+ }
- /**
- * A location on the floor of a nearby cave
- */
- public class Point {
- final int x;
- final int y;
+ public static class Rope {
- public Point(final int x, final int y) {
- this.x = x;
- this.y = y;
- }
+ Coordinate[] knotCoordinates;
- public int x() {
- return this.x;
- }
+ final SortedMap> visited = new TreeMap<>();
- public int y() {
- return this.y;
- }
+ public Rope(final int knots) {
+ knotCoordinates = new Coordinate[knots];
+ for(int i = knots; --i >= 0; knotCoordinates[i] = new Coordinate(0, 0));
+ visited.computeIfAbsent(0, (key) -> new TreeSet<>()).add(0);
+ }
- public int getBasinSize() {
- return getBasinPoints().size();
+ public int countVisited() {
+ int result = 0;
+ for( final var map : visited.values() ) {
+ result += map.size();
}
+ return result;
+ }
- /**
- * Identify all the higher points that are also part of the same basin, assuming this location is part of a
- * basin.
- *
- * @return all the higher points, if any, that are part of the same basin.
- */
- public Set getBasinPoints() {
- if (getHeight() >= 9) {
- return Collections.emptySet();
+ public void process(final Instruction instruction) {
+ final int xStep = instruction.direction().xStep();
+ final int yStep = instruction.direction().yStep();
+
+ for(int i = instruction.distance(); --i >= 0; ) {
+ knotCoordinates[0] = knotCoordinates[0].step(xStep, yStep);
+ for(int j = 1; j < knotCoordinates.length; j++) {
+ moveKnot(j);
}
- final var result = new HashSet();
- result.add(this);
- final Function> basinPointRetriever = neighbour -> {
- if (neighbour.getHeight() >= 9 || neighbour.getHeight() <= getHeight() || result.contains(neighbour)) {
- return Stream.empty();
- }
- return neighbour.getBasinPoints().stream();
- };
- above().stream().flatMap(basinPointRetriever).forEach(result::add);
- below().stream().flatMap(basinPointRetriever).forEach(result::add);
- left().stream().flatMap(basinPointRetriever).forEach(result::add);
- right().stream().flatMap(basinPointRetriever).forEach(result::add);
- return Collections.unmodifiableSet(result);
}
+ }
- /**
- * @return true if and only if this location is lower than all of its adjacent locations (up to four,
- * diagonals do not count)
- */
- public boolean isLowPoint() {
- final var compareTo = new ArrayList(4);
- above().ifPresent(compareTo::add);
- below().ifPresent(compareTo::add);
- left().ifPresent(compareTo::add);
- right().ifPresent(compareTo::add);
- return compareTo.stream().allMatch(neighbour -> neighbour.getHeight() > getHeight());
+ protected void moveKnot(int knotIndex) {
+ if(knotIndex <= 0) {
+ throw new IllegalArgumentException("Cannot move head");
}
+ final var leader = knotCoordinates[knotIndex - 1];
+ var follower = knotCoordinates[knotIndex];
- /**
- * @return an assessment of the risk from smoke flowing through the cave
- */
- public int getRiskLevel() {
- return getHeight() + 1;
+ if(leader.equals(follower)) {
+ return;
+ } else if (leader.distance(follower) <= 1) {
+ return;
}
- /**
- * @return the height of this particular location, from 0-9
- */
- public int getHeight() {
- return grid()[x()][y()];
- }
+ follower = follower.stepTowards(leader);
+ knotCoordinates[knotIndex] = follower;
- public Optional above() {
- return x() > 0 ? Optional.of(new Point(x() - 1, y())) : Optional.empty();
+ if(knotIndex == knotCoordinates.length - 1) {
+ visited.computeIfAbsent(follower.x(), (key) -> new TreeSet<>()).add(follower.y());
}
+ }
- public Optional below() {
- return x() < grid().length - 1 ? Optional.of(new Point(x() + 1, y())) : Optional.empty();
- }
+ }
- public Optional left() {
- return y() > 0 ? Optional.of(new Point(x(), y() - 1)) : Optional.empty();
+ enum Direction {
+ Up {
+ int xStep() {
+ return -1;
}
-
- public Optional right() {
- return y() < grid()[x()].length - 1 ? Optional.of(new Point(x(), y() + 1)) : Optional.empty();
+ int yStep() {
+ return 0;
}
-
- public int hashCode() {
- return Objects.hash(x(), y());
+ },
+ Down {
+ int xStep() {
+ return 1;
}
-
- public boolean equals(final Object o) {
- try {
- final Point other = (Point) o;
- return this.x() == other.x() && this.y() == other.y();
- } catch (final ClassCastException cce) {
- return false;
- }
+ int yStep() {
+ return 0;
+ }
+ },
+ Left {
+ int xStep() {
+ return 0;
}
+ int yStep() {
+ return -1;
+ }
+ },
+ Right {
+ int xStep() {
+ return 0;
+ }
+ int yStep() {
+ return 1;
+ }
+ };
+
+ abstract int xStep();
+ abstract int yStep();
+
+ static Direction parse(final String string) {
+ return switch(string.trim()) {
+ case "U" -> Up;
+ case "D" -> Down;
+ case "L" -> Left;
+ case "R" -> Right;
+ default -> throw new IllegalArgumentException("Invalid direction: " + string);
+ };
}
+ }
+ record Instruction(Direction direction, int distance) {
+ static Instruction parse(final String string) {
+ final var components = string.split(" ");
+ final var direction = Direction.parse(components[0]);
+ final var distance = Integer.parseInt(components[1]);
+ return new Instruction(direction, distance);
+ }
+ }
+
+ protected Stream getInput() {
+ return StreamSupport
+ .stream(new LineSpliterator("day-09.txt"),
+ false)
+ .map(Instruction::parse);
}
@Test
public final void part1() {
- final var map = getHeightMap();
- final int sum = map.points()
- .filter(HeightMap.Point::isLowPoint)
- .mapToInt(HeightMap.Point::getRiskLevel)
- .sum();
- System.out.println("Part 1: " + sum);
+ final var rope = new Rope(2);
+ getInput().forEach(rope::process);
+ final var result = rope.countVisited();
+ System.out.println("Part 1: " + result);
}
@Test
public final void part2() {
- final var map = getHeightMap();
- final var basinSizes = map.points()
- .filter(HeightMap.Point::isLowPoint)
- .mapToInt(HeightMap.Point::getBasinSize)
- .collect(() -> new TreeSet(Comparator.reverseOrder()), SortedSet::add, SortedSet::addAll);
- final var iterator = basinSizes.iterator();
- final var result = iterator.next() * iterator.next() * iterator.next();
+ final var rope = new Rope(10);
+ getInput().forEach(rope::process);
+ final var result = rope.countVisited();
System.out.println("Part 2: " + result);
}
diff --git a/src/test/java/com/macasaet/Day10.java b/src/test/java/com/macasaet/Day10.java
index a7723f4..a289ed3 100644
--- a/src/test/java/com/macasaet/Day10.java
+++ b/src/test/java/com/macasaet/Day10.java
@@ -1,137 +1,153 @@
package com.macasaet;
-import java.util.ArrayList;
-import java.util.LinkedList;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 10: Syntax Scoring ---
+ * --- Day 10: Cathode-Ray Tube ---
+ * https://adventofcode.com/2022/day/10
*/
public class Day10 {
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-10.txt"),
- false);
- }
+ public enum Instruction {
+ noop {
+ public int cycles() {
+ return 0;
+ }
+ },
+ addx {
+ public int cycles() {
+ return 2;
+ }
+ };
+
+ public abstract int cycles();
- /**
- * The type of open and closing delimiter for a chunk in the navigation subsystem
- */
- public enum BracketType {
- PARENTHESIS('(', ')', 3, 1),
- SQUARE('[', ']', 57, 2),
- CURLY('{', '}', 1197, 3),
- ANGLED('<', '>', 25137, 4);
-
- private final char open;
- private final char close;
- private final int corruptionPoints;
- private final int autocompletePoints;
-
- BracketType(char open, char close, int corruptionPoints, final int autocompletePoints) {
- this.open = open;
- this.close = close;
- this.corruptionPoints = corruptionPoints;
- this.autocompletePoints = autocompletePoints;
+ public static Instruction parse(final String string) {
+ return Instruction.valueOf(string);
}
+ }
- public static BracketType forOpen(final char c) {
- return switch (c) {
- case '(' -> PARENTHESIS;
- case '[' -> SQUARE;
- case '{' -> CURLY;
- case '<' -> ANGLED;
- default -> throw new IllegalStateException("Unexpected value: " + c);
+ public record Operation(Instruction instruction, Integer argument) {
+ public List execute(int cycle, int register) {
+ return switch (instruction()) {
+ case noop -> Collections.singletonList(new CycleSnapshot(cycle + 1, register));
+ case addx ->
+ List.of(new CycleSnapshot(cycle + 1, register),
+ new CycleSnapshot(cycle + 2, register + argument));
};
}
- public static BracketType forClose(final char c) {
- return switch (c) {
- case ')' -> PARENTHESIS;
- case ']' -> SQUARE;
- case '}' -> CURLY;
- case '>' -> ANGLED;
- default -> throw new IllegalStateException("Unexpected value: " + c);
- };
+ public static Operation parse(final String line) {
+ final var components = line.split(" ");
+ final var instruction = Instruction.parse(components[0]);
+ Integer argument = null;
+ if(instruction == Instruction.addx) {
+ argument = Integer.parseInt(components[1]);
+ }
+ return new Operation(instruction, argument);
}
}
- /**
- * @param line a line in the navigation subsystem
- * @return a score of how corrupt the line is. A score of zero means it is not corrupt. The higher the value, the
- * more corrupt the line is.
- */
- public int calculateCorruptionScore(final char[] line) {
- final var stack = new LinkedList();
- for (int i = 0; i < line.length; i++) {
- final var c = line[i];
- if (c == '(' || c == '[' || c == '{' || c == '<') {
- stack.push(BracketType.forOpen(c));
- } else if (c == ')' || c == ']' || c == '}' || c == '>') {
- if (stack.peek().close == c) {
- stack.pop();
- } else {
- // corrupt
- return BracketType.forClose(c).corruptionPoints;
- }
- }
+ public record CycleSnapshot(int cycle, int register) {
+ public int signalStrength() {
+ return cycle() * register();
}
- // if stack is not empty, it's incomplete
- return 0;
}
- /**
- * @param line a non-corrupt line in the navigation subsystem. Behaviour is undefined for corrupt lines.
- * @return the score for the suffix required to complete the line
- */
- public long calculateCompletionScore(final char[] line) {
- final var stack = new LinkedList();
- for (int i = 0; i < line.length; i++) {
- final var c = line[i];
- if (c == '(' || c == '[' || c == '{' || c == '<') {
- stack.push(BracketType.forOpen(c));
- } else if (c == ')' || c == ']' || c == '}' || c == '>') {
- if (stack.peek().close == c) {
- stack.pop();
- } else {
- throw new IllegalArgumentException("Corrupt: " + new String(line));
- }
+ public static class State {
+ private int register = 1;
+ private int cycle = 1;
+
+ public List getActivePixels() {
+ return Arrays.asList(register - 1, register, register + 1);
+ }
+
+ public List execute(final Operation operation) {
+ final var result = operation.execute(cycle, register);
+ final var last = result.get(result.size() - 1);
+ cycle = last.cycle();
+ register = last.register();
+ return result;
+ }
+ }
+
+ public static class Display {
+ final char[][] pixels = new char[6][];
+
+ {
+ for(int i = pixels.length; --i >= 0; pixels[i] = new char[40]);
+ }
+
+ public void update(final CycleSnapshot snapshot) {
+ final var pixelIndex = snapshot.cycle() - 1;
+ final var spritePositions =
+ Arrays.asList(snapshot.register() - 1, snapshot.register(), snapshot.register() + 1);
+ final int row = pixelIndex / 40;
+ final int column = pixelIndex % 40;
+ if(row >= pixels.length) {
+ return;
+ }
+ if(spritePositions.contains(column)) {
+ pixels[row][column] = '#';
+ } else {
+ pixels[row][column] = '.';
}
}
- long result = 0;
- while (!stack.isEmpty()) {
- final var unclosed = stack.pop();
- result = result * 5 + unclosed.autocompletePoints;
+ public String toString() {
+ final var buffer = new StringBuilder();
+ buffer.append(pixels[0], 0, 40);
+ buffer.append('\n');
+ buffer.append(pixels[1], 0, 40);
+ buffer.append('\n');
+ buffer.append(pixels[2], 0, 40);
+ buffer.append('\n');
+ buffer.append(pixels[3], 0, 40);
+ buffer.append('\n');
+ buffer.append(pixels[4], 0, 40);
+ buffer.append('\n');
+ buffer.append(pixels[5], 0, 40);
+ return buffer.toString();
}
- return result;
+ }
+
+ protected Stream getInput() {
+ return StreamSupport
+ .stream(new LineSpliterator("day-10.txt"),
+ false)
+ .map(Operation::parse);
}
@Test
public final void part1() {
- final var result = getInput()
- .map(String::toCharArray)
- .filter(line -> line.length > 0)
- .mapToInt(this::calculateCorruptionScore)
- .sum();
+ final var interestingCycles = Arrays.asList(20, 60, 100, 140, 180, 220);
+ final var state = new State();
+ final var accumulator = new AtomicInteger(0);
+ getInput().forEach(instruction -> {
+ final var sideEffects = state.execute(instruction);
+ for(final var sideEffect : sideEffects) {
+ if(interestingCycles.contains(sideEffect.cycle)) {
+ accumulator.addAndGet(sideEffect.signalStrength());
+ }
+ }
+ });
+ final var result = accumulator.get();
System.out.println("Part 1: " + result);
}
@Test
public final void part2() {
- final var list = getInput()
- .map(String::toCharArray)
- .filter(line -> line.length > 0)
- .filter(line -> calculateCorruptionScore(line) <= 0) // discard corrupted lines
- .mapToLong(this::calculateCompletionScore)
- .sorted()
- .collect(ArrayList::new, List::add, List::addAll);
- final var result = list.get(list.size() / 2);
- System.out.println("Part 2: " + result);
+ final var state = new State();
+ final var display = new Display();
+ display.update(new CycleSnapshot(1, 1));
+ getInput().forEach(instruction -> state.execute(instruction).forEach(display::update));
+ System.out.println("Part 2:\n" + display);
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day11.java b/src/test/java/com/macasaet/Day11.java
index f566fd3..45db617 100644
--- a/src/test/java/com/macasaet/Day11.java
+++ b/src/test/java/com/macasaet/Day11.java
@@ -1,169 +1,189 @@
package com.macasaet;
-import java.util.ArrayList;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.List;
-import java.util.stream.Stream;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 11: Dumbo Octopus ---
+ * --- Day 11: Monkey in the Middle ---
+ * https://adventofcode.com/2022/day/11
*/
public class Day11 {
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-11.txt"),
- false);
+ public enum Operator implements BiFunction {
+ ADD {
+ public BigInteger apply(BigInteger x, BigInteger y) {
+ return x.add(y);
+ }
+ },
+ MULTIPLY {
+ public BigInteger apply(BigInteger x, BigInteger y) {
+ return x.multiply(y);
+ }
+ };
+
+ public static Operator parse(final String string) {
+ return switch(string) {
+ case "*" -> MULTIPLY;
+ case "+" -> ADD;
+ default -> throw new IllegalArgumentException("Invalid operator: " + string);
+ };
+ }
}
- /**
- * @return a spatial grid of the cavern indicating the location of the octopuses
- */
- protected Octopus[][] getOctopusGrid() {
- final var list = getInput().map(line -> {
- final var chars = line.toCharArray();
- final byte[] result = new byte[chars.length];
- for (int i = chars.length; --i >= 0; result[i] = (byte) (chars[i] - '0')) ;
- return result;
- }).collect(ArrayList::new, List::add, List::addAll);
- final var result = new Octopus[list.size()][];
- for (int i = list.size(); --i >= 0; ) {
- final var row = list.get(i);
- result[i] = new Octopus[row.length];
- for (int j = row.length; --j >= 0; result[i][j] = new Octopus(i, j, row[j])) ;
+ public record Operation(Operator operator,
+ Function lValueSupplier,
+ Function rValueSupplier) implements Function {
+
+ public BigInteger apply(final BigInteger oldValue) {
+ Objects.requireNonNull(oldValue);
+ final var lValue = lValueSupplier.apply(oldValue);
+ final var rValue = rValueSupplier.apply(oldValue);
+ return operator.apply(lValue, rValue);
+ }
+
+ public static Operation parse(String line) {
+ line = line.strip();
+ if(!line.trim().startsWith("Operation:")) {
+ throw new IllegalArgumentException("Not an operation: " + line);
+ }
+ final var components = line.split(" ");
+ final var lValueExpression = components[3];
+ final Function lValueSupplier = "old".equalsIgnoreCase(lValueExpression)
+ ? old -> old
+ : ignored -> new BigInteger(lValueExpression);
+ final var operator = Operator.parse(components[4]);
+ final var rValueExpression = components[5];
+ final Function rValueSupplier = "old".equalsIgnoreCase(rValueExpression)
+ ? old -> old
+ : ignored -> new BigInteger(rValueExpression);
+ return new Operation(operator, lValueSupplier, rValueSupplier);
}
- return result;
}
/**
- * A rare bioluminescent dumbo octopus
+ * An observation of how a single monkey behaves
+ *
+ * @param id the monkey's unique identifier
+ * @param items your worry level for each belonging currently held by this monkey
+ * @param operation a function describing how your worry level changes when the monkey inspects the item
+ * @param divisor used by the monkey to evaluate your worry level and decide what to do with the item
+ * @param targetIfTrue the ID of the monkey who will receive the item should the test pass
+ * @param targetIfFalse the ID of the monkey who will receive the item should the test fail
+ * @param itemsInspected the total number of times this monkey has inspected an item
*/
- public static class Octopus {
- private final int x, y;
- private byte energyLevel;
-
- public Octopus(final int x, final int y, final byte energyLevel) {
- this.x = x;
- this.y = y;
- this.energyLevel = energyLevel;
+ public record Monkey(int id, List items, Operation operation, BigInteger divisor, int targetIfTrue, int targetIfFalse, AtomicReference itemsInspected) {
+ public static Monkey parse(final String block) {
+ final var lines = block.split("\n");
+ final var id = Integer.parseInt(lines[0].replaceAll("[^0-9]", ""));
+ final var startingItems = Arrays.stream(lines[1].strip()
+ .replaceAll("^Starting items: ", "")
+ .split(", "))
+ .map(item -> new BigInteger(item))
+ .collect(Collectors.toList()); // must be mutable
+ final var operation = Operation.parse(lines[2]);
+ final var divisor = new BigInteger(lines[3].replaceAll("[^0-9]", ""));
+ final var targetIfTrue = Integer.parseInt(lines[4].replaceAll("[^0-9]", ""));
+ final var targetIfFalse = Integer.parseInt(lines[5].replaceAll("[^0-9]", ""));
+ return new Monkey(id, startingItems, operation, divisor, targetIfTrue, targetIfFalse, new AtomicReference<>(BigInteger.ZERO));
}
- /**
- * Increase the octopus' energy level and, if appropriate, propagate side effects to its neighbours.
- *
- * @param population the full population of octopuses
- */
- public void prepareStep(final Population population) {
- if (this.energyLevel > 9) {
- // "An octopus can only flash at most once per step."
- return;
- }
- // "First, the energy level of each octopus increases by 1."
- this.energyLevel++;
- if (this.energyLevel > 9) {
- // "Then, any octopus with an energy level greater than 9 flashes. This increases the energy level of
- // all adjacent octopuses by 1, including octopuses that are diagonally adjacent."
- final var grid = population.grid();
- final var hasRowAbove = x > 0;
- final var hasRowBelow = x < grid.length - 1;
- final var hasColumnToLeft = y > 0;
- final var hasColumnToRight = y < grid[x].length - 1;
-
- if (hasRowAbove) {
- grid[x - 1][y].prepareStep(population);
- if (hasColumnToLeft) {
- grid[x - 1][y - 1].prepareStep(population);
- }
- if (hasColumnToRight) {
- grid[x - 1][y + 1].prepareStep(population);
- }
- }
- if (hasColumnToLeft) {
- grid[x][y - 1].prepareStep(population);
- }
- if (hasColumnToRight) {
- grid[x][y + 1].prepareStep(population);
- }
- if (hasRowBelow) {
- grid[x + 1][y].prepareStep(population);
- if (hasColumnToLeft) {
- grid[x + 1][y - 1].prepareStep(population);
- }
- if (hasColumnToRight) {
- grid[x + 1][y + 1].prepareStep(population);
- }
- }
- }
+ public BigInteger countItemsInspected() {
+ return itemsInspected.get();
}
- /**
- * Complete the step and finalise any side effects.
- *
- * @return true if and only if the octopus flashed during this step.
- */
- public boolean finishStep() {
- if (this.energyLevel > 9) {
- // "Finally, any octopus that flashed during this step has its energy level set to 0, as it used all of
- // its energy to flash."
- this.energyLevel = 0;
- return true;
+ public Throw inspectItem(BigInteger reliefFactor) {
+ // this assumes monkeys can throw items to themselves
+ if(items.isEmpty()) {
+ return null;
}
- return false;
+ var worryLevel = items().remove(0);
+ worryLevel = operation().apply(worryLevel);
+ worryLevel = worryLevel.divide(reliefFactor);
+ final var target = worryLevel.mod(divisor()).equals(BigInteger.ZERO)
+ ? targetIfTrue()
+ : targetIfFalse();
+ itemsInspected().updateAndGet(old -> old.add(BigInteger.ONE));
+ return new Throw(target, worryLevel);
}
- }
- /**
- * The full population of dumbo octopuses. The population members will be modified with each step.
- */
- public record Population(Octopus[][] grid) {
- public int step() {
- for (int i = grid.length; --i >= 0; ) {
- final var row = grid[i];
- for (int j = row.length; --j >= 0; ) {
- row[j].prepareStep(this);
- }
- }
- int flashes = 0;
- for (int i = grid.length; --i >= 0; ) {
- final var row = grid[i];
- for (int j = row.length; --j >= 0; ) {
- if (row[j].finishStep()) flashes++;
- }
- }
- return flashes;
+ public List inspectItems(Function worryUpdater) {
+ // this assumes monkeys cannot throw items to themselves
+ final var result = items().stream().map(worryLevel -> {
+ worryLevel = operation().apply(worryLevel);
+ worryLevel = worryUpdater.apply(worryLevel);
+ final var target = worryLevel.mod(divisor()).equals(BigInteger.ZERO)
+ ? targetIfTrue()
+ : targetIfFalse();
+ return new Throw(target, worryLevel);
+ }).toList();
+ itemsInspected().updateAndGet(old -> old.add(BigInteger.valueOf(result.size())));
+ items().clear();
+ return result;
}
}
- @Test
- public final void part1() {
- final var energyLevels = getOctopusGrid();
- final var population = new Population(energyLevels);
+ public record Throw(int target, BigInteger itemWorryLevel) {
+ }
- int flashes = 0;
+ protected List getInput() {
+ final var input = StreamSupport
+ .stream(new LineSpliterator("day-11.txt"),
+ false).collect(Collectors.joining("\n"));
+ return Arrays.stream(input.split("\n\n"))
+ .map(Monkey::parse)
+ .toList();
+ }
- for (int step = 0; step < 100; step++) {
- flashes += population.step();
+ @Test
+ public final void part1() {
+ final var monkeys = getInput();
+ final Function worryUpdater = worryLevel -> worryLevel.divide(BigInteger.valueOf(3));
+ for(int i = 20; --i >= 0; ) {
+ for(final var monkey : monkeys) {
+ for(final var toss : monkey.inspectItems(worryUpdater)) {
+ monkeys.get(toss.target()).items().add(toss.itemWorryLevel());
+ }
+ }
}
-
- System.out.println("Part 1: " + flashes);
+ final var result = monkeys.stream()
+ .map(Monkey::countItemsInspected)
+ .sorted(Comparator.reverseOrder())
+ .limit(2)
+ .reduce(BigInteger::multiply)
+ .get();
+ System.out.println("Part 1: " + result);
}
@Test
public final void part2() {
- final var energyLevels = getOctopusGrid();
- final var population = new Population(energyLevels);
- int step = 0;
- while(true) {
- final int flashes = population.step();
- step++;
- if(flashes == 100) {
- System.out.println("Part 2: " + step);
- break;
+ final var monkeys = getInput();
+ final var productOfDivisors = monkeys.stream().map(Monkey::divisor).reduce(BigInteger::multiply).get();
+ final Function worryUpdater = worryLevel -> worryLevel.mod(productOfDivisors);
+ for(int i = 10_000; --i >= 0; ) {
+ for(final var monkey : monkeys) {
+ for(final var toss : monkey.inspectItems(worryUpdater)) {
+ monkeys.get(toss.target()).items().add(toss.itemWorryLevel());
+ }
}
}
+ final var result = monkeys.stream()
+ .map(Monkey::countItemsInspected)
+ .sorted(Comparator.reverseOrder())
+ .limit(2)
+ .reduce(BigInteger::multiply)
+ .get();
+ System.out.println("Part 2: " + result);
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day12.java b/src/test/java/com/macasaet/Day12.java
index af7de96..3257259 100644
--- a/src/test/java/com/macasaet/Day12.java
+++ b/src/test/java/com/macasaet/Day12.java
@@ -1,182 +1,164 @@
package com.macasaet;
-import java.util.*;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
import org.junit.jupiter.api.Test;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.PriorityQueue;
+import java.util.stream.StreamSupport;
+
/**
- * --- Day 12: Passage Pathing ---
+ * --- Day 12: Hill Climbing Algorithm ---
+ * https://adventofcode.com/2022/day/12
*/
public class Day12 {
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-12.txt"),
- false);
- }
-
- /**
- * @return a map of the connected caves
- */
- protected Map getMap() {
- final var map = new HashMap();
- getInput().forEach(line -> {
- final var components = line.split("-");
- final var sourceLabel = components[0];
- final var targetLabel = components[1];
- final var source = map.computeIfAbsent(sourceLabel, Node::new);
- final var target = map.computeIfAbsent(targetLabel, Node::new);
- source.connected.add(target);
- target.connected.add(source);
- });
- return Collections.unmodifiableMap(map);
+ record Coordinate(int x, int y) {
}
- public Node getStartingPoint() {
- return getMap().get("start");
- }
-
- /**
- * A distinct path through the cave system
- */
- public record Path(List nodes, Node specialCave, int specialCaveVisits) {
+ public record HeightMap(int[][] grid, Coordinate start, Coordinate end) {
- public int hashCode() {
- int result = 0;
- for (final var node : nodes()) {
- result = result * 31 + node.hashCode();
- }
- return result;
+ public int lengthOfShortestPath() {
+ return this.lengthOfShortestPath(this.start);
}
- public boolean equals(final Object o) {
- if (o == null) {
- return false;
- }
- try {
- final var other = (Path) o;
- return nodes().equals(other.nodes());
- } catch (final ClassCastException cce) {
- return false;
+ public int lengthOfShortestPath(final Coordinate startingPoint) {
+ final var cheapestCostToNode = new HashMap();
+ cheapestCostToNode.put(startingPoint, 0);
+ final var estimatedCostToFinish = new HashMap();
+ estimatedCostToFinish.put(startingPoint, estimateDistance(startingPoint, this.end));
+ final var openSet = new PriorityQueue((x, y) -> {
+ final var xScore = estimatedCostToFinish.getOrDefault(x, Integer.MAX_VALUE);
+ final var yScore = estimatedCostToFinish.getOrDefault(y, Integer.MAX_VALUE);
+ return xScore.compareTo(yScore);
+ });
+ openSet.add(startingPoint);
+ while (!openSet.isEmpty()) {
+ final var current = openSet.remove();
+ if (current.equals(this.end)) {
+ return cheapestCostToNode.get(current);
+ }
+ for (final var neighbour : neighbours(current)) {
+ final var tentativeGScore = cheapestCostToNode.get(current) + 1;
+ if (tentativeGScore < cheapestCostToNode.getOrDefault(neighbour, Integer.MAX_VALUE)) {
+ cheapestCostToNode.put(neighbour, tentativeGScore);
+ estimatedCostToFinish.put(neighbour, tentativeGScore + estimateDistance(neighbour, this.end));
+ if (!openSet.contains(neighbour)) {
+ openSet.add(neighbour);
+ }
+ }
+ }
+ }
+ return Integer.MAX_VALUE;
}
- }
- }
-
- public static class Node {
- private final boolean isStart;
- private final boolean isEnd;
- private final boolean isSmallCave;
- private final String label;
-
- private final Set connected = new HashSet<>();
- public Node(final String label) {
- this("start".equalsIgnoreCase(label), "end".equalsIgnoreCase(label),
- label.toLowerCase(Locale.ROOT).equals(label), label);
- }
-
- protected Node(boolean isStart, boolean isEnd, boolean isSmallCave, final String label) {
- this.isStart = isStart;
- this.isEnd = isEnd;
- this.isSmallCave = isSmallCave;
- this.label = label;
- }
-
- public int hashCode() {
- int result = 0;
- result += result * 31 + label.hashCode();
- return result;
- }
-
- public boolean equals(final Object o) {
- if (o == null) {
- return false;
+ public List getPotentialTrailHeads() {
+ final var list = new ArrayList();
+ for(int i = this.grid().length; --i >= 0; ) {
+ final var row = this.grid()[i];
+ for(int j = row.length; --j >= 0; ) {
+ if(row[j] == 0) {
+ list.add(new Coordinate(i, j));
+ }
+ }
+ }
+ return Collections.unmodifiableList(list);
}
- try {
- final Node other = (Node) o;
- return label.equals(other.label);
- } catch (final ClassCastException cce) {
- return false;
+
+ int height(final Coordinate coordinate) {
+ return this.grid[coordinate.x()][coordinate.y()];
}
- }
- }
+ List neighbours(final Coordinate coordinate) {
+ final var list = new ArrayList(4);
+ if (coordinate.x() > 0) {
+ final var up = new Coordinate(coordinate.x() - 1, coordinate.y());
+ if (height(coordinate) + 1 >= height(up)) {
+ list.add(up);
+ }
+ }
+ if (coordinate.x() < this.grid.length - 1) {
+ final var down = new Coordinate(coordinate.x() + 1, coordinate.y());
+ if (height(coordinate) + 1 >= height(down)) {
+ list.add(down);
+ }
+ }
+ if (coordinate.y() > 0) {
+ final var left = new Coordinate(coordinate.x(), coordinate.y() - 1);
+ if (height(coordinate) + 1 >= height(left)) {
+ list.add(left);
+ }
+ }
+ final var row = this.grid[coordinate.x()];
+ if (coordinate.y() < row.length - 1) {
+ final var right = new Coordinate(coordinate.x(), coordinate.y() + 1);
+ if (height(coordinate) + 1 >= height(right)) {
+ list.add(right);
+ }
+ }
+ return Collections.unmodifiableList(list);
+ }
- protected Set getPaths(final Node node, final Path pathSoFar) {
- final var result = new HashSet();
+ int estimateDistance(final Coordinate from, final Coordinate to) {
+ return (int) Math.sqrt(Math.pow(from.x() - to.x(), 2.0) + Math.pow(from.y() - to.y(), 2.0));
+ }
- if (node.isStart && pathSoFar.nodes.size() > 1) {
- // "once you leave the start cave, you may not return to it"
- return Collections.emptySet();
}
- final var nodes = new ArrayList<>(pathSoFar.nodes());
- if (node.isEnd) {
- // "once you reach the end cave, the path must end immediately"
- nodes.add(node);
- return Collections.singleton(new Path(Collections.unmodifiableList(nodes), pathSoFar.specialCave(), pathSoFar.specialCaveVisits()));
- }
- int specialCaveVisits = pathSoFar.specialCaveVisits();
- if (node.isSmallCave) {
- if (node.equals(pathSoFar.specialCave())) {
- // "a single small cave can be visited at most twice"
- if (pathSoFar.specialCaveVisits() < 1) {
- specialCaveVisits++;
+ protected HeightMap getInput() {
+ final var charGrid = StreamSupport.stream(new LineSpliterator("day-12.txt"), false).map(line -> {
+ final var list = new ArrayList(line.length());
+ for (final var c : line.toCharArray()) {
+ list.add(c);
+ }
+ return list;
+ }).toList();
+ Coordinate origin = null;
+ Coordinate destination = null;
+ int[][] grid = new int[charGrid.size()][];
+ for(int i = charGrid.size(); --i >= 0; ) {
+ final var row = charGrid.get(i);
+ grid[i] = new int[row.size()];
+ for(int j = row.size(); --j >= 0; ) {
+ final char c = row.get(j);
+ if(c == 'S') {
+ origin = new Coordinate(i, j);
+ grid[i][j] = 0;
+ } else if(c == 'E') {
+ destination = new Coordinate(i, j);
+ grid[i][j] = 'z' - 'a';
} else {
- return Collections.emptySet();
- }
- } else {
- if (pathSoFar.nodes().contains(node)) {
- // "the remaining small caves can be visited at most once"
- return Collections.emptySet();
+ grid[i][j] = c - 'a';
}
}
}
- nodes.add(node);
- for (final var neighbour : node.connected) {
- if (neighbour.isSmallCave && pathSoFar.specialCave() == null) {
- result.addAll(getPaths(neighbour, new Path(Collections.unmodifiableList(nodes), null, 0)));
- result.addAll(getPaths(neighbour, new Path(Collections.unmodifiableList(nodes), neighbour, 0)));
- } else {
- result.addAll(getPaths(neighbour, new Path(Collections.unmodifiableList(nodes), pathSoFar.specialCave(), specialCaveVisits)));
- }
- }
- return Collections.unmodifiableSet(result);
- }
-
- protected int countPaths(final Node node, final Set visitedSmallCaves) {
- int result = 0;
- if (node.isEnd) {
- return 1;
- }
- if (visitedSmallCaves.contains(node)) {
- // invalid path
- return 0;
- }
- if (node.isSmallCave) {
- visitedSmallCaves.add(node);
- }
- for (final var connected : node.connected) {
- final var set = new HashSet<>(visitedSmallCaves);
- result += countPaths(connected, set);
- }
- return result;
+ Objects.requireNonNull(origin);
+ Objects.requireNonNull(destination);
+ return new HeightMap(grid, origin, destination);
}
@Test
public final void part1() {
- final var start = getStartingPoint();
- final int result = countPaths(start, new HashSet<>());
+ final var map = getInput();
+ final var result = map.lengthOfShortestPath();
System.out.println("Part 1: " + result);
}
@Test
public final void part2() {
- final var start = getStartingPoint();
- final var paths = getPaths(start, new Path(Collections.emptyList(), null, 0));
- System.out.println("Part 2: " + paths.size());
+ final var map = getInput();
+ var result = Integer.MAX_VALUE;
+ for(final var candidate : map.getPotentialTrailHeads()) {
+ final var length = map.lengthOfShortestPath(candidate);
+ if(length < result) {
+ result = length;
+ }
+ }
+
+ System.out.println("Part 2: " + result);
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day13.java b/src/test/java/com/macasaet/Day13.java
index b60355f..457145f 100644
--- a/src/test/java/com/macasaet/Day13.java
+++ b/src/test/java/com/macasaet/Day13.java
@@ -1,187 +1,161 @@
package com.macasaet;
-import java.util.*;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 13: Transparent Origami ---
+ * --- Day 13: Distress Signal ---
+ * https://adventofcode.com/2022/day/13
*/
public class Day13 {
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-13.txt"),
- false);
- }
-
- /**
- * A point on the translucent sheet of paper. Note that x and y correspond to a particular
- * {@link Axis}.
- */
- public record Point(int x, int y) {
- }
+ public record Pair(ListItem x, ListItem y) {
+ static Pair parse(final String lines) {
+ final var components = lines.split("\n");
+ final var x = ListItem.parse(components[0]);
+ final var y = ListItem.parse(components[1]);
+ return new Pair(x, y);
+ }
- /**
- * An axis of the translucent sheet of paper
- */
- public enum Axis {
- /**
- * The axis that increases to the right
- */
- X,
-
- /**
- * The axis that increases downward
- */
- Y,
- }
+ public boolean isInOrder() {
+ return x().compareTo(y()) < 0;
+ }
- /**
- * An equation for a line
- */
- public record Line(Axis axis, int value) {
- public String toString() {
- return switch (axis()) {
- case X -> "x=" + value;
- case Y -> "y=" + value;
- };
+ public Stream stream() {
+ return Stream.of(x(), y()).sorted();
}
}
- public record Input(Collection points, List folds, int maxX, int maxY) {
- public Sheet getSheet() {
- final boolean[][] grid = new boolean[maxY + 1][];
- for (int i = grid.length; --i >= 0; ) {
- grid[i] = new boolean[maxX + 1];
- }
- for (final var point : points) {
- /* The first value, x, increases to the right. The second value, y, increases downward. */
- grid[point.y()][point.x()] = true;
+ interface Item extends Comparable- {
+
+ int compareToList(ListItem other);
+ int compareToLiteral(Literal other);
+
+ default int compareTo(Item other) {
+ if(other instanceof ListItem) {
+ return compareToList((ListItem)other);
+ } else {
+ if(!(other instanceof Literal)) {
+ throw new IllegalArgumentException("Unknown implementation");
+ }
+ return compareToLiteral((Literal) other);
}
- return new Sheet(grid);
}
}
- /**
- * A sheet of translucent paper
- */
- public record Sheet(boolean[][] grid) {
-
- public int countDots() {
- int result = 0;
- final var grid = grid();
- for (int i = grid.length; --i >= 0; ) {
- for (int j = grid[i].length; --j >= 0; ) {
- if (grid[i][j]) {
- result++;
+ public record ListItem(List
- items) implements Item {
+ public static ListItem parse(final String string) {
+ final var stack = new ArrayDeque();
+ StringBuilder numberBuffer = new StringBuilder();
+ for(final char c : string.toCharArray()) {
+ if(c == '[') {
+ stack.push(new ListItem(new ArrayList<>()));
+ } else if(c == ']') {
+ if(!numberBuffer.isEmpty()) {
+ final var numberString = numberBuffer.toString();
+ numberBuffer.delete(0, numberBuffer.length());
+ final var number = Integer.parseInt(numberString);
+ stack.peek().items().add(new Literal(number));
+ }
+ if(stack.size() > 1) {
+ final var completed = stack.pop();
+ stack.peek().items().add(completed);
}
+ } else if(c == ',') {
+ if(!numberBuffer.isEmpty()) {
+ final var numberString = numberBuffer.toString();
+ numberBuffer.delete(0, numberBuffer.length());
+ final var number = Integer.parseInt(numberString);
+ stack.peek().items().add(new Literal(number));
+ }
+ } else {
+ numberBuffer.append(c);
}
}
- return result;
+ return stack.pop();
}
- public String toString() {
- final var builder = new StringBuilder();
- for (final var row : grid) {
- for (final var cell : row) {
- builder.append(cell ? '#' : '.');
+ public int compareToList(ListItem other) {
+ final Iterator
- x = this.items().iterator();
+ final Iterator
- y = other.items().iterator();
+ while(x.hasNext() && y.hasNext()) {
+ final var xItem = x.next();
+ final var yItem = y.next();
+ final var comparison = xItem.compareTo(yItem);
+ if(comparison != 0) {
+ return comparison;
}
- builder.append('\n');
}
- return builder.toString();
+ if(y.hasNext()) {
+ return -1;
+ } else if(x.hasNext()) {
+ return 1;
+ }
+ return 0;
}
- public Sheet fold(final Line line) {
- // note, value is always positive
- return switch (line.axis()) {
- case X -> {
- // fold along the x-axis (vertical line)
- final var newGrid = new boolean[grid.length][];
- for (int i = newGrid.length; --i >= 0; ) {
- final var newRow = new boolean[line.value() + 1];
- for (int j = newRow.length; --j >= 0; newRow[j] = grid[i][j]) ;
- for(int j = grid[i].length - line.value(); --j > 0; ) {
- if(grid[i][line.value() + j]) {
- newRow[line.value() - j] = true;
- }
- }
- newGrid[i] = newRow;
- }
- yield new Sheet(newGrid);
- }
- case Y -> {
- // fold along the y-axis (horizontal line)
- final var newGrid = new boolean[line.value()][];
- for (int i = newGrid.length; --i >= 0; ) {
- final var newRow = new boolean[grid[i].length];
- for (int j = grid[i].length; --j >= 0; newRow[j] = grid[i][j]) ;
- newGrid[i] = newRow;
- }
- for (int i = grid.length - line.value(); --i > 0; ) {
- final var oldRow = grid[line.value() + i];
- for (int j = oldRow.length;
- --j >= 0;
- newGrid[line.value() - i][j] |= oldRow[j])
- ;
- }
- yield new Sheet(newGrid);
- }
- };
+ public int compareToLiteral(Literal other) {
+ return compareToList(other.asList());
}
+
}
- public Input parseInput() {
- int section = 0;
- final var points = new HashSet();
- final var folds = new ArrayList();
- int maxX = Integer.MIN_VALUE;
- int maxY = Integer.MIN_VALUE;
- for (final var line : getInput().collect(Collectors.toList())) {
- if (line.isBlank()) {
- section++;
- continue;
- }
- if (section == 0) { // points
- final var components = line.split(",");
- final var x = Integer.parseInt(components[0]);
- maxX = Math.max(x, maxX);
- final var y = Integer.parseInt(components[1]);
- maxY = Math.max(y, maxY);
- final var point = new Point(x, y);
- points.add(point);
- } else { // commands
- final var equation = line.replaceFirst("fold along ", "");
- final var components = equation.split("=");
- final var axis = Axis.valueOf(components[0].toUpperCase(Locale.ROOT));
- final var value = Integer.parseInt(components[1]);
- final var fold = new Line(axis, value);
- folds.add(fold);
- }
+ public record Literal(int item) implements Item {
+ public int compareToList(ListItem other) {
+ return asList().compareToList(other);
}
- return new Input(points, folds, maxX, maxY);
+
+ public int compareToLiteral(Literal other) {
+ return Integer.compare(item(), other.item());
+ }
+
+ public ListItem asList() {
+ return new ListItem(Collections.singletonList(this));
+ }
+ }
+
+ protected List getInput() {
+ final var lines = StreamSupport.stream(new LineSpliterator("day-13.txt"), false)
+ .collect(Collectors.joining("\n"));
+ final var blocks = lines.split("\n\n");
+ return Arrays.stream(blocks).map(block -> Pair.parse(block)).toList();
}
@Test
public final void part1() {
- final var input = parseInput();
- final var sheet = input.getSheet();
- final var firstFold = input.folds().get(0);
- final var result = sheet.fold(firstFold);
- System.out.println("Part 1: " + result.countDots());
+ final var pairs = getInput();
+ var result = 0;
+ for(int i = 0; i < pairs.size(); i++) {
+ final var pair = pairs.get(i);
+ if(pair.isInOrder()) {
+ result += i + 1;
+ }
+ }
+ System.out.println("Part 1: " + result);
}
@Test
public final void part2() {
- final var input = parseInput();
- var sheet = input.getSheet();
- for (final var fold : input.folds()) {
- sheet = sheet.fold(fold);
- }
- System.out.println("Part 2:\n" + sheet);
+ final var pairs = getInput();
+ final var packets = pairs.stream().flatMap(Pair::stream).sorted().toList();
+ final int leftSearchResult = Collections.binarySearch(packets,
+ new ListItem(Collections.singletonList(new ListItem(Collections.singletonList(new Literal(2))))));
+ final int leftInsertionPoint = -(leftSearchResult + 1) + 1;
+ final int rightSearchResult = Collections.binarySearch(packets,
+ new ListItem(Collections.singletonList(new ListItem(Collections.singletonList(new Literal(6))))));
+ final int rightInsertionPoint = -(rightSearchResult + 1) + 2;
+ final int result = leftInsertionPoint * rightInsertionPoint;
+
+ System.out.println("Part 2: " + result);
}
-}
+}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day14.java b/src/test/java/com/macasaet/Day14.java
index c44d50b..7f7d777 100644
--- a/src/test/java/com/macasaet/Day14.java
+++ b/src/test/java/com/macasaet/Day14.java
@@ -1,166 +1,212 @@
package com.macasaet;
-import java.math.BigInteger;
-import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
import org.junit.jupiter.api.Test;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.StreamSupport;
+
/**
- * --- Day 14: Extended Polymerization ---
+ * --- Day 14: Regolith Reservoir ---
+ * https://adventofcode.com/2022/day/14
*/
public class Day14 {
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-14.txt"),
- false);
+ public enum Cell {
+ ROCK,
+ SAND
}
- public record Polymer(Map pairCounts, char firstElement, char lastElement) {
- public static Polymer forTemplate(final String templateString) {
- final var firstElement = templateString.charAt(0);
- final var lastElement = templateString.charAt(templateString.length() - 1);
- final var map = new HashMap();
- for (int i = 1; i < templateString.length(); i++) {
- map.merge(new ElementPair(templateString.charAt(i - 1), templateString.charAt(i)),
- BigInteger.ONE,
- BigInteger::add);
- }
- return new Polymer(Collections.unmodifiableMap(map), firstElement, lastElement);
+ record Coordinate(int verticalDepth, int horizontalOffset) {
+
+ public static Coordinate parse(final String string) {
+ final var components = string.split(",");
+ final var verticalDepth = Integer.parseInt(components[1]);
+ final var horizontalOffset = Integer.parseInt(components[0]);
+ return new Coordinate(verticalDepth, horizontalOffset);
}
+ }
- /**
- * Apply the pair insertion process one time.
- *
- * @param rules pair insertion rules for generating a new polymer
- * @return the new polymer that results
- */
- public Polymer applyRules(final Map rules) {
- final var map = new HashMap();
- for (final var entry : pairCounts().entrySet()) {
- final var key = entry.getKey();
- final var count = entry.getValue();
- final var rule = rules.get(key);
- final var left = new ElementPair(key.start(), rule.insert());
- final var right = new ElementPair(rule.insert(), key.end());
-
- map.compute(left, (_key, oldCount) -> oldCount == null ? count : oldCount.add(count));
- map.compute(right, (_key, oldCount) -> oldCount == null ? count : oldCount.add(count));
+ public record Cave(Map> grid, int maxDepth, int minHorizontalOffset, int maxHorizontalOffset) {
+
+ public int pourSandIntoAbyss() {
+ int settledSand = 0;
+ while(true) {
+ var fallingSandCoordinate = new Coordinate(0, 500);
+ while(true) {
+ final var next = getNextCoordinate(fallingSandCoordinate, null);
+ if(next != null) {
+ fallingSandCoordinate = next;
+ if(fallingSandCoordinate.verticalDepth() >= maxDepth()) {
+ return settledSand;
+ }
+ } else {
+ final var row = grid.computeIfAbsent(fallingSandCoordinate.verticalDepth(), key -> new HashMap<>());
+ row.put(fallingSandCoordinate.horizontalOffset(), Cell.SAND);
+ settledSand++;
+ break;
+ }
+ }
}
- return new Polymer(Collections.unmodifiableMap(map), firstElement(), lastElement());
}
- /**
- * Determine how many times each element appears in the polymer
- *
- * @return the number of times each element appears in the polymer
- */
- public SortedMap> histogram() {
- final var map = new HashMap();
- for (final var entry : pairCounts().entrySet()) {
- final var pair = entry.getKey();
- final var count = entry.getValue();
- map.compute(pair.start(),
- (_key, oldValue) -> oldValue == null ? count : oldValue.add(count));
- map.compute(pair.end(),
- (_key, oldValue) -> oldValue == null ? count : oldValue.add(count));
- }
- for (final var entry : map.entrySet()) {
- final var element = entry.getKey();
- final var count = entry.getValue();
- if (element.equals(firstElement()) || element.equals(lastElement())) {
- entry.setValue(count.divide(BigInteger.TWO).add(BigInteger.ONE));
- } else {
- entry.setValue(count.divide(BigInteger.TWO));
+ public int fillAperture() {
+ int settledSand = 0;
+ while(true) {
+ var fallingSandCoordinate = new Coordinate(0, 500);
+ while(true) {
+ final var next = getNextCoordinate(fallingSandCoordinate, floorDepth());
+ if(next != null) {
+ fallingSandCoordinate = next;
+ } else {
+ final var secondRow = grid().computeIfAbsent(1, key -> new HashMap<>());
+ if(secondRow.containsKey(499) && secondRow.containsKey(500) && secondRow.containsKey(501)) {
+ return settledSand + 1;
+ }
+ final var row = grid.computeIfAbsent(fallingSandCoordinate.verticalDepth(), key -> new HashMap<>());
+ row.put(fallingSandCoordinate.horizontalOffset(), Cell.SAND);
+ settledSand++;
+ break;
+ }
}
}
- final var result = new TreeMap>();
- for (final var entry : map.entrySet()) {
- final var target = result.computeIfAbsent(entry.getValue(), _key -> new HashSet<>());
- target.add(entry.getKey());
- }
- return Collections.unmodifiableSortedMap(result);
}
- }
- /**
- * A pair of elements that appear adjacent to each other. This may be used in the context of a pair insertion rule
- * definition or a polymer.
- *
- * @see Polymer
- * @see PairInsertionRule
- */
- protected record ElementPair(char start, char end) {
- }
-
- /**
- * A single instruction to aid in finding the optimal polymer formula
- */
- public record PairInsertionRule(char start, char end, char insert) {
+ int floorDepth() {
+ return maxDepth() + 2;
+ }
- public static PairInsertionRule parse(final String string) {
- final var components = string.split(" -> ");
- final var match = components[0].toCharArray();
- return new PairInsertionRule(match[0], match[1], components[1].toCharArray()[0]);
+ Coordinate getNextCoordinate(final Coordinate start, Integer floorDepth) {
+ final var x = start.verticalDepth();
+ final var y = start.horizontalOffset();
+ if(floorDepth != null && x + 1 >= floorDepth) {
+ return null;
+ }
+ final var nextRow = grid().computeIfAbsent(x + 1, key -> new HashMap<>());
+ if(!nextRow.containsKey(y)) {
+ return new Coordinate(x + 1, y);
+ } else if(!nextRow.containsKey(y - 1)) {
+ return new Coordinate(x + 1, y - 1);
+ } else if(!nextRow.containsKey(y + 1)) {
+ return new Coordinate(x + 1, y + 1);
+ }
+ return null;
}
- }
+ public static Cave parse(final Collection extends String> lines) {
+ int maxDepth = 0;
+ int maxHorizontalOffset = Integer.MIN_VALUE;
+ int minHorizontalOffset = Integer.MAX_VALUE;
+
+ final var grid = new HashMap>();
+ for(final var line : lines) {
+ final var rockPath = parseRockPaths(line);
+ var last = rockPath.get(0);
+ if(last.verticalDepth() > maxDepth) {
+ maxDepth = last.verticalDepth();
+ }
+ if(last.horizontalOffset() < minHorizontalOffset) {
+ minHorizontalOffset = last.horizontalOffset();
+ }
+ if(last.horizontalOffset() > maxHorizontalOffset) {
+ maxHorizontalOffset = last.horizontalOffset();
+ }
+ for(int i = 1; i < rockPath.size(); i++) {
+ final var current = rockPath.get(i);
+ if(last.verticalDepth() == current.verticalDepth()) {
+ // horizontal line
+ int start;
+ int end;
+ if(last.horizontalOffset() < current.horizontalOffset()) {
+ start = last.horizontalOffset();
+ end = current.horizontalOffset();
+ } else {
+ start = current.horizontalOffset();
+ end = last.horizontalOffset();
+ }
+ final var row = grid.computeIfAbsent(last.verticalDepth(), key -> new HashMap<>());
+ for(int y = start; y <= end; y++) {
+ row.put(y, Cell.ROCK);
+ }
+ } else {
+ if(last.horizontalOffset() != current.horizontalOffset()) {
+ throw new IllegalStateException("Line segments are not on the same vertical axis");
+ }
+ // vertical line
+ int start;
+ int end;
+ if(last.verticalDepth() < current.verticalDepth()) {
+ start = last.verticalDepth();
+ end = current.verticalDepth();
+ } else {
+ start = current.verticalDepth();
+ end = last.verticalDepth();
+ }
+ for(int x = start; x <= end; x++) {
+ final var row = grid.computeIfAbsent(x, key -> new HashMap<>());
+ row.put(last.horizontalOffset(), Cell.ROCK);
+ }
+ }
+ if(current.verticalDepth() > maxDepth) {
+ maxDepth = current.verticalDepth();
+ }
+ if(current.horizontalOffset() < minHorizontalOffset) {
+ minHorizontalOffset = current.horizontalOffset();
+ }
+ if(current.horizontalOffset() > maxHorizontalOffset) {
+ maxHorizontalOffset = current.horizontalOffset();
+ }
+ last = current;
+ }
+ }
+ return new Cave(grid, maxDepth, minHorizontalOffset, maxHorizontalOffset);
+ }
- protected record Input(Polymer polymerTemplate, List rules) {
- }
+ static List parseRockPaths(final String line) {
+ return Arrays.stream(line.split(" -> ")).map(Coordinate::parse).toList();
+ }
- protected Input parseInput() {
- final var list = getInput().collect(Collectors.toList());
- int mode = 0;
- Polymer polymer = null;
- final var rules = new ArrayList();
- for (final var line : list) {
- if (line.isBlank()) {
- mode++;
- continue;
- }
- if (mode == 0) {
- polymer = Polymer.forTemplate(line);
- } else {
- rules.add(PairInsertionRule.parse(line));
+ @Override
+ public String toString() {
+ final var buffer = new StringBuilder();
+ for(int i = 0; i <= floorDepth(); i++) {
+ buffer.append(i).append(' ');
+ final var row = grid.getOrDefault(i, Collections.emptyMap());
+ for(int j = minHorizontalOffset(); j <= maxHorizontalOffset(); j++) {
+ final var cell = row.get(j);
+ final char marker = cell == null ? ' ' : Cell.ROCK.equals(cell) ? '#' : 'o';
+ buffer.append(marker);
+ }
+ buffer.append('\n');
}
+ return buffer.toString();
}
- return new Input(polymer, rules);
+ }
+
+ protected Cave getInput() {
+ final var lines = StreamSupport.stream(new LineSpliterator("day-14.txt"), false)
+ .toList();
+ return Cave.parse(lines);
}
@Test
public final void part1() {
- final var input = parseInput();
- var polymer = input.polymerTemplate();
- final var rules = input.rules();
- final var ruleMap = new HashMap();
- for (final var rule : rules) {
- ruleMap.put(new ElementPair(rule.start(), rule.end()), rule);
- }
- for (int _i = 0; _i < 10; _i++) {
- polymer = polymer.applyRules(ruleMap);
- }
- final var histogram = polymer.histogram();
- System.out.println("Part 1: " + histogram.lastKey().subtract(histogram.firstKey()));
+ final var cave = getInput();
+ final var result = cave.pourSandIntoAbyss();
+
+ System.out.println("Part 1: " + result);
}
@Test
public final void part2() {
- final var input = parseInput();
- var polymer = input.polymerTemplate();
- final var rules = input.rules();
- final var ruleMap = new HashMap();
- for (final var rule : rules) {
- ruleMap.put(new ElementPair(rule.start(), rule.end()), rule);
- }
- for (int _i = 0; _i < 40; _i++) {
- polymer = polymer.applyRules(ruleMap);
- }
- final var histogram = polymer.histogram();
- System.out.println("Part 2: " + histogram.lastKey().subtract(histogram.firstKey()));
+ final var cave = getInput();
+ final var result = cave.fillAperture();
+
+ System.out.println("Part 2: " + result);
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day15.java b/src/test/java/com/macasaet/Day15.java
index e93e89f..2fea62c 100644
--- a/src/test/java/com/macasaet/Day15.java
+++ b/src/test/java/com/macasaet/Day15.java
@@ -1,228 +1,205 @@
package com.macasaet;
-import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.IntPredicate;
+import java.util.stream.StreamSupport;
+
/**
- * --- Day 15: Chiton ---
+ * --- Day 15: Beacon Exclusion Zone ---
+ * https://adventofcode.com/2022/day/15
*/
public class Day15 {
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-15.txt"),
- false);
- }
-
- protected int[][] getGrid() {
- final var list = getInput().collect(Collectors.toList());
- final int[][] grid = new int[list.size()][];
- for (int i = 0; i < grid.length; i++) {
- final var chars = list.get(i).toCharArray();
- final var row = new int[chars.length];
- for (int j = chars.length; --j >= 0; row[j] = chars[j] - '0') ;
- grid[i] = row;
+ record Coordinate(int x, int y) {
+ static Coordinate parse(final String string) {
+ final var components = string.split(", ");
+ return new Coordinate(Integer.parseInt(components[1].replaceAll("^y=", "")),
+ Integer.parseInt(components[0].replaceAll("^x=", "")));
}
- return grid;
- }
-
- public record Point(int x, int y) {
- public int risk(int[][] risks) {
- return risks[x][y];
+ Map getRow(final Map> grid) {
+ return grid.computeIfAbsent(x(), ignored -> new HashMap<>());
}
+ int distanceTo(final Coordinate other) {
+ return Math.abs(x() - other.x()) + Math.abs(y() - other.y());
+ }
}
- public record Cavern(int[][] grid) {
-
- public Set predecessors(final Point source) {
- final var result = new HashSet();
- if (source.x() > 0) {
- result.add(new Point(source.x() - 1, source.y()));
- }
- if (source.y() > 0) {
- result.add(new Point(source.x(), source.y() - 1));
- }
- return Collections.unmodifiableSet(result);
+ public record Sensor(Coordinate location, Coordinate beaconLocation) {
+ public static Sensor parse(final String line) {
+ final var location = Coordinate.parse(line.replaceAll("^Sensor at ", "").replaceAll(": closest beacon is at .*$", ""));
+ final var beaconLocation = Coordinate.parse(line.replaceAll("^.*: closest beacon is at ", ""));
+ return new Sensor(location, beaconLocation);
}
- public Set successors(final Point source) {
- final var result = new HashSet();
- if (source.x() < grid().length - 1) {
- result.add(new Point(source.x() + 1, source.y()));
- }
- if (source.y() < grid()[source.x()].length - 1) {
- result.add(new Point(source.x(), source.y() + 1));
+ void setSensor(final Map> grid, IntPredicate includeRow, IntPredicate includeColumn) {
+ if(includeRow.test(location().x()) && includeColumn.test(location().y())) {
+ location().getRow(grid).put(location().y(), Item.SENSOR);
}
- return Collections.unmodifiableSet(result);
}
- public Cavern explode() {
- final int[][] largeGrid = new int[grid.length * 5][];
- for (int i = largeGrid.length; --i >= 0; ) {
- largeGrid[i] = new int[grid.length * 5];
- }
- for (int i = grid.length; --i >= 0; ) {
- for (int j = grid.length; --j >= 0; ) {
- largeGrid[i][j] = grid[i][j];
- }
+ void setBeacon(final Map> grid, IntPredicate includeRow, IntPredicate includeColumn) {
+ if(includeRow.test(beaconLocation().x()) && includeColumn.test(beaconLocation().y())) {
+ beaconLocation().getRow(grid).put(beaconLocation().y(), Item.BEACON);
}
- for (int tileRow = 0; tileRow < 5; tileRow++) {
- for (int tileColumn = 0; tileColumn < 5; tileColumn++) {
- if (tileRow > 0) {
- for (int i = grid.length; --i >= 0; ) {
- for (int j = grid.length; --j >= 0; ) {
- // copy from row above
- int value = largeGrid[(tileRow - 1) * grid.length + i][tileColumn * grid.length + j] + 1;
- if (value == 10) {
- value = 1;
- }
- largeGrid[tileRow * grid.length + i][tileColumn * grid.length + j] = value;
- }
- }
- } else if (tileColumn > 0) {
- for (int i = grid.length; --i >= 0; ) {
- for (int j = grid.length; --j >= 0; ) {
- // copy from column to the left
- int value = largeGrid[tileRow * grid.length + i][(tileColumn - 1) * grid.length + j] + 1;
- if (value == 10) {
- value = 1;
- }
- largeGrid[tileRow * grid.length + i][tileColumn * grid.length + j] = value;
- }
- }
- }
- }
- }
- return new Cavern(largeGrid);
}
- public long[][] calculateCumulativeRisk() {
- final var cumulative = new long[grid().length][];
- for (int i = cumulative.length; --i >= 0; cumulative[i] = new long[grid()[i].length]) ;
- final var visited = new HashSet();
- final var queue = new LinkedList();
- final var destination = new Point(grid().length - 1, grid()[grid().length - 1].length - 1);
- queue.add(destination);
- visited.add(destination);
-
- while (!queue.isEmpty()) {
- final var node = queue.remove();
- final var successors = successors(node);
- if (successors.isEmpty()) {
- // destination
- cumulative[node.x][node.y] = node.risk(grid());
- } else {
- var minSuccessorRisk = Long.MAX_VALUE;
- for (final var successor : successors) {
- if (!visited.contains(successor)) {
- throw new IllegalStateException("Successor has not been visited");
- }
- minSuccessorRisk = Math.min(minSuccessorRisk, cumulative[successor.x][successor.y]);
- }
- cumulative[node.x][node.y] = node.risk(grid()) + minSuccessorRisk;
+ void setCoverageArea(final Map> grid, IntPredicate includeRow, IntPredicate includeColumn) {
+ final var distance = distanceToBeacon();
+ final var x = location().x();
+ final var y = location().y();
+
+ for(int i = 0; i <= distance; i++ ) {
+ if(!includeRow.test(x + i) && !includeRow.test(x - i)) {
+ continue;
}
+ final var lowerRow = includeRow.test(x + i)
+ ? grid.computeIfAbsent(x + i, ignored -> new HashMap<>())
+ : new HashMap();
+ final var upperRow = includeRow.test(x - i)
+ ? grid.computeIfAbsent(x - i, ignored -> new HashMap<>())
+ : new HashMap();
+ for(int j = 0; j <= distance - i; j++ ) {
+ if(includeColumn.test(y + j)) {
+ // SE
+ lowerRow.putIfAbsent(y + j, Item.COVERED);
+ // NE
+ upperRow.putIfAbsent(y + j, Item.COVERED);
+ }
+ if(includeColumn.test(y - j)) {
+ // SW
+ lowerRow.putIfAbsent(y - j, Item.COVERED);
- for (final var predecessor : predecessors(node)) {
- if (!visited.contains(predecessor)) {
- queue.add(predecessor);
- visited.add(predecessor);
+ // NW
+ upperRow.putIfAbsent(y - j, Item.COVERED);
}
}
}
- return cumulative;
}
- /**
- * @return the risk level associated with the path through the cavern that avoids the most chitons
- */
- public int lowestRiskThroughTheCavern() {
- // the lowest known risk from origin to a given node
- final var lowestRiskToNode = new HashMap();
- // the estimated risk from origin to destination if it goes through a given node
- final var estimatedRiskThroughNode = new HashMap();
- final var openSet = new PriorityQueue(Comparator.comparing(estimatedRiskThroughNode::get));
-
- for (int i = grid().length; --i >= 0; ) {
- final var row = grid()[i];
- for (int j = row.length; --j >= 0; ) {
- final var point = new Point(i, j);
- if (i == 0 && j == 0) {
- lowestRiskToNode.put(point, 0);
- estimatedRiskThroughNode.put(point, manhattanDistance(point));
- openSet.add(point);
- } else {
- lowestRiskToNode.put(point, Integer.MAX_VALUE);
- estimatedRiskThroughNode.put(point, Integer.MAX_VALUE);
- }
- }
- }
+ int distanceToBeacon() {
+ return location().distanceTo(beaconLocation());
+ }
+ }
- while(!openSet.isEmpty()) {
- final var current = openSet.poll();
- if(current.x() == grid().length - 1 && current.y() == grid()[grid().length - 1].length - 1) {
- return lowestRiskToNode.get(current);
- }
- final var lowestRiskToCurrent = lowestRiskToNode.get(current);
- for(final var neighbour : neighbours(current)) {
- final var tentativeRisk = lowestRiskToCurrent + neighbour.risk(grid());
- if(tentativeRisk < lowestRiskToNode.get(neighbour)) {
- lowestRiskToNode.put(neighbour, tentativeRisk);
- estimatedRiskThroughNode.put(neighbour, tentativeRisk + manhattanDistance(neighbour));
- if(!openSet.contains(neighbour)) {
- openSet.add(neighbour);
- }
- }
+ enum Item {
+ SENSOR,
+ BEACON,
+ COVERED
+ }
+ public record CaveMap(Map> grid, int minX, int maxX, int minY, int maxY) {
+ public int countCoveredCellsInRow(final int x) {
+ final var row = grid().getOrDefault(x, Collections.emptyMap());
+ int result = 0;
+ for(int j = minY(); j <= maxY(); j++) {
+ final var cell = row.get(j);
+ if(cell != null && cell != Item.BEACON) {
+ result++;
}
}
- throw new IllegalStateException("No path out of the cavern!");
+ return result;
}
- /**
- * @param point
- * @return
- */
- protected int manhattanDistance(Point point) {
- return Math.abs(point.x() - (grid().length - 1))
- + Math.abs(point.y() - (grid()[grid().length - 1].length - 1));
+ public static CaveMap fromSensors(final Iterable extends Sensor> sensors, IntPredicate includeRow, IntPredicate includeColumn) {
+ int minX = Integer.MAX_VALUE;
+ int maxX = Integer.MIN_VALUE;
+ int minY = Integer.MAX_VALUE;
+ int maxY = Integer.MIN_VALUE;
+ final var grid = new HashMap>();
+ for(final var sensor : sensors) {
+ minX = Math.min(minX, sensor.location().x() - sensor.distanceToBeacon());
+ maxX = Math.max(maxX, sensor.location().x() + sensor.distanceToBeacon());
+ minY = Math.min(minY, sensor.location().y() - sensor.distanceToBeacon());
+ maxY = Math.max(maxY, sensor.location().y() + sensor.distanceToBeacon());
+
+ sensor.setCoverageArea(grid, includeRow, includeColumn);
+ sensor.setBeacon(grid, includeRow, includeColumn);
+ sensor.setSensor(grid, includeRow, includeColumn);
+ }
+
+ return new CaveMap(grid, minX, maxX, minY, maxY);
}
- public Set neighbours(final Point point) {
- final var result = new HashSet();
- if (point.x() > 0) {
- result.add(new Point(point.x() - 1, point.y()));
- }
- if (point.x() < grid().length - 1) {
- result.add(new Point(point.x() + 1, point.y()));
- }
- if (point.y() > 0) {
- result.add(new Point(point.x(), point.y() - 1));
- }
- if (point.y() < grid()[point.x()].length - 1) {
- result.add(new Point(point.x(), point.y() + 1));
+ public String toString() {
+ final var builder = new StringBuilder();
+ for(int i = minX(); i <= maxX(); i++) {
+ builder.append(i).append('\t');
+ final var row = grid().getOrDefault(i, Collections.emptyMap());
+ for(int j = minY(); j <= maxY(); j++) {
+ final var item = row.get(j);
+ final var marker = item == null
+ ? '.'
+ : item == Item.BEACON
+ ? 'B'
+ : item == Item.SENSOR
+ ? 'S'
+ : '#';
+ builder.append(marker);
+ }
+ builder.append('\n');
}
- return Collections.unmodifiableSet(result);
+ return builder.toString();
}
+ }
+ protected CaveMap getInput(final IntPredicate includeRow, IntPredicate includeColumn) {
+ final var sensors = getSensors();
+ return CaveMap.fromSensors(sensors, includeRow, includeColumn);
+ }
+
+ protected static List getSensors() {
+ return StreamSupport.stream(new LineSpliterator("day-15.txt"), false)
+ .map(Sensor::parse)
+ .toList();
}
@Test
public final void part1() {
- final var grid = getGrid();
- final var cavern = new Cavern(grid);
- System.out.println("Part 1: " + cavern.lowestRiskThroughTheCavern());
+ final int rowOfInterest = 2_000_000;
+ final var map = getInput(row -> row == rowOfInterest, _column -> true);
+ final var result = map.countCoveredCellsInRow(rowOfInterest);
+
+ System.out.println("Part 1: " + result);
}
@Test
public final void part2() {
- final var grid = getGrid();
- final var cavern = new Cavern(grid).explode();
- System.out.println("Part 2: " + cavern.lowestRiskThroughTheCavern());
+ final int max = 4_000_000;
+ final var sensors = getSensors();
+ for(final var sensor : sensors) {
+ final var x = sensor.location().x();
+ final var y = sensor.location().y();
+ final var reach = sensor.distanceToBeacon();
+ // Find all the points just outside this sensor's reach
+ for(int horizontalOffset = 0; horizontalOffset <= reach + 1; horizontalOffset++) {
+ final var verticalOffset = reach + 1 - horizontalOffset;
+ Assertions.assertEquals(horizontalOffset + verticalOffset, reach + 1);
+ for(final var candidate : Arrays.asList(new Coordinate(x + verticalOffset, y + horizontalOffset), // SE
+ new Coordinate(x + verticalOffset, y - horizontalOffset), // SW
+ new Coordinate(x - verticalOffset, y + horizontalOffset), // NE
+ new Coordinate(x - verticalOffset, y - horizontalOffset))) { // NW
+ if(candidate.x() < 0 || candidate.y() < 0 || candidate.x() > max || candidate.y() > max) {
+ continue;
+ }
+ Assertions.assertTrue(candidate.distanceTo(sensor.location()) > sensor.distanceToBeacon());
+ // Check if the point is also outside the reach of every other sensor
+ if(sensors.stream().allMatch(other -> candidate.distanceTo(other.location()) > other.distanceToBeacon())) {
+ final long result = (long)candidate.y() * 4_000_000l + (long)candidate.x();
+ System.out.println("Part 2: " + result);
+ return;
+ }
+ }
+ }
+ }
+ throw new IllegalStateException("No uncovered point found");
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day16.java b/src/test/java/com/macasaet/Day16.java
deleted file mode 100644
index b3eb4b0..0000000
--- a/src/test/java/com/macasaet/Day16.java
+++ /dev/null
@@ -1,352 +0,0 @@
-package com.macasaet;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-
-/**
- * --- Day 16: Packet Decoder ---
- */
-public class Day16 {
-
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-16.txt"),
- false);
- }
-
- public interface Packet {
- long version();
-
- void accept(PacketVisitor packetVisitor);
-
- long evaluate();
- }
-
- public record Literal(long version, long value) implements Packet {
-
- public void accept(PacketVisitor packetVisitor) {
- packetVisitor.visit(this);
- }
-
- public long evaluate() {
- return value();
- }
- }
-
- public enum OperatorType {
- SUM {
- public long evaluate(List extends Packet> operands) {
- return operands.stream().mapToLong(Packet::evaluate).sum();
- }
- },
- PRODUCT {
- public long evaluate(List extends Packet> operands) {
- return operands.stream().mapToLong(Packet::evaluate).reduce(1, (x, y) -> x * y);
- }
- },
- MINIMUM {
- public long evaluate(List extends Packet> operands) {
- return operands.stream().mapToLong(Packet::evaluate).min().orElseThrow();
- }
- },
- MAXIMUM {
- public long evaluate(List extends Packet> operands) {
- return operands.stream().mapToLong(Packet::evaluate).max().orElseThrow();
- }
- },
- GREATER_THAN {
- public long evaluate(List extends Packet> operands) {
- if (operands.size() != 2) {
- throw new IllegalArgumentException("Invalid operand list for \"greater than\" operator: " + operands);
- }
- final var x = operands.get(0).evaluate();
- final var y = operands.get(1).evaluate();
- return x > y ? 1 : 0;
- }
- },
- LESS_THAN {
- public long evaluate(List extends Packet> operands) {
- if (operands.size() != 2) {
- throw new IllegalStateException("Invalid operand list for \"less than\" operator: " + operands);
- }
- final var x = operands.get(0).evaluate();
- final var y = operands.get(1).evaluate();
- return x < y ? 1 : 0;
- }
- },
- EQUAL_TO {
- public long evaluate(List extends Packet> operands) {
- if (operands.size() != 2) {
- throw new IllegalStateException("Invalid operand list for \"equal to\" operator: " + operands);
- }
- final var x = operands.get(0).evaluate();
- final var y = operands.get(1).evaluate();
- return x == y ? 1 : 0;
- }
- };
-
- public abstract long evaluate(List extends Packet> operands);
-
- public static OperatorType forId(final int typeId) {
- return switch (typeId) {
- case 0 -> SUM;
- case 1 -> PRODUCT;
- case 2 -> MINIMUM;
- case 3 -> MAXIMUM;
- case 5 -> GREATER_THAN;
- case 6 -> LESS_THAN;
- case 7 -> EQUAL_TO;
- default -> throw new IllegalArgumentException("Invalid operator type ID: " + typeId);
- };
- }
- }
-
- public record Operator(long version, OperatorType operatorType, List operands) implements Packet {
-
- public void accept(PacketVisitor packetVisitor) {
- packetVisitor.enter(this);
- for (final var subPacket : operands()) {
- subPacket.accept(packetVisitor);
- }
- packetVisitor.exit(this);
- }
-
- public long evaluate() {
- return operatorType().evaluate(operands());
- }
- }
-
- public interface PacketVisitor {
- void visit(Literal literal);
-
- void enter(Operator operator);
-
- void exit(Operator operator);
- }
-
- public static class PacketBuilder {
-
- private long version;
- private long typeId;
- private OptionalLong literalValue = OptionalLong.empty();
- private final List subPackets = new ArrayList<>();
-
- public Packet readHex(final String hexString) {
- final var hexDigits = hexString.toCharArray();
- final var bits = hexToBits(hexDigits);
- read(bits, 0);
- return toPacket();
- }
-
- public int read(final List bits, int transmissionCursor) {
- final var versionBits = bits.subList(transmissionCursor, transmissionCursor + 3);
- transmissionCursor += 3;
- this.version = toLong(versionBits);
-
- final var typeBits = bits.subList(transmissionCursor, transmissionCursor + 3);
- transmissionCursor += 3;
- this.typeId = toLong(typeBits);
-
- // TODO consider adding methods to parse each type specifically
- if (this.typeId == 4) {
- boolean finalGroup = false;
- final var literalBits = new ArrayList();
- while (!finalGroup) {
- final var groupBits = bits.subList(transmissionCursor, transmissionCursor + 5);
- transmissionCursor += 5;
- finalGroup = groupBits.get(0) == 0;
- literalBits.addAll(groupBits.subList(1, 5));
- }
- if (literalBits.size() > 63) {
- throw new IllegalArgumentException("Literal is too large for an long: " + literalBits.size());
- }
- literalValue = OptionalLong.of(toLong(literalBits));
- return transmissionCursor;
- } else {
- final var lengthTypeIdBits = bits.subList(transmissionCursor, transmissionCursor + 1);
- transmissionCursor += 1;
- final var lengthTypeId = toLong(lengthTypeIdBits);
- if (lengthTypeId == 0) {
- final var lengthOfSubPacketsBits = bits.subList(transmissionCursor, transmissionCursor + 15);
- transmissionCursor += 15;
- final var lengthOfSubPackets = toLong(lengthOfSubPacketsBits);
- int bitsRead = 0;
- while (bitsRead < lengthOfSubPackets) {
- final var subPacketBuilder = new PacketBuilder();
- final var newCursor = subPacketBuilder.read(bits, transmissionCursor);
- final var subPacketSize = newCursor - transmissionCursor; // size of sub-packet in bits
- transmissionCursor = newCursor;
-
- subPackets.add(subPacketBuilder.toPacket());
- bitsRead += subPacketSize;
- }
- return transmissionCursor;
- } else if (lengthTypeId == 1) {
- final var numSubPacketsBits = bits.subList(transmissionCursor, transmissionCursor + 11);
- transmissionCursor += 11;
- final var numSubPackets = toLong(numSubPacketsBits);
- for (int packetsRead = 0; packetsRead < numSubPackets; packetsRead++) {
- final var subPacketBuilder = new PacketBuilder();
- transmissionCursor = subPacketBuilder.read(bits, transmissionCursor);
- subPackets.add(subPacketBuilder.toPacket());
- }
- return transmissionCursor;
- } else {
- throw new IllegalArgumentException("Invalid length type ID: " + lengthTypeId);
- }
- }
- }
-
- public Packet toPacket() {
- if (typeId == 4) {
- return new Literal(version, literalValue.orElseThrow());
- } else {
- return new Operator(version, OperatorType.forId((int) typeId), subPackets);
- }
- }
-
- protected long toLong(final List bits) {
- long result = 0;
- for (int i = 0; i < bits.size(); i++) {
- final var bit = bits.get(i);
- if (bit == 1) {
- final long shiftDistance = bits.size() - i - 1;
- result |= 1L << shiftDistance;
- } else if (bit != 0) {
- throw new IllegalArgumentException("Invalid bit representation of an integer: " + bits);
- }
- }
- return result;
- }
-
- protected List hexToBits(final char[] hexDigits) {
- final var result = new ArrayList(hexDigits.length * 4);
- for (final var digit : hexDigits) {
- final var bits = switch (digit) {
- case '0' -> Arrays.asList((byte) 0, (byte) 0, (byte) 0, (byte) 0);
- case '1' -> Arrays.asList((byte) 0, (byte) 0, (byte) 0, (byte) 1);
- case '2' -> Arrays.asList((byte) 0, (byte) 0, (byte) 1, (byte) 0);
- case '3' -> Arrays.asList((byte) 0, (byte) 0, (byte) 1, (byte) 1);
- case '4' -> Arrays.asList((byte) 0, (byte) 1, (byte) 0, (byte) 0);
- case '5' -> Arrays.asList((byte) 0, (byte) 1, (byte) 0, (byte) 1);
- case '6' -> Arrays.asList((byte) 0, (byte) 1, (byte) 1, (byte) 0);
- case '7' -> Arrays.asList((byte) 0, (byte) 1, (byte) 1, (byte) 1);
- case '8' -> Arrays.asList((byte) 1, (byte) 0, (byte) 0, (byte) 0);
- case '9' -> Arrays.asList((byte) 1, (byte) 0, (byte) 0, (byte) 1);
- case 'A', 'a' -> Arrays.asList((byte) 1, (byte) 0, (byte) 1, (byte) 0);
- case 'B', 'b' -> Arrays.asList((byte) 1, (byte) 0, (byte) 1, (byte) 1);
- case 'C', 'c' -> Arrays.asList((byte) 1, (byte) 1, (byte) 0, (byte) 0);
- case 'D', 'd' -> Arrays.asList((byte) 1, (byte) 1, (byte) 0, (byte) 1);
- case 'E', 'e' -> Arrays.asList((byte) 1, (byte) 1, (byte) 1, (byte) 0);
- case 'F', 'f' -> Arrays.asList((byte) 1, (byte) 1, (byte) 1, (byte) 1);
- default -> throw new IllegalStateException("Unexpected value: " + digit);
- };
- result.addAll(bits);
- }
- return Collections.unmodifiableList(result);
- }
- }
-
- @Test
- public final void testParseLiteral() {
- // given
- final var input = "D2FE28";
- final var builder = new PacketBuilder();
-
- // when
- final var result = builder.readHex(input);
-
- // then
- assertTrue(result instanceof Literal);
- final var literal = (Literal) result;
- assertEquals(2021, literal.value);
- }
-
- @Test
- public final void testOperatorWithTwoSubPackets() {
- // given
- final var input = "38006F45291200";
- final var builder = new PacketBuilder();
-
- // when
- final var result = builder.readHex(input);
-
- // then
- assertTrue(result instanceof Operator);
- final var operator = (Operator) result;
- assertEquals(1, operator.version());
- assertEquals(OperatorType.LESS_THAN, operator.operatorType());
- assertEquals(2, operator.operands().size());
- final var a = (Literal) operator.operands().get(0);
- assertEquals(10, a.value());
- final var b = (Literal) operator.operands().get(1);
- assertEquals(20, b.value());
- }
-
- @Test
- public final void part1() {
- final var line = getInput().collect(Collectors.toList()).get(0);
- final var builder = new PacketBuilder();
- final var packet = builder.readHex(line);
- class VersionSummer implements PacketVisitor {
-
- int sum = 0;
-
- public void visit(Literal literal) {
- sum += literal.version();
- }
-
- public void enter(Operator operator) {
- }
-
- public void exit(Operator operator) {
- sum += operator.version();
- }
- }
- final var summer = new VersionSummer();
- packet.accept(summer);
-
- System.out.println("Part 1: " + summer.sum);
- }
-
- @Test
- public final void part2() {
- final var line = getInput().collect(Collectors.toList()).get(0);
- final var builder = new PacketBuilder();
- final var packet = builder.readHex(line);
- System.out.println("Part 2: " + packet.evaluate());
- }
-
- @Nested
- public class PacketBuilderTest {
- @Test
- public void testToInt() {
- // given
- final var builder = new PacketBuilder();
- // when
- // then
- assertEquals(2021, builder.toLong(Arrays.asList((byte) 0, (byte) 1, (byte) 1, (byte) 1, (byte) 1, (byte) 1, (byte) 1, (byte) 0, (byte) 0, (byte) 1, (byte) 0, (byte) 1)));
- }
-
- @Test
- public final void testMaths() {
- assertEquals(3, new PacketBuilder().readHex("C200B40A82").evaluate());
- assertEquals(54, new PacketBuilder().readHex("04005AC33890").evaluate());
- assertEquals(7, new PacketBuilder().readHex("880086C3E88112").evaluate());
- assertEquals(9, new PacketBuilder().readHex("CE00C43D881120").evaluate());
- assertEquals(1, new PacketBuilder().readHex("D8005AC2A8F0").evaluate());
- assertEquals(0, new PacketBuilder().readHex("F600BC2D8F").evaluate());
- assertEquals(0, new PacketBuilder().readHex("9C005AC2F8F0").evaluate());
- assertEquals(1, new PacketBuilder().readHex("9C0141080250320F1802104A08").evaluate());
- }
- }
-
-}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day17.java b/src/test/java/com/macasaet/Day17.java
deleted file mode 100644
index 66dc6f1..0000000
--- a/src/test/java/com/macasaet/Day17.java
+++ /dev/null
@@ -1,138 +0,0 @@
-package com.macasaet;
-
-import java.util.Optional;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-import org.junit.jupiter.api.Test;
-
-/**
- * --- Day 17: Trick Shot ---
- */
-public class Day17 {
-
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-17.txt"),
- false);
- }
-
- /**
- * The target area in a large ocean trench
- */
- public record Target(int minX, int maxX, int minY, int maxY) {
- }
-
- /**
- * A probe at a given point in time
- */
- public record Probe(int xVelocity, int yVelocity, int x, int y) {
-
- /**
- * Launch a probe from the origin
- *
- * @param xVelocity the starting horizontal velocity
- * @param yVelocity the starting vertical velocity
- * @return the initial state of the probe at the origin
- */
- public static Probe launch(final int xVelocity, final int yVelocity) {
- return new Probe(xVelocity, yVelocity, 0, 0);
- }
-
- public Optional step() {
- if(x > 0 && x + xVelocity < 0) {
- return Optional.empty();
- }
- if(y < 0 && y + yVelocity > 0) {
- return Optional.empty();
- }
- final int newX = x + xVelocity;
- final int newY = y + yVelocity;
- final int newXVelocity = xVelocity > 0
- ? xVelocity - 1
- : xVelocity < 0
- ? xVelocity + 1
- : xVelocity;
- return Optional.of(new Probe(newXVelocity,
- yVelocity - 1,
- newX,
- newY));
- }
-
- public Optional peak(final Target target) {
- var peak = Integer.MIN_VALUE;
- var p = Optional.of(this);
- while (p.isPresent()) {
- final var probe = p.get();
- peak = Math.max(peak, probe.y());
- if (probe.x() < target.minX() && probe.y() < target.minY()) {
- // short
- return Optional.empty();
- } else if (probe.x() > target.maxX()) {
- // long
- return Optional.empty();
- } else if (probe.x() >= target.minX() && probe.x() <= target.maxX()
- && probe.y() >= target.minY() && probe.y() <= target.maxY()) {
- return Optional.of(peak);
- }
- p = probe.step();
- }
- return Optional.empty();
- }
-
- }
-
- @Test
- public final void part1() {
- final var line = getInput().collect(Collectors.toList()).get(0);
- final var bounds = line.replaceFirst("target area: ", "").split(", ");
- final var xBounds = bounds[0].replaceFirst("x=", "").split("\\.\\.");
- final var yBounds = bounds[1].replaceFirst("y=", "").split("\\.\\.");
- final int minX = Integer.parseInt(xBounds[0]);
- final int maxX = Integer.parseInt(xBounds[1]);
- final int minY = Integer.parseInt(yBounds[0]);
- final int maxY = Integer.parseInt(yBounds[1]);
- final var target = new Target(minX, maxX, minY, maxY);
-
- final var max = IntStream.range(0, 50)
- .parallel()
- .mapToObj(x -> IntStream.range(-50, 50)
- .parallel()
- .mapToObj(y -> Probe.launch(x, y))
- ).flatMap(probes -> probes)
- .flatMapToInt(probe -> probe.peak(target)
- .stream()
- .mapToInt(peak -> peak))
- .max();
-
-
- System.out.println("Part 1: " + max.getAsInt());
- }
-
- @Test
- public final void part2() {
- final var line = getInput().collect(Collectors.toList()).get(0);
- final var bounds = line.replaceFirst("target area: ", "").split(", ");
- final var xBounds = bounds[0].replaceFirst("x=", "").split("\\.\\.");
- final var yBounds = bounds[1].replaceFirst("y=", "").split("\\.\\.");
- final int minX = Integer.parseInt(xBounds[0]);
- final int maxX = Integer.parseInt(xBounds[1]);
- final int minY = Integer.parseInt(yBounds[0]);
- final int maxY = Integer.parseInt(yBounds[1]);
- final var target = new Target(minX, maxX, minY, maxY);
- int count = 0;
- for (int x = 1; x <= 400; x++) {
- for (int y = -400; y <= 400; y++) {
- final var probe = Probe.launch(x, y);
- if (probe.peak(target).isPresent()) {
- count++;
- }
- }
- }
-
- System.out.println("Part 2: " + count);
- }
-
-}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day18.java b/src/test/java/com/macasaet/Day18.java
index afd2133..3c29e60 100644
--- a/src/test/java/com/macasaet/Day18.java
+++ b/src/test/java/com/macasaet/Day18.java
@@ -1,432 +1,176 @@
package com.macasaet;
-import static com.macasaet.Day18.SnailfishNumber.parse;
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import org.junit.jupiter.api.Test;
-import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 18: Snailfish ---
+ * --- Day 18: Boiling Boulders ---
+ * https://adventofcode.com/2022/day/18
*/
public class Day18 {
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-18.txt"),
- false);
- }
+ public static final int SCAN_DIMENSION = 32;
- /**
- * An element of a {@link SnailfishNumber}
- */
- public interface Token {
+ protected static Droplet getInput() {
+ final var cubeCoordinates = StreamSupport.stream(new LineSpliterator("day-18.txt"), false)
+ .map(Cube::parse)
+ .toList();
+ return new Droplet(cubeCoordinates);
}
- /**
- * A symbol in a {@link SnailfishNumber}
- */
- public enum Symbol implements Token {
- START_PAIR,
- END_PAIR,
- SEPARATOR,
+ @Test
+ public final void part1() {
+ final var droplet = getInput();
+ final var result = droplet.surfaceArea(CubeType.Air);
+ System.out.println("Part 1: " + result);
}
- /**
- * An integer in a {@link SnailfishNumber}
- */
- public record Number(int value) implements Token {
+ @Test
+ public final void part2() {
+ final var droplet = getInput();
+ droplet.immerse();
+ final var result = droplet.surfaceArea(CubeType.Water);
+
+ System.out.println("Part 2: " + result);
}
- /**
- * "Snailfish numbers aren't like regular numbers. Instead, every snailfish number is a pair - an ordered list of
- * two elements. Each element of the pair can be either a regular number or another pair."
- */
- public record SnailfishNumber(List expression) {
+ public enum CubeType {
+ Air,
+ Lava,
+ Water,
+ }
- public SnailfishNumber(final String string) {
- this(parse(string));
+ record Cube(int x, int y, int z) {
+ public static Cube parse(final String line) {
+ final var components = line.split(",");
+ return new Cube(Integer.parseInt(components[0]), Integer.parseInt(components[1]), Integer.parseInt(components[2]));
}
- /**
- * "The magnitude of a pair is 3 times the magnitude of its left element plus 2 times the magnitude of its right
- * element. The magnitude of a regular number is just that number."
- *
- * @return the snailfish number distilled into a single value
- */
- public int magnitude() {
- var stack = new LinkedList();
- for (final var token : expression) {
- if (token.equals(Symbol.START_PAIR)) {
- } else if (token instanceof final Number number) {
- stack.push(number.value());
- } else if (token.equals(Symbol.END_PAIR)) {
- final var rightValue = stack.pop();
- final var leftValue = stack.pop();
- stack.push(leftValue * 3 + rightValue * 2);
- }
- }
- if (stack.size() != 1) {
- throw new IllegalStateException("Invalid stack: " + stack);
- }
- return stack.get(0);
+ public CubeType getType(final CubeType[][][] grid) {
+ return grid[x()][y()][z()];
}
- /**
- * Repeatedly explode or split this snailfish number until those operations can no longer be performed.
- *
- * @return a representation of this snailfish number that cannot be reduced any further
- */
- public SnailfishNumber reduce() {
- var newExpression = expression;
- while (true) {
- var explosionIndex = getExplosionIndex(newExpression);
- var splitIndex = getSplitIndex(newExpression);
- if (explosionIndex > 0) {
- newExpression = explode(newExpression, explosionIndex);
- } else if (splitIndex > 0) {
- newExpression = split(newExpression, splitIndex);
- } else {
- break;
- }
- }
- return new SnailfishNumber(newExpression);
+ public void setType(final CubeType[][][] grid, final CubeType type) {
+ grid[x()][y()][z()] = type;
}
+ }
- /**
- * Add a snailfish number. Note, this operation is *not commutative*: `x.add(y)` is not the same as `y.add(x)`.
- * Also note that the process of addition may yield a snailfish number that needs to be reduced.
- *
- * @param addend the number to add to this snailfish number
- * @return the sum of the snailfish numbers (may need to be reduced
- * @see SnailfishNumber#reduce()
- */
- public SnailfishNumber add(final SnailfishNumber addend) {
- final var tokens = new ArrayList();
- tokens.add(Symbol.START_PAIR);
- tokens.addAll(expression());
- tokens.add(Symbol.SEPARATOR);
- tokens.addAll(addend.expression());
- tokens.add(Symbol.END_PAIR);
- return new SnailfishNumber(Collections.unmodifiableList(tokens));
- }
+ public static class Droplet {
- static List parse(final String expression) {
- final var result = new ArrayList();
- for (int i = 0; i < expression.length(); i++) {
- final var c = expression.charAt(i);
- if (c == '[') {
- result.add(Symbol.START_PAIR);
- } else if (c == ']') {
- result.add(Symbol.END_PAIR);
- } else if (c == ',') {
- result.add(Symbol.SEPARATOR);
- } else if (c >= '0' && c <= '9') {
- int endExclusive = i + 1;
- while (endExclusive < expression.length()) {
- final var d = expression.charAt(endExclusive);
- if (d < '0' || d > '9') {
- break;
- }
- endExclusive++;
- }
- final int value = Integer.parseInt(expression.substring(i, endExclusive));
- result.add(new Number(value));
- i = endExclusive - 1;
+ private final Collection extends Cube> cubes;
+ private final CubeType[][][] grid;
+
+ public Droplet(final Collection extends Cube> cubes) {
+ final CubeType[][][] grid = new CubeType[SCAN_DIMENSION][][];
+ for (int i = SCAN_DIMENSION; --i >= 0; ) {
+ grid[i] = new CubeType[SCAN_DIMENSION][];
+ for (int j = grid[i].length; --j >= 0; ) {
+ grid[i][j] = new CubeType[SCAN_DIMENSION];
+ for (int k = grid[i][j].length; --k >= 0; grid[i][j][k] = CubeType.Air);
}
}
- return Collections.unmodifiableList(result);
- }
-
- /**
- * Split a regular number. "To split a regular number, replace it with a pair; the left element of the pair
- * should be the regular number divided by two and rounded down, while the right element of the pair should be
- * the regular number divided by two and rounded up."
- *
- * @param expression a raw representation of a snailfish number
- * @param index the index of a regular number to split. The caller is responsible for ensuring that this number
- * can be split and that it is the most appropriate action to take.
- * @return the reduced snailfish number in raw tokens
- */
- List split(final List expression, final int index) {
- final var result = new ArrayList();
- if (index > 0) {
- result.addAll(expression.subList(0, index));
+ for (final var cube : cubes) {
+ cube.setType(grid, CubeType.Lava);
}
- final var regularNumber = (Number) expression.get(index);
-
- final var left = Math.floorDiv(regularNumber.value(), 2);
- final var right = (int) Math.ceil(regularNumber.value() / 2.0d);
-
- result.add(Symbol.START_PAIR);
- result.add(new Number(left));
- result.add(Symbol.SEPARATOR);
- result.add(new Number(right));
- result.add(Symbol.END_PAIR);
- if (index + 1 < expression.size()) {
- result.addAll(expression.subList(index + 1, expression.size()));
- }
- return Collections.unmodifiableList(result);
+ this.grid = grid;
+ this.cubes = cubes;
}
- /**
- * Determine whether any of the regular numbers can be split and if so, the highest-priority number to split.
- *
- * @param expression a raw representation of a snailfish number
- * @return the index of the best regular number to split or -1 if none can be split
- */
- int getSplitIndex(final List expression) {
- for (int i = 0; i < expression.size(); i++) {
- final var token = expression.get(i);
- if (token instanceof final Number number) {
- if (number.value() >= 10) {
- return i;
-
+ public int surfaceArea(final CubeType element) {
+ int result = 0;
+ for (final var cube : getCubes()) {
+ result += exposedFaces(cube, element);
+ }
+ return result;
+ }
+
+ public void immerse() {
+ final var grid = getGrid();
+ final var queue = new ArrayDeque();
+ final var encountered = new HashSet();
+ final var origin = new Cube(0, 0, 0);
+ encountered.add(origin);
+ queue.add(origin);
+
+ while (!queue.isEmpty()) {
+ final var cube = queue.remove();
+ for (final var neighbour : neighbours(cube).stream().filter(neighbour -> neighbour.getType(grid) == CubeType.Air).toList()) {
+ if (!encountered.contains(neighbour)) {
+ encountered.add(neighbour);
+ queue.add(neighbour);
}
}
+ cube.setType(grid, CubeType.Water);
}
- return -1;
}
- /**
- * Explode the pair starting at `index`. "To explode a pair, the pair's left value is added to the first regular
- * number to the left of the exploding pair (if any), and the pair's right value is added to the first regular
- * number to the right of the exploding pair (if any). Exploding pairs will always consist of two regular
- * numbers. Then, the entire exploding pair is replaced with the regular number 0."
- *
- * @param expression a raw representation of a snailfish number
- * @param index the index of the opening brace of the pair to explode. The caller must ensure that an explosion
- * operation is valid at the index and that the index represents the most appropriate pair to
- * explode.
- * @return the reduced expression in raw format
- */
- List explode(final List expression, final int index) {
- final var result = new ArrayList<>(expression);
- final int leftNumberIndex = index + 1;
- final int rightNumberIndex = index + 3;
- final int left = ((Number) expression.get(leftNumberIndex)).value();
- final int right = ((Number) expression.get(rightNumberIndex)).value();
- int leftIndex = -1;
- int rightIndex = -1;
-
- for (int i = index; --i >= 0; ) {
- final var c = expression.get(i);
- if (c instanceof Number) {
- leftIndex = i;
- break;
- }
+ protected Collection extends Cube> neighbours(final Cube cube) {
+ final var result = new HashSet();
+ final var x = cube.x();
+ final var y = cube.y();
+ final var z = cube.z();
+ if (x > 0) {
+ result.add(new Cube(x - 1, y, z));
}
- for (int i = rightNumberIndex + 1; i < expression.size(); i++) {
- final var c = expression.get(i);
- if (c instanceof Number) {
- rightIndex = i;
- break;
- }
+ if (x < SCAN_DIMENSION - 1) {
+ result.add(new Cube(x + 1, y, z));
}
- if (leftIndex < 0 && rightIndex < 0) {
- throw new IllegalArgumentException("Cannot be exploded: " + expression);
+ if (y > 0) {
+ result.add(new Cube(x, y - 1, z));
}
- // "the pair's left value is added to the first regular number to the left of the exploding pair (if any)"
- if (leftIndex > 0) {
- final int leftOperand = ((Number) expression.get(leftIndex)).value();
- final int replacement = leftOperand + left;
- result.set(leftIndex, new Number(replacement));
+ if (y < SCAN_DIMENSION - 1) {
+ result.add(new Cube(x, y + 1, z));
}
- // "the pair's right value is added to the first regular number to the right of the exploding pair (if any)"
- if (rightIndex > 0) {
- final int rightOperand = ((Number) expression.get(rightIndex)).value();
- final int replacement = rightOperand + right;
- result.set(rightIndex, new Number(replacement));
+ if (z > 0) {
+ result.add(new Cube(x, y, z - 1));
}
- // "Exploding pairs will always consist of two regular numbers. Then, the entire exploding pair is replaced
- // with the regular number 0."
- result.set(index, new Number(0));
- result.remove(index + 1);
- result.remove(index + 1);
- result.remove(index + 1);
- result.remove(index + 1);
- return Collections.unmodifiableList(result);
- }
-
- /**
- * @param expression a raw representation of a snailfish number
- * @return the index of the most appropriate pair to explode (opening brace) or -1 if no explosion is appropriate
- */
- int getExplosionIndex(final List expression) {
- int depth = -1;
- int maxDepth = Integer.MIN_VALUE;
- int result = -1;
- for (int i = 0; i < expression.size(); i++) {
- final var token = expression.get(i);
- if (token == Symbol.START_PAIR) {
- depth++;
- } else if (token == Symbol.END_PAIR) {
- depth--;
- }
- if (depth > maxDepth) {
- maxDepth = depth;
- result = i;
- }
+ if (z < SCAN_DIMENSION - 1) {
+ result.add(new Cube(x, y, z + 1));
}
- return result > 3 ? result : -1;
+ return Collections.unmodifiableSet(result);
}
- }
+ public int exposedFaces(final Cube cube, final CubeType element) {
+ final int x = cube.x();
+ final int y = cube.y();
+ final int z = cube.z();
+ final var grid = getGrid();
- @Nested
- public class SnailfishNumberTest {
-
- @Test
- public final void testAdd() {
- assertEquals(new SnailfishNumber("[[[[0,7],4],[[7,8],[6,0]]],[8,1]]"),
- new SnailfishNumber("[[[[4,3],4],4],[7,[[8,4],9]]]")
- .add(new SnailfishNumber("[1,1]"))
- .reduce());
- // either this example is broken or my bug is not triggered in the real puzzle input T_T
-// assertEquals(new SnailfishNumber("[[[[7,8],[6,6]],[[6,0],[7,7]]],[[[7,8],[8,8]],[[7,9],[0,6]]]]"),
-// new SnailfishNumber("[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]")
-// .add(new SnailfishNumber("[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]"))
-// .reduce());
- }
-
- @Test
- public final void testAddList() {
- // given
- final var lines = """
- [[[0,[4,5]],[0,0]],[[[4,5],[2,6]],[9,5]]]
- [7,[[[3,7],[4,3]],[[6,3],[8,8]]]]
- [[2,[[0,8],[3,4]]],[[[6,7],1],[7,[1,6]]]]
- [[[[2,4],7],[6,[0,5]]],[[[6,8],[2,8]],[[2,1],[4,5]]]]
- [7,[5,[[3,8],[1,4]]]]
- [[2,[2,2]],[8,[8,1]]]
- [2,9]
- [1,[[[9,3],9],[[9,0],[0,7]]]]
- [[[5,[7,4]],7],1]
- [[[[4,2],2],6],[8,7]]""";
- final var list = Arrays.stream(lines.split("\n"))
- .map(SnailfishNumber::new)
- .collect(Collectors.toList());
-
- // when
- var sum = list.get(0);
- for (final var addend : list.subList(1, list.size())) {
- sum = sum.add(addend).reduce();
+ int result = 0;
+ if (grid[x + 1][y][z] == element) {
+ result++;
}
-
- // then
- assertEquals(new SnailfishNumber("[[[[8,7],[7,7]],[[8,6],[7,7]]],[[[0,7],[6,6]],[8,7]]]"),
- sum);
- }
-
- @Test
- public final void testSplit() {
- final var instance = new SnailfishNumber(Collections.emptyList());
-
- assertEquals(parse("[5, 5]"),
- instance.split(parse("10"), 0));
- assertEquals(parse("[5, 6]"),
- instance.split(parse("11"), 0));
- assertEquals(parse("[6, 6]"),
- instance.split(parse("12"), 0));
- }
-
- @Test
- public final void testExplosionIndex() {
- final var instance = new SnailfishNumber(Collections.emptyList());
- assertEquals(4,
- instance.getExplosionIndex(parse("[[[[[9,8],1],2],3],4]")));
- assertEquals(12,
- instance.getExplosionIndex(parse("[7,[6,[5,[4,[3,2]]]]]")));
- assertEquals(10,
- instance.getExplosionIndex(parse("[[6,[5,[4,[3,2]]]],1]")));
- assertEquals(10,
- instance.getExplosionIndex(parse("[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]")));
- assertEquals(24,
- instance.getExplosionIndex(parse("[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]")));
- }
-
- @Test
- public final void testExplode() {
- final var instance = new SnailfishNumber(Collections.emptyList());
- assertEquals(parse("[[[[0,9],2],3],4]"),
- instance
- .explode(parse("[[[[[9,8],1],2],3],4]"), 4));
- assertEquals(parse("[7,[6,[5,[7,0]]]]"),
- instance
- .explode(parse("[7,[6,[5,[4,[3,2]]]]]"), 12));
- assertEquals(parse("[[6,[5,[7,0]]],3]"),
- instance
- .explode(parse("[[6,[5,[4,[3,2]]]],1]"), 10));
- assertEquals(parse("[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]"),
- instance
- .explode(parse("[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]"), 10));
- assertEquals(parse("[[3,[2,[8,0]]],[9,[5,[7,0]]]]"),
- instance
- .explode(parse("[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]"), 24));
- }
-
- @Test
- public final void testMagnitude() {
- assertEquals(29, new SnailfishNumber("[9,1]").magnitude());
- assertEquals(21, new SnailfishNumber("[1,9]").magnitude());
- assertEquals(143, new SnailfishNumber("[[1,2],[[3,4],5]]").magnitude());
- assertEquals(1384, new SnailfishNumber("[[[[0,7],4],[[7,8],[6,0]]],[8,1]]").magnitude());
- assertEquals(445, new SnailfishNumber("[[[[1,1],[2,2]],[3,3]],[4,4]]").magnitude());
- assertEquals(791, new SnailfishNumber("[[[[3,0],[5,3]],[4,4]],[5,5]]").magnitude());
- assertEquals(1137, new SnailfishNumber("[[[[5,0],[7,4]],[5,5]],[6,6]]").magnitude());
- assertEquals(3488, new SnailfishNumber("[[[[8,7],[7,7]],[[8,6],[7,7]]],[[[0,7],[6,6]],[8,7]]]").magnitude());
- assertEquals(3993, new SnailfishNumber("[[[[7,8],[6,6]],[[6,0],[7,7]]],[[[7,8],[8,8]],[[7,9],[0,6]]]]").magnitude());
- }
-
- @Test
- public final void verifyStableState() {
- // given
- final var original = new SnailfishNumber("[[[[7,8],[6,6]],[[6,0],[7,7]]],[[[7,8],[8,8]],[[7,9],[0,6]]]]");
-
- // when
- final var reduced = original.reduce();
-
- // then
- assertEquals(original, reduced);
+ if (x <= 0 || grid[x - 1][y][z] == element) {
+ result++;
+ }
+ if (grid[x][y + 1][z] == element) {
+ result++;
+ }
+ if (y == 0 || grid[x][y - 1][z] == element) {
+ result++;
+ }
+ if (grid[x][y][z + 1] == element) {
+ result++;
+ }
+ if (z == 0 || grid[x][y][z - 1] == element) {
+ result++;
+ }
+ return result;
}
- }
-
- @Test
- public final void part1() {
- final var list =
- getInput().map(SnailfishNumber::new).collect(Collectors.toList());
- var sum = list.get(0);
- for (final var addend : list.subList(1, list.size())) {
- sum = sum.add(addend).reduce();
+ public Collection extends Cube> getCubes() {
+ return cubes;
}
- System.out.println("Part 1: " + sum.magnitude());
- }
- @Test
- public final void part2() {
- final var list =
- getInput().map(SnailfishNumber::new).collect(Collectors.toList());
- int max = Integer.MIN_VALUE;
- for (final var x : list) {
- for (final var y : list) {
- if (x.equals(y)) {
- continue;
- }
- final var sum = x.add(y).reduce();
- final var magnitude = sum.magnitude();
- if (magnitude > max) {
- max = magnitude;
- }
- }
+ public CubeType[][][] getGrid() {
+ return grid;
}
- System.out.println("Part 2: " + max);
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day19.java b/src/test/java/com/macasaet/Day19.java
deleted file mode 100644
index a11c202..0000000
--- a/src/test/java/com/macasaet/Day19.java
+++ /dev/null
@@ -1,395 +0,0 @@
-package com.macasaet;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-
-/**
- * --- Day 19: Beacon Scanner ---
- */
-public class Day19 {
-
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-19.txt"),
- false);
- }
-
- protected List getScanners() {
- final var list = getInput().toList();
- final var result = new ArrayList();
- var observations = new HashSet();
- int id = -1;
- for (final var line : list) {
- if (line.startsWith("--- scanner ")) {
- id = Integer.parseInt(line
- .replaceFirst("^--- scanner ", "")
- .replaceFirst(" ---$", ""));
- } else if (!line.isBlank()) {
- observations.add(Position.parse(line));
- } else { // line is blank
- result.add(new Scanner(id, Collections.unmodifiableSet(observations)));
- observations = new HashSet<>();
- id = -1;
- }
- }
- result.add(new Scanner(id, Collections.unmodifiableSet(observations)));
- return Collections.unmodifiableList(result);
- }
-
- public enum Direction {
- POSITIVE_X {
- public Position face(Position position) {
- return position;
- }
- },
- NEGATIVE_X {
- public Position face(Position position) {
- return new Position(-position.x(), position.y(), -position.z());
- }
- },
- POSITIVE_Y {
- public Position face(Position position) {
- return new Position(position.y(), -position.x(), position.z());
- }
- },
- NEGATIVE_Y {
- public Position face(Position position) {
- return new Position(-position.y(), position.x(), position.z());
- }
- },
- POSITIVE_Z {
- public Position face(Position position) {
- return new Position(position.z(), position.y(), -position.x());
- }
- },
- NEGATIVE_Z {
- public Position face(Position position) {
- return new Position(-position.z(), position.y(), position.x());
- }
- };
-
- public abstract Position face(final Position position);
-
- }
-
- public enum Rotation {
- r0 {
- public Position rotate(final Position position) {
- return position;
- }
- },
- r90 {
- public Position rotate(Position position) {
- return new Position(position.x(), -position.z(), position.y());
- }
- },
- r180 {
- public Position rotate(Position position) {
- return new Position(position.x(), -position.y(), -position.z());
- }
- },
- r270 {
- public Position rotate(Position position) {
- return new Position(position.x(), position.z(), -position.y());
- }
- };
-
- public abstract Position rotate(final Position position);
- }
-
- public record Transformation(Direction direction, Rotation rotation) {
- /**
- * Look at a position from a specific orientation
- *
- * @param position a position relative to one point of view
- * @return the same position relative to a different point of view
- */
- public Position reorient(final Position position) {
- return rotation.rotate(direction.face(position));
- }
-
- }
-
- public record Position(int x, int y, int z) {
- public static Position parse(final String line) {
- final var components = line.split(",");
- return new Position(Integer.parseInt(components[0]),
- Integer.parseInt(components[1]),
- Integer.parseInt(components[2]));
- }
-
- public Position plus(Position amount) {
- return new Position(x() + amount.x(), y() + amount.y(), z() + amount.z());
- }
-
- public Position minus(final Position other) {
- return new Position(x() - other.x(), y() - other.y(), z() - other.z());
- }
- }
-
- public interface OverlapResult {
- }
-
- public record Overlap(Position distance, Transformation transformation,
- Set overlappingBeacons) implements OverlapResult {
- }
-
- public record None() implements OverlapResult {
- }
-
- public record Scanner(int id, Set observations) {
-
- public OverlapResult getOverlappingBeacons(final Scanner other) {
- for (final var direction : Direction.values()) {
- for (final var rotation : Rotation.values()) {
- final var transformation = new Transformation(direction, rotation);
- final var distances = observations().stream()
- .flatMap(a -> other.observations()
- .stream()
- .map(transformation::reorient)
- .map(a::minus))
- .collect(Collectors.toList());
- for (final var offset : distances) {
- final var intersection = other.observations()
- .stream()
- .map(transformation::reorient)
- .map(observation -> observation.plus(offset))
- .filter(observations()::contains)
- .collect(Collectors.toUnmodifiableSet());
- if (intersection.size() >= 12) {
- return new Overlap(offset, transformation, intersection);
- }
- }
- }
- }
- return new None();
- }
-
- }
-
- @Nested
- public class ScannerTest {
- @Test
- public final void testOverlapWithOrigin() {
- // given
- final var scanner0Observations = """
- 404,-588,-901
- 528,-643,409
- -838,591,734
- 390,-675,-793
- -537,-823,-458
- -485,-357,347
- -345,-311,381
- -661,-816,-575
- -876,649,763
- -618,-824,-621
- 553,345,-567
- 474,580,667
- -447,-329,318
- -584,868,-557
- 544,-627,-890
- 564,392,-477
- 455,729,728
- -892,524,684
- -689,845,-530
- 423,-701,434
- 7,-33,-71
- 630,319,-379
- 443,580,662
- -789,900,-551
- 459,-707,401
- """;
- final var scanner1Observations = """
- 686,422,578
- 605,423,415
- 515,917,-361
- -336,658,858
- 95,138,22
- -476,619,847
- -340,-569,-846
- 567,-361,727
- -460,603,-452
- 669,-402,600
- 729,430,532
- -500,-761,534
- -322,571,750
- -466,-666,-811
- -429,-592,574
- -355,545,-477
- 703,-491,-529
- -328,-685,520
- 413,935,-424
- -391,539,-444
- 586,-435,557
- -364,-763,-893
- 807,-499,-711
- 755,-354,-619
- 553,889,-390
- """;
- final var scanner0 = new Scanner(0,
- Arrays.stream(scanner0Observations.split("\n"))
- .map(Position::parse)
- .collect(Collectors.toUnmodifiableSet()));
- final var scanner1 = new Scanner(0,
- Arrays.stream(scanner1Observations.split("\n"))
- .map(Position::parse)
- .collect(Collectors.toUnmodifiableSet()));
-
- // when
- final var result = scanner0.getOverlappingBeacons(scanner1);
-
- // then
- assertTrue(result instanceof Overlap);
- final var overlap = (Overlap) result;
- assertEquals(12, overlap.overlappingBeacons().size());
- }
-
- @Test
- public final void testOverlapWithNonOrigin() {
- // given
- final var scanner1Observations = """
- 686,422,578
- 605,423,415
- 515,917,-361
- -336,658,858
- 95,138,22
- -476,619,847
- -340,-569,-846
- 567,-361,727
- -460,603,-452
- 669,-402,600
- 729,430,532
- -500,-761,534
- -322,571,750
- -466,-666,-811
- -429,-592,574
- -355,545,-477
- 703,-491,-529
- -328,-685,520
- 413,935,-424
- -391,539,-444
- 586,-435,557
- -364,-763,-893
- 807,-499,-711
- 755,-354,-619
- 553,889,-390
- """;
- final var scanner4Observations = """
- 727,592,562
- -293,-554,779
- 441,611,-461
- -714,465,-776
- -743,427,-804
- -660,-479,-426
- 832,-632,460
- 927,-485,-438
- 408,393,-506
- 466,436,-512
- 110,16,151
- -258,-428,682
- -393,719,612
- -211,-452,876
- 808,-476,-593
- -575,615,604
- -485,667,467
- -680,325,-822
- -627,-443,-432
- 872,-547,-609
- 833,512,582
- 807,604,487
- 839,-516,451
- 891,-625,532
- -652,-548,-490
- 30,-46,-14
- """;
- final var scanner1 = new Scanner(0,
- Arrays.stream(scanner1Observations.split("\n"))
- .map(Position::parse)
- .collect(Collectors.toUnmodifiableSet()));
- final var scanner4 = new Scanner(0,
- Arrays.stream(scanner4Observations.split("\n"))
- .map(Position::parse)
- .collect(Collectors.toUnmodifiableSet()));
-
- // when
- final var result = scanner1.getOverlappingBeacons(scanner4);
-
- // then
- assertTrue(result instanceof Overlap);
- final var overlap = (Overlap) result;
- assertEquals(12, overlap.overlappingBeacons().size());
- }
- }
-
- @Test
- public final void part1() {
- final var scanners = getScanners();
- final var knownBeacons = new HashSet();
- final var origin = new Scanner(-1, knownBeacons);
- final var remaining = new ArrayList<>(scanners);
- while (!remaining.isEmpty()) {
- final var other = remaining.remove(0);
- if (knownBeacons.isEmpty()) {
- knownBeacons.addAll(other.observations());
- continue;
- }
- final var result = origin.getOverlappingBeacons(other);
- if (result instanceof final Overlap overlap) {
- knownBeacons.addAll(other.observations()
- .stream()
- .map(overlap.transformation()::reorient)
- .map(observation -> observation.plus(overlap.distance()))
- .collect(Collectors.toList()));
- } else {
- remaining.add(other);
- }
- }
- System.out.println("Part 1: " + knownBeacons.size());
- }
-
- @Test
- public final void part2() {
- final var scanners = getScanners();
- final var knownBeacons = new HashSet();
- final var origin = new Scanner(-1, knownBeacons);
- final var remaining = new ArrayList<>(scanners);
- final var distances = new HashSet();
- while (!remaining.isEmpty()) {
- final var other = remaining.remove(0);
- if (knownBeacons.isEmpty()) {
- knownBeacons.addAll(other.observations());
- continue;
- }
- final var result = origin.getOverlappingBeacons(other);
- if (result instanceof final Overlap overlap) {
- knownBeacons.addAll(other.observations()
- .stream()
- .map(overlap.transformation()::reorient)
- .map(observation -> observation.plus(overlap.distance()))
- .collect(Collectors.toList()));
- distances.add(overlap.distance());
- } else {
- remaining.add(other);
- }
- }
- int maxDistance = Integer.MIN_VALUE;
- for (final var x : distances) {
- for (final var y : distances) {
- final int distance = Math.abs(x.x() - y.x())
- + Math.abs(x.y() - y.y())
- + Math.abs(x.z() - y.z());
- maxDistance = Math.max(maxDistance, distance);
- }
- }
- System.out.println("Part 2: " + maxDistance);
- }
-
-}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day20.java b/src/test/java/com/macasaet/Day20.java
index 85115e9..4125a47 100644
--- a/src/test/java/com/macasaet/Day20.java
+++ b/src/test/java/com/macasaet/Day20.java
@@ -1,233 +1,106 @@
package com.macasaet;
-import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Test;
-import java.util.*;
+import java.math.BigInteger;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 20: Trench Map ---
+ * --- Day 20: Grove Positioning System ---
+ * https://adventofcode.com/2022/day/20
*/
public class Day20 {
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-20.txt"),
- false);
- }
-
- public record ImageEnhancementAlgorithm(boolean[] map) {
-
- public boolean isPixelLit(final int code) {
- return map()[code];
- }
-
- public static ImageEnhancementAlgorithm parse(final String line) {
- final var map = new boolean[line.length()];
- for (int i = line.length(); --i >= 0; map[i] = line.charAt(i) == '#') ;
- return new ImageEnhancementAlgorithm(map);
+ public record Number(int originalIndex, int value, BigInteger decryptedValue) {
+ Number(int originalIndex, int value) {
+ this(originalIndex, value, BigInteger.valueOf(value).multiply(BigInteger.valueOf(811_589_153)));
}
}
- protected record Coordinate(int x, int y) {
- }
-
- public record Image(SortedMap> pixels, int minX, int maxX, int minY,
- int maxY, boolean isEven) {
- public Image enhance(final ImageEnhancementAlgorithm algorithm) {
- final var enhancedPixelMap = new TreeMap>();
- int enhancedMinX = minX;
- int enhancedMaxX = maxX;
- int enhancedMinY = minY;
- int enhancedMaxY = maxY;
- for (int i = minX - 1; i <= maxX + 1; i++) {
- final var targetRow = new TreeMap();
- for (int j = minY - 1; j <= maxY + 1; j++) {
- final int replacementId = decode(i, j, !isEven && algorithm.isPixelLit(0));
- final var shouldLight = algorithm.isPixelLit(replacementId);
- if (shouldLight) {
- // save space by only storing an entry when lit
- targetRow.put(j, true);
- enhancedMinY = Math.min(enhancedMinY, j);
- enhancedMaxY = Math.max(enhancedMaxY, j);
- }
- }
- if (!targetRow.isEmpty()) {
- // save space by only storing a row if at least one cell is lit
- enhancedPixelMap.put(i, Collections.unmodifiableSortedMap(targetRow));
- enhancedMinX = Math.min(enhancedMinX, i);
- enhancedMaxX = Math.max(enhancedMaxX, i);
- }
- }
- return new Image(Collections.unmodifiableSortedMap(enhancedPixelMap), enhancedMinX, enhancedMaxX, enhancedMinY, enhancedMaxY, !isEven);
+ protected static List getInput() {
+ final List numbers = StreamSupport.stream(new LineSpliterator("day-20.txt"), false)
+ .mapToInt(Integer::parseInt)
+ .collect(ArrayList::new, (x, y) -> x.add(y), (x, y) -> x.addAll(y));
+ final var result = new ArrayList(numbers.size());
+ for(int i = 0; i < numbers.size(); i++) {
+ result.add(new Number(i, numbers.get(i)));
}
+ return Collections.unmodifiableList(result);
+ }
- int decode(final int x, final int y, boolean voidIsLit) {
- final var list = getNeighbouringCoordinates(x, y).stream()
- .map(coordinate -> isBitSet(coordinate, voidIsLit))
- .toList();
- int result = 0;
- for (int i = list.size(); --i >= 0; ) {
- if (list.get(i)) {
- final int shiftDistance = list.size() - i - 1;
- result |= 1 << shiftDistance;
- }
- }
- if (result < 0 || result > 512) {
- throw new IllegalStateException("Unable to decode pixel at " + x + ", " + y);
+ @Test
+ public final void part1() {
+ final var numbers = getInput();
+ final var indexMap = new HashMap(numbers.size());
+ Number zero = null;
+ for(final var number : numbers) {
+ indexMap.put(number.originalIndex, number.value());
+ if(number.value() == 0) {
+ zero = number;
}
- return result;
}
-
- boolean isBitSet(final Coordinate coordinate, boolean voidIsLit) {
- final var row = pixels().get(coordinate.x());
- if((coordinate.x() < minX || coordinate.x() > maxX) && row == null) {
- return voidIsLit;
- }
- else if(row == null) {
- return false;
+ final var workingSet = new ArrayList<>(numbers);
+
+ for(final var number : numbers) {
+ final var originalIndex = workingSet.indexOf(number);
+ workingSet.remove(originalIndex);
+ var newIndex = (originalIndex + number.value()) % (numbers.size() - 1);
+ if(newIndex < 0) {
+ newIndex += numbers.size() - 1;
}
- return row.getOrDefault(coordinate.y(), (coordinate.y() < minY || coordinate.y() > maxY) && voidIsLit);
- }
-
- List getNeighbouringCoordinates(int x, int y) {
- return Arrays.asList(
- new Coordinate(x - 1, y - 1),
- new Coordinate(x - 1, y),
- new Coordinate(x - 1, y + 1),
- new Coordinate(x, y - 1),
- new Coordinate(x, y),
- new Coordinate(x, y + 1),
- new Coordinate(x + 1, y - 1),
- new Coordinate(x + 1, y),
- new Coordinate(x + 1, y + 1)
- );
+ workingSet.add(newIndex, number);
}
- public long countLitPixels() {
- return pixels().values()
- .stream()
- .flatMap(row -> row.values().stream())
- .filter(isLit -> isLit)
- .count();
- }
+ final var x = workingSet.get((workingSet.indexOf(zero) + 1000) % workingSet.size()).value();
+ final var y = workingSet.get((workingSet.indexOf(zero) + 2000) % workingSet.size()).value();
+ final var z = workingSet.get((workingSet.indexOf(zero) + 3000) % workingSet.size()).value();
- public int width() {
- return maxX - minX + 1;
- }
+ final var result = (long)x + (long)y + (long)z;
- public int height() {
- return maxY - minY + 1;
- }
+ System.out.println("Part 1: " + result);
+ }
- public String toString() {
- final var builder = new StringBuilder();
- builder.append(width()).append('x').append(height()).append('\n');
- for (int i = minX; i <= maxX; i++) {
- final var row = pixels.getOrDefault(i, Collections.emptySortedMap());
- for (int j = minY; j <= maxY; j++) {
- final var value = row.getOrDefault(j, false);
- builder.append(value ? '#' : '.');
- }
- builder.append('\n');
+ @Test
+ public final void part2() {
+ final var numbers = getInput();
+ final var indexMap = new HashMap(numbers.size());
+ Number zero = null;
+ for(final var number : numbers) {
+ indexMap.put(number.originalIndex, number.value());
+ if(number.value() == 0) {
+ zero = number;
}
- return builder.toString();
}
-
- public static Image parse(final List lines) {
- final var pixels = new TreeMap>();
- final int minX = 0;
- final int minY = 0;
- int maxX = 0;
- int maxY = 0;
- for (int i = lines.size(); --i >= 0; ) {
- final var line = lines.get(i);
- final var row = new TreeMap();
- for (int j = line.length(); --j >= 0; ) {
- final var pixel = line.charAt(j);
- row.put(j, pixel == '#');
- if (pixel == '#') {
- row.put(j, true);
- maxY = Math.max(maxY, j);
- }
- }
- if (!row.isEmpty()) {
- maxX = Math.max(maxX, i);
- pixels.put(i, Collections.unmodifiableSortedMap(row));
+ final var workingSet = new ArrayList<>(numbers);
+
+ for(int i = 10; --i >= 0; ) {
+ for (final var number : numbers) {
+ final var originalIndex = workingSet.indexOf(number);
+ workingSet.remove(originalIndex);
+ var newIndex = number.decryptedValue().add(BigInteger.valueOf(originalIndex)).mod(BigInteger.valueOf(numbers.size() - 1)).intValue();
+ if (newIndex < 0) {
+ newIndex += numbers.size() - 1;
}
+ workingSet.add(newIndex, number);
}
- return new Image(Collections.unmodifiableSortedMap(pixels), minX, maxX, minY, maxY, true);
- }
-
- }
-
- @Nested
- public class ImageTest {
- @Test
- public final void testToInt() {
- final var string = """
- #..#.
- #....
- ##..#
- ..#..
- ..###
- """;
- final var image = Image.parse(Arrays.asList(string.split("\n")));
- assertEquals(34, image.decode(2, 2, false));
}
- @Test
- public final void flipAllOn() {
- final var template = "#........";
- final var imageString = """
- ...
- ...
- ...
- """;
- final var image = Image.parse(Arrays.asList(imageString.split("\n")));
- final var result = image.enhance(ImageEnhancementAlgorithm.parse(template));
- assertTrue(result.pixels().get(1).get(1));
- }
+ final var x = workingSet.get((workingSet.indexOf(zero) + 1000) % workingSet.size()).decryptedValue();
+ final var y = workingSet.get((workingSet.indexOf(zero) + 2000) % workingSet.size()).decryptedValue();
+ final var z = workingSet.get((workingSet.indexOf(zero) + 3000) % workingSet.size()).decryptedValue();
- @Test
- public final void turnOffPixel() {
- final var templateBuilder = new StringBuilder();
- for (int i = 511; --i >= 0; templateBuilder.append('#')) ;
- templateBuilder.append('.');
- final var template = templateBuilder.toString();
- final var imageString = """
- ###
- ###
- ###
- """;
- final var image = Image.parse(Arrays.asList(imageString.split("\n")));
- final var result = image.enhance(ImageEnhancementAlgorithm.parse(template));
- final var middleRow = result.pixels().get(1);
- assertFalse(middleRow.containsKey(1));
- }
- }
+ final var result = x.add(y).add(z);
- @Test
- public final void part1() {
- final var list = getInput().toList();
- final var algorithm = ImageEnhancementAlgorithm.parse(list.get(0));
- final var image = Image.parse(list.subList(2, list.size()))
- .enhance(algorithm)
- .enhance(algorithm);
- System.out.println("Part 1: " + image.countLitPixels());
- }
-
- @Test
- public final void part2() {
- final var list = getInput().toList();
- final var algorithm = ImageEnhancementAlgorithm.parse(list.get(0));
- var image = Image.parse(list.subList(2, list.size()));
- for(int _i = 50; --_i >= 0; image = image.enhance(algorithm));
- System.out.println("Part 2: " + image.countLitPixels());
+ System.out.println("Part 2: " + result);
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day21.java b/src/test/java/com/macasaet/Day21.java
index 1223048..b2fbfc8 100644
--- a/src/test/java/com/macasaet/Day21.java
+++ b/src/test/java/com/macasaet/Day21.java
@@ -1,159 +1,323 @@
package com.macasaet;
-import java.math.BigInteger;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.junit.jupiter.api.Test;
-
/**
- * --- Day 21: Dirac Dice ---
+ * --- Day 21: Monkey Math ---
+ * https://adventofcode.com/2022/day/21
*/
public class Day21 {
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-21.txt"),
- false);
+ public record Monkey(String name, Job job) {
+ public long yell(final Map monkeys, final Map results) {
+ return job().yell(monkeys, results);
+ }
+
+ public Simplification simplify(final Map monkeys, final Map results) {
+ return job().simplify(monkeys, results);
+ }
+
+ public static Monkey parse(final String line) {
+ final var components = line.split(": ");
+ final var name = components[0].trim();
+ final var job = Job.parse(components[1].trim());
+ return new Monkey(name, job);
+ }
+ }
+
+ public interface Simplification {
+ Simplification simplify();
}
- public static class DeterministicDie {
- int value;
- int totalRolls = 0;
+ record Expression(Simplification x, Operation operation, Simplification y) implements Simplification {
- protected DeterministicDie(final int startingValue) {
- this.value = startingValue;
+ public Simplification simplify() {
+ final var simpleX = x().simplify();
+ final var simpleY = y().simplify();
+ if (!x().equals(simpleX) || !y().equals(simpleY)) {
+ return new Expression(simpleX, operation(), simpleY);
+ } else if (x() instanceof Value && y() instanceof Value) {
+ return new Value(operation().operate(((Value) x).value(), ((Value) y).value()));
+ } else if (operation() == Operation.IS_EQUAL) {
+ if (x() instanceof final Value constant && y() instanceof final Expression expression) {
+ // e.g. 5=2/x or 5=x/2
+ final var inverse = expression.operation().inverse();
+ if (expression.x() instanceof Value) {
+ // e.g. 5=2/x
+ if (expression.operation.isSymmetric()) {
+ // e.g. 2=5x -> 10=x
+ final var lValue = new Expression(constant, inverse, expression.x()).simplify();
+ return new Expression(lValue, Operation.IS_EQUAL, expression.y());
+ } else {
+ // e.g. 5=2/x -> 5x=2
+ final var lValue = new Expression(constant, inverse, expression.y());
+ return new Expression(lValue, Operation.IS_EQUAL, expression.x());
+ }
+ } else if (expression.y() instanceof Value) {
+ // e.g. 5=x/2 -> 5*2=x -> 10=x
+ final var lValue = new Expression(constant, inverse, expression.y()).simplify();
+ return new Expression(lValue, Operation.IS_EQUAL, expression.x());
+ }
+ // cannot simplify further
+ return this;
+ } else if (x() instanceof final Expression expression && y() instanceof final Value constant) {
+ // e.g. 5/x=2 or x/5=2
+ final var inverse = expression.operation().inverse();
+ if (expression.x() instanceof Value) {
+ // e.g. 5/x=2 or x/5=2
+ if (expression.operation().isSymmetric()) {
+ // e.g. 2x=5 -> x=5*2 -> x=10
+ final var rValue = new Expression(constant, inverse, expression.x()).simplify();
+ return new Expression(expression.y(), Operation.IS_EQUAL, rValue);
+ } else {
+ // e.g. 5/x=2 -> 5=2x
+ final var rValue = new Expression(constant, inverse, expression.y());
+ return new Expression(expression.x(), Operation.IS_EQUAL, rValue);
+ }
+ } else if (expression.y() instanceof Value) {
+ // e.g. x/5=2 -> x=2*5 -> x=10
+ final var rValue = new Expression(constant, inverse, expression.y()).simplify();
+ return new Expression(expression.x(), Operation.IS_EQUAL, rValue);
+ }
+ // cannot simplify further
+ return this;
+ }
+ }
+ return this;
}
- public DeterministicDie() {
- this(1);
+ public String toString() {
+ return "(" + x() + ") " + operation() + " (" + y() + ")";
}
+ }
- public int roll() {
- final int result = value;
- value += 1;
- if (value > 100) {
- value -= 100;
- }
- totalRolls++;
- return result;
+ record Value(long value) implements Simplification {
+ public String toString() {
+ return "" + value();
+ }
+
+ public Simplification simplify() {
+ return this;
}
}
- public record Pawn(int position, int score) {
- public Pawn fork() {
- return new Pawn(position(), score());
+ record Variable() implements Simplification {
+ public String toString() {
+ return "x";
+ }
+
+ public Simplification simplify() {
+ return this;
}
+ }
+
+ public interface Job {
+ long yell(final Map monkeys, final Map results);
+
+ Simplification simplify(final Map monkeys, final Map results);
- public Pawn move(final int distance) {
- int newPosition = (position() + distance) % 10;
- if (newPosition == 0) {
- newPosition = 10;
+ static Job parse(final String string) {
+ final var components = string.trim().split(" ");
+ if (components.length == 1) {
+ return Yell.parse(string.trim());
}
- final int newScore = score() + newPosition;
- return new Pawn(newPosition, newScore);
+ return Math.parse(components);
}
+ }
+
+ public enum Operation {
+ Add {
+ public long operate(final long x, final long y) {
+ return x + y;
+ }
+
+ public Operation inverse() {
+ return Subtract;
+ }
+
+ public boolean isSymmetric() {
+ return true;
+ }
+ },
+ Subtract {
+ public long operate(final long x, final long y) {
+ return x - y;
+ }
- public Pawn takeTurn(final DeterministicDie die) {
- final int distance = die.roll() + die.roll() + die.roll();
- return move(distance);
+ public Operation inverse() {
+ return Add;
+ }
+
+ public boolean isSymmetric() {
+ return false;
+ }
+ },
+ Multiply {
+ public long operate(final long x, final long y) {
+ return x * y;
+ }
+
+ public Operation inverse() {
+ return Divide;
+ }
+
+ public boolean isSymmetric() {
+ return true;
+ }
+ },
+ Divide {
+ public long operate(final long x, final long y) {
+ return x / y;
+ }
+
+ public Operation inverse() {
+ return Multiply;
+ }
+
+ public boolean isSymmetric() {
+ return false;
+ }
+ },
+ IS_EQUAL {
+ public long operate(final long x, final long y) {
+ // what a horrible hack, who would do this?
+ return x == y ? 1L : 0L;
+ }
+
+ public Operation inverse() {
+ return IS_EQUAL;
+ }
+
+ public boolean isSymmetric() {
+ return true;
+ }
+ };
+
+ public abstract long operate(long x, long y);
+
+ public abstract Operation inverse();
+
+ public abstract boolean isSymmetric();
+
+ public String toString() {
+ return switch (this) {
+ case Add -> "+";
+ case Subtract -> "-";
+ case Multiply -> "*";
+ case Divide -> "/";
+ case IS_EQUAL -> "=";
+ };
+ }
+
+ public static Operation parse(final String operator) {
+ return switch (operator.trim()) {
+ case "+" -> Add;
+ case "-" -> Subtract;
+ case "*" -> Multiply;
+ case "/" -> Divide;
+ default -> throw new IllegalArgumentException("Invalid operator: " + operator);
+ };
}
}
- public record Game(Pawn player1, Pawn player2, boolean playerOnesTurn) {
+ public record Unknown() implements Job {
+ public long yell(Map monkeys, Map results) {
+ throw new UnsupportedOperationException(); // Oof
+ }
+ public Simplification simplify(Map monkeys, Map results) {
+ return new Variable();
+ }
}
- public record ScoreCard(BigInteger playerOneWins, BigInteger playerTwoWins) {
- public ScoreCard add(final ScoreCard other) {
- return new ScoreCard(playerOneWins().add(other.playerOneWins()), playerTwoWins().add(other.playerTwoWins()));
+ record Yell(long number) implements Job {
+ public long yell(Map monkeys, Map results) {
+ return number;
+ }
+
+ public Simplification simplify(Map monkeys, Map results) {
+ return new Value(number());
+ }
+
+ public static Yell parse(final String string) {
+ return new Yell(Integer.parseInt(string));
}
}
- public static class QuantumDie {
- private final Map cache = new HashMap<>();
-
- public ScoreCard play(final Game game) {
- if (cache.containsKey(game)) {
- return cache.get(game);
- }
- final var reverseScenario = new Game(game.player2(), game.player1(), !game.playerOnesTurn());
- if (cache.containsKey(reverseScenario)) {
- final var reverseResult = cache.get(reverseScenario);
- return new ScoreCard(reverseResult.playerTwoWins(), reverseResult.playerOneWins());
- }
-
- if (game.player1().score() >= 21) {
- final var result = new ScoreCard(BigInteger.ONE, BigInteger.ZERO);
- cache.put(game, result);
- return result;
- } else if (game.player2().score() >= 21) {
- final var result = new ScoreCard(BigInteger.ZERO, BigInteger.ONE);
- cache.put(game, result);
- return result;
- }
-
- var result = new ScoreCard(BigInteger.ZERO, BigInteger.ZERO);
- for (int i = 1; i <= 3; i++) {
- for (int j = 1; j <= 3; j++) {
- for (int k = 1; k <= 3; k++) {
- final int movementDistance = i + j + k;
- final var forkResult = game.playerOnesTurn()
- ? play(new Game(game.player1().move(movementDistance), game.player2(), false))
- : play(new Game(game.player1(), game.player2().fork().move(movementDistance), true));
- result = result.add(forkResult);
- }
- }
+ public record Math(String monkeyX, Operation operation, String monkeyY) implements Job {
+
+ public long yell(Map monkeys, Map results) {
+ final var x = getVariable(monkeyX(), monkeys, results);
+ final var y = getVariable(monkeyY(), monkeys, results);
+ return operation().operate(x, y);
+ }
+
+ public Simplification simplify(Map monkeys, Map results) {
+ final var x = monkeys.get(monkeyX()).simplify(monkeys, results);
+ final var y = monkeys.get(monkeyY()).simplify(monkeys, results);
+ if (x instanceof final Value xValue && y instanceof final Value yValue) {
+ return new Value(operation().operate(xValue.value(), yValue.value()));
+ }
+ return new Expression(x, operation(), y);
+ }
+
+ long getVariable(final String monkeyName, final Map monkeys, final Map results) {
+ if (results.containsKey(monkeyName)) {
+ return results.get(monkeyName);
}
- cache.put(game, result);
+ final var result = monkeys.get(monkeyName).yell(monkeys, results);
+ results.put(monkeyName, result);
return result;
}
+
+ public static Math parse(final String[] components) {
+ final var monkeyX = components[0].trim();
+ final var operation = Operation.parse(components[1].trim());
+ final var monkeyY = components[2].trim();
+ return new Math(monkeyX, operation, monkeyY);
+ }
+ }
+
+ protected static Map getInput() {
+ final Map result = new HashMap<>();
+ StreamSupport.stream(new LineSpliterator("day-21.txt"), false)
+ .map(Monkey::parse)
+ .forEach(monkey -> result.put(monkey.name(), monkey));
+ return Collections.unmodifiableMap(result);
}
@Test
public final void part1() {
- final var lines = getInput().toList();
- final int playerOnePosition =
- Integer.parseInt(lines.get(0).replaceAll("Player . starting position: ", ""));
- final int playerTwoPosition =
- Integer.parseInt(lines.get(1).replaceAll("Player . starting position: ", ""));
- var playerOne = new Pawn(playerOnePosition, 0);
- var playerTwo = new Pawn(playerTwoPosition, 0);
- final var die = new DeterministicDie();
- while (true) {
- // player 1
- playerOne = playerOne.takeTurn(die);
- if (playerOne.score() >= 1000) {
- break;
- }
-
- // player 2
- playerTwo = playerTwo.takeTurn(die);
- if (playerTwo.score() >= 1000) {
- break;
- }
- }
- int losingScore = Math.min(playerOne.score(), playerTwo.score());
+ final var monkeys = getInput();
+ final var results = new HashMap();
+ final var result = monkeys.get("root").yell(monkeys, results);
- System.out.println("Part 1: " + (losingScore * die.totalRolls));
+ System.out.println("Part 1: " + result);
}
@Test
public final void part2() {
- final var lines = getInput().toList();
- final int playerOnePosition =
- Integer.parseInt(lines.get(0).replaceAll("Player . starting position: ", ""));
- final int playerTwoPosition =
- Integer.parseInt(lines.get(1).replaceAll("Player . starting position: ", ""));
- final var playerOne = new Pawn(playerOnePosition, 0);
- final var playerTwo = new Pawn(playerTwoPosition, 0);
- final var die = new QuantumDie();
- final var game = new Game(playerOne, playerTwo, true);
- final var result = die.play(game);
- final var winningScore = result.playerOneWins().max(result.playerTwoWins());
- System.out.println("Part 2: " + winningScore);
+ final var monkeys = new HashMap<>(getInput());
+ final var results = new HashMap();
+ final var oldRoot = monkeys.get("root");
+ final var oldJob = (Math) oldRoot.job();
+ monkeys.put("root", new Monkey("root", new Math(oldJob.monkeyX(), Operation.IS_EQUAL, oldJob.monkeyY())));
+ monkeys.put("humn", new Monkey("humn", new Unknown()));
+
+ var simplification = monkeys.get("root").simplify(monkeys, results);
+ while (true) {
+ final var candidate = simplification.simplify();
+ if (candidate.equals(simplification)) {
+ break;
+ }
+ simplification = candidate;
+ }
+ System.out.println("Part 2: " + simplification);
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day22.java b/src/test/java/com/macasaet/Day22.java
deleted file mode 100644
index 333be59..0000000
--- a/src/test/java/com/macasaet/Day22.java
+++ /dev/null
@@ -1,202 +0,0 @@
-package com.macasaet;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.math.BigInteger;
-import java.util.*;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-
-/**
- * --- Day 22: Reactor Reboot ---
- */
-public class Day22 {
-
- protected Stream getInput() {
- return StreamSupport
- .stream(new LineSpliterator("day-22.txt"),
- false);
- }
-
- public record ReactorCore(
- SortedMap>> cubes) {
-
- public long count(final long xMin, final long xMax, final long yMin, final long yMax, final long zMin, final long zMax) {
- long sum = 0;
- for (var i = xMin; i <= xMax; i++) {
- final var xDimension = cubes().getOrDefault(i, Collections.emptySortedMap());
- for (var j = yMin; j <= yMax; j++) {
- final var yDimension = xDimension.getOrDefault(j, Collections.emptySortedMap());
- for (var k = zMin; k <= zMax; k++) {
- if (yDimension.getOrDefault(k, false)) {
- sum++;
- }
- }
- }
- }
- return sum;
- }
-
- public void process(final Instruction instruction) {
- final var block = instruction.block();
- final var on = instruction.on();
- for (var i = block.xMin(); i <= block.xMax(); i++) {
- final var xDimension = cubes().computeIfAbsent(i, _key -> new TreeMap<>());
- for (var j = block.yMin(); j <= block.yMax(); j++) {
- final var yDimension = xDimension.computeIfAbsent(j, _key -> new TreeMap<>());
- for (var k = block.zMin(); k <= block.zMax(); k++) {
- yDimension.put(k, on);
- }
- }
- }
- }
-
- }
-
- public record Instruction(boolean on, Block block) {
- public static Instruction parse(final String string) {
- final var components = string.split(" ");
- final boolean on = "on".equalsIgnoreCase(components[0]);
- final var block = Block.parse(components[1]);
- return new Instruction(on, block);
- }
-
- }
-
- public record Block(long xMin, long xMax, long yMin, long yMax, long zMin, long zMax) {
-
- public BigInteger volume() {
- return (BigInteger.valueOf(xMax).subtract(BigInteger.valueOf(xMin)).add(BigInteger.ONE))
- .multiply(BigInteger.valueOf(yMax).subtract(BigInteger.valueOf(yMin)).add(BigInteger.ONE))
- .multiply(BigInteger.valueOf(zMax).subtract(BigInteger.valueOf(zMin)).add(BigInteger.ONE));
- }
-
- public static Block parse(final String string) {
- final var ranges = string.split(",");
- final var xRange = ranges[0].split("\\.\\.");
- final var xMin = Long.parseLong(xRange[0].replaceAll("x=", ""));
- final var xMax = Long.parseLong((xRange[1]));
- final var yRange = ranges[1].split("\\.\\.");
- final var yMin = Long.parseLong((yRange[0].replaceAll("y=", "")));
- final var yMax = Long.parseLong((yRange[1]));
- final var zRange = ranges[2].split("\\.\\.");
- final var zMin = Long.parseLong((zRange[0].replaceAll("z=", "")));
- final var zMax = Long.parseLong((zRange[1]));
- return new Block(xMin, xMax, yMin, yMax, zMin, zMax);
- }
-
- public boolean overlaps(final Block other) {
- return intersection(other).isPresent();
- }
-
- public Optional intersection(final Block other) {
- if (xMin > other.xMax() || xMax < other.xMin()
- || yMin > other.yMax() || yMax < other.yMin()
- || zMin > other.zMax() || zMax < other.zMin()) {
- return Optional.empty();
- }
- final var result = new Block(Math.max(xMin, other.xMin()), Math.min(xMax, other.xMax()),
- Math.max(yMin, other.yMin()), Math.min(yMax, other.yMax()),
- Math.max(zMin, other.zMin()), Math.min(zMax, other.zMax()));
- return Optional.of(result);
- }
- }
-
- @Nested
- public class BlockTest {
- @Test
- public final void verifyEqualBlocksOverlap() {
- // given
- final var x = new Block(-2, 2, -2, 2, -2, 2);
- final var y = new Block(-2, 2, -2, 2, -2, 2);
-
- // when
-
- // then
- assertTrue(x.overlaps(y));
- assertTrue(y.overlaps(x));
- }
-
- @Test
- public final void verifyNestedBlocksOverlap() {
- final var inner = new Block(-2, 2, -2, 2, -2, 2);
- final var outer = new Block(-4, 4, -4, 4, -4, 4);
-
- assertTrue(inner.overlaps(outer));
- assertTrue(outer.overlaps(inner));
- }
-
- @Test
- public final void verifyIntersectingBlocksOverlap() {
- final var x = new Block(10, 12, 10, 12, 10, 12);
- final var y = new Block(11, 13, 11, 13, 11, 13);
-
- assertTrue(x.overlaps(y));
- assertTrue(y.overlaps(x));
- }
-
- @Test
- public final void testIntersection() {
- final var x = new Block(10, 12, 10, 12, 10, 12);
- final var y = new Block(11, 13, 11, 13, 11, 13);
-
- assertTrue(x.intersection(y).isPresent());
- assertEquals(BigInteger.valueOf(8), x.intersection(y).orElseThrow().volume());
- assertTrue(y.intersection(x).isPresent());
- assertEquals(BigInteger.valueOf(8), y.intersection(x).orElseThrow().volume());
- assertEquals(x.intersection(y).orElseThrow(), y.intersection(x).orElseThrow());
- }
- }
-
- @Test
- public final void part1() {
- final var core = new ReactorCore(new TreeMap<>());
- getInput().map(Instruction::parse).map(fullInstruction -> {
- final var fullBlock = fullInstruction.block();
- final var truncatedBlock = new Block(Math.max(fullBlock.xMin(), -50), Math.min(fullBlock.xMax(), 50),
- Math.max(fullBlock.yMin(), -50), Math.min(fullBlock.yMax(), 50),
- Math.max(fullBlock.zMin(), -50), Math.min(fullBlock.zMax(), 50));
- return new Instruction(fullInstruction.on(), truncatedBlock);
- }).forEach(core::process);
- System.out.println("Part 1: " + core.count(-50, 50, -50, 50, -50, 50));
- }
-
- @Test
- public final void part2() {
- final var originalList = getInput().map(Instruction::parse).toList();
- final var appliedInstructions = new ArrayList