diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ffeb9d9..007257b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,10 +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@v3
+ with:
+ path: ~/.m2
+ key: m2-${{ runner.os }}-19-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ m2-${{ runner.os }}-19
+ m2-${{ runner.os }}
+ m2
- run: mvn clean install
diff --git a/.gitignore b/.gitignore
index 5c56b6a..4d131ae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,7 @@
.settings
advent-of-code.iml
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 6947951..29e9c19 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,12 @@
-# Advent of Code 2020 (Java)
+# Advent of Code 2022 (Java)
+
+This is the code I used to solve the
+[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 ([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 22284a9..fc13d64 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,14 +6,37 @@
com.macasaet
advent-of-code
- 0.2020.0-SNAPSHOT
+ 0.2022.0-SNAPSHOT
- Advent of Code 2020
+ Advent of Code 2022
UTF-8
- 17
- 17
+ 19
+ 19
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.9.0
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.0.0-M5
+
+
+ Day*.java
+
+
+
+
+
+
diff --git a/src/test/java/com/macasaet/Day01.java b/src/test/java/com/macasaet/Day01.java
index 678b200..d70e9ed 100644
--- a/src/test/java/com/macasaet/Day01.java
+++ b/src/test/java/com/macasaet/Day01.java
@@ -1,58 +1,76 @@
package com.macasaet;
-import java.io.IOException;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigInteger;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
import java.util.List;
-import java.util.Spliterator;
import java.util.stream.StreamSupport;
-
+/**
+ * --- Day 1: Calorie Counting ---
+ */
public class Day01 {
- public static void main(String[] args) throws IOException {
- try( var spliterator = new LineSpliterator(Day01.class.getResourceAsStream("/day-1-input.txt")) ) {
-// part1(spliterator);
- part2(spliterator);
- }
+ protected Iterator getInput() {
+ return StreamSupport
+ .stream(new LineSpliterator("day-01.txt"),
+ false)
+ .iterator();
}
- protected static void part1(final Spliterator spliterator) {
- final var items = StreamSupport.stream(spliterator, false)
- .mapToInt(Integer::parseInt)
- .filter(candidate -> candidate <= 2020) // filter out the obvious
- .sorted() // sort to ensure the complement is to the left
- .collect(ArrayList::new, List::add, List::addAll);
- for( int i = items.size(); --i >= 0; ) {
- final int x = items.get(i);
- for( int j = i; --j >= 0; ) { // avoid retrying combinations
- final int y = items.get(j);
- if( x + y == 2020 ) {
- System.out.println( "" + ( x * y ) );
- return;
- }
+ 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);
}
- protected static void part2(final Spliterator spliterator) {
- final var items = StreamSupport.stream(spliterator, false)
- .mapToInt(Integer::parseInt)
- .filter(candidate -> candidate <= 2020) // filter out the obvious
- .sorted() // sort to ensure the complements are to the left
- .collect(ArrayList::new, List::add, List::addAll);
- for( int i = items.size(); --i >= 0; ) {
- final int x = items.get(i);
- for( int j = i; --j >= 0; ) {
- final int y = items.get(j);
- for( int k = j; --k >= 0; ) {
- final int z = items.get(k);
- if( x + y + z == 2020 ) {
- System.out.println( "" + ( x * y * z ) );
- return;
- }
- }
+ @Test
+ public final void part1() {
+ 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 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())));
+ }
+
+ /**
+ * An elf who collects food for the reindeer.
+ *
+ * @param itemCalories The number of calories of each item carried by the elf
+ */
+ public record Elf(List itemCalories) {
+ public BigInteger totalCaloriesCarried() {
+ return itemCalories().stream()
+ .reduce(BigInteger::add)
+ .get();
}
}
-}
+
+}
\ 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 7bfb3d0..424b241 100644
--- a/src/test/java/com/macasaet/Day02.java
+++ b/src/test/java/com/macasaet/Day02.java
@@ -1,91 +1,175 @@
package com.macasaet;
-import java.io.IOException;
-import java.util.regex.Pattern;
+import org.junit.jupiter.api.Test;
+
+import java.util.stream.Stream;
import java.util.stream.StreamSupport;
+/**
+ * --- Day 2: Rock Paper Scissors ---
+ * https://adventofcode.com/2022/day/2
+ */
public class Day02 {
- public static void main(String[] args) throws IOException {
- try( var spliterator = new LineSpliterator( Day02.class.getResourceAsStream("/day-2-input.txt" ) ) ) {
- final long count = StreamSupport.stream(spliterator, false)
-// .map(SledEntry::build) // part 1
- .map(TobogganEntry::build) // part 2
- .filter(Entry::isValid).count();
- System.out.println("" + count);
- }
-
+ 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)));
+ });
}
- public static abstract class Entry {
-
- // TODO consider a more robust pattern:
- // https://stackoverflow.com/a/4731164/914887
- protected static final Pattern separator = Pattern.compile("\\s");
- protected static final Pattern rangeSeparator = Pattern.compile("-");
- protected final char c; // TODO support code points
- protected final String password;
+ @Test
+ public final void part1() {
+ final var result = getInput().mapToInt(Round::naiveScore).sum();
- protected Entry(final char c, final String password) {
- this.c = c;
- this.password = password;
- }
+ System.out.println("Part 1: " + result);
+ }
- public abstract boolean isValid();
+ @Test
+ public final void part2() {
+ final var result = getInput().mapToInt(Round::score).sum();
+ System.out.println("Part 2: " + result);
}
- public static class SledEntry extends Entry {
- private final long minIterations;
- private final long maxIterations;
-
- public SledEntry( final char c, final String password, final long minIterations, final long maxIterations ) {
- super(c, password);
- this.minIterations = minIterations;
- this.maxIterations = maxIterations;
+ /**
+ * 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 Shape beats() {
+ return Scissors;
+ }
+ },
+ Paper {
+ public int score() {
+ return 2;
+ }
+
+ public Shape beatenBy() {
+ return Scissors;
+ }
+
+ public Shape beats() {
+ return Rock;
+ }
+ },
+ Scissors {
+ public int score() {
+ return 3;
+ }
+
+ @Override
+ public Shape beatenBy() {
+ return Rock;
+ }
+
+ public Shape beats() {
+ return Paper;
+ }
+ };
+
+ 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 static Entry build(final String line) {
- final var components = separator.split(line, 3);
- final var range = components[0];
- final var rangeComponents = rangeSeparator.split(range, 2);
- return new SledEntry(components[1].charAt(0),
- components[2],
- Long.parseLong(rangeComponents[ 0 ]),
- Long.parseLong(rangeComponents[ 1 ] ));
- }
+ /**
+ * @return the inherent value of this shape
+ */
+ public abstract int score();
- public boolean isValid() {
- final var count = password.chars()
- .filter(candidate -> (char) candidate == this.c)
- .count();
- return count >= minIterations && count <= maxIterations;
- }
- }
+ /**
+ * @return the shape that beats this one
+ */
+ public abstract Shape beatenBy();
- public static class TobogganEntry extends Entry {
- private final int firstPosition;
- private final int secondPosition;
+ /**
+ * @return the shape this one beats
+ */
+ public abstract Shape beats();
+ }
- public TobogganEntry( final char c, final String password, final int firstPosition, final int secondPosition ) {
- super( c, password );
- this.firstPosition = firstPosition;
- this.secondPosition = secondPosition;
+ /**
+ * 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);
+ };
}
- public static Entry build( final String line ) {
- final var components = separator.split( line, 3 );
- final var positionComponents = rangeSeparator.split( components[ 0 ], 2 );
- return new TobogganEntry( components[ 1 ].charAt( 0 ),
- components[ 2 ],
- Integer.parseInt( positionComponents[ 0 ] ),
- Integer.parseInt( positionComponents[ 1 ] ) );
+ public abstract Shape respond(final Shape opponent);
+ }
+
+ /**
+ * 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();
}
- public boolean isValid() {
- final var x = password.charAt(firstPosition - 1);
- final var y = password.charAt(secondPosition - 1);
- return x == c ^ y == c;
+ /**
+ * @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();
}
}
+
}
\ 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 344fc9f..af6e98a 100644
--- a/src/test/java/com/macasaet/Day03.java
+++ b/src/test/java/com/macasaet/Day03.java
@@ -1,57 +1,117 @@
package com.macasaet;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.util.Arrays;
-import java.util.stream.Collectors;
+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.Set;
+import java.util.stream.Stream;
import java.util.stream.StreamSupport;
+/**
+ * --- Day 3: Rucksack Reörganisation ---
+ * https://adventofcode.com/2022/day/3
+ */
public class Day03 {
- public static void main(String[] args) throws IOException {
- try( var spliterator = new LineSpliterator( Day03.class.getResourceAsStream("/day-3-input.txt" ) ) ) {
- final var rowList = StreamSupport.stream(spliterator, false)
- .map(String::toCharArray)
- .collect(Collectors.toUnmodifiableList());
- final var matrix = new char[rowList.size()][];
- for (int i = rowList.size(); --i >= 0; matrix[i] = rowList.get(i));
-
- final var slopes = new int[][] {
- new int[] { 1, 1 },
- new int[] { 3, 1 }, // uncomment all but this for "part 1"
- new int[] { 5, 1 },
- new int[] { 7, 1 },
- new int[] { 1, 2 },
- };
- final var totalTrees = Arrays.stream(slopes)
- .mapToLong(pair -> {
- final int slopeRight = pair[0];
- final int slopeDown = pair[1];
-
- long numTrees = 0;
- int rowIndex = 0;
- int columnIndex = 0;
-
- do {
- final var row = matrix[rowIndex];
- final var cell = row[columnIndex];
- if (cell == '#') {
- numTrees += 1l;
- }
- rowIndex += slopeDown;
- columnIndex = columnIndex + slopeRight;
- columnIndex = columnIndex % row.length; // "These aren't the only trees, though; due to
- // something you read about once involving
- // arboreal genetics and biome stability, the
- // same pattern repeats to the right many times"
- } while( rowIndex < matrix.length );
- return numTrees;
- }).mapToObj(BigInteger::valueOf) // I wasn't sure how large these (multiplied) values could get, in retrospect, `long` would have been fine
- .reduce(BigInteger::multiply)
- .get();
- System.out.println("" + totalTrees.toString());
+ 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(Rucksack::parse);
+ }
+
+ @Test
+ public final void part1() {
+ final var result = getInput().mapToInt(Rucksack::priority).sum();
+
+ System.out.println("Part 1: " + result);
+ }
+
+ @Test
+ 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);
+ }
+ if (currentGroup.size() == 3) {
+ groups.add(Collections.unmodifiableList(currentGroup));
}
+ final var result = groups.stream().map(this::getBadge).mapToInt(Day03::priority).sum();
+ System.out.println("Part 2: " + result);
}
+ 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;
+ }
+ }
+ 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));
+ }
+
+ public int priority() {
+ final var intersection = new HashSet();
+ for (final char c : firstCompartment) {
+ if (secondCompartment.contains(c)) {
+ intersection.add(c);
+ }
+ }
+ if (intersection.size() != 1) {
+ throw new IllegalStateException("There should only be one common item between compartments");
+ }
+ return Day03.priority(intersection.iterator().next());
+ }
+
+
+ }
}
\ 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 138675b..8b3d13d 100644
--- a/src/test/java/com/macasaet/Day04.java
+++ b/src/test/java/com/macasaet/Day04.java
@@ -1,83 +1,69 @@
package com.macasaet;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Set;
-import java.util.stream.Collectors;
+import org.junit.jupiter.api.Test;
+
+import java.util.stream.Stream;
import java.util.stream.StreamSupport;
+/**
+ * --- Day 4: Camp Cleanup ---
+ * https://adventofcode.com/2022/day/4
+ */
public class Day04 {
- public static void main(String[] args) throws IOException {
- try (var spliterator = new LineSpliterator( Day04.class.getResourceAsStream( "/day-4-input.txt" ) ) ) {
- final var rawLines = StreamSupport.stream(spliterator, false)
- .collect(Collectors.toUnmodifiableList());
- String current = "";
- // collect all the text blocks into entries (separated by empty lines)
- final var entries = new ArrayList();
- for (final var line : rawLines) {
- if (line.isBlank()) {
- if (!current.isBlank()) {
- entries.add(current);
- current = "";
- }
- } else {
- current += " " + line;
- }
- }
- if (!current.isBlank()) {
- entries.add(current);
- }
- int numValid = 0;
- for (final var entry : entries) {
- final var pairs = entry.split("\\s");
- final var map = new HashMap();
- for (final var pair : pairs) {
- if (pair.isBlank()) {
- continue;
- }
- final var components = pair.split(":", 2);
- map.put(components[0].trim(), components[1].trim());
- }
- final var birthYearString = map.get("byr");
- final var issueYearString = map.get("iyr");
- final var expirationYearString = map.get("eyr");
- final var heightString = map.get("hgt");
- final var hairColour = map.get("hcl");
- final var eyeColour = map.get("ecl");
- final var validEyeColours = Set.of("amb", "blu", "brn", "gry", "grn", "hzl", "oth");
- final var passportId = map.get("pid");
- if (birthYearString == null) continue;
- final int birthYear = Integer.parseInt(birthYearString);
- if (birthYear < 1920 || birthYear > 2002) continue;
- if (issueYearString == null) continue;
- final int issueYear = Integer.parseInt(issueYearString);
- if (issueYear < 2010 || issueYear > 2020) continue;
- if (expirationYearString == null) continue;
- final int expirationYear = Integer.parseInt(expirationYearString);
- if (expirationYear < 2020 || expirationYear > 2030) continue;
- if (heightString == null) continue;
- if (heightString.endsWith("cm")) {
- final int centimetres = Integer.parseInt(heightString.replace("cm", ""));
- if (centimetres < 150 || centimetres > 193) continue;
- } else if (heightString.endsWith("in")) {
- final int inches = Integer.parseInt(heightString.replace("in", ""));
- if (inches < 59 || inches > 76) continue;
- } else {
- continue;
- }
- if (hairColour == null) continue;
- if (!hairColour.matches("#[0-9a-f]{6}")) continue;
- if (eyeColour == null) continue;
- if (!validEyeColours.contains(eyeColour)) continue;
- if (passportId == null) continue;
- if (!passportId.matches("[0-9]{9}")) continue;
- numValid++;
- }
- System.out.println(numValid);
+ /**
+ * 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).
+ */
+ public record Elf(int sectionMin, int sectionMax) {
+
+ public boolean fullyContains(final Elf other) {
+ return sectionMin() <= other.sectionMin() && sectionMax() >= other.sectionMax();
+ }
+
+ public static Elf parse(final String string) {
+ final var components = string.split("-");
+ return new Elf(Integer.parseInt(components[0]), Integer.parseInt(components[1]));
+ }
+ }
+
+ /**
+ * 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 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() {
+ return StreamSupport
+ .stream(new LineSpliterator("day-04.txt"),
+ false)
+ .map(Pair::parse);
+ }
+
+ @Test
+ public final void part1() {
+ final var result = getInput().filter(Pair::oneFullyContainsTheOther).count();
+
+ System.out.println("Part 1: " + result);
+ }
+
+ @Test
+ public final void part2() {
+ 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 dcb1d6a..97c43c4 100644
--- a/src/test/java/com/macasaet/Day05.java
+++ b/src/test/java/com/macasaet/Day05.java
@@ -1,55 +1,138 @@
package com.macasaet;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
+import org.junit.jupiter.api.Test;
+
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.stream.Stream;
import java.util.stream.StreamSupport;
+/**
+ * --- Day 5: Supply Stacks ---
+ * https://adventofcode.com/2022/day/5
+ */
public class Day05 {
- public static void main(String[] args) throws IOException {
- try (var spliterator = new LineSpliterator( Day05.class.getResourceAsStream( "/day-5-input.txt" ) ) ) {
- final var seatIds = StreamSupport.stream(spliterator, false)
- .mapToInt(id -> {
- int maxRowExclusive = 128;
- int row = 0;
- for( int i = 0; i < 7; i++ ) {
- final char partition = id.charAt(i);
- if( partition == 'B' ) {
- row = ( ( maxRowExclusive - row ) / 2 ) + row;
- } else if( partition == 'F' ) {
- maxRowExclusive = maxRowExclusive - ( ( maxRowExclusive - row ) / 2 );
- } else {
- throw new IllegalArgumentException("Invalid row partition: " + partition);
- }
+ 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);
+ }
+ 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 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 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() {
+ return StreamSupport
+ .stream(new LineSpliterator("day-05.txt"),
+ false);
+ }
+
+ @Test
+ public final void part1() {
+ 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;
}
- int column = 0;
- int maxColumnExclusive = 8;
- for( int i = 7; i < 10; i++ ) {
- final char half = id.charAt(i);
- if( half == 'R' ) {
- column = ( ( maxColumnExclusive - column ) / 2 ) + column;
- } else if( half == 'L' ) {
- maxColumnExclusive = maxColumnExclusive - ( ( maxColumnExclusive - column ) / 2 );
- } else {
- throw new IllegalArgumentException("Invalid column partition: " + half);
- }
+ }
+ } else {
+ if(line.isBlank()) {
+ continue;
+ }
+ final var instruction = CrateMover9000Instruction.parse(line);
+ instruction.execute(columns);
+ }
+ }
+ 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() {
+ 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;
}
- final int seatId = row * 8 + column;
- return seatId;
- })
- .sorted()
- .collect(ArrayList::new, List::add, List::addAll);
- System.out.println("part 1: " + seatIds.get(seatIds.size() - 1));
- for( int i = seatIds.size(); --i >= 1; ) {
- final int x = seatIds.get( i - 1 );
- final int y = seatIds.get( i );
- if( y - x > 1 ) {
- System.out.println("part 2: " + ( x + 1 ) );
- return;
}
+ } 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());
+ }
+ }
+ final var result = builder.toString();
+
+ System.out.println("Part 2: " + result);
}
diff --git a/src/test/java/com/macasaet/Day06.java b/src/test/java/com/macasaet/Day06.java
index 91a46e6..769fb1b 100644
--- a/src/test/java/com/macasaet/Day06.java
+++ b/src/test/java/com/macasaet/Day06.java
@@ -1,48 +1,55 @@
package com.macasaet;
-import java.io.IOException;
-import java.util.ArrayList;
+import org.junit.jupiter.api.Test;
+
import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.stream.Stream;
import java.util.stream.StreamSupport;
+/**
+ * --- Day 6: ---
+ * https://adventofcode.com/2022/day/6
+ */
public class Day06 {
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day06.class.getResourceAsStream("/day-6-input.txt"))) {
- final var rawLines = StreamSupport.stream(spliterator, false)
- .collect(Collectors.toUnmodifiableList());
- var currentGroup = new ArrayList();
- // collect all the text blocks into entries (separated by empty lines)
- final var groups = new ArrayList>();
- for (final var line : rawLines) {
- if (line.isBlank()) {
- if (currentGroup != null && !currentGroup.isEmpty()) {
- groups.add(currentGroup);
- currentGroup = new ArrayList<>();
- }
- } else {
- currentGroup.add(line);
- }
+ protected Stream getInput() {
+ return StreamSupport
+ .stream(new LineSpliterator("day-06.txt"),
+ false);
+ }
+
+ @Test
+ public final void part1() {
+ 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;
+ }
+ }
+ throw new IllegalStateException();
+ }
+
+ @Test
+ public final void part2() {
+ 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 (currentGroup != null && !currentGroup.isEmpty()) {
- groups.add(currentGroup);
+ if(set.size() >= 14) {
+ final var result = i;
+ System.out.println("Part 2: " + result);
+ return;
}
- final int sum = groups.stream().mapToInt(group -> {
- final var uniqueCharacters = group.stream()
- .flatMapToInt(String::chars)
- .collect(HashSet::new, Set::add, Set::addAll);
- return (int) uniqueCharacters.stream()
- .filter(c -> (int) group.stream()
- .map(String::chars)
- .filter(chars -> chars.anyMatch(answer -> answer == c))
- .count() == group.size())
- .count();
- }).sum();
- System.out.println(sum);
}
+ 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 00c3dc8..5aee1bc 100644
--- a/src/test/java/com/macasaet/Day07.java
+++ b/src/test/java/com/macasaet/Day07.java
@@ -1,75 +1,241 @@
package com.macasaet;
-import java.io.IOException;
-import java.util.Arrays;
+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;
+/**
+ * --- Day 7: ---
+ * https://adventofcode.com/2022/day/7
+ */
public class Day07 {
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day07.class.getResourceAsStream("/day-7-input.txt"))) {
- final var bag = "shiny gold";
- final var ruleMap = StreamSupport.stream(spliterator, false)
- .map(Rule::fromSentence)
- .collect(HashMap::new,
- (map, rule) -> map.put(rule.containerColour, rule),
- Map::putAll);
- final var count = ruleMap.values()
- .stream()
- .filter(rule -> rule.canContain(bag, ruleMap))
- .count();
- System.out.println("part 1: " + count);
- System.out.println("part 2: " + (ruleMap.get(bag).countContained(ruleMap) - 1));
+ 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);
+ }
}
- protected static class Rule {
- final String containerColour;
- final Map containedCounts;
+ 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);
+ }
+ }
- public Rule(final String containerColour, final Map containedCounts) {
- // TODO validation
- this.containerColour = containerColour;
- this.containedCounts = containedCounts;
+ static class ListContents extends Command {
+ void execute(final Session session) {
+ }
+ static Command parse(final String ignored) {
+ return new ListContents();
}
+ }
- public int countContained(final Map ruleMap) {
- return 1 + containedCounts.entrySet().stream().mapToInt(entry -> {
- final var containedColour = entry.getKey();
- final int multiplier = entry.getValue();
+ static class ChangeDirectory extends Command {
+ private final String argument;
- final var subRule = ruleMap.get(containedColour);
- final int base = subRule.countContained(ruleMap);
- return base * multiplier;
- }).sum();
+ public ChangeDirectory(String argument) {
+ this.argument = argument;
}
- public boolean canContain(final String colour, final Map ruleMap) {
- if (containedCounts.containsKey(colour)) {
- return true;
+ 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;
}
- return containedCounts.keySet()
- .stream()
- .map(ruleMap::get)
- .anyMatch(rule -> rule != null && rule.canContain(colour, ruleMap));
}
- public static Rule fromSentence(final String sentence) {
- final var components = sentence.split(" bags contain ", 2);
- if ("no other bags.".equalsIgnoreCase(components[1])) {
- return new Rule(components[0], Collections.emptyMap());
+ static ChangeDirectory parse(final String line) {
+ return new ChangeDirectory(line.split(" ")[2]);
+ }
+ }
+
+ static abstract class Output extends Line {
+ static Output parse(final String line) {
+ if(line.startsWith("dir")) {
+ return DirectoryListing.parse(line);
}
- final var containedPhrases = components[1].split(", ");
- final var containedCounts = Arrays.stream(containedPhrases)
- .map(phrase -> phrase.replaceFirst(" bag.*$", ""))
- .map(phrase -> phrase.split(" ", 2))
- .collect(HashMap::new,
- (map, phraseComponents) -> map.put(phraseComponents[1], Integer.parseInt(phraseComponents[0])),
- Map::putAll);
- return new Rule(components[0], Collections.unmodifiableMap(containedCounts));
+ return LeafListing.parse(line);
}
}
+
+ 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() {
+ 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() {
+ 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);
+ }
+
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day08.java b/src/test/java/com/macasaet/Day08.java
index 290c2c2..98c498e 100644
--- a/src/test/java/com/macasaet/Day08.java
+++ b/src/test/java/com/macasaet/Day08.java
@@ -1,96 +1,161 @@
package com.macasaet;
-import java.io.IOException;
-import java.util.HashSet;
+import org.junit.jupiter.api.Test;
+
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
+/**
+ * --- Day 8: Treetop Tree House ---
+ * https://adventofcode.com/2022/day/8
+ */
public class Day08 {
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day08.class.getResourceAsStream("/day-8-input.txt"))) {
- final var instructions =
- StreamSupport.stream(spliterator, false)
- .map(Instruction::fromLine)
- .collect(Collectors.toUnmodifiableList());
-
- instructions:
- // "exactly one instruction is corrupted" - try them all
- for (int i = instructions.size(); --i >= 0; ) {
- final var toReplace = instructions.get(i);
-
- if (toReplace.operation == Operation.acc) {
- // "No acc instructions were harmed in the corruption of this boot code."
- continue;
- }
- final var opposite = toReplace.operation == Operation.nop ? Operation.jmp : Operation.nop;
- final var replacement = new Instruction(opposite, toReplace.argument);
-
- int total = 0;
- int index = 0;
-
- final var visited = new HashSet();
- // "The program is supposed to terminate by attempting to execute an instruction immediately after the last instruction in the file."
- while (index < instructions.size()) {
- final var instruction = index == i ? replacement : instructions.get(index);
- if (visited.contains(index)) {
- // replacing the instruction causes an infinite loop
- // try replacing a different one
- // NB: Simply re-visiting this instruction is an infinite loop because the argument to each instruction is constant and never dependent on the value of "total"
- continue instructions;
+ 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++;
}
- visited.add(index);
- total = instruction.updateTotal(total);
- index = instruction.updateIndex(index);
}
- System.out.println("part 2: " + total);
- return; // "exactly one instruction is corrupted"
}
+ return result;
}
- }
- public enum Operation {
- acc {
- public int updateTotal(final int previousTotal, final int argument) {
- return previousTotal + argument;
+ 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;
+ }
}
- },
- nop,
- jmp {
- public int updateIndex(final int previousIndex, final int argument) {
- return previousIndex + argument;
+ int southScore = 0;
+ for(int i = x + 1; i < grid().length; i++) {
+ final var height = grid()[i][y];
+ southScore += 1;
+ if(height >= treeHeight) {
+ break;
+ }
}
- };
-
- public int updateIndex(final int previousIndex, final int argument) {
- return previousIndex + 1;
+ int westScore = 0;
+ for(int j = y; --j >= 0; ) {
+ final var height = grid()[x][j];
+ westScore += 1;
+ if(height >= treeHeight) {
+ break;
+ }
+ }
+ 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 northScore * eastScore * southScore * westScore;
}
- public int updateTotal(final int previousTotal, final int argument) {
- return previousTotal;
+ 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;
+ }
+ final int treeHeight = grid()[x][y];
+ if (!isObstructedFromTheNorth(x, y, treeHeight)) {
+ return true;
+ }
+ if (!isObstructedFromTheSouth(x, y, treeHeight)) {
+ return true;
+ }
+ if (!isObstructedFromTheWest(x, y, treeHeight)) {
+ return true;
+ }
+ if (!isObstructedFromTheEast(x, y, treeHeight)) {
+ return true;
+ }
+ return false;
}
- }
- public static class Instruction {
- private final Operation operation;
- private final int argument;
+ 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;
+ }
- public Instruction(final Operation operation, final int argument) {
- this.operation = operation;
- this.argument = argument;
+ 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 Instruction fromLine(final String line) {
- final var components = line.split(" ", 2);
- return new Instruction(Operation.valueOf(components[0]), Integer.parseInt(components[1]));
+ 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;
}
- public int updateIndex(final int previousIndex) {
- return operation.updateIndex(previousIndex, argument);
+ private boolean isObstructedFromTheNorth(int x, int y, int treeHeight) {
+ for(int i = x; --i >= 0; ) {
+ if(grid()[i][y] >= treeHeight) {
+ return true;
+ }
+ }
+ return false;
}
+ }
- public int updateTotal(final int previousTotal) {
- return operation.updateTotal(previousTotal, argument);
+ 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 forest = getInput();
+ final var result = forest.countVisible();
+ System.out.println("Part 1: " + result);
+ }
+
+ @Test
+ public final void part2() {
+ 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);
}
+
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day09.java b/src/test/java/com/macasaet/Day09.java
index ac86ab7..b6bcb00 100644
--- a/src/test/java/com/macasaet/Day09.java
+++ b/src/test/java/com/macasaet/Day09.java
@@ -1,57 +1,169 @@
package com.macasaet;
-import java.io.IOException;
-import java.util.stream.Collectors;
+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;
+/**
+ * --- Day 9: Rope Bridge ---
+ * https://adventofcode.com/2022/day/9
+ */
public class Day09 {
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day09.class.getResourceAsStream("/day-9-input.txt"))) {
- final int preambleSize = 25;
- final var list = StreamSupport.stream(spliterator, false)
- .map(Long::parseLong)
- .collect(Collectors.toUnmodifiableList());
- long invalid = Long.MIN_VALUE;
- outer: for (int i = preambleSize; i < list.size(); i++) {
- final var current = list.get(i);
- final var valid = list
- .subList(i - preambleSize, i)
- .stream()
- .filter(l -> l <= current) // no negative numbers in the input, so filter out anything larger than our target
- .collect(Collectors.toUnmodifiableList());
-
- for (int j = valid.size(); --j >= 1; ) {
- final var x = valid.get(j);
- for (int k = j; --k >= 0; ) {
- final var y = valid.get(k);
- if (x + y == current) {
- continue outer;
- }
- }
- }
- invalid = current;
- System.out.println("Part 1: " + invalid);
- break;
- }
- outer: for (int i = list.size(); --i >= 1;) {
- var total = list.get(i);
- var min = total;
- var max = total;
- for (int j = i; --j >= 0;) {
- final var current = list.get(j);
- if( current < min ) min = current;
- if( current > max ) max = current;
- total += current;
- if (total == invalid) {
- final var result = min + max;
- System.out.println("Part 2: " + result);
- return;
- } else if (total > invalid) { // I can only do this because there are no negative numbers
- continue outer;
- }
+ record Coordinate(int x, int y) {
+
+ 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 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);
+ }
+ }
+
+ public static class Rope {
+
+ Coordinate[] knotCoordinates;
+
+ final SortedMap> visited = new TreeMap<>();
+
+ 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 countVisited() {
+ int result = 0;
+ for( final var map : visited.values() ) {
+ result += map.size();
+ }
+ return result;
+ }
+
+ 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);
}
}
}
+
+ protected void moveKnot(int knotIndex) {
+ if(knotIndex <= 0) {
+ throw new IllegalArgumentException("Cannot move head");
+ }
+ final var leader = knotCoordinates[knotIndex - 1];
+ var follower = knotCoordinates[knotIndex];
+
+ if(leader.equals(follower)) {
+ return;
+ } else if (leader.distance(follower) <= 1) {
+ return;
+ }
+
+ follower = follower.stepTowards(leader);
+ knotCoordinates[knotIndex] = follower;
+
+ if(knotIndex == knotCoordinates.length - 1) {
+ visited.computeIfAbsent(follower.x(), (key) -> new TreeSet<>()).add(follower.y());
+ }
+ }
+
}
+
+ enum Direction {
+ Up {
+ int xStep() {
+ return -1;
+ }
+ int yStep() {
+ return 0;
+ }
+ },
+ Down {
+ int xStep() {
+ return 1;
+ }
+ 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 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 rope = new Rope(10);
+ getInput().forEach(rope::process);
+ final var result = rope.countVisited();
+ System.out.println("Part 2: " + result);
+ }
+
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day10.java b/src/test/java/com/macasaet/Day10.java
index 66a5189..a289ed3 100644
--- a/src/test/java/com/macasaet/Day10.java
+++ b/src/test/java/com/macasaet/Day10.java
@@ -1,80 +1,153 @@
package com.macasaet;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Stream;
import java.util.stream.StreamSupport;
+/**
+ * --- Day 10: Cathode-Ray Tube ---
+ * https://adventofcode.com/2022/day/10
+ */
public class Day10 {
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day10.class.getResourceAsStream("/day-10-input.txt"))) {
- final var adapterJoltages = StreamSupport.stream(spliterator, false)
- .map(Integer::parseInt)
- .sorted() // ensure the next adapter in the chain is always to the right
- .distinct()
- .collect(Collectors.toUnmodifiableList());
- final var targetJoltage = adapterJoltages.get(adapterJoltages.size() - 1) + 3;
-
- int current = 0;
- var distribution = new int[]{0, 0, 1}; // the last chain link has a difference of 3
- while (current < targetJoltage - 3) {
- final var _current = current;
- final var next = adapterJoltages
- .stream()
- .filter(candidate -> candidate - _current >= 1 && candidate - _current <= 3)
- .findFirst()
- .get();
- final var difference = next - current;
- distribution[difference - 1]++;
- current = next;
+ public enum Instruction {
+ noop {
+ public int cycles() {
+ return 0;
+ }
+ },
+ addx {
+ public int cycles() {
+ return 2;
}
- System.out.println("Part 1: " + (distribution[0] * distribution[2]));
- System.out.println("Part 2: " + count(adapterJoltages, 0, targetJoltage));
+ };
+
+ public abstract int cycles();
+
+ public static Instruction parse(final String string) {
+ return Instruction.valueOf(string);
}
}
- private static final Map cache = new HashMap<>();
+ 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));
+ };
+ }
- protected static int hash(final Collection adapters, final int from, final int to) {
- int retval = 0;
- retval = 31 * retval + from;
- retval = 31 * retval + to;
- retval = 31 * retval + adapters.hashCode();
- return retval;
+ 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);
+ }
}
- protected static long count(final List adapters, final int from, final int to) {
- final var hash = hash(adapters, from, to);
- if (cache.containsKey(hash)) {
- return cache.get(hash);
+ public record CycleSnapshot(int cycle, int register) {
+ public int signalStrength() {
+ return cycle() * register();
+ }
+ }
+
+ public static class State {
+ private int register = 1;
+ private int cycle = 1;
+
+ public List getActivePixels() {
+ return Arrays.asList(register - 1, register, register + 1);
}
- if (adapters.isEmpty()) {
- return from + 3 == to ? 1 : 0;
- } else if (adapters.size() == 1) {
- final int single = adapters.get(0);
- return single == to - 3 ? 1 : 0;
+ 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][];
- long retval = 0;
- for (int i = 3; --i >= 0; ) {
- if (i >= adapters.size()) continue;
- final int first = adapters.get(i);
- final int difference = first - from;
- if (difference >= 1 && difference <= 3) {
- final var remaining =
- i < adapters.size()
- ? adapters.subList(i + 1, adapters.size())
- : new ArrayList();
- retval += count(remaining, first, to);
+ {
+ 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] = '.';
}
}
- cache.put(hash, retval);
- return retval;
+ 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();
+ }
+ }
+
+ protected Stream getInput() {
+ return StreamSupport
+ .stream(new LineSpliterator("day-10.txt"),
+ false)
+ .map(Operation::parse);
}
+
+ @Test
+ public final void part1() {
+ 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 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 a74e3e9..45db617 100644
--- a/src/test/java/com/macasaet/Day11.java
+++ b/src/test/java/com/macasaet/Day11.java
@@ -1,277 +1,189 @@
package com.macasaet;
-import java.io.IOException;
+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.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;
+/**
+ * --- Day 11: Monkey in the Middle ---
+ * https://adventofcode.com/2022/day/11
+ */
public class Day11 {
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day11.class.getResourceAsStream("/day-11-input.txt"))) {
- final var list = StreamSupport.stream(spliterator, false).map(String::toCharArray).collect(Collectors.toList());
- final var original = list.toArray(new char[list.size()][]);
- var current = copy(original);
- while (true) {
- final var transformed = transform(current);
- if (areEqual(current, transformed)) {
- break;
- }
- current = transformed;
+ public enum Operator implements BiFunction {
+ ADD {
+ public BigInteger apply(BigInteger x, BigInteger y) {
+ return x.add(y);
}
- System.out.println( "Part 1: " + countOccupiedSeats(current));
- current = copy(original);
- while( true ) {
- final var transformed = transform2(current);
- if (areEqual(current, transformed)) {
- break;
- }
- current = transformed;
+ },
+ MULTIPLY {
+ public BigInteger apply(BigInteger x, BigInteger y) {
+ return x.multiply(y);
}
- System.out.println( "Part 2: " + countOccupiedSeats(current));
- }
- }
+ };
- protected static char[][] copy(final char[][] original) {
- final char[][] retval = new char[ original.length ][];
- for( int i = original.length; --i >= 0; ) {
- final var row = original[ i ];
- final var copiedRow = new char[ row.length ];
- System.arraycopy(row, 0, copiedRow, 0, row.length);
- retval[ i ] = copiedRow;
+ public static Operator parse(final String string) {
+ return switch(string) {
+ case "*" -> MULTIPLY;
+ case "+" -> ADD;
+ default -> throw new IllegalArgumentException("Invalid operator: " + string);
+ };
}
- return retval;
}
- protected static char[][] transform2(final char[][] source) {
- final char[][] retval = new char[source.length][];
- for (int i = retval.length; --i >= 0; retval[i] = new char[source[0].length]) ;
- rows: for (int i = source.length; --i >= 0; ) {
- final var row = source[i];
- columns: for (int j = row.length; --j >= 0; ) {
- final var original = source[ i ][ j ];
- retval[ i ][ j ] = original;
- if( original == '.' ) {
- continue;
- } else if( original == 'L' ) {
- // North
- for( int x = i; --x >= 0; ) {
- final var visibleSeat = source[ x ][ j ];
- if( visibleSeat == 'L' ) break;
- if( visibleSeat == '#' ) continue columns;
- }
- // North-West
- for( int x = i, y = j; --x >= 0 && -- y >= 0; ) {
- final var visibleSeat = source[ x ][ y ];
- if( visibleSeat == 'L' ) break;
- if( visibleSeat == '#' ) continue columns;
- }
- // West
- for( int y = j; --y >= 0; ) {
- final var visibleSeat = source[ i ][ y ];
- if( visibleSeat == 'L' ) break;
- if( visibleSeat == '#' ) continue columns;
- }
- // South-West
- for( int x = i + 1, y = j - 1; x < source.length && y >= 0; x++, y-- ) {
- final var visibleSeat = source[ x ][ y ];
- if( visibleSeat == 'L' ) break;
- if( visibleSeat == '#' ) continue columns;
- }
- // South
- for( int x = i + 1; x < source.length; x++ ) {
- final var visibleSeat = source[ x ][ j ];
- if( visibleSeat == 'L' ) break;
- if( visibleSeat == '#' ) continue columns;
- }
- // South-East
- for( int x = i + 1, y = j + 1; x < source.length && y < row.length; x++, y++ ) {
- final var visibleSeat = source[ x ][ y ];
- if( visibleSeat == 'L' ) break;
- if( visibleSeat == '#' ) continue columns;
- }
- // East
- for( int y = j + 1; y < row.length; y++ ) {
- final var visibleSeat = source[ i ][ y ];
- if( visibleSeat == 'L' ) break;
- if( visibleSeat == '#' ) continue columns;
- }
- // North-East
- for( int x = i - 1, y = j + 1; x >= 0 && y < row.length; x--, y++ ) {
- final var visibleSeat = source[ x ][ y ];
- if( visibleSeat == 'L' ) break;
- if( visibleSeat == '#' ) continue columns;
- }
- retval[ i ][ j ] = '#';
- } else if( original == '#' ) {
- int visibleNeighbours = 0;
- // North
- for( int x = i; --x >= 0 && visibleNeighbours < 5; ) {
- final var visibleSeat = source[x][j];
- if( visibleSeat == '#' ) {
- visibleNeighbours++;
- break;
- } else if( visibleSeat == 'L' ) {
- break;
- }
- }
- // North-West
- for( int x = i, y = j; --x >= 0 && -- y >= 0 && visibleNeighbours < 5; ) {
- final var visibleSeat = source[ x ][ y ];
- if( visibleSeat == '#' ) {
- visibleNeighbours++;
- break;
- } else if( visibleSeat == 'L' ) {
- break;
- }
- }
- // West
- for( int y = j; --y >= 0 && visibleNeighbours < 5; ) {
- final var visibleSeat = source[i][y];
- if( visibleSeat == '#' ) {
- visibleNeighbours++;
- break;
- } else if( visibleSeat == 'L' ) {
- break;
- }
- }
- // South-West
- for( int x = i + 1, y = j - 1; x < source.length && y >= 0 && visibleNeighbours < 5; x++, y-- ) {
- final var visibleSeat = source[x][y];
- if( visibleSeat == '#' ) {
- visibleNeighbours++;
- break;
- } else if( visibleSeat == 'L' ) {
- break;
- }
- }
- // South
- for( int x = i + 1; x < source.length && visibleNeighbours < 5; x++ ) {
- final var visibleSeat = source[x][j];
- if( visibleSeat == '#' ) {
- visibleNeighbours++;
- break;
- } else if( visibleSeat == 'L' ) {
- break;
- }
- }
- // South-East
- for( int x = i + 1, y = j + 1; x < source.length && y < row.length && visibleNeighbours < 5; x++, y++ ) {
- final var visibleSeat = source[x][y];
- if( visibleSeat == '#' ) {
- visibleNeighbours++;
- break;
- } else if( visibleSeat == 'L' ) {
- break;
- }
- }
- // East
- for( int y = j + 1; y < row.length && visibleNeighbours < 5; y++ ) {
- final var visibleSeat = source[i][y];
- if( visibleSeat == '#' ) {
- visibleNeighbours++;
- break;
- } else if( visibleSeat == 'L' ) {
- break;
- }
- }
- // North-East
- for( int x = i - 1, y = j + 1; x >= 0 && y < row.length && visibleNeighbours < 5; x--, y++ ) {
- final var visibleSeat = source[x][y];
- if( visibleSeat == '#' ) {
- visibleNeighbours++;
- break;
- } else if( visibleSeat == 'L' ) {
- break;
- }
- }
- if( visibleNeighbours >= 5 ) {
- retval[ i ][ j ] = 'L';
- }
- } else {
- throw new IllegalArgumentException("Unsupported char: " + original);
- }
+ 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);
}
- return retval;
- }
- protected static int countOccupiedSeats(final char[][] matrix) {
- int retval = 0;
- for( int i = matrix.length; --i >= 0; ) {
- final var row = matrix[ i ];
- for( int j = row.length; --j >= 0; ) {
- if( matrix[ i ][ j ] == '#' ) retval++;
+ 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 retval;
}
- protected static void print(final char[][] matrix) {
- for (int i = 0; i < matrix.length; i++) {
- final var row = matrix[i];
- System.err.println(new String(row));
+ /**
+ * 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 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));
}
- }
- protected static char[][] transform(final char[][] source) {
- final char[][] retval = new char[source.length][];
- for (int i = retval.length; --i >= 0; retval[i] = new char[source[0].length]) ;
- for (int i = source.length; --i >= 0; ) {
- final var row = source[i];
- for (int j = row.length; --j >= 0; ) {
- process(i, j, source, retval);
- }
+ public BigInteger countItemsInspected() {
+ return itemsInspected.get();
}
- return retval;
- }
- protected static boolean areEqual(final char[][] x, final char[][] y) {
- for (int i = x.length; --i >= 0; ) {
- final var row = x[i];
- for (int j = row.length; --j >= 0; ) {
- if (x[i][j] != y[i][j]) {
- return false;
- }
+ public Throw inspectItem(BigInteger reliefFactor) {
+ // this assumes monkeys can throw items to themselves
+ if(items.isEmpty()) {
+ return null;
}
+ 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);
}
- return true;
- }
- protected static void process(final int x, final int y, final char[][] source, final char[][] target) {
- final var original = source[x][y];
- if (original == '.') {
- target[x][y] = '.';
- return;
- } else if (original == 'L') {
- target[x][y] = !isOccupied(x - 1, y - 1, source) && !isOccupied(x - 1, y, source)
- && !isOccupied(x - 1, y + 1, source) && !isOccupied(x, y - 1, source) && !isOccupied(x, y + 1, source)
- && !isOccupied(x + 1, y - 1, source) && !isOccupied(x + 1, y, source) && !isOccupied(x + 1, y + 1, source) ? '#' : original;
- } else if (original == '#') {
- int occupiedNeighbors = 0;
- occupiedNeighbors += isOccupied(x - 1, y - 1, source) ? 1 : 0;
- occupiedNeighbors += isOccupied(x - 1, y, source) ? 1 : 0;
- occupiedNeighbors += isOccupied(x - 1, y + 1, source) ? 1 : 0;
- occupiedNeighbors += isOccupied(x, y - 1, source) ? 1 : 0;
- occupiedNeighbors += isOccupied(x, y + 1, source) ? 1 : 0;
- occupiedNeighbors += isOccupied(x + 1, y - 1, source) ? 1 : 0;
- occupiedNeighbors += isOccupied(x + 1, y, source) ? 1 : 0;
- occupiedNeighbors += isOccupied(x + 1, y + 1, source) ? 1 : 0;
- target[x][y] = occupiedNeighbors >= 4 ? 'L' : original;
- } else {
- throw new IllegalArgumentException("Unsupported char: " + original);
+ 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;
}
+
+ }
+
+ public record Throw(int target, BigInteger itemWorryLevel) {
+ }
+
+ 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();
}
- protected static boolean isOccupied(final int x, final int y, final char[][] matrix) {
- if (x < 0 || y < 0 || x >= matrix.length) {
- return false;
+ @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());
+ }
+ }
}
- final char[] row = matrix[x];
- if (y >= row.length) {
- return false;
+ 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 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());
+ }
+ }
}
- return row[y] == '#';
+ 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 c494842..3257259 100644
--- a/src/test/java/com/macasaet/Day12.java
+++ b/src/test/java/com/macasaet/Day12.java
@@ -1,200 +1,164 @@
package com.macasaet;
-import java.io.IOException;
-import java.util.stream.Collectors;
+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: Hill Climbing Algorithm ---
+ * https://adventofcode.com/2022/day/12
+ */
public class Day12 {
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day12.class.getResourceAsStream("/day-12-input.txt"))) {
- // 0, 360 % 360 = North
- // 90 = East
- // 180 = South
- // 270 = West
- int direction = 90;
- int x = 0; // positive means East, negative means West
- int y = 0; // positive means North, negative means South
-
-
- final var actions = StreamSupport.stream(spliterator, false)
- .map(Action::fromLine)
- .collect(Collectors.toUnmodifiableList());
- for (final var action : actions) {
- switch (action.actionCode) {
- case 'N':
- y += action.value;
- break;
- case 'S':
- y -= action.value;
- break;
- case 'E':
- x += action.value;
- break;
- case 'W':
- x -= action.value;
- break;
- case 'L':
- final int relativeDirection = direction - action.value;
- direction = relativeDirection < 0 ? relativeDirection + 360 : relativeDirection;
- break;
- case 'R':
- direction = (direction + action.value) % 360;
- break;
- case 'F':
- switch (direction) {
- case 0:
- y += action.value;
- break;
- case 90:
- x += action.value;
- break;
- case 180:
- y -= action.value;
- break;
- case 270:
- x -= action.value;
- break;
- default:
- throw new IllegalStateException("Unhandled direction: " + direction);
+ record Coordinate(int x, int y) {
+ }
+
+ public record HeightMap(int[][] grid, Coordinate start, Coordinate end) {
+
+ public int lengthOfShortestPath() {
+ return this.lengthOfShortestPath(this.start);
+ }
+
+ 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);
+ }
}
- break;
- default:
- throw new IllegalArgumentException("Invalid action: " + action.actionCode);
+ }
}
+ return Integer.MAX_VALUE;
}
- System.out.println("Part 1: " + (Math.abs(x) + Math.abs(y)));
-
- // waypoint coordinates
- int wx = 10; // positive means North, negative means South
- int wy = 1; // positive means East, negative means West
-
- // ship coordinates
- int sx = 0;
- int sy = 0;
-
- for (final var action : actions) {
- int xDiff = wx - sx;
- int yDiff = wy - sy;
- switch (action.actionCode) {
- case 'N':
- wy += action.value;
- break;
- case 'S':
- wy -= action.value;
- break;
- case 'E':
- wx += action.value;
- break;
- case 'W':
- wx -= action.value;
- break;
- case 'L':
- for (int i = action.value / 90; --i >= 0; ) {
- wx = sx - yDiff;
- wy = sy + xDiff;
- xDiff = wx - sx;
- yDiff = wy - sy;
- }
- break;
- case 'R':
- for (int i = action.value / 90; --i >= 0; ) {
- wx = sx + yDiff;
- wy = sy - xDiff;
- xDiff = wx - sx;
- yDiff = wy - sy;
+
+ 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));
}
- break;
- case 'F':
- sx += xDiff * action.value;
- sy += yDiff * action.value;
- wx = sx + xDiff;
- wy = sy + yDiff;
- break;
- default:
- throw new IllegalArgumentException("Unsupported argument: " + action.actionCode);
+ }
}
+ return Collections.unmodifiableList(list);
+ }
+
+ 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);
}
- System.out.println("Part 2: " + (Math.abs(sx) + Math.abs(sy)));
- }
- }
- protected static class Action {
- final char actionCode;
- final int value;
+ 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));
+ }
- public Action(final char actionCode, final int value) {
- this.actionCode = actionCode;
- this.value = value;
}
- public static Action fromLine(final String line) {
- final var direction = line.charAt(0);
- final var valueString = line.substring(1);
- final int value = Integer.parseInt(valueString);
- return new Action(direction, value);
+ 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 {
+ grid[i][j] = c - 'a';
+ }
+ }
}
+ Objects.requireNonNull(origin);
+ Objects.requireNonNull(destination);
+ return new HeightMap(grid, origin, destination);
+ }
-// public SimpleNavState navigate(final SimpleNavState current) {
-// switch (actionCode) {
-// case 'N':
-// return new SimpleNavState(current.x, current.y + value, actionCode);
-// case 'S':
-// return new SimpleNavState(current.x, current.y - value, actionCode);
-// case 'E':
-// return new SimpleNavState(current.x + value, current.y, actionCode);
-// case 'W':
-// return new SimpleNavState(current.x - value, current.y, actionCode);
-// case 'L':
-// final int relativeDirection = current.direction - value;
-// final int direction = relativeDirection < 0 ? relativeDirection + 360 : relativeDirection;
-// return new SimpleNavState(current.x, current.y, direction);
-// case 'R':
-// return new SimpleNavState(current.x, current.y, (current.direction + value) % 360);
-// case 'F':
-// switch (current.direction) {
-// case 0:
-// return new SimpleNavState(current.x, current.y + value, current.direction);
-// case 90:
-// return new SimpleNavState(current.x + value, current.y, current.direction);
-// case 180:
-// return new SimpleNavState(current.x, current.y - value, current.direction);
-// case 270:
-// return new SimpleNavState(current.x - value, current.y, current.direction);
-// default:
-// throw new IllegalStateException("Unhandled direction: " + current.direction);
-// }
-// default:
-// throw new IllegalArgumentException("Invalid action: " + current.direction);
-// }
-// }
+ @Test
+ public final void part1() {
+ final var map = getInput();
+ final var result = map.lengthOfShortestPath();
+ System.out.println("Part 1: " + result);
+ }
+ @Test
+ public final void part2() {
+ 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);
}
-//
-// protected static class SimpleNavState {
-// private final int x;
-// private final int y;
-// private final int direction;
-//
-// public SimpleNavState(int x, int y, int direction) {
-// this.x = x;
-// this.y = y;
-// this.direction = direction;
-// }
-// }
-//
-// protected static class AdvancedNavState {
-// private final int sx;
-// private final int sy;
-// private final int wx;
-//
-// public AdvancedNavState(int sx, int sy, int wx, int wy) {
-// this.sx = sx;
-// this.sy = sy;
-// this.wx = wx;
-// this.wy = wy;
-// }
-//
-// private final int wy;
-// }
+
}
\ 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 cfb7520..457145f 100644
--- a/src/test/java/com/macasaet/Day13.java
+++ b/src/test/java/com/macasaet/Day13.java
@@ -1,93 +1,161 @@
package com.macasaet;
-import java.io.IOException;
+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;
+/**
+ * --- Day 13: Distress Signal ---
+ * https://adventofcode.com/2022/day/13
+ */
public class Day13 {
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day13.class.getResourceAsStream("/day-13-input.txt"))) {
- final var lines = StreamSupport.stream(spliterator, false).collect(Collectors.toUnmodifiableList());
- final int earliestDeparture = Integer.parseInt(lines.get(0));
- final var idsString = lines.get(1);
- final var idStrings = idsString.split(",");
- final var candidate = Arrays.stream(idStrings).filter(busId -> !"x".equalsIgnoreCase(busId)).map(Integer::parseInt).map(busId -> {
- if (earliestDeparture % busId == 0) {
- return new Candidate(busId, 0);
+ 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);
+ }
+
+ public boolean isInOrder() {
+ return x().compareTo(y()) < 0;
+ }
+
+ public Stream stream() {
+ return Stream.of(x(), y()).sorted();
+ }
+ }
+
+ 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");
}
- final int d = earliestDeparture / busId;
- return new Candidate(busId, ((d + 1) * busId) - earliestDeparture);
- }).sorted().findFirst().get();
- final int result = candidate.busId * candidate.timeToWait;
- System.out.println("Part 1: " + result);
-
- final List list = new ArrayList(idStrings.length);
- for (int i = 0; i < idStrings.length; i++) {
- final var idString = idStrings[i];
- if ("x".equalsIgnoreCase(idString)) continue;
- final var busId = Long.parseLong(idString);
- list.add(new Bus(busId, i));
+ return compareToLiteral((Literal) other);
}
- long sum = 0;
- long productOfModSpaces = 1;
- for (int i = list.size(); --i >= 0; ) {
- final var bus = list.get(i);
- productOfModSpaces *= bus.busId;
- long remainder = bus.busId - bus.index;
- remainder = remainder % bus.busId;
- long productOfOtherModSpaces = 1;
- for (int j = list.size(); --j >= 0; ) {
- if (j == i) continue;
- final var otherBus = list.get(j);
- productOfOtherModSpaces *= otherBus.busId;
+ }
+ }
+
+ 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);
}
- final long inverse = findInverse(productOfOtherModSpaces, bus.busId);
- sum += remainder * productOfOtherModSpaces * inverse;
- }
- while (true) {
- final long smallerSolution = sum - productOfModSpaces;
- if (smallerSolution < 0) break;
- sum = smallerSolution;
}
- System.out.println("Part 2: " + sum);
+ return stack.pop();
}
- }
- protected static long findInverse(final long partialProduct, final long modSpace) {
- for (long multiplier = 1; ; multiplier++) {
- if ((partialProduct * multiplier) % modSpace == 1) {
- return multiplier;
+ 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;
+ }
}
+ if(y.hasNext()) {
+ return -1;
+ } else if(x.hasNext()) {
+ return 1;
+ }
+ return 0;
}
+
+ public int compareToLiteral(Literal other) {
+ return compareToList(other.asList());
+ }
+
}
- protected static class Bus {
- final long busId;
- final long index;
+ public record Literal(int item) implements Item {
+ public int compareToList(ListItem other) {
+ return asList().compareToList(other);
+ }
- public Bus(final long busId, final long index) {
- this.busId = busId;
- this.index = index;
+ public int compareToLiteral(Literal other) {
+ return Integer.compare(item(), other.item());
}
+ public ListItem asList() {
+ return new ListItem(Collections.singletonList(this));
+ }
}
- protected static class Candidate implements Comparable {
- final int busId;
- final int timeToWait;
+ 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();
+ }
- public Candidate(final int busId, final int timeToWait) {
- this.busId = busId;
- this.timeToWait = timeToWait;
+ @Test
+ public final void part1() {
+ 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);
+ }
- public int compareTo(Candidate other) {
- return Integer.compare(timeToWait, other.timeToWait);
- }
+ @Test
+ public final void part2() {
+ 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 a86ae7f..7f7d777 100644
--- a/src/test/java/com/macasaet/Day14.java
+++ b/src/test/java/com/macasaet/Day14.java
@@ -1,127 +1,212 @@
package com.macasaet;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.util.ArrayList;
+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.stream.Collectors;
+import java.util.Map;
import java.util.stream.StreamSupport;
+/**
+ * --- Day 14: Regolith Reservoir ---
+ * https://adventofcode.com/2022/day/14
+ */
public class Day14 {
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day14.class.getResourceAsStream("/day-14-input.txt"))) {
- final var memory = new HashMap();
- char[] mask = null;
- final var lines = StreamSupport.stream(spliterator, false).collect(Collectors.toUnmodifiableList());
- for (final var line : lines) {
- final var components = line.split(" = ");
- final var stringValue = components[1].strip();
- if ("mask".equalsIgnoreCase(components[0].strip())) {
- mask = stringValue.strip().toCharArray();
- } else {
- final var address = new BigInteger(components[0].replaceAll("[^0-9]", ""));
- var binaryString = Integer.toBinaryString(Integer.parseInt(stringValue));
- final int padSize = 36 - binaryString.length();
- final var pad = new char[padSize];
- for (int i = padSize; --i >= 0; pad[i] = '0') ;
- binaryString = new String(pad) + binaryString;
-
- final var valueBinary = new char[36];
- for (int j = 36; --j >= 0; ) {
- if (mask[j] == 'X') {
- valueBinary[j] = binaryString.charAt(j);
- } else {
- valueBinary[j] = mask[j];
+ public enum Cell {
+ ROCK,
+ SAND
+ }
+
+ 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);
+ }
+ }
+
+ 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;
}
-
- final var result = toInt(valueBinary);
- memory.put(address, result);
}
}
- var sum = memory.values().stream().reduce(BigInteger::add).get();
- System.out.println("Part 1: " + sum);
-
- memory.clear();
- mask = null;
- for (final var line : lines) {
- final var components = line.split(" = ");
- final var stringValue = components[1].strip();
- if ("mask".equalsIgnoreCase(components[0].strip())) {
- mask = stringValue.toCharArray();
- } else {
- final var addressDecimal = Integer.parseInt(components[0].strip().replaceAll("[^0-9]", ""));
- var addressBinaryString = Integer.toBinaryString(addressDecimal);
- final int padSize = 36 - addressBinaryString.length();
- final var addressSpec = new char[36];
- for (int i = 0; i < padSize; addressSpec[i++] = '0') ;
- for (int i = 0; i < addressBinaryString.length(); i++) {
- addressSpec[i + padSize] = addressBinaryString.charAt(i);
+ }
+
+ 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;
}
- for (int i = 36; --i >= 0; ) {
- if (mask[i] == '1') {
- addressSpec[i] = '1';
- } else if (mask[i] == 'X') {
- addressSpec[i] = 'X';
+ }
+ }
+ }
+
+ int floorDepth() {
+ return maxDepth() + 2;
+ }
+
+ 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);
}
}
- final var value = toInt(Integer.parseInt(components[1].strip()));
- for (final var address : explode(addressSpec)) {
- memory.put(address, value);
+ if(current.verticalDepth() > maxDepth) {
+ maxDepth = current.verticalDepth();
}
+ if(current.horizontalOffset() < minHorizontalOffset) {
+ minHorizontalOffset = current.horizontalOffset();
+ }
+ if(current.horizontalOffset() > maxHorizontalOffset) {
+ maxHorizontalOffset = current.horizontalOffset();
+ }
+ last = current;
}
}
- sum = memory.values().stream().reduce(BigInteger::add).get();
- System.out.println("Part 2: " + sum);
+ return new Cave(grid, maxDepth, minHorizontalOffset, maxHorizontalOffset);
}
- }
-
- protected static BigInteger toInt(final int decimal) {
- final var string = Integer.toBinaryString(decimal);
- return toInt(string.toCharArray());
- }
- protected static BigInteger toInt(final char[] chars) {
- var retval = BigInteger.ZERO;
- for (int i = chars.length; --i >= 0; ) {
- final int power = chars.length - i - 1;
- final var multiplier = chars[i] == '0' ? BigInteger.ZERO : BigInteger.ONE;
- retval = retval.add(BigInteger.TWO.pow(power).multiply(multiplier));
+ static List parseRockPaths(final String line) {
+ return Arrays.stream(line.split(" -> ")).map(Coordinate::parse).toList();
}
- return retval;
- }
- protected static List explode(final char[] chars) {
- final var floatingIndices = new ArrayList();
- for (int i = 36; --i >= 0; ) {
- if (chars[i] == 'X') {
- floatingIndices.add(i);
+ @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 explode(chars, floatingIndices);
}
- protected static List explode(final char[] chars, final List floatingIndices) {
- if (floatingIndices.isEmpty()) {
- return Collections.singletonList(toInt(chars));
- }
- final var index = floatingIndices.get(0);
+ protected Cave getInput() {
+ final var lines = StreamSupport.stream(new LineSpliterator("day-14.txt"), false)
+ .toList();
+ return Cave.parse(lines);
+ }
- var list = new ArrayList();
+ @Test
+ public final void part1() {
+ final var cave = getInput();
+ final var result = cave.pourSandIntoAbyss();
- final var copy = Arrays.copyOf(chars, 36);
- final var sub = floatingIndices.subList(1, floatingIndices.size());
+ System.out.println("Part 1: " + result);
+ }
- copy[index] = '0';
- list.addAll(explode(copy, sub));
- copy[index] = '1';
- list.addAll(explode(copy, sub));
+ @Test
+ public final void part2() {
+ final var cave = getInput();
+ final var result = cave.fillAperture();
- return Collections.unmodifiableList(list);
+ 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 3de8788..2fea62c 100644
--- a/src/test/java/com/macasaet/Day15.java
+++ b/src/test/java/com/macasaet/Day15.java
@@ -1,49 +1,205 @@
package com.macasaet;
-import java.io.IOException;
+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.LinkedList;
import java.util.List;
+import java.util.Map;
+import java.util.function.IntPredicate;
+import java.util.stream.StreamSupport;
+/**
+ * --- Day 15: Beacon Exclusion Zone ---
+ * https://adventofcode.com/2022/day/15
+ */
public class Day15 {
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day15.class.getResourceAsStream("/day-15-test.txt"))) {
-// final var lines = StreamSupport.stream(spliterator, false).collect(Collectors.toUnmodifiableList());
-// final var line = lines.get(0);
-// final var numbers = Arrays.stream(line.split(",")).map(String::strip).map(Integer::parseInt).collect(Collectors.toUnmodifiableList());
-// final var numbers = new int[]{ 0, 3, 6 };
- final var numbers = new int[]{ 13,16,0,12,15,1 };
-
- int last = -1;
- final var oralHistory = new HashMap>();
- for( int i = 0; i < numbers.length; i++ ) {
- final int number = numbers[ i ];
- oralHistory.computeIfAbsent(number, k -> new LinkedList<>()).add( i );
- last = number;
- System.err.println( "Speak: " + number );
+ 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=", "")));
+ }
+
+ 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 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);
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+
+ // NW
+ upperRow.putIfAbsent(y - j, Item.COVERED);
+ }
+ }
+ }
+ }
+
+ int distanceToBeacon() {
+ return location().distanceTo(beaconLocation());
+ }
+ }
+
+ 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++;
+ }
+ }
+ return result;
+ }
+
+ 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);
}
- for( int i = numbers.length; i < 30_000_000; i++ ) {
- final var history = oralHistory.computeIfAbsent(last, k -> new LinkedList<>());
- if( history.isEmpty() ) {
- throw new IllegalStateException("No history for: " + last );
- } else if( history.size() == 1 ) { // spoken only once before
- final int numberToSpeak = 0;
- System.err.println( "Turn " + ( i + 1 ) + ": Speak: " + numberToSpeak );
- oralHistory.getOrDefault( numberToSpeak, new LinkedList<>() ).add( i );
- last = numberToSpeak;
- } else { // spoken 2+ times
- final int lastMention = history.get(history.size() - 1);
- final int penultimateMention = history.get(history.size() - 2);
- final int numberToSpeak = lastMention - penultimateMention;
- System.err.println( "Turn " + ( i + 1 ) + ": Speak: " + numberToSpeak );
- oralHistory.computeIfAbsent(numberToSpeak, k -> new LinkedList<>()).add( i );
- last = numberToSpeak;
+ return new CaveMap(grid, minX, maxX, minY, maxY);
+ }
+
+ 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 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 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 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;
+ }
}
}
- System.out.println( "Part 1: " + last );
}
+ 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 01bd853..0000000
--- a/src/test/java/com/macasaet/Day16.java
+++ /dev/null
@@ -1,198 +0,0 @@
-package com.macasaet;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.stream.StreamSupport;
-
-public class Day16 {
-
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day16.class.getResourceAsStream("/day-16-input.txt"))) {
- final var lines = StreamSupport.stream(spliterator, false).collect(Collectors.toUnmodifiableList());
-
- final var fields = new HashSet();
- int section = 0;
- Ticket myTicket = null;
- final var nearbyTickets = new ArrayList();
- for (final var line : lines) {
- if (line.isBlank()) {
- section++;
- continue;
- } else if ("your ticket:".equalsIgnoreCase(line.strip()) || "nearby tickets:".equalsIgnoreCase(line.strip())) {
- continue;
- }
- switch (section) {
- case 0 -> {
- final var sections = line.split(": ");
- final var label = sections[0].strip().replaceAll(":$", "");
- final var rangesString = sections[1].strip();
- final var rangeStringArray = rangesString.split(" or ");
- fields.add(new Field(label,
- Arrays.stream(rangeStringArray)
- .map(Range::fromString)
- .sorted()
- .collect(Collectors.toUnmodifiableList())));
- }
- case 1 -> myTicket = new Ticket(Arrays.stream(line.split(","))
- .map(Integer::parseInt)
- .collect(Collectors.toUnmodifiableList()));
- case 2 -> nearbyTickets.add(new Ticket(Arrays.stream(line.split(","))
- .map(Integer::parseInt)
- .collect(Collectors.toUnmodifiableList())));
- }
- }
-
- final int sum = nearbyTickets
- .stream()
- .flatMapToInt(ticket -> ticket
- .getInvalidNumbers(fields)
- .stream()
- .mapToInt(Integer::intValue))
- .sum();
- System.out.println("Part 1: " + sum);
-
- final var validTickets = nearbyTickets
- .stream()
- .filter(candidate -> candidate.isValid(fields))
- .collect(Collectors.toUnmodifiableSet());
- final var unmappedIndices = IntStream
- .range(0, myTicket.numbers.size())
- .collect(HashSet::new, Set::add, Set::addAll);
- final var fieldTable = new HashMap();
- final var unmappedFields = new HashSet<>(fields);
- while (!unmappedFields.isEmpty()) {
- final var indicesToRemove = new HashSet();
- for (final int index : unmappedIndices) {
- final var candidates = new HashSet<>(unmappedFields);
- final var toRemove = new HashSet();
- for (final var ticket : validTickets) {
- final var number = ticket.numbers.get(index);
- for (final var candidate : candidates) {
- if (!candidate.contains(number)) {
- toRemove.add(candidate);
- }
- }
- candidates.removeAll(toRemove);
- }
- if (candidates.isEmpty()) {
- throw new IllegalStateException("no candidates for index: " + index);
- } else if (candidates.size() == 1) {
- // map candidate to index
- final var field = candidates.iterator().next();
- fieldTable.put(field.label, index);
- unmappedFields.remove(field);
- indicesToRemove.add(index);
- }
- }
- unmappedIndices.removeAll(indicesToRemove);
- }
- final var numbers = myTicket.numbers;
- final long product = fieldTable
- .keySet()
- .stream()
- .filter(candidate -> candidate.startsWith("departure"))
- .map(fieldTable::get)
- .map(numbers::get)
- .mapToLong(Long::valueOf)
- .reduce((x, y) -> x * y).getAsLong();
- System.out.println("Part 2: " + product);
- }
- }
-
- protected static class Ticket {
- private final List numbers;
-
- public Ticket(final List numbers) {
- this.numbers = numbers;
- }
-
- public boolean isValid(final Collection extends Field> fields) {
- return getInvalidNumbers(fields).isEmpty();
- }
-
- public List getInvalidNumbers(final Collection extends Field> fields) {
- final List list = new ArrayList<>();
- outer:
- for (final var number : numbers) {
- for (final var field : fields) {
- if (field.contains(number)) {
- continue outer;
- }
- }
- list.add(number);
- }
- return Collections.unmodifiableList(list);
- }
- }
-
- protected static class Field {
- private final String label;
- private final List ranges;
-
- public Field(final String label, final List ranges) {
- this.label = label;
- this.ranges = ranges;
- }
-
- public boolean contains(final int number) {
- for (final var range : ranges) {
- if (range.contains(number)) {
- return true;
- }
- }
- return false;
- }
-
- public int hashCode() {
- return label.hashCode();
- }
-
- public boolean equals(final Object object) {
- if( this == object ) {
- return true;
- } else if( object == null ) {
- return false;
- }
- try {
- final Field other = ( Field )object;
- return label.equals(other.label);
- } catch( final ClassCastException cce ) {
- return false;
- }
- }
- }
-
- protected static class Range implements Comparable {
-
- private final int minInclusive;
- private final int maxInclusive;
-
- public Range(final int minInclusive, final int maxInclusive) {
- this.minInclusive = minInclusive;
- this.maxInclusive = maxInclusive;
- }
-
- public static Range fromString(final String string) {
- final var ends = string.split("-", 2);
- return new Range(Integer.parseInt(ends[0].strip()), Integer.parseInt(ends[1].strip()));
- }
-
- public int compareTo(final Range other) {
- return Integer.compare(minInclusive, other.minInclusive);
- }
-
- public boolean contains(final int candidate) {
- return candidate >= minInclusive && candidate <= maxInclusive;
- }
-
- }
-}
\ 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 cf0456c..0000000
--- a/src/test/java/com/macasaet/Day17.java
+++ /dev/null
@@ -1,150 +0,0 @@
-package com.macasaet;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-
-public class Day17 {
-
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day17.class.getResourceAsStream("/day-17-input.txt"))) {
- final var grid = new Grid();
-
- final var lines = StreamSupport.stream(spliterator, false).collect(Collectors.toUnmodifiableList());
- for( int x = 0; x < lines.size(); x++ ) {
- final var line = lines.get(x);
- for( int y = 0; y < line.length(); y++ ) {
- final char state = line.charAt(y);
- final int z = 0;
- final int w = 0;
- grid.setInitial(x, y, z, w, state);
- }
- }
-
- for( int i = 0; i < 6; i++ ) {
- grid.cycle();
- }
- System.out.println( "Part 2: " + grid.countActive() );
- }
- }
-
- public static class Grid {
-
- private final SortedMap>>> map = new TreeMap<>();
- private int minX = 0, maxX = 0, minY = 0, maxY = 0, minZ = 0, maxZ = 0, minW = 0, maxW = 0;
-
- public void setInitial(final int x, final int y, final int z, final int w, final char state) {
- final var dimX = map.computeIfAbsent(x, key -> new TreeMap<>());
- final var dimY = dimX.computeIfAbsent(y, key -> new TreeMap<>());
- final var dimZ = dimY.computeIfAbsent(z, key -> new TreeMap<>());
- dimZ.put(w, state);
- minX = Math.min(minX, x);
- maxX = Math.max(maxX, x);
- minY = Math.min(minY, y);
- maxY = Math.max(maxY, y);
- minZ = Math.min(minZ, z);
- maxZ = Math.max(maxZ, z);
- minW = Math.min(minW, w);
- maxW = Math.max(maxW, w);
- }
-
- public Runnable defer(final int x, final int y, final int z, final int w) {
- int activeNeighbours = 0;
- for( final var neighbour : neighbours( x, y, z, w ) ) {
- if( neighbour.getState() == '#' ) {
- activeNeighbours++;
- }
- }
- final var cell = new Cell(x, y, z, w);
- if( cell.getState() == '#' ) { // active
- return activeNeighbours == 2 || activeNeighbours == 3 ? () -> {} : () -> cell.setState('.');
- } else { // inactive
- return activeNeighbours == 3 ? () -> cell.setState('#') : () -> {};
- }
- }
-
- public void cycle() {
- final var updateTasks = new LinkedList();
- for( int x = minX - 1; x <= maxX + 1; x++ ) {
- for( int y = minY - 1; y <= maxY + 1; y++ ) {
- for( int z = minZ - 1; z <= maxZ + 1; z++ ) {
- for( int w = minW - 1; w <= maxW + 1; w++ ) {
- updateTasks.add(defer(x, y, z, w));
- }
- }
- }
- }
- updateTasks.forEach(Runnable::run);
- }
-
- public int countActive() {
- return map.values()
- .stream()
- .flatMapToInt(yDim -> yDim.values()
- .stream()
- .flatMapToInt(zDim -> zDim.values()
- .stream()
- .flatMapToInt(wDim -> wDim.values()
- .stream()
- .mapToInt(state -> state == '#' ? 1 : 0 ))))
- .sum();
- }
-
- protected Collection
neighbours(final int x, final int y, final int z, int w) {
- final var list = new ArrayList(80);
- for( int i = x - 1; i <= x + 1; i++ ) {
- for( int j = y - 1; j <= y + 1; j++ ) {
- for( int k = z - 1; k <= z + 1; k++ ) {
- for( int l = w - 1; l <= w + 1; l++ ) {
- if (i == x && j == y && k == z && l == w) continue;
- list.add(new Cell(i, j, k, l));
- }
- }
- }
- }
- if( list.size() != 80 ) {
- throw new IllegalStateException("There should be 80 neighbours :-(");
- }
- return Collections.unmodifiableList(list);
- }
-
- protected class Cell {
- final int x, y, z, w;
-
- public Cell(final int x, final int y, final int z, int w) {
- this.x = x;
- this.y = y;
- this.z = z;
- this.w = w;
- }
-
- public char getState() {
- final var dimensionX = map.getOrDefault(x, Collections.emptySortedMap());
- final var dimensionY = dimensionX.getOrDefault(y, Collections.emptySortedMap());
- final var dimensionZ = dimensionY.getOrDefault(z, Collections.emptySortedMap());
- return dimensionZ.getOrDefault(w, '.');
- }
-
- public void setState(final char state) {
- final var dimensionX = map.computeIfAbsent(x, key -> new TreeMap<>());
- final var dimensionY = dimensionX.computeIfAbsent(y, key -> new TreeMap<>());
- final var dimensionZ = dimensionY.computeIfAbsent(z, key -> new TreeMap<>());
- dimensionZ.put(w, state);
- minX = Math.min(minX, x);
- maxX = Math.max(maxX, x);
- minY = Math.min(minY, y);
- maxY = Math.max(maxY, y);
- minZ = Math.min(minZ, z);
- maxZ = Math.max(maxZ, z);
- minW = Math.min(minW, w);
- maxW = Math.max(maxW, w);
- }
- }
- }
-}
\ 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 61b25aa..3c29e60 100644
--- a/src/test/java/com/macasaet/Day18.java
+++ b/src/test/java/com/macasaet/Day18.java
@@ -1,91 +1,175 @@
package com.macasaet;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.util.LinkedList;
-import java.util.stream.Collectors;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.stream.StreamSupport;
+/**
+ * --- Day 18: Boiling Boulders ---
+ * https://adventofcode.com/2022/day/18
+ */
public class Day18 {
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day18.class.getResourceAsStream("/day-18-input.txt"))) {
- final var lines = StreamSupport.stream(spliterator, false).collect(Collectors.toUnmodifiableList());
- BigInteger sum = BigInteger.ZERO;
- for (final var line : lines) {
- final var outputQueue = new LinkedList();
- final var operatorStack = new LinkedList();
-
- String numberString = "";
- for (final char c : line.toCharArray()) {
- if (Character.isDigit(c)) {
- numberString += c;
- } else {
- if (!"".equals(numberString)) {
- outputQueue.add(numberString);
- numberString = "";
- }
- switch (c) {
- case '+', '*' -> {
- Character topOperator = operatorStack.peek();
- while (topOperator != null && topOperator > c /*&& topOperator != '('*/) {
- outputQueue.add("" + operatorStack.pop());
- topOperator = operatorStack.peek();
- }
- operatorStack.push(c);
- }
- case '(' -> operatorStack.push(c);
- case ')' -> {
- Character topOperator = operatorStack.peek();
- while (topOperator != null && topOperator != '(') {
- outputQueue.add("" + operatorStack.pop());
- topOperator = operatorStack.peek();
- }
- if (topOperator == null /*|| topOperator != '('*/) {
- throw new IllegalStateException("mis-matched parentheses :-(");
- }
- operatorStack.pop();
- }
- case ' ' -> {
- }
- default -> throw new IllegalStateException("Unexpected value: " + c);
- }
- }
+ public static final int SCAN_DIMENSION = 32;
+ protected static Droplet getInput() {
+ final var cubeCoordinates = StreamSupport.stream(new LineSpliterator("day-18.txt"), false)
+ .map(Cube::parse)
+ .toList();
+ return new Droplet(cubeCoordinates);
+ }
+
+ @Test
+ public final void part1() {
+ final var droplet = getInput();
+ final var result = droplet.surfaceArea(CubeType.Air);
+
+ System.out.println("Part 1: " + result);
+ }
+
+ @Test
+ public final void part2() {
+ final var droplet = getInput();
+ droplet.immerse();
+ final var result = droplet.surfaceArea(CubeType.Water);
+
+ System.out.println("Part 2: " + result);
+ }
+
+ public enum CubeType {
+ Air,
+ Lava,
+ Water,
+ }
+
+ 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]));
+ }
+
+ public CubeType getType(final CubeType[][][] grid) {
+ return grid[x()][y()][z()];
+ }
+
+ public void setType(final CubeType[][][] grid, final CubeType type) {
+ grid[x()][y()][z()] = type;
+ }
+ }
+
+ public static class Droplet {
+
+ 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);
}
- if (!numberString.isBlank()) {
- outputQueue.add(numberString);
- }
- while (!operatorStack.isEmpty()) {
- outputQueue.add("" + operatorStack.pop());
- }
- final var stack = new LinkedList();
- for (final var item : outputQueue) {
- try {
- stack.push(new BigInteger(item));
- } catch (final NumberFormatException nfe) {
- switch (item) {
- case "+" -> {
- final var rValue = stack.pop();
- final var lValue = stack.pop();
- final var result = lValue.add(rValue);
- stack.push(result);
- }
- case "*" -> {
- final var rValue = stack.pop();
- final var lValue = stack.pop();
- final var result = lValue.multiply(rValue);
- stack.push(result);
- }
- }
+ }
+ for (final var cube : cubes) {
+ cube.setType(grid, CubeType.Lava);
+ }
+ this.grid = grid;
+ this.cubes = cubes;
+ }
+
+ 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);
}
}
- if (stack.size() != 1) {
- throw new IllegalStateException("Invalid stack: " + stack);
- }
- sum = sum.add(stack.get(0));
+ cube.setType(grid, CubeType.Water);
+ }
+ }
+
+ 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));
+ }
+ if (x < SCAN_DIMENSION - 1) {
+ result.add(new Cube(x + 1, y, z));
+ }
+ if (y > 0) {
+ result.add(new Cube(x, y - 1, z));
+ }
+ if (y < SCAN_DIMENSION - 1) {
+ result.add(new Cube(x, y + 1, z));
+ }
+ if (z > 0) {
+ result.add(new Cube(x, y, z - 1));
+ }
+ if (z < SCAN_DIMENSION - 1) {
+ result.add(new Cube(x, y, z + 1));
}
- System.out.println("Part 2: " + sum);
+ 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();
+
+ int result = 0;
+ if (grid[x + 1][y][z] == element) {
+ result++;
+ }
+ 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;
+ }
+
+ public Collection extends Cube> getCubes() {
+ return cubes;
+ }
+
+ public CubeType[][][] getGrid() {
+ return grid;
}
}
diff --git a/src/test/java/com/macasaet/Day19.java b/src/test/java/com/macasaet/Day19.java
deleted file mode 100644
index 007972a..0000000
--- a/src/test/java/com/macasaet/Day19.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package com.macasaet;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Spliterators;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-public class Day19 {
-
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day19.class.getResourceAsStream("/day-19-input.txt"))) {
- final var lines = StreamSupport.stream(spliterator, false).collect(Collectors.toUnmodifiableList());
-
- final var ruleMap = new HashMap();
- int sum = 0;
- int mode = 0;
- for (var line : lines) {
- // comment out the remapping for Part 1
- if ("8: 42".equals(line.strip())) {
- line = "8: 42 | 42 8";
- } else if ("11: 42 31".equals(line.strip())) {
- line = "11: 42 31 | 42 11 31";
- }
- if (line.isBlank()) {
- mode++;
- continue;
- }
- if (mode == 0) {
- final var components = line.split(": ", 2);
- final var ruleId = Integer.parseInt(components[0].strip());
- final var value = components[1].strip();
- if (value.matches("\"[a-z]\"")) {
- final var c = value.charAt(1);
- final var rule = new CharacterRule(ruleId, c);
- ruleMap.put(ruleId, rule);
- } else if (value.contains("|")) {
- final var ruleSets = value.split(" \\| ");
- final var set = Arrays.stream(ruleSets)
- .map(ruleSet -> Arrays.stream(ruleSet.split(" "))
- .map(String::strip)
- .map(Integer::parseInt)
- .collect(Collectors.toUnmodifiableList()))
- .collect(Collectors.toUnmodifiableSet());
- final var rule = new DisjunctionRule(ruleId, set);
- ruleMap.put(ruleId, rule);
- } else {
- final var subRules = Arrays.stream(value.split(" "))
- .map(String::strip)
- .map(Integer::parseInt)
- .collect(Collectors.toUnmodifiableList());
- final var rule = new SequenceRule(ruleId, subRules);
- ruleMap.put(ruleId, rule);
- }
- } else {
- final var rule = ruleMap.get(0);
- if (rule.matches(line.strip(), ruleMap)) {
- sum++;
- }
- }
- }
- System.out.println("Result: " + sum);
- }
- }
-
- public abstract static class Rule {
- protected final int id;
-
- protected Rule(final int id) {
- this.id = id;
- }
-
- public boolean matches(final String string, final Map super Integer, ? extends Rule> map) {
- return getMatchingSuffixes(Stream.of(string), map).distinct().anyMatch(String::isBlank);
- }
-
- protected abstract Stream getMatchingSuffixes(Stream strings, Map super Integer, ? extends Rule> map);
- }
-
- public static class CharacterRule extends Rule {
-
- private final char c;
-
- public CharacterRule(final int id, final char c) {
- super(id);
- this.c = c;
- }
-
- protected Stream getMatchingSuffixes(Stream strings, Map super Integer, ? extends Rule> map) {
- return strings.flatMap(string ->
- string.startsWith("" + c)
- ? Stream.of(string.substring(1))
- : Stream.empty());
- }
- }
-
- public static class SequenceRule extends Rule {
- private final List extends Integer> ruleIds;
-
- public SequenceRule(final int id, final List extends Integer> ruleIds) {
- super(id);
- this.ruleIds = ruleIds;
- }
-
- protected Stream getMatchingSuffixes(Stream strings, Map super Integer, ? extends Rule> map) {
- return strings.flatMap(string -> {
- var result = Stream.of(string);
- for (final var ruleId : ruleIds) { // match all in sequence
- final var rule = map.get(ruleId);
- final var iterator = rule.getMatchingSuffixes(result, map).iterator();
- if (!iterator.hasNext()) {
- result = Stream.empty();
- break;
- }
- result = StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
- }
- return result;
- }).distinct();
- }
- }
-
- public static class DisjunctionRule extends Rule {
-
- private final Collection extends List extends Integer>> options;
-
- public DisjunctionRule(final int id, final Collection extends List extends Integer>> options) {
- super(id);
- this.options = options;
- }
-
- protected Stream getMatchingSuffixes(final Stream strings, final Map super Integer, ? extends Rule> map) {
- return strings.flatMap(string -> options.stream().flatMap(option -> {
- var result = Stream.of(string);
- for (final var ruleId : option) { // match all in sequence
- final var rule = map.get(ruleId);
- final var iterator = rule.getMatchingSuffixes(result, map).iterator();
- if (!iterator.hasNext()) {
- result = Stream.empty();
- break;
- }
- result = StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
- }
- return result;
- })).distinct();
- }
- }
-}
\ 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 de4daeb..4125a47 100644
--- a/src/test/java/com/macasaet/Day20.java
+++ b/src/test/java/com/macasaet/Day20.java
@@ -1,406 +1,106 @@
package com.macasaet;
-import java.io.IOException;
+import org.junit.jupiter.api.Test;
+
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.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.IntConsumer;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
+/**
+ * --- Day 20: Grove Positioning System ---
+ * https://adventofcode.com/2022/day/20
+ */
public class Day20 {
- private static final String seaMonsterString =
- " # \n" +
- "# ## ## ###\n" +
- " # # # # # # ";
- private static final char[][] seaMonster;
-
- static {
- final var seaMonsterLines = seaMonsterString.split("\n");
- seaMonster = new char[seaMonsterLines.length][];
- for (int i = seaMonsterLines.length; --i >= 0; seaMonster[i] = seaMonsterLines[i].toCharArray()) ;
- }
-
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day20.class.getResourceAsStream("/day-20-input.txt"))) {
- final var tiles = new LinkedList();
- final var lines =
- StreamSupport.stream(spliterator, false)
- .map(String::strip)
- .collect(Collectors.toUnmodifiableList());
- int tileId = -1;
- var rows = new ArrayList();
- for (final var line : lines) {
- if (line.startsWith("Tile ")) {
- tileId = Integer.parseInt(line.replaceAll("[^0-9]", ""));
- rows = new ArrayList<>();
- } else if (line.isBlank()) {
- final var tile = new Tile(tileId, rows.toArray(new char[rows.size()][]));
- tiles.add(tile);
- tileId = -1;
- rows = new ArrayList<>();
- } else {
- rows.add(line.toCharArray());
- }
- }
- if (tileId > 0) {
- final var tile = new Tile(tileId, rows.toArray(new char[rows.size()][]));
- tiles.add(tile);
- }
- final int size = (int) Math.sqrt(tiles.size()); // the image is square
-
- final var possibleArrangements = getValidArrangements(Collections.emptyList(), tiles, size);
- if (possibleArrangements.isEmpty()) {
- throw new IllegalStateException("No possible arrangements");
- } else {
- // there are multiple possible arrangements, but they are just rotated and/or flipped versions of each other
- // TODO is there a matrix transform I can put in hashCode/equals that will treat these as equivalent?
- System.err.println(possibleArrangements.size() + " possible arrangements: " + possibleArrangements);
- for (final var arrangement : possibleArrangements) {
- final var topLeft = arrangement.get(0);
- final var topRight = arrangement.get(size - 1);
- final var bottomLeft = arrangement.get(arrangement.size() - size);
- final var bottomRight = arrangement.get(arrangement.size() - 1);
- final var result =
- Stream.of(topLeft, topRight, bottomLeft, bottomRight)
- .map(corner -> corner.id)
- .map(Long::valueOf)
- .map(BigInteger::valueOf)
- .reduce(BigInteger::multiply)
- .get();
- System.out.println("Part 1: " + result);
-
- final var orderedCroppedTiles =
- arrangement.stream().map(Tile::removeBorders).collect(Collectors.toUnmodifiableList());
- final var combined = combine(orderedCroppedTiles);
-
- for (final var permutation : combined.getPermutations()) {
- final var counter = new AtomicInteger(0);
- highlightSeaMonsters(permutation, counter::set);
- final var numSeaMonsters = counter.get();
- if (numSeaMonsters > 0) {
- System.err.println(permutation + " has " + numSeaMonsters + " sea monsters");
- int sum = 0;
- for (int i = permutation.edgeLength; --i >= 0; ) {
- for (int j = permutation.edgeLength; --j >= 0; ) {
- if (permutation.grid[i][j] == '#') {
- sum++;
- }
- }
- }
- System.out.println("Part 2: " + sum);
- }
- }
- }
- }
+ 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)));
}
}
- public static void highlightSeaMonsters(final Tile tile, final IntConsumer counter) {
- final int windowHeight = seaMonster.length;
- final int windowWidth = seaMonster[0].length;
- final int verticalWindows = tile.edgeLength - windowHeight;
- final int horizontalWindows = tile.edgeLength - windowWidth;
-
- int sum = 0;
- for (int i = 0; i < verticalWindows; i++) {
- for (int j = 0; j < horizontalWindows; j++) {
- if (contains(tile.grid, i, j)) {
- sum++;
- highlight(tile.grid, i, j);
- }
- }
+ 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)));
}
- counter.accept(sum);
+ return Collections.unmodifiableList(result);
}
- protected static boolean contains(final char[][] image, final int verticalOffset, final int horizontalOffset) {
- for (int i = verticalOffset; i < verticalOffset + seaMonster.length; i++) { // loop the height of the pattern
- final var patternRow = seaMonster[i - verticalOffset];
- final var imageRow = image[i];
- for (int j = horizontalOffset; j < horizontalOffset + patternRow.length; j++) { // loop the width of the pattern
- final var p = patternRow[j - horizontalOffset];
- // spaces can be anything
- if (p == '#' && imageRow[j] != '#') {
- // only the # need to match
- return false;
- }
+ @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 true;
- }
+ final var workingSet = new ArrayList<>(numbers);
- protected static void highlight(final char[][] image, final int verticalOffset, final int horizontalOffset) {
- for (int i = verticalOffset; i < verticalOffset + seaMonster.length; i++) {
- final var patternRow = seaMonster[i - verticalOffset];
- final var imageRow = image[i];
- for (int j = horizontalOffset; j < horizontalOffset + patternRow.length; j++) {
- final var p = patternRow[j - horizontalOffset];
- if (p == ' ') continue;
- if (p == '#') imageRow[j] = 'O';
+ 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;
}
+ workingSet.add(newIndex, number);
}
- }
- protected static Set> getValidArrangements(final List partialArrangement,
- final List remainingTiles,
- final int edgeLength) {
- if (remainingTiles.isEmpty()) {
- return Collections.singleton(partialArrangement);
- } else if (partialArrangement.isEmpty()) {
- // find the candidates for the top-left tile
- final Set> set = new HashSet<>();
- for (int i = remainingTiles.size(); --i >= 0; ) {
- final var candidate = remainingTiles.get(i); // candidate for first tile
- // consider all possible orientations
- for (final var orientation : candidate.getPermutations()) {
-// System.err.println("Trying " + orientation + " in the top left with.");
- final var partial = Collections.singletonList(orientation);
- final var remaining = new ArrayList(remainingTiles.size() - 1);
- remaining.addAll(remainingTiles.subList(0, i));
- remaining.addAll(remainingTiles.subList(i + 1, remainingTiles.size()));
+ 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();
- final var validArrangements =
- getValidArrangements(partial, Collections.unmodifiableList(remaining), edgeLength);
- if (!validArrangements.isEmpty()) {
- System.err.println("Found arrangement with " + orientation + " in the top left.");
- }
- set.addAll(validArrangements);
- }
- }
- return Collections.unmodifiableSet(set);
- }
+ final var result = (long)x + (long)y + (long)z;
- final Set> set = new HashSet<>();
- for (int i = remainingTiles.size(); --i >= 0; ) {
- final var candidate = remainingTiles.get(i);
- final var oriented = fits(partialArrangement, candidate, edgeLength);
- if (oriented != null) {
-// System.err.println(oriented + " fits at index " + partialArrangement.size());
- final var permutation = new ArrayList<>(partialArrangement);
- permutation.add(oriented); // this is a new valid partial arrangement (is it the only one?)
-
- final var remaining = new ArrayList(remainingTiles.size() - 1);
- remaining.addAll(remainingTiles.subList(0, i));
- remaining.addAll(remainingTiles.subList(i + 1, remainingTiles.size()));
-
- final var validArrangements =
- getValidArrangements(Collections.unmodifiableList(permutation),
- Collections.unmodifiableList(remaining), edgeLength);
- set.addAll(validArrangements);
- }
- }
- return Collections.unmodifiableSet(set);
- }
-
- protected static Tile getTileAbove(final List partialArrangement, final int index, final int edgeLength) {
- if (index < edgeLength) {
- return null;
- }
- return partialArrangement.get(index - edgeLength);
- }
-
- protected static Tile getTileToLeft(final List partialArrangement, final int index, final int edgeLength) {
- if (index % edgeLength == 0) {
- return null;
- }
- return partialArrangement.get(index - 1);
- }
-
- protected static Tile fits(final List partialArrangement, final Tile candidate, final int edgeLength) {
- final int index = partialArrangement.size();
- final var tileAbove = getTileAbove(partialArrangement, index, edgeLength);
- final var tileToLeft = getTileToLeft(partialArrangement, index, edgeLength);
- for (final var orientation : candidate.getPermutations()) {
- final var topFits = tileAbove == null || tileAbove.getBottomBorder().equals(orientation.getTopBorder());
- final var leftFits = tileToLeft == null || tileToLeft.getRightBorder().equals(orientation.getLeftBorder());
- if (topFits && leftFits) {
- return orientation;
- }
- }
- return null;
- }
-
- protected static Tile combine(final List arrangement) {
- final int tilesOnEdge = (int) Math.sqrt(arrangement.size());
- final var combinedLength = tilesOnEdge * arrangement.get(0).edgeLength;
- char[][] combinedGrid = new char[combinedLength][];
- int maxId = Integer.MIN_VALUE;
- int id = 0;
- for (int index = arrangement.size(); --index >= 0; ) {
- final var tile = arrangement.get(index);
- maxId = Math.max(maxId, tile.id);
- id = id * 31 + tile.id;
- final int tileRow = index / tilesOnEdge;
- final int tileColumn = index % tilesOnEdge;
- final int rowOffset = tile.edgeLength * tileRow;
- final int columnOffset = tile.edgeLength * tileColumn;
- for (int row = tile.edgeLength; --row >= 0; ) {
- if (combinedGrid[row + rowOffset] == null) {
- combinedGrid[row + rowOffset] = new char[combinedLength];
- }
- for (int column = tile.edgeLength; --column >= 0; ) {
- combinedGrid[row + rowOffset][column + columnOffset] = tile.grid[row][column];
- }
- }
- }
- return new Tile(id % maxId, combinedGrid);
+ System.out.println("Part 1: " + result);
}
- protected static class Tile {
- private final int id;
- private final char[][] grid;
- private final int edgeLength;
- private final String label;
-
- public Tile(final int id, final char[][] grid) {
- this(id, grid, "original");
- }
-
- protected Tile(final int id, final char[][] grid, final String label) {
- this.id = id;
- this.grid = grid;
- this.edgeLength = grid.length;
- this.label = label;
- }
-
- public String getTopBorder() {
- return new String(grid[0]);
- }
-
- public String getBottomBorder() {
- return new String(grid[edgeLength - 1]);
- }
-
- public String getLeftBorder() {
- final char[] array = new char[edgeLength];
- for (int i = edgeLength; --i >= 0; array[i] = grid[i][0]) ;
- return new String(array);
- }
-
- public String getRightBorder() {
- final char[] array = new char[edgeLength];
- for (int i = edgeLength; --i >= 0; array[i] = grid[i][edgeLength - 1]) ;
- return new String(array);
- }
-
- public Collection getPermutations() {
- return List.of(this, flipHorizontal(), flipVertical(), rotate90(), rotate180(), rotate270(),
- rotate90().flipHorizontal(), rotate90().flipVertical(),
- rotate180().flipHorizontal(), rotate180().flipVertical(),
- rotate270().flipHorizontal(), rotate270().flipVertical());
- }
-
- public Tile flipVertical() {
- final var flipped = new char[edgeLength][];
- for (int row = edgeLength; --row >= 0; ) {
- final var flippedRow = new char[edgeLength];
- for (int oldColumn = edgeLength; --oldColumn >= 0; ) {
- final int newColumn = edgeLength - oldColumn - 1;
- flippedRow[newColumn] = grid[row][oldColumn];
+ @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;
+ }
+ }
+ 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;
}
- flipped[row] = flippedRow;
+ workingSet.add(newIndex, number);
}
- return new Tile(id, flipped, label + ", flipped around vertical axis");
}
- public Tile flipHorizontal() {
- final var flipped = new char[edgeLength][];
- for (int i = edgeLength; --i >= 0; ) {
- final int newRowId = edgeLength - i - 1;
- final char[] row = new char[edgeLength];
- System.arraycopy(grid[i], 0, row, 0, edgeLength);
- flipped[newRowId] = row;
- }
- return new Tile(id, flipped, label + ", flipped around horizontal axis");
- }
+ 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();
- public Tile transpose() {
- final var transposed = new char[edgeLength][]; // should this be its own permutation?
- for (int i = edgeLength; --i >= 0; ) {
- transposed[i] = new char[edgeLength];
- for (int j = edgeLength; --j >= 0; ) {
- transposed[i][j] = grid[j][i];
- }
- }
- return new Tile(id, transposed, label + ", transposed");
- }
+ final var result = x.add(y).add(z);
- public Tile rotate90() {
- final var transposed = transpose().grid;
- final var reversedRows = new char[edgeLength][];
- for (int i = edgeLength; --i >= 0; ) {
- final var newRow = new char[edgeLength];
- for (int j = edgeLength; --j >= 0; ) {
- final int newColumn = edgeLength - j - 1;
- newRow[newColumn] = transposed[i][j];
- }
- reversedRows[i] = newRow;
- }
- return new Tile(id, reversedRows, label + ", rotated 90 degrees");
- }
-
- public Tile rotate180() {
- return new Tile(id, rotate90().rotate90().grid, label + ", rotated 180 degrees");
- }
-
- public Tile rotate270() {
- return new Tile(id, rotate180().rotate90().grid, label + ", rotated 270 degrees");
- }
-
- public Tile removeBorders() {
- final int length = edgeLength - 2;
- final var cropped = new char[length][];
- for (int i = edgeLength - 1; --i >= 1; ) {
- final var row = new char[length];
- System.arraycopy(grid[i], 1, row, 0, length);
- cropped[i - 1] = row;
- }
- return new Tile(id, cropped, label + " cropped");
- }
-
- public String toString() {
- return "Tile{ " + id + ", " + label + ", top=" + getTopBorder() + " }";
- }
-
- public int hashCode() {
- int retval = 0;
- retval = 31 * retval + id;
- for (int i = edgeLength; --i >= 0; ) {
- for (int j = edgeLength; --j >= 0; ) {
- retval = 31 * retval + grid[i][j];
- }
- }
- return retval;
- }
-
- public boolean equals(final Object o) {
- if (o == null) {
- return false;
- } else if (this == o) {
- return true;
- }
- try {
- final Tile other = (Tile) o;
- boolean retval = id == other.id;
- for (int i = edgeLength; --i >= 0 && retval; ) {
- for (int j = edgeLength; --j >= 0 && retval; ) {
- retval &= grid[i][j] == other.grid[i][j];
- }
- }
- return retval;
- } catch (final ClassCastException cce) {
- return false;
- }
- }
+ 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 db1a5c6..b2fbfc8 100644
--- a/src/test/java/com/macasaet/Day21.java
+++ b/src/test/java/com/macasaet/Day21.java
@@ -1,120 +1,323 @@
package com.macasaet;
-import java.io.IOException;
-import java.util.Comparator;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.Map;
import java.util.stream.StreamSupport;
+/**
+ * --- Day 21: Monkey Math ---
+ * https://adventofcode.com/2022/day/21
+ */
public class Day21 {
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day21.class.getResourceAsStream("/day-21-input.txt"))) {
-
- final var foods = StreamSupport.stream(spliterator, false)
- .map(String::strip)
- .map(Food::fromLine)
- .collect(Collectors.toUnmodifiableSet());
- final var allergenToFood = new HashMap>();
- final var ingredientToFood = new HashMap>();
- for (final var food : foods) {
- for (final var allergen : food.allergens) {
- allergenToFood.computeIfAbsent(allergen, key -> new HashSet<>()).add(food);
- }
- for (final var ingredient : food.ingredients) {
- ingredientToFood.computeIfAbsent(ingredient, key -> new HashSet<>()).add(food);
- }
- }
+ public record Monkey(String name, Job job) {
+ public long yell(final Map monkeys, final Map results) {
+ return job().yell(monkeys, results);
+ }
- final var ingredientsThatContainNoAllergens = new HashSet<>(ingredientToFood.keySet());
- final var allergenToIngredient = new HashMap>();
- allergenToFood.entrySet().stream().forEach(entry -> {
- final var allergen = entry.getKey();
- final var appearances = entry.getValue();
- Set commonIngredients = null;
- for (final var food : appearances) {
- if (commonIngredients == null) {
- commonIngredients = new HashSet<>(food.ingredients);
- } else {
- commonIngredients.retainAll(food.ingredients);
- }
- }
- System.err.println(allergen + " may be found in: " + commonIngredients);
- allergenToIngredient.put(allergen, commonIngredients);
- ingredientsThatContainNoAllergens.removeAll(commonIngredients);
- });
-
- int sum = 0;
- for (final var food : foods) {
- for (final var ingredient : ingredientsThatContainNoAllergens) {
- if (food.ingredients.contains(ingredient)) {
- sum++;
- }
- }
- }
- System.out.println("Part 1: " + sum);
-
- final var ingredientToAllergen = new HashMap();
-
- final var dangerousIngredients = new HashSet<>(ingredientToFood.keySet());
- dangerousIngredients.removeAll(ingredientsThatContainNoAllergens);
- while (!dangerousIngredients.isEmpty()) {
- for (final var i = dangerousIngredients.iterator(); i.hasNext(); ) {
- final var ingredient = i.next();
- boolean mappedIngredient = false;
- for (final var j = allergenToIngredient.entrySet().iterator(); j.hasNext(); ) {
- final var entry = j.next();
- final var allergen = entry.getKey();
- final var ingredients = entry.getValue();
- if (ingredients.size() == 1 && ingredients.contains(ingredient)) {
- // this is the only ingredient known to contain this allergen
- ingredientToAllergen.put(ingredient, allergen);
- System.err.println("Mapping " + ingredient + " to " + allergen);
- j.remove();
- mappedIngredient |= true;
- break;
+ 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();
+ }
+
+ record Expression(Simplification x, Operation operation, Simplification y) implements Simplification {
+
+ 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());
}
- if (mappedIngredient) {
- for (final var entry : allergenToIngredient.entrySet()) {
- final var ingredients = entry.getValue();
- ingredients.remove(ingredient);
+ // 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);
}
- i.remove();
+ } 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;
+ }
- final var result =
- ingredientToAllergen
- .entrySet()
- .stream()
- .sorted(Comparator.comparing(Entry::getValue))
- .map(Entry::getKey)
- .collect(Collectors.joining(","));
- System.out.println("Part 2: " + result);
+ public String toString() {
+ return "(" + x() + ") " + operation() + " (" + y() + ")";
}
}
- public static class Food {
- private final Set ingredients;
- private final Set allergens;
+ record Value(long value) implements Simplification {
+ public String toString() {
+ return "" + value();
+ }
- public Food(final Set ingredients, final Set allergens) {
- this.ingredients = ingredients;
- this.allergens = allergens;
+ public Simplification simplify() {
+ return this;
}
+ }
- public static Food fromLine(final String line) {
- final var components = line.split("\\(contains ");
- final var ingredientsArray = components[0].strip().split(" ");
- final var allergensString = components[1].strip().replaceAll("\\)", "");
- final var allergensArray = allergensString.split(", ");
- return new Food(Set.of(ingredientsArray), Set.of(allergensArray));
+ 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);
+
+ static Job parse(final String string) {
+ final var components = string.trim().split(" ");
+ if (components.length == 1) {
+ return Yell.parse(string.trim());
+ }
+ 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 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 Unknown() implements Job {
+ public long yell(Map monkeys, Map results) {
+ throw new UnsupportedOperationException(); // Oof
+ }
+
+ public Simplification simplify(Map monkeys, Map results) {
+ return new Variable();
+ }
+ }
+
+ 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 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);
+ }
+ 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 monkeys = getInput();
+ final var results = new HashMap();
+ final var result = monkeys.get("root").yell(monkeys, results);
+
+ System.out.println("Part 1: " + result);
+ }
+
+ @Test
+ public final void part2() {
+ 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 c6e1598..0000000
--- a/src/test/java/com/macasaet/Day22.java
+++ /dev/null
@@ -1,157 +0,0 @@
-package com.macasaet;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-
-public class Day22 {
-
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day22.class.getResourceAsStream("/day-22-input.txt"))) {
- final var lines = StreamSupport.stream(spliterator, false)
- .collect(Collectors.toUnmodifiableList());
-
- final var player1cards = new LinkedList();
- final var player2cards = new LinkedList();
- var target = player1cards;
- for (final var line : lines) {
- if (line.isBlank()) {
- target = player2cards;
- }
- if (line.matches("^[0-9]+$")) {
- target.add(Integer.parseInt(line));
- }
- }
- var player1 = new Player(1, new LinkedList<>(player1cards));
- var player2 = new Player(2, new LinkedList<>(player2cards));
-
- // Part 1
- while (player1.hasCards() && player2.hasCards()) {
- final int p1 = player1.drawTop();
- final int p2 = player2.drawTop();
- if (p1 > p2) {
- player1.addToBottom(p1);
- player1.addToBottom(p2);
- } else {
- player2.addToBottom(p2);
- player2.addToBottom(p1);
- }
- }
- var winner = player1.hasCards() ? player1 : player2;
- System.out.println("Part 1: " + winner.score());
-
- // Part 2
- player1 = new Player(1, new LinkedList<>(player1cards));
- player2 = new Player(2, new LinkedList<>(player2cards));
- winner = playGame(player1, player2);
- System.out.println("Part 2: " + winner.score());
- }
- }
-
- protected static Player playGame(final Player player1, final Player player2) {
- final var rounds = new HashSet<>();
- while (player1.hasCards() && player2.hasCards()) {
- final var round = new Round(player1, player2);
- if (rounds.contains(round)) {
- return player1;
- }
- rounds.add(round);
- final int p1 = player1.drawTop();
- final int p2 = player2.drawTop();
- if (player1.cardCountIsAtLeast(p1) && player2.cardCountIsAtLeast(p2)) {
- final var winner = playGame(player1.clone(p1), player2.clone(p2));
- if (winner.id == player1.id) {
- player1.addToBottom(p1);
- player1.addToBottom(p2);
- } else {
- player2.addToBottom(p2);
- player2.addToBottom(p1);
- }
- } else {
- if (p1 > p2) {
- player1.addToBottom(p1);
- player1.addToBottom(p2);
- } else {
- player2.addToBottom(p2);
- player2.addToBottom(p1);
- }
- }
- }
- return player1.hasCards() ? player1 : player2;
- }
-
- protected static class Player {
- final int id;
- final List deck;
-
- public Player(final int id, final List deck) {
- this.id = id;
- this.deck = deck;
- }
-
- public boolean hasCards() {
- return !deck.isEmpty();
- }
-
- public boolean cardCountIsAtLeast(final int count) {
- return deck.size() >= count;
- }
-
- public int drawTop() {
- return deck.remove(0);
- }
-
- public void addToBottom(final int card) {
- deck.add(card);
- }
-
- public Player clone(final int cardCount) {
- return new Player(id, new LinkedList<>(deck.subList(0, cardCount)));
- }
-
- public int score() {
- int retval = 0;
- int multiplier = deck.size();
- for (final var card : deck) {
- retval += card * (multiplier--);
- }
- return retval;
- }
- }
-
- protected static class Round {
- private final List x;
- private final List y;
-
- public Round(final List x, final List y) {
- this.x = List.copyOf(x);
- this.y = List.copyOf(y);
- }
-
- public Round(final Player x, final Player y) {
- this(x.deck, y.deck);
- }
-
- public int hashCode() {
- return Objects.hash(x, y);
- }
-
- public boolean equals(final Object o) {
- if (this == o) {
- return true;
- } else if (o == null) {
- return false;
- }
- try {
- final Round other = (Round) o;
- return Objects.equals(x, other.x) && Objects.equals(y, other.y);
- } catch (final ClassCastException cce) {
- return false;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day23.java b/src/test/java/com/macasaet/Day23.java
index aa1f327..b701a77 100644
--- a/src/test/java/com/macasaet/Day23.java
+++ b/src/test/java/com/macasaet/Day23.java
@@ -1,104 +1,644 @@
package com.macasaet;
-import java.io.IOException;
-import java.math.BigInteger;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+/**
+ * --- Day 23: Unstable Diffusion ---
+ * https://adventofcode.com/2022/day/23
+ */
public class Day23 {
- public static void main(final String[] args) throws IOException {
- final var labels = new ArrayList();
- try (var inputStream = Day23.class.getResourceAsStream("/day-23-input.txt")) {
- for (final var c : new String(inputStream.readAllBytes()).strip().toCharArray()) {
- labels.add(Integer.parseInt("" + c));
- }
- }
-
- final var map = new Cup[1_000_0000 + 1];
- Cup first = null;
- Cup current = null;
- int min = Integer.MAX_VALUE;
- int max = Integer.MIN_VALUE;
- for (final int label : labels) {
- final var cup = new Cup(label);
- map[label] = cup;
- if (first == null) {
- first = cup;
- } else {
- current.next = cup;
- }
- current = cup;
-
- min = Math.min(min, label);
- max = Math.max(max, label);
- }
-
- for (int i = max + 1; i <= 1_000_000; i++) { // Part 2
- final var cup = new Cup(i);
- map[i] = cup;
- current.next = cup;
- current = cup;
- }
- max = 1_000_000;
- current.next = first;
- current = current.next;
-
- for (int moveId = 1; moveId <= 10_000_000; moveId++) { // Part 1 looped only 100 times
- final var a = current.take();
- final var b = current.take();
- final var c = current.take();
-
- int destinationValue = current.label - 1;
- while (destinationValue == a.label || destinationValue == b.label || destinationValue == c.label || destinationValue < min || destinationValue > max) {
- destinationValue--;
- if (destinationValue < min) {
- destinationValue = max;
+ record Coordinate(int x, int y) {
+ public boolean hasElf(final Map> grid) {
+ return grid.getOrDefault(x(), Collections.emptyMap()).getOrDefault(y(), false);
+ }
+
+ public Set neighbours() {
+ final var neighbours = new HashSet(8);
+ for (int i = x() - 1; i <= x() + 1; i++) {
+ for (int j = y() - 1; j <= y() + 1; j++) {
+ if (i == x() && j == y()) {
+ continue;
+ }
+ neighbours.add(new Coordinate(i, j));
}
}
+ return Collections.unmodifiableSet(neighbours);
+ }
+ }
+
+ enum Direction {
+ North {
+ public Set relativeCoordinates(Coordinate reference) {
+ return Set.of(new Coordinate(reference.x() - 1, reference.y() - 1),
+ adjacent(reference),
+ new Coordinate(reference.x() - 1, reference.y() + 1));
+ }
+
+ public Coordinate adjacent(Coordinate reference) {
+ return new Coordinate(reference.x() - 1, reference.y());
+ }
+ },
+ East {
+ public Set relativeCoordinates(Coordinate reference) {
+ return Set.of(new Coordinate(reference.x() - 1, reference.y() + 1),
+ adjacent(reference),
+ new Coordinate(reference.x() + 1, reference.y() + 1));
+ }
+
+ public Coordinate adjacent(Coordinate reference) {
+ return new Coordinate(reference.x(), reference.y() + 1);
+ }
+ },
+ South {
+ public Set relativeCoordinates(Coordinate reference) {
+ return Set.of(new Coordinate(reference.x() + 1, reference.y() - 1),
+ adjacent(reference),
+ new Coordinate(reference.x() + 1, reference.y() + 1));
+ }
+
+ public Coordinate adjacent(Coordinate reference) {
+ return new Coordinate(reference.x() + 1, reference.y());
+ }
+ },
+ West {
+ public Set relativeCoordinates(Coordinate reference) {
+ return Set.of(new Coordinate(reference.x() - 1, reference.y() - 1),
+ adjacent(reference),
+ new Coordinate(reference.x() + 1, reference.y() - 1));
+ }
+
+ public Coordinate adjacent(Coordinate reference) {
+ return new Coordinate(reference.x(), reference.y() - 1);
+ }
+ };
- final var destination = map[destinationValue];
- c.linkBefore(destination.next);
- b.linkBefore(c);
- a.linkBefore(b);
- destination.linkBefore(a);
-
- current = current.next;
- }
- final var reference = map[1];
-// String result = "";
-// var cursor = reference.next;
-// for (int i = labels.size() - 1; --i >= 0; ) {
-// result += cursor.label;
-// cursor = cursor.next;
-// }
-// System.out.println("Part 1: " + result);
- final BigInteger x = new BigInteger("" + map[reference.next.label].label);
- final BigInteger y = new BigInteger("" + map[reference.next.next.label].label);
- System.out.println("Part 2: " + x.multiply(y).toString());
+ public abstract Set relativeCoordinates(Coordinate reference);
+
+ public abstract Coordinate adjacent(Coordinate reference);
}
- protected static class Cup {
- private final int label;
- private Cup next;
+ public static class Crater {
+
+ private final Map> grid;
+ private int minX;
+ private int maxX;
+ private int minY;
+ private int maxY;
+ private List movementPriority =
+ new ArrayList<>(Arrays.asList(Direction.North, Direction.South, Direction.West, Direction.East));
- public Cup(final int label) {
- this.label = label;
+ public Crater(final Map> grid, int minX, int maxX, int minY, int maxY) {
+ this.grid = grid;
+ setMinX(minX);
+ setMinY(minY);
+ setMaxX(maxX);
+ setMaxY(maxY);
}
- public Cup take() {
- final Cup retval = next;
- next = retval.next;
- retval.next = null;
- return retval;
+ public int round() {
+ final var destinations = new HashMap>();
+ final var moves = new HashMap();
+ // first half of round: planning phase
+ for (int i = getMinX(); i <= getMaxX(); i++) {
+ final var row = getGrid().get(i);
+ for (int j = getMinY(); j <= getMaxY(); j++) {
+ final var from = new Coordinate(i, j);
+ if (row.getOrDefault(j, false)) {
+ // determine destination
+ final var hasNeighbour = from.neighbours()
+ .stream()
+ .anyMatch(c -> getGrid().getOrDefault(c.x(), Collections.emptyMap())
+ .getOrDefault(c.y(), false));
+ if (!hasNeighbour) {
+ // "If no other Elves are in one of those eight positions, the Elf does not do anything
+ // during this round."
+ continue;
+ }
+ for (final var direction : getMovementPriority()) {
+ final var clear = direction.relativeCoordinates(from)
+ .stream()
+ .noneMatch(neighbour -> neighbour.hasElf(getGrid()));
+ if (clear) {
+ final var to = direction.adjacent(from);
+ destinations.computeIfAbsent(to, _row -> new HashSet<>()).add(from);
+ moves.put(from, to);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // second half of round: movement phase
+ for (final var move : moves.entrySet()) {
+ final var from = move.getKey();
+ final var to = move.getValue();
+ // "each Elf moves to their proposed destination tile if they were the only Elf to propose moving to
+ // that position. If two or more Elves propose moving to the same position, none of those Elves move."
+ if (destinations.get(to).size() == 1) {
+ getGrid().computeIfAbsent(to.x(), _row -> new HashMap<>()).put(to.y(), true);
+ getGrid().get(from.x()).put(from.y(), false);
+ setMinX(Math.min(getMinX(), to.x()));
+ setMaxX(Math.max(getMaxX(), to.x()));
+ setMinY(Math.min(getMinY(), to.y()));
+ setMaxY(Math.max(getMaxY(), to.y()));
+ }
+ }
+ // prune edges
+ // minX
+ final var highestRow = getGrid().get(getMinX());
+ if (highestRow.values().stream().noneMatch(hasElf -> hasElf)) {
+ getGrid().remove(getMinX());
+ setMinX(getMinX() + 1);
+ }
+ // maxX
+ final var lowestRow = getGrid().get(getMaxX());
+ if (lowestRow.values().stream().noneMatch(hasElf -> hasElf)) {
+ getGrid().remove(getMaxX());
+ setMaxX(getMaxX() - 1);
+ }
+ // minY
+ if (getGrid().values().stream().map(row -> row.get(getMinY())).noneMatch(hasElf -> hasElf != null && hasElf)) {
+ for (final var row : getGrid().values()) {
+ row.remove(getMinY());
+ }
+ setMinY(getMinY() + 1);
+ }
+ // maxY
+ if (getGrid().values().stream().map(row -> row.get(getMaxY())).noneMatch(hasElf -> hasElf != null && hasElf)) {
+ for (final var row : getGrid().values()) {
+ row.remove(getMaxY());
+ }
+ setMaxY(getMaxY() + 1);
+ }
+
+ // "Finally, at the end of the round, the first direction the Elves considered is moved to the end of the
+ // list of directions."
+ final var previousFirst = getMovementPriority().remove(0);
+ getMovementPriority().add(previousFirst);
+ return moves.size();
+ }
+
+ public int countEmptyGroundTiles() {
+ int result = 0;
+ for (int i = getMinX(); i <= getMaxX(); i++) {
+ final var row = getGrid().getOrDefault(i, Collections.emptyMap());
+ for (int j = getMinY(); j <= getMaxY(); j++) {
+ if (!row.getOrDefault(j, false)) {
+ result++;
+ }
+ }
+ }
+ return result;
}
- public void linkBefore(final Cup cup) {
- next = cup;
+ public static Crater fromString(final String block) {
+ final var lines = block.split("\n");
+ final Map> grid = new HashMap<>(lines.length);
+ int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE;
+ int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE;
+ for (int i = lines.length; --i >= 0; ) {
+ final var line = lines[i];
+ final var chars = line.toCharArray();
+ final Map row = new HashMap<>(chars.length);
+ boolean rowHasElf = false;
+ for (int j = chars.length; --j >= 0; ) {
+ if (chars[j] == '#') {
+ minY = Math.min(minY, j);
+ maxY = Math.max(maxY, j);
+ row.put(j, true);
+ rowHasElf |= true;
+ } else {
+ row.put(j, false);
+ }
+ }
+ grid.put(i, row);
+ if (rowHasElf) {
+ minX = Math.min(minX, i);
+ maxX = Math.max(maxX, i);
+ }
+ }
+ return new Crater(grid, minX, maxX, minY, maxY);
}
public String toString() {
- return "Cup{ label=" + label + ", next=" + (next != null ? next.label : null) + " }";
+ final var builder = new StringBuilder();
+ builder.append("X: [").append(getMinX()).append(", ").append(getMaxX()).append("]\n");
+ builder.append("Y: [").append(getMinY()).append(", ").append(getMaxY()).append("]\n");
+ builder.append("Movement priority: ").append(getMovementPriority()).append("\n");
+ appendGrid(builder);
+ return builder.toString();
+ }
+
+ protected void appendGrid(final StringBuilder builder) {
+ for (int i = getMinX(); i <= getMaxX(); i++) {
+ final var row = getGrid().getOrDefault(i, Collections.emptyMap());
+ for (int j = getMinY(); j <= getMaxY(); j++) {
+ final var c = row.getOrDefault(j, false)
+ ? '#'
+ : '.';
+ builder.append(c);
+ }
+ builder.append('\n');
+ }
+ }
+
+ protected Map> getGrid() {
+ return grid;
+ }
+
+ protected int getMinX() {
+ return minX;
+ }
+
+ protected void setMinX(int minX) {
+ this.minX = minX;
+ }
+
+ protected int getMaxX() {
+ return maxX;
+ }
+
+ protected void setMaxX(int maxX) {
+ this.maxX = maxX;
+ }
+
+ protected int getMinY() {
+ return minY;
+ }
+
+ protected void setMinY(int minY) {
+ this.minY = minY;
+ }
+
+ protected int getMaxY() {
+ return maxY;
+ }
+
+ protected void setMaxY(int maxY) {
+ this.maxY = maxY;
+ }
+
+ protected List getMovementPriority() {
+ return movementPriority;
+ }
+
+ protected void setMovementPriority(List movementPriority) {
+ this.movementPriority = movementPriority;
+ }
+ }
+
+ protected static Crater getInput() {
+ final var lines = StreamSupport.stream(new LineSpliterator("day-23.txt"), false)
+ .collect(Collectors.joining("\n"));
+ return Crater.fromString(lines);
+ }
+
+ @Test
+ public final void part1() {
+ final var crater = getInput();
+ for (int i = 10; --i >= 0; crater.round()) ;
+ final var result = crater.countEmptyGroundTiles();
+
+ System.out.println("Part 1: " + result);
+ }
+
+ @Test
+ public final void testRound1() {
+ // given
+ final var block = """
+ ##
+ #.
+ ..
+ ##
+ """;
+ final var crater = Crater.fromString(block);
+
+ // when
+ crater.round();
+
+ // then
+ final var builder = new StringBuilder();
+ crater.appendGrid(builder);
+ final var result = builder.toString();
+ Assertions.assertEquals("""
+ ##
+ ..
+ #.
+ .#
+ #.
+ """, result);
+ }
+
+ @Test
+ public final void testRound2() {
+ // given
+ final var block = """
+ ##
+ ..
+ #.
+ .#
+ #.
+ """;
+ final var crater = Crater.fromString(block);
+ crater.setMovementPriority(new ArrayList<>(Arrays.asList(Direction.South, Direction.West, Direction.East, Direction.North)));
+
+ // when
+ crater.round();
+
+ // then
+ final var builder = new StringBuilder();
+ crater.appendGrid(builder);
+ final var result = builder.toString();
+ Assertions.assertEquals("""
+ .##.
+ #...
+ ...#
+ ....
+ .#..
+ """, result);
+ }
+
+ @Test
+ public final void testRound3() {
+ // given
+ final var block = """
+ .##.
+ #...
+ ...#
+ ....
+ .#..
+ """;
+ final var crater = Crater.fromString(block);
+ crater.setMovementPriority(new ArrayList<>(Arrays.asList(Direction.West, Direction.East, Direction.North, Direction.South)));
+
+ // when
+ crater.round();
+
+ // then
+ final var builder = new StringBuilder();
+ crater.appendGrid(builder);
+ final var result = builder.toString();
+ Assertions.assertEquals("""
+ ..#..
+ ....#
+ #....
+ ....#
+ .....
+ ..#..
+ """, result);
+ }
+
+ @Test
+ public final void testLargerRound1() {
+ // given
+ final var block = """
+ .......#......
+ .....###.#....
+ ...#...#.#....
+ ....#...##....
+ ...#.###......
+ ...##.#.##....
+ ....#..#......
+ """;
+ final var crater = Crater.fromString(block);
+
+ // when
+ crater.round();
+
+ // then
+ final var builder = new StringBuilder();
+ crater.appendGrid(builder);
+ final var result = builder.toString();
+ Assertions.assertEquals("""
+ .....#...
+ ...#...#.
+ .#..#.#..
+ .....#..#
+ ..#.#.##.
+ #..#.#...
+ #.#.#.##.
+ .........
+ ..#..#...
+ """, result);
+ }
+
+ @Test
+ public final void testLargerRound2() {
+ // given
+ final var block = """
+ .....#...
+ ...#...#.
+ .#..#.#..
+ .....#..#
+ ..#.#.##.
+ #..#.#...
+ #.#.#.##.
+ .........
+ ..#..#...
+ """;
+ final var crater = Crater.fromString(block);
+ crater.setMovementPriority(new ArrayList<>(Arrays.asList(Direction.South, Direction.West, Direction.East, Direction.North)));
+
+ // when
+ crater.round();
+
+ // then
+ final var builder = new StringBuilder();
+ crater.appendGrid(builder);
+ final var result = builder.toString();
+ Assertions.assertEquals("""
+ ......#....
+ ...#.....#.
+ ..#..#.#...
+ ......#...#
+ ..#..#.#...
+ #...#.#.#..
+ ...........
+ .#.#.#.##..
+ ...#..#....
+ """, result);
+ }
+
+ @Test
+ public final void testLargerRound3() {
+ // given
+ final var block = """
+ ......#....
+ ...#.....#.
+ ..#..#.#...
+ ......#...#
+ ..#..#.#...
+ #...#.#.#..
+ ...........
+ .#.#.#.##..
+ ...#..#....
+ """;
+ final var crater = Crater.fromString(block);
+ crater.setMovementPriority(new ArrayList<>(Arrays.asList(Direction.West, Direction.East, Direction.North, Direction.South)));
+
+ // when
+ crater.round();
+
+ // then
+ final var builder = new StringBuilder();
+ crater.appendGrid(builder);
+ final var result = builder.toString();
+ Assertions.assertEquals("""
+ ......#....
+ ....#....#.
+ .#..#...#..
+ ......#...#
+ ..#..#.#...
+ #..#.....#.
+ ......##...
+ .##.#....#.
+ ..#........
+ ......#....
+ """, result);
+ }
+
+ @Test
+ public final void testLargerRound4() {
+ // given
+ final var block = """
+ ......#....
+ ....#....#.
+ .#..#...#..
+ ......#...#
+ ..#..#.#...
+ #..#.....#.
+ ......##...
+ .##.#....#.
+ ..#........
+ ......#....
+ """;
+ final var crater = Crater.fromString(block);
+ crater.setMovementPriority(new ArrayList<>(Arrays.asList(Direction.East, Direction.North, Direction.South, Direction.West)));
+
+ // when
+ crater.round();
+
+ // then
+ final var builder = new StringBuilder();
+ crater.appendGrid(builder);
+ final var result = builder.toString();
+ Assertions.assertEquals("""
+ ......#....
+ .....#....#
+ .#...##....
+ ..#.....#.#
+ ........#..
+ #...###..#.
+ .#......#..
+ ...##....#.
+ ...#.......
+ ......#....
+ """, result);
+ }
+
+ @Test
+ public final void testLargerRound5() {
+ // given
+ final var block = """
+ ......#....
+ .....#....#
+ .#...##....
+ ..#.....#.#
+ ........#..
+ #...###..#.
+ .#......#..
+ ...##....#.
+ ...#.......
+ ......#....
+ """;
+ final var crater = Crater.fromString(block);
+ crater.setMovementPriority(new ArrayList<>(Arrays.asList(Direction.North, Direction.South, Direction.West, Direction.East)));
+
+ // when
+ crater.round();
+
+ // then
+ final var builder = new StringBuilder();
+ crater.appendGrid(builder);
+ final var result = builder.toString();
+ Assertions.assertEquals("""
+ ......#....
+ ...........
+ .#..#.....#
+ ........#..
+ .....##...#
+ #.#.####...
+ ..........#
+ ...##..#...
+ .#.........
+ .........#.
+ ...#..#....
+ """, result);
+ }
+
+ @Test
+ public final void testLargerRounds() {
+ // given
+ final var block = """
+ .......#......
+ .....###.#....
+ ...#...#.#....
+ ....#...##....
+ ...#.###......
+ ...##.#.##....
+ ....#..#......
+ """;
+ final var crater = Crater.fromString(block);
+
+ // when
+ for (int i = 10; --i >= 0; crater.round()) ;
+
+ // then
+ final var builder = new StringBuilder();
+ crater.appendGrid(builder);
+ final var result = builder.toString();
+ Assertions.assertEquals("""
+ ......#.....
+ ..........#.
+ .#.#..#.....
+ .....#......
+ ..#.....#..#
+ #......##...
+ ....##......
+ .#........#.
+ ...#.#..#...
+ ............
+ ...#..#..#..
+ """, result);
+ Assertions.assertEquals(110, crater.countEmptyGroundTiles());
+ }
+
+ @Test
+ public final void part2() {
+ final var crater = getInput();
+ int rounds = 0;
+ while (true) {
+ final var moves = crater.round();
+ rounds++;
+ if (moves == 0) {
+ break;
+ }
}
+ System.out.println("Part 2: " + rounds);
}
}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day24.java b/src/test/java/com/macasaet/Day24.java
deleted file mode 100644
index 3d3282e..0000000
--- a/src/test/java/com/macasaet/Day24.java
+++ /dev/null
@@ -1,179 +0,0 @@
-package com.macasaet;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map.Entry;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
-
-public class Day24 {
-
- public static void main(final String[] args) throws IOException {
- final Address origin = new Address(0, 0, 0);
- final var lobbyFloor = new HashMap();
- try (var spliterator = new LineSpliterator(Day22.class.getResourceAsStream("/day-24-input.txt"))) {
- StreamSupport.stream(spliterator, false)
- .map(String::toCharArray)
- .map(array -> {
- int i = 0;
- var directions = new ArrayList(array.length);
- while (i < array.length) {
- if (i < array.length - 1) { // check if there are at least 2 chars available
- final var twoLetterDirection = "" + array[i] + array[i + 1];
- // check if two chars is a valid direction
- if ("se".equalsIgnoreCase(twoLetterDirection)
- || "sw".equalsIgnoreCase(twoLetterDirection)
- || "nw".equalsIgnoreCase(twoLetterDirection)
- || "ne".equalsIgnoreCase(twoLetterDirection)) {
- directions.add(Direction.forAbbreviation(twoLetterDirection));
- i = i + 2;
- continue;
- }
- }
- final var oneLetterDirection = "" + array[i];
- if ("e".equalsIgnoreCase(oneLetterDirection) || "w".equalsIgnoreCase(oneLetterDirection)) {
- directions.add(Direction.forAbbreviation(oneLetterDirection));
- i = i + 1;
- continue;
- }
- throw new IllegalArgumentException("Invalid direction: " + oneLetterDirection);
- }
- return Collections.unmodifiableList(directions);
- }).map(directions -> {
- Address cursor = origin;
- for (final var direction : directions) {
- cursor = direction.travel(cursor);
- }
- return cursor;
- }).forEach(address -> lobbyFloor.merge(address, 1, (old, def) -> old + 1));
- }
- final int blackTiles = lobbyFloor.values().stream().mapToInt(count -> count % 2).sum();
- System.out.println("Part 1: " + blackTiles);
-
- for (int i = 1; i <= 100; i++) {
- final var tasks = lobbyFloor.entrySet().stream().flatMap(entry -> {
- // get the mapped tile
- // as well as unmapped (white) adjacent tiles
- final var from = entry.getKey();
- return Stream.concat(Stream.of(entry), Arrays.stream(Direction.values()).flatMap(direction -> {
- final var neighbour = direction.travel(from);
- if (!lobbyFloor.containsKey(neighbour)) {
- // neighbour has never been visited, create a virtual entry for them
- return Stream.of(new Entry() {
- public Address getKey() {
- return neighbour;
- }
-
- public Integer getValue() {
- return 0;
- }
-
- public Integer setValue(Integer value) {
- throw new UnsupportedOperationException();
- }
- });
- }
- return Stream.empty();
- }));
- }).map(entry -> { // NB: this might not be a real tile
- final var address = entry.getKey();
- final int adjacentBlackTiles = Arrays.stream(Direction.values())
- .map(direction -> direction.travel(address))
- .mapToInt(neighbour -> {
- final Integer neighbouringCount = lobbyFloor.get(neighbour);
- return neighbouringCount != null && neighbouringCount % 2 == 1 ? 1 : 0;
- }).sum();
- if (entry.getValue() % 2 == 1 && (adjacentBlackTiles == 0 || adjacentBlackTiles > 2)) {
- // Any black tile with zero or more than 2 black tiles immediately adjacent to it is flipped to white.
- return (Runnable) () -> lobbyFloor.put(address, 0);
- } else if (entry.getValue() % 2 == 0 && adjacentBlackTiles == 2) {
- // Any white tile with exactly 2 black tiles immediately adjacent to it is flipped to black.
- return (Runnable) () -> lobbyFloor.put(address, 1);
- }
- return (Runnable) () -> {
- };
- }).collect(Collectors.toUnmodifiableList());
- for (final var task : tasks) {
- task.run();
- }
- }
- final int count = lobbyFloor.values().stream().mapToInt(value -> value % 2).sum();
- System.out.println("Part 2: " + count);
- }
-
- public enum Direction {
- EAST("e", 1, -1, 0),
- SOUTH_EAST("se", 0, -1, 1),
- SOUTH_WEST("sw", -1, 0, 1),
- WEST("w", -1, 1, 0),
- NORTH_WEST("nw", 0, 1, -1),
- NORTH_EAST("ne", 1, 0, -1);
-
- private final String abbreviation;
- private final int xOffset;
- private final int yOffset;
- private final int zOffset;
-
- Direction(final String abbreviation, final int xOffset, final int yOffset, final int zOffset) {
- this.abbreviation = abbreviation;
- this.xOffset = xOffset;
- this.yOffset = yOffset;
- this.zOffset = zOffset;
- }
-
- public static Direction forAbbreviation(final String abbreviation) {
- for (final var candidate : values()) {
- if (candidate.abbreviation.equalsIgnoreCase(abbreviation)) {
- return candidate;
- }
- }
- throw new IllegalArgumentException("Invalid direction: " + abbreviation);
- }
-
- public Address travel(final Address from) {
- return new Address(from.x + xOffset, from.y + yOffset, from.z + zOffset);
- }
-
- }
-
- public static class Address {
-
- private final int x;
- private final int y;
- private final int z;
-
- public Address(final int x, final int y, final int z) {
- this.x = x;
- this.y = y;
- this.z = z;
- }
-
- public int hashCode() {
- int retval = 0;
- retval = 31 * retval + x;
- retval = 31 * retval + y;
- retval = 31 * retval + z;
- return retval;
- }
-
- public boolean equals(final Object o) {
- if (this == o) {
- return true;
- } else if (o == null) {
- return false;
- }
- try {
- final Address other = (Address) o;
- return x == other.x && y == other.y && z == other.z;
- } catch (final ClassCastException cce) {
- return false;
- }
- }
-
- }
-
-}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/Day25.java b/src/test/java/com/macasaet/Day25.java
deleted file mode 100644
index 66be1df..0000000
--- a/src/test/java/com/macasaet/Day25.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.macasaet;
-
-import java.io.IOException;
-import java.util.stream.Collectors;
-import java.util.stream.StreamSupport;
-
-public class Day25 {
-
- public static void main(final String[] args) throws IOException {
- try (var spliterator = new LineSpliterator(Day22.class.getResourceAsStream("/day-25-input.txt"))) {
- final var publicKeys = StreamSupport.stream(spliterator, false)
- .map(Integer::parseInt)
- .collect(Collectors.toUnmodifiableList());
- final int cardPublicKey = publicKeys.get(0);
- final int doorPublicKey = publicKeys.get(1);
- final long initialSubjectNumber = 7;
-
- int cardLoopSize = 0;
- for (long value = 1; value != cardPublicKey; cardLoopSize++) {
- value = transformOnce(value, initialSubjectNumber);
- }
- System.out.println("cardLoopSize: " + cardLoopSize);
-
- int doorLoopSize = 0;
- for (long value = 1; value != doorPublicKey; doorLoopSize++) {
- value = transformOnce(value, initialSubjectNumber);
- }
- System.out.println("doorLoopSize: " + doorLoopSize);
-
- final long e1 = transformCompletely(doorPublicKey, cardLoopSize);
- final long e2 = transformCompletely(cardPublicKey, doorLoopSize);
- System.out.println("e1: " + e1);
- System.out.println("e2: " + e2);
- }
- }
-
- protected static long transformCompletely(final long subjectNumber, final int loopSize) {
- long value = 1;
- for (int i = loopSize; --i >= 0; value = transformOnce(value, subjectNumber)) ;
- return value;
- }
-
- protected static long transformOnce(final long value, final long subjectNumber) {
- return (value * subjectNumber) % 20201227;
- }
-
-}
\ No newline at end of file
diff --git a/src/test/java/com/macasaet/LineSpliterator.java b/src/test/java/com/macasaet/LineSpliterator.java
index 5333f65..6e3c360 100644
--- a/src/test/java/com/macasaet/LineSpliterator.java
+++ b/src/test/java/com/macasaet/LineSpliterator.java
@@ -1,18 +1,26 @@
package com.macasaet;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
+import java.io.*;
import java.util.Objects;
+import java.util.Properties;
import java.util.Spliterator;
import java.util.function.Consumer;
public class LineSpliterator implements Spliterator, AutoCloseable {
+ private static final String prefix;
private final BufferedReader reader;
+ static {
+ final var properties = new Properties();
+ try {
+ final var config = LineSpliterator.class.getResourceAsStream("/config.properties");
+ if (config != null) properties.load(config);
+ } catch (final IOException ignored) {
+ }
+ prefix = properties.getProperty("prefix", "/sample");
+ }
+
public LineSpliterator(final BufferedReader reader) {
Objects.requireNonNull(reader);
this.reader = reader;
@@ -26,6 +34,10 @@ public LineSpliterator(final InputStream stream) {
this(new InputStreamReader(stream));
}
+ public LineSpliterator(final String fileName) {
+ this(LineSpliterator.class.getResourceAsStream(prefix + "/" + fileName));
+ }
+
public boolean tryAdvance(final Consumer super String> action) {
try {
final var line = reader.readLine();
@@ -35,7 +47,6 @@ public boolean tryAdvance(final Consumer super String> action) {
}
reader.close();
} catch (final IOException ioe) {
- ioe.printStackTrace();
}
return false;
}
diff --git a/src/test/resources/sample/day-01.txt b/src/test/resources/sample/day-01.txt
new file mode 100644
index 0000000..444e241
--- /dev/null
+++ b/src/test/resources/sample/day-01.txt
@@ -0,0 +1,14 @@
+1000
+2000
+3000
+
+4000
+
+5000
+6000
+
+7000
+8000
+9000
+
+10000
\ No newline at end of file
diff --git a/src/test/resources/sample/day-02.txt b/src/test/resources/sample/day-02.txt
new file mode 100644
index 0000000..25097e8
--- /dev/null
+++ b/src/test/resources/sample/day-02.txt
@@ -0,0 +1,3 @@
+A Y
+B X
+C Z
\ No newline at end of file
diff --git a/src/test/resources/sample/day-03.txt b/src/test/resources/sample/day-03.txt
new file mode 100644
index 0000000..9919ffa
--- /dev/null
+++ b/src/test/resources/sample/day-03.txt
@@ -0,0 +1,6 @@
+vJrwpWtwJgWrhcsFMMfFFhFp
+jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
+PmmdzqPrVvPwwTWBwg
+wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
+ttgJtRGJQctTZtZT
+CrZsJsPPZsGzwwsLwLmpwMDw
\ No newline at end of file
diff --git a/src/test/resources/sample/day-04.txt b/src/test/resources/sample/day-04.txt
new file mode 100644
index 0000000..99a66c5
--- /dev/null
+++ b/src/test/resources/sample/day-04.txt
@@ -0,0 +1,6 @@
+2-4,6-8
+2-3,4-5
+5-7,7-9
+2-8,3-7
+6-6,4-6
+2-6,4-8
\ No newline at end of file
diff --git a/src/test/resources/sample/day-05.txt b/src/test/resources/sample/day-05.txt
new file mode 100644
index 0000000..e98aba4
--- /dev/null
+++ b/src/test/resources/sample/day-05.txt
@@ -0,0 +1,9 @@
+ [D]
+[N] [C]
+[Z] [M] [P]
+ 1 2 3
+
+move 1 from 2 to 1
+move 3 from 1 to 3
+move 2 from 2 to 1
+move 1 from 1 to 2
\ No newline at end of file
diff --git a/src/test/resources/sample/day-06.txt b/src/test/resources/sample/day-06.txt
new file mode 100644
index 0000000..5a2b0a7
--- /dev/null
+++ b/src/test/resources/sample/day-06.txt
@@ -0,0 +1 @@
+mjqjpqmgbljsphdztnvjfqwrcgsmlb
\ No newline at end of file
diff --git a/src/test/resources/sample/day-07.txt b/src/test/resources/sample/day-07.txt
new file mode 100644
index 0000000..bcbb513
--- /dev/null
+++ b/src/test/resources/sample/day-07.txt
@@ -0,0 +1,23 @@
+$ cd /
+$ ls
+dir a
+14848514 b.txt
+8504156 c.dat
+dir d
+$ cd a
+$ ls
+dir e
+29116 f
+2557 g
+62596 h.lst
+$ cd e
+$ ls
+584 i
+$ cd ..
+$ cd ..
+$ cd d
+$ ls
+4060174 j
+8033020 d.log
+5626152 d.ext
+7214296 k
\ No newline at end of file
diff --git a/src/test/resources/sample/day-08.txt b/src/test/resources/sample/day-08.txt
new file mode 100644
index 0000000..6557024
--- /dev/null
+++ b/src/test/resources/sample/day-08.txt
@@ -0,0 +1,5 @@
+30373
+25512
+65332
+33549
+35390
\ No newline at end of file
diff --git a/src/test/resources/sample/day-09.txt b/src/test/resources/sample/day-09.txt
new file mode 100644
index 0000000..cbea2b3
--- /dev/null
+++ b/src/test/resources/sample/day-09.txt
@@ -0,0 +1,8 @@
+R 4
+U 4
+L 3
+D 1
+R 4
+D 1
+L 5
+R 2
\ No newline at end of file
diff --git a/src/test/resources/sample/day-10.txt b/src/test/resources/sample/day-10.txt
new file mode 100644
index 0000000..94cd0a8
--- /dev/null
+++ b/src/test/resources/sample/day-10.txt
@@ -0,0 +1,146 @@
+addx 15
+addx -11
+addx 6
+addx -3
+addx 5
+addx -1
+addx -8
+addx 13
+addx 4
+noop
+addx -1
+addx 5
+addx -1
+addx 5
+addx -1
+addx 5
+addx -1
+addx 5
+addx -1
+addx -35
+addx 1
+addx 24
+addx -19
+addx 1
+addx 16
+addx -11
+noop
+noop
+addx 21
+addx -15
+noop
+noop
+addx -3
+addx 9
+addx 1
+addx -3
+addx 8
+addx 1
+addx 5
+noop
+noop
+noop
+noop
+noop
+addx -36
+noop
+addx 1
+addx 7
+noop
+noop
+noop
+addx 2
+addx 6
+noop
+noop
+noop
+noop
+noop
+addx 1
+noop
+noop
+addx 7
+addx 1
+noop
+addx -13
+addx 13
+addx 7
+noop
+addx 1
+addx -33
+noop
+noop
+noop
+addx 2
+noop
+noop
+noop
+addx 8
+noop
+addx -1
+addx 2
+addx 1
+noop
+addx 17
+addx -9
+addx 1
+addx 1
+addx -3
+addx 11
+noop
+noop
+addx 1
+noop
+addx 1
+noop
+noop
+addx -13
+addx -19
+addx 1
+addx 3
+addx 26
+addx -30
+addx 12
+addx -1
+addx 3
+addx 1
+noop
+noop
+noop
+addx -9
+addx 18
+addx 1
+addx 2
+noop
+noop
+addx 9
+noop
+noop
+noop
+addx -1
+addx 2
+addx -37
+addx 1
+addx 3
+noop
+addx 15
+addx -21
+addx 22
+addx -6
+addx 1
+noop
+addx 2
+addx 1
+noop
+addx -10
+noop
+noop
+addx 20
+addx 1
+addx 2
+addx 2
+addx -6
+addx -11
+noop
+noop
+noop
\ No newline at end of file
diff --git a/src/test/resources/sample/day-11.txt b/src/test/resources/sample/day-11.txt
new file mode 100644
index 0000000..c04eddb
--- /dev/null
+++ b/src/test/resources/sample/day-11.txt
@@ -0,0 +1,27 @@
+Monkey 0:
+ Starting items: 79, 98
+ Operation: new = old * 19
+ Test: divisible by 23
+ If true: throw to monkey 2
+ If false: throw to monkey 3
+
+Monkey 1:
+ Starting items: 54, 65, 75, 74
+ Operation: new = old + 6
+ Test: divisible by 19
+ If true: throw to monkey 2
+ If false: throw to monkey 0
+
+Monkey 2:
+ Starting items: 79, 60, 97
+ Operation: new = old * old
+ Test: divisible by 13
+ If true: throw to monkey 1
+ If false: throw to monkey 3
+
+Monkey 3:
+ Starting items: 74
+ Operation: new = old + 3
+ Test: divisible by 17
+ If true: throw to monkey 0
+ If false: throw to monkey 1
\ No newline at end of file
diff --git a/src/test/resources/sample/day-12.txt b/src/test/resources/sample/day-12.txt
new file mode 100644
index 0000000..86e9cac
--- /dev/null
+++ b/src/test/resources/sample/day-12.txt
@@ -0,0 +1,5 @@
+Sabqponm
+abcryxxl
+accszExk
+acctuvwj
+abdefghi
diff --git a/src/test/resources/sample/day-13.txt b/src/test/resources/sample/day-13.txt
new file mode 100644
index 0000000..27c8912
--- /dev/null
+++ b/src/test/resources/sample/day-13.txt
@@ -0,0 +1,23 @@
+[1,1,3,1,1]
+[1,1,5,1,1]
+
+[[1],[2,3,4]]
+[[1],4]
+
+[9]
+[[8,7,6]]
+
+[[4,4],4,4]
+[[4,4],4,4,4]
+
+[7,7,7,7]
+[7,7,7]
+
+[]
+[3]
+
+[[[]]]
+[[]]
+
+[1,[2,[3,[4,[5,6,7]]]],8,9]
+[1,[2,[3,[4,[5,6,0]]]],8,9]
\ No newline at end of file
diff --git a/src/test/resources/sample/day-14.txt b/src/test/resources/sample/day-14.txt
new file mode 100644
index 0000000..1926028
--- /dev/null
+++ b/src/test/resources/sample/day-14.txt
@@ -0,0 +1,2 @@
+498,4 -> 498,6 -> 496,6
+503,4 -> 502,4 -> 502,9 -> 494,9
\ No newline at end of file
diff --git a/src/test/resources/sample/day-15.txt b/src/test/resources/sample/day-15.txt
new file mode 100644
index 0000000..652e631
--- /dev/null
+++ b/src/test/resources/sample/day-15.txt
@@ -0,0 +1,14 @@
+Sensor at x=2, y=18: closest beacon is at x=-2, y=15
+Sensor at x=9, y=16: closest beacon is at x=10, y=16
+Sensor at x=13, y=2: closest beacon is at x=15, y=3
+Sensor at x=12, y=14: closest beacon is at x=10, y=16
+Sensor at x=10, y=20: closest beacon is at x=10, y=16
+Sensor at x=14, y=17: closest beacon is at x=10, y=16
+Sensor at x=8, y=7: closest beacon is at x=2, y=10
+Sensor at x=2, y=0: closest beacon is at x=2, y=10
+Sensor at x=0, y=11: closest beacon is at x=2, y=10
+Sensor at x=20, y=14: closest beacon is at x=25, y=17
+Sensor at x=17, y=20: closest beacon is at x=21, y=22
+Sensor at x=16, y=7: closest beacon is at x=15, y=3
+Sensor at x=14, y=3: closest beacon is at x=15, y=3
+Sensor at x=20, y=1: closest beacon is at x=15, y=3
\ No newline at end of file
diff --git a/src/test/resources/sample/day-18.txt b/src/test/resources/sample/day-18.txt
new file mode 100644
index 0000000..d18bf98
--- /dev/null
+++ b/src/test/resources/sample/day-18.txt
@@ -0,0 +1,13 @@
+2,2,2
+1,2,2
+3,2,2
+2,1,2
+2,3,2
+2,2,1
+2,2,3
+2,2,4
+2,2,6
+1,2,5
+3,2,5
+2,1,5
+2,3,5
\ No newline at end of file
diff --git a/src/test/resources/sample/day-20.txt b/src/test/resources/sample/day-20.txt
new file mode 100644
index 0000000..5cbf3d9
--- /dev/null
+++ b/src/test/resources/sample/day-20.txt
@@ -0,0 +1,7 @@
+1
+2
+-3
+3
+-2
+0
+4
\ No newline at end of file
diff --git a/src/test/resources/sample/day-21.txt b/src/test/resources/sample/day-21.txt
new file mode 100644
index 0000000..7993b87
--- /dev/null
+++ b/src/test/resources/sample/day-21.txt
@@ -0,0 +1,15 @@
+root: pppw + sjmn
+dbpl: 5
+cczh: sllz + lgvd
+zczc: 2
+ptdq: humn - dvpt
+dvpt: 3
+lfqf: 4
+humn: 5
+ljgn: 2
+sjmn: drzm * dbpl
+sllz: 4
+pppw: cczh / lfqf
+lgvd: ljgn * ptdq
+drzm: hmdt - zczc
+hmdt: 32
\ No newline at end of file
diff --git a/src/test/resources/sample/day-23.txt b/src/test/resources/sample/day-23.txt
new file mode 100644
index 0000000..7ac3ba9
--- /dev/null
+++ b/src/test/resources/sample/day-23.txt
@@ -0,0 +1,7 @@
+....#..
+..###.#
+#...#.#
+.#...##
+#.###..
+##.#.##
+.#..#..
\ No newline at end of file
| |