From 64a26f94f89adfe85fd56ec3f56293c1991f76e5 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Tue, 29 Nov 2022 21:42:40 -0800 Subject: [PATCH 01/21] WIP Days 23-25 --- .gitignore | 1 + src/test/java/com/macasaet/Day23.java | 11 +- src/test/java/com/macasaet/Day24.java | 284 ++++++++++++++++++++++++++ src/test/java/com/macasaet/Day25.java | 159 ++++++++++++++ src/test/resources/sample/day-24.txt | 2 + src/test/resources/sample/day-25.txt | 9 + 6 files changed, 462 insertions(+), 4 deletions(-) create mode 100755 src/test/java/com/macasaet/Day24.java create mode 100755 src/test/java/com/macasaet/Day25.java create mode 100755 src/test/resources/sample/day-24.txt create mode 100755 src/test/resources/sample/day-25.txt diff --git a/.gitignore b/.gitignore index db33628..4d131ae 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ target/ src/test/resources/config.properties src/test/resources/input src/test/resources/2020 +.DS_Store diff --git a/src/test/java/com/macasaet/Day23.java b/src/test/java/com/macasaet/Day23.java index b023161..c545b82 100644 --- a/src/test/java/com/macasaet/Day23.java +++ b/src/test/java/com/macasaet/Day23.java @@ -1,7 +1,8 @@ package com.macasaet; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -12,8 +13,8 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * --- Day 23: Amphipod --- @@ -796,6 +797,7 @@ public final void verifyEstimatedDistanceIsZero() { assertEquals(0, result); } + @Disabled @Test public final void verifyEstimationOrdering() { // given @@ -883,6 +885,7 @@ public final void part1() { System.out.println("Part 1: " + lowest(initial)); } + @Disabled @Test public final void part2() { final var lines = getInput().collect(Collectors.toList()); diff --git a/src/test/java/com/macasaet/Day24.java b/src/test/java/com/macasaet/Day24.java new file mode 100755 index 0000000..51eea06 --- /dev/null +++ b/src/test/java/com/macasaet/Day24.java @@ -0,0 +1,284 @@ +package com.macasaet; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.PrimitiveIterator; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * --- Day 24: Arithmetic Logic Unit --- + */ +public class Day24 { + + protected Stream getInput() { + return StreamSupport + .stream(new LineSpliterator("day-24.txt"), + false); + } + + public static class ArithmeticLogicUnit { + public BigInteger getW() { + return w; + } + + public void setW(BigInteger w) { + this.w = w; + } + + public BigInteger getX() { + return x; + } + + public void setX(BigInteger x) { + this.x = x; + } + + public BigInteger getY() { + return y; + } + + public void setY(BigInteger y) { + this.y = y; + } + + public BigInteger getZ() { + return z; + } + + public void setZ(BigInteger z) { + this.z = z; + } + + private BigInteger w = BigInteger.ZERO, x = BigInteger.ZERO, y = BigInteger.ZERO, z = BigInteger.ZERO; + + public List getInstructions() { + return instructions; + } + + public void setInstructions(List instructions) { + this.instructions = instructions; + } + + private List instructions = new ArrayList<>(); + + public boolean isValid(final String modelNumber) { + final var iterator = modelNumber.chars().map(Character::getNumericValue).iterator(); + for (final var instruction : getInstructions()) { + instruction.evaluate(iterator); + } + return BigInteger.ZERO.equals(getZ()); + } + + public static ArithmeticLogicUnit parse(final Stream lines) { + final var result = new ArithmeticLogicUnit(); + final List instructions = lines.map(line -> { + final var components = line.split(" "); + + final Consumer resultSetter = switch (components[1]) { + case "w" -> result::setW; + case "x" -> result::setX; + case "y" -> result::setY; + case "z" -> result::setZ; + default -> throw new IllegalArgumentException("Invalid instruction, invalid l-value: " + line); + }; + final Supplier xSupplier = switch (components[1]) { + case "w" -> result::getW; + case "x" -> result::getX; + case "y" -> result::getY; + case "z" -> result::getZ; + default -> throw new IllegalArgumentException("Invalid instruction, invalid l-value: " + line); + }; + final Supplier ySupplier = components.length > 2 ? switch (components[2]) { + case "w" -> result::getW; + case "x" -> result::getX; + case "y" -> result::getY; + case "z" -> result::getZ; + default -> () -> new BigInteger(components[2]); + } : () -> { + throw new IllegalStateException(); + }; + return switch (components[0]) { + case "inp" -> new Input(resultSetter); + case "add" -> new Add(resultSetter, xSupplier, ySupplier); + case "mul" -> new Multiply(resultSetter, xSupplier, ySupplier); + case "div" -> new Divide(resultSetter, xSupplier, ySupplier); + case "mod" -> new Modulo(resultSetter, xSupplier, ySupplier); + case "eql" -> new Equals(resultSetter, xSupplier, ySupplier); + default -> throw new IllegalArgumentException("Invalid instruction: " + line); + }; + }).toList(); + result.setInstructions(instructions); + return result; + } + + public void reset() { + setW(BigInteger.ZERO); + setX(BigInteger.ZERO); + setY(BigInteger.ZERO); + setZ(BigInteger.ZERO); + } + } + + public interface Instruction { + void evaluate(PrimitiveIterator.OfInt input); + } + + public record Input(Consumer setter) implements Instruction { + + public void evaluate(PrimitiveIterator.OfInt input) { + setter.accept(BigInteger.valueOf(input.nextInt())); + } + } + + public record Add(Consumer resultSetter, Supplier xSupplier, + Supplier ySupplier) implements Instruction { + public void evaluate(PrimitiveIterator.OfInt _input) { + resultSetter.accept(xSupplier.get().add(ySupplier.get())); + } + } + + public record Multiply(Consumer resultSetter, Supplier xSupplier, + Supplier ySupplier) implements Instruction { + public void evaluate(PrimitiveIterator.OfInt _input) { + resultSetter.accept(xSupplier.get().multiply(ySupplier.get())); + } + } + + public record Divide(Consumer resultSetter, Supplier xSupplier, + Supplier ySupplier) implements Instruction { + public void evaluate(PrimitiveIterator.OfInt _input) { + resultSetter.accept(xSupplier.get().divide(ySupplier.get())); + } + } + + public record Modulo(Consumer resultSetter, Supplier xSupplier, + Supplier ySupplier) implements Instruction { + public void evaluate(PrimitiveIterator.OfInt _input) { + resultSetter.accept(xSupplier.get().mod(ySupplier.get())); + } + } + + public record Equals(Consumer resultSetter, Supplier xSupplier, + Supplier ySupplier) implements Instruction { + public void evaluate(PrimitiveIterator.OfInt _input) { + resultSetter.accept(xSupplier.get().equals(ySupplier.get()) ? BigInteger.ONE : BigInteger.ZERO); + } + } + + @Nested + public class ArithmeticLogicUnitTest { + @Disabled + @Test + public void testNegation() { + // given + final var input = """ + inp x + mul x -1 + """; + final var alu = ArithmeticLogicUnit.parse(input.lines()); + + // when + alu.isValid("7"); + + // then + assertEquals(-7, alu.getX()); + } + + @Disabled + @Test + public final void testThreeTimes() { + // given + final var input = """ + inp z + inp x + mul z 3 + eql z x + """; + final var alu = ArithmeticLogicUnit.parse(input.lines()); + + // when + alu.isValid("39"); + + // then + assertEquals(1, alu.getZ()); + } + + @Disabled + @Test + public final void testBinaryConversion() { + // given + final var input = """ + inp w + add z w + mod z 2 + div w 2 + add y w + mod y 2 + div w 2 + add x w + mod x 2 + div w 2 + mod w 2 + """; + final var alu = ArithmeticLogicUnit.parse(input.lines()); + + // when + alu.isValid("9"); + + // then + assertEquals(1, alu.getW()); + assertEquals(0, alu.getX()); + assertEquals(0, alu.getY()); + assertEquals(1, alu.getZ()); + } + + @Test + public final void testIsValid() { + // given + final var alu = ArithmeticLogicUnit.parse(getInput()); + + // when + final var result = alu.isValid("13579246899999"); + + // then + System.err.println("z=" + alu.getZ()); + } + } + + @Disabled + @Test + public final void part1() { + final var monad = getInput().toList(); + final var counter = new AtomicInteger(0); + final var result = Stream.iterate(new BigInteger("99999999999999"), previous -> previous.subtract(BigInteger.ONE)) + .parallel() + .filter(candidate -> { + final int count = counter.updateAndGet(previous -> previous + 1); + if(count % 10000 == 0) { + System.err.println("Testing: " + candidate); + } + final var alu = ArithmeticLogicUnit.parse(monad.stream()); + return alu.isValid(candidate.toString()); + }).findFirst(); + System.out.println("Part 1: " + result.orElseThrow()); + } + + @Disabled + @Test + public final void part2() { + + System.out.println("Part 2: " + null); + } + +} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day25.java b/src/test/java/com/macasaet/Day25.java new file mode 100755 index 0000000..d9e5335 --- /dev/null +++ b/src/test/java/com/macasaet/Day25.java @@ -0,0 +1,159 @@ +package com.macasaet; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * + */ +public class Day25 { + + protected Stream getInput() { + return StreamSupport + .stream(new LineSpliterator("day-25.txt"), + false); + } + + public record Herd() { + + } + + public record SeaCucumber() { + + } + + public record OceanFloor(char[][] grid) { + public static OceanFloor parse(final Stream lines) { + final var list = lines.toList(); + final var grid = new char[list.size()][]; + for (int i = list.size(); --i >= 0; ) { + final var line = list.get(i); + grid[i] = new char[line.length()]; + for (int j = line.length(); --j >= 0; grid[i][j] = line.charAt(j)) ; + } + return new OceanFloor(grid); + } + + public OceanFloor step() { + return stepEast().stepSouth(); + } + + boolean isOccupied(final int x, final int y) { + return grid[x][y] != '.'; + } + + char[][] createBlankGrid() { + final char[][] result = new char[grid().length][]; + for (int i = grid().length; --i >= 0; ) { + final var originalRow = grid()[i]; + final var newRow = new char[originalRow.length]; + for (int j = originalRow.length; --j >= 0; newRow[j] = '.') ; + result[i] = newRow; + } + return result; + } + + OceanFloor stepEast() { + final char[][] copy = createBlankGrid(); + for (int i = grid().length; --i >= 0; ) { + final var originalRow = grid()[i]; + for (int j = originalRow.length; --j >= 0; ) { + final var nextIndex = (j + 1) % originalRow.length; + if (originalRow[j] == '>') { + if (!isOccupied(i, nextIndex)) { + copy[i][nextIndex] = '>'; + } else { + copy[i][j] = '>'; + } + } else if (originalRow[j] != '.') { + copy[i][j] = originalRow[j]; + } + } + } + return new OceanFloor(copy); + } + + OceanFloor stepSouth() { + final char[][] copy = createBlankGrid(); + for (int i = grid().length; --i >= 0; ) { + final var originalRow = grid()[i]; + final var nextIndex = (i + 1) % grid().length; + for (int j = originalRow.length; --j >= 0; ) { + if (originalRow[j] == 'v') { + if (!isOccupied(nextIndex, j)) { + copy[nextIndex][j] = 'v'; + } else { + copy[i][j] = 'v'; + } + } else if (originalRow[j] != '.') { + copy[i][j] = originalRow[j]; + } + } + } + return new OceanFloor(copy); + } + + public String toString() { + final var builder = new StringBuilder(); + for (final var row : grid()) { + builder.append(row).append('\n'); + } + return builder.toString(); + } + + public int hashCode() { + int result = 1; + for (final var row : grid()) { + result += 31 * result + Arrays.hashCode(row); + } + return result; + } + + public boolean equals(final Object o) { + if (o == null) { + return false; + } else if (this == o) { + return true; + } + try { + final OceanFloor other = (OceanFloor) o; + if (grid().length != other.grid().length) { + return false; + } + for (int i = grid.length; --i >= 0; ) { + final var mine = grid()[i]; + final var theirs = other.grid()[i]; + if (!Arrays.equals(mine, theirs)) { + return false; + } + } + return true; + } catch (final ClassCastException _cce) { + return false; + } + } + } + + @Test + public final void part1() { + var oceanFloor = OceanFloor.parse(getInput()); + for (int i = 1; ; i++) { + final var next = oceanFloor.step(); + if (next.equals(oceanFloor)) { + System.out.println("Part 1: " + i); + break; + } + oceanFloor = next; + } + } + + @Test + public final void part2() { + + System.out.println("Part 2: " + null); + } + +} \ No newline at end of file diff --git a/src/test/resources/sample/day-24.txt b/src/test/resources/sample/day-24.txt new file mode 100755 index 0000000..a41747d --- /dev/null +++ b/src/test/resources/sample/day-24.txt @@ -0,0 +1,2 @@ +inp x +mul x -1 diff --git a/src/test/resources/sample/day-25.txt b/src/test/resources/sample/day-25.txt new file mode 100755 index 0000000..0f3c0cd --- /dev/null +++ b/src/test/resources/sample/day-25.txt @@ -0,0 +1,9 @@ +v...>>.vv> +.vv>>.vv.. +>>.>v>...v +>>v>>.>.v. +v>v.vv.v.. +>.>>..v... +.vv..>.>v. +v.v..>>v.v +....v..v.> From c6cad88a788d5a74acc164e27369db4eae5fdcb8 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Tue, 29 Nov 2022 21:54:08 -0800 Subject: [PATCH 02/21] Prepare for 2022 --- .github/workflows/ci.yml | 10 +- .java-version | 2 +- README.md | 8 +- pom.xml | 10 +- src/test/java/com/macasaet/Day01.java | 46 +- src/test/java/com/macasaet/Day02.java | 110 ---- src/test/java/com/macasaet/Day03.java | 120 ---- src/test/java/com/macasaet/Day04.java | 181 ------ src/test/java/com/macasaet/Day05.java | 188 ------ src/test/java/com/macasaet/Day06.java | 120 ---- src/test/java/com/macasaet/Day07.java | 113 ---- src/test/java/com/macasaet/Day08.java | 225 ------- src/test/java/com/macasaet/Day09.java | 177 ----- src/test/java/com/macasaet/Day10.java | 137 ---- src/test/java/com/macasaet/Day11.java | 169 ----- src/test/java/com/macasaet/Day12.java | 182 ------ src/test/java/com/macasaet/Day13.java | 187 ------ src/test/java/com/macasaet/Day14.java | 166 ----- src/test/java/com/macasaet/Day15.java | 228 ------- src/test/java/com/macasaet/Day16.java | 352 ---------- src/test/java/com/macasaet/Day17.java | 138 ---- src/test/java/com/macasaet/Day18.java | 432 ------------- src/test/java/com/macasaet/Day19.java | 395 ----------- src/test/java/com/macasaet/Day20.java | 233 ------- src/test/java/com/macasaet/Day21.java | 159 ----- src/test/java/com/macasaet/Day22.java | 202 ------ src/test/java/com/macasaet/Day23.java | 900 -------------------------- src/test/java/com/macasaet/Day24.java | 284 -------- src/test/java/com/macasaet/Day25.java | 159 ----- src/test/resources/sample/day-01.txt | 10 - src/test/resources/sample/day-02.txt | 6 - src/test/resources/sample/day-03.txt | 12 - src/test/resources/sample/day-04.txt | 19 - src/test/resources/sample/day-05.txt | 10 - src/test/resources/sample/day-06.txt | 1 - src/test/resources/sample/day-07.txt | 1 - src/test/resources/sample/day-08.txt | 10 - src/test/resources/sample/day-09.txt | 5 - src/test/resources/sample/day-10.txt | 10 - src/test/resources/sample/day-11.txt | 10 - src/test/resources/sample/day-12.txt | 7 - src/test/resources/sample/day-13.txt | 21 - src/test/resources/sample/day-14.txt | 18 - src/test/resources/sample/day-15.txt | 10 - src/test/resources/sample/day-16.txt | 1 - src/test/resources/sample/day-17.txt | 1 - src/test/resources/sample/day-18.txt | 10 - src/test/resources/sample/day-19.txt | 136 ---- src/test/resources/sample/day-20.txt | 7 - src/test/resources/sample/day-21.txt | 2 - src/test/resources/sample/day-22.txt | 60 -- src/test/resources/sample/day-23.txt | 5 - src/test/resources/sample/day-24.txt | 2 - src/test/resources/sample/day-25.txt | 9 - 54 files changed, 28 insertions(+), 5988 deletions(-) delete mode 100644 src/test/java/com/macasaet/Day02.java delete mode 100644 src/test/java/com/macasaet/Day03.java delete mode 100644 src/test/java/com/macasaet/Day04.java delete mode 100644 src/test/java/com/macasaet/Day05.java delete mode 100644 src/test/java/com/macasaet/Day06.java delete mode 100644 src/test/java/com/macasaet/Day07.java delete mode 100644 src/test/java/com/macasaet/Day08.java delete mode 100644 src/test/java/com/macasaet/Day09.java delete mode 100644 src/test/java/com/macasaet/Day10.java delete mode 100644 src/test/java/com/macasaet/Day11.java delete mode 100644 src/test/java/com/macasaet/Day12.java delete mode 100644 src/test/java/com/macasaet/Day13.java delete mode 100644 src/test/java/com/macasaet/Day14.java delete mode 100644 src/test/java/com/macasaet/Day15.java delete mode 100644 src/test/java/com/macasaet/Day16.java delete mode 100644 src/test/java/com/macasaet/Day17.java delete mode 100644 src/test/java/com/macasaet/Day18.java delete mode 100644 src/test/java/com/macasaet/Day19.java delete mode 100644 src/test/java/com/macasaet/Day20.java delete mode 100644 src/test/java/com/macasaet/Day21.java delete mode 100644 src/test/java/com/macasaet/Day22.java delete mode 100644 src/test/java/com/macasaet/Day23.java delete mode 100755 src/test/java/com/macasaet/Day24.java delete mode 100755 src/test/java/com/macasaet/Day25.java delete mode 100644 src/test/resources/sample/day-01.txt delete mode 100644 src/test/resources/sample/day-02.txt delete mode 100644 src/test/resources/sample/day-03.txt delete mode 100644 src/test/resources/sample/day-04.txt delete mode 100644 src/test/resources/sample/day-05.txt delete mode 100644 src/test/resources/sample/day-06.txt delete mode 100644 src/test/resources/sample/day-07.txt delete mode 100644 src/test/resources/sample/day-08.txt delete mode 100644 src/test/resources/sample/day-09.txt delete mode 100644 src/test/resources/sample/day-10.txt delete mode 100644 src/test/resources/sample/day-11.txt delete mode 100644 src/test/resources/sample/day-12.txt delete mode 100644 src/test/resources/sample/day-13.txt delete mode 100644 src/test/resources/sample/day-14.txt delete mode 100644 src/test/resources/sample/day-15.txt delete mode 100644 src/test/resources/sample/day-16.txt delete mode 100644 src/test/resources/sample/day-17.txt delete mode 100644 src/test/resources/sample/day-18.txt delete mode 100644 src/test/resources/sample/day-19.txt delete mode 100644 src/test/resources/sample/day-20.txt delete mode 100644 src/test/resources/sample/day-21.txt delete mode 100644 src/test/resources/sample/day-22.txt delete mode 100644 src/test/resources/sample/day-23.txt delete mode 100755 src/test/resources/sample/day-24.txt delete mode 100755 src/test/resources/sample/day-25.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8c1a94..1e789d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,18 +10,18 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 with: distribution: 'temurin' - java-version: '17' + java-version: '19' check-latest: true - uses: actions/cache@v2 with: path: ~/.m2 - key: m2-${{ runner.os }}-${{ matrix.java-version }}-${{ hashFiles('**/pom.xml') }} + key: m2-${{ runner.os }}-19-${{ hashFiles('**/pom.xml') }} restore-keys: | - m2-${{ runner.os }}-${{ matrix.java-version }} + m2-${{ runner.os }}-19 m2-${{ runner.os }} m2 - run: mvn clean install diff --git a/.java-version b/.java-version index 98d9bcb..6ea9a3b 100644 --- a/.java-version +++ b/.java-version @@ -1 +1 @@ -17 +19.0 diff --git a/README.md b/README.md index 8235f91..29e9c19 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -# Advent of Code 2021 (Java) +# Advent of Code 2022 (Java) This is the code I used to solve the -[Advent of Code](https://adventofcode.com/2021) puzzles. The main branch +[Advent of Code](https://adventofcode.com/2022) puzzles. The main branch contains the most recent year in which I participated. Other years have their own branch. ## Other Editions -* 2020 Advent of Code ([Java](https://github.com/l0s/advent-of-code-java/tree/2020) | [Rust](https://github.com/l0s/advent-of-code-rust)) +* 2020 Advent of Code ([Java](https://github.com/l0s/advent-of-code-java/tree/2020) | [Rust](https://github.com/l0s/advent-of-code-rust/releases/tag/y2020)) +* 2021 Advent of Code ([Java](https://github.com/l0s/advent-of-code-java/releases/tag/2021)) +* 2022 Advent of Code ([Rust](https://github.com/l0s/advent-of-code-rust)) \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7099d59..fc13d64 100644 --- a/pom.xml +++ b/pom.xml @@ -6,21 +6,21 @@ com.macasaet advent-of-code - 0.2021.0-SNAPSHOT + 0.2022.0-SNAPSHOT - Advent of Code 2021 + Advent of Code 2022 UTF-8 - 17 - 17 + 19 + 19 org.junit.jupiter junit-jupiter - 5.8.2 + 5.9.0 diff --git a/src/test/java/com/macasaet/Day01.java b/src/test/java/com/macasaet/Day01.java index 0485635..7af31fc 100644 --- a/src/test/java/com/macasaet/Day01.java +++ b/src/test/java/com/macasaet/Day01.java @@ -1,65 +1,43 @@ package com.macasaet; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; import java.util.stream.StreamSupport; -import org.junit.jupiter.api.Test; - /** - * --- Day 1: Sonar Sweep --- + * --- Day 1: --- */ public class Day01 { /** - * Perform a sonar sweep of the nearby sea floor. * - * @return measurements of the sea floor depth further and further away from the submarine + * + * @return */ - protected List getInput() { + protected List getInput() { return StreamSupport .stream(new LineSpliterator("day-01.txt"), false) - .mapToInt(Integer::parseInt) .collect(ArrayList::new, List::add, List::addAll); } + @Disabled @Test public final void part1() { final var list = getInput(); - int increases = countIncreases(list); - System.out.println("Part 1: " + increases); + + System.out.println("Part 1: " + null); } + @Disabled @Test public final void part2() { final var list = getInput(); - final var windows = new LinkedList(); - for (int i = 2; i < list.size(); i++) { - windows.add(list.get(i) + list.get(i - 1) + list.get(i - 2)); - } - final int increases = countIncreases(windows); - System.out.println("Part 2: " + increases); - } - /** - * Determine how quickly the depth increases. - * - * @param list progressively further measurements of the sea floor depth - * @return the number of times a depth measurement increase from the previous measurement - */ - protected int countIncreases(final List list) { - int previous = list.get(0); - int increases = 0; - for (int i = 1; i < list.size(); i++) { - final var current = list.get(i); - if (current > previous) { - increases++; - } - previous = current; - } - return increases; + System.out.println("Part 2: " + null); } } \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day02.java b/src/test/java/com/macasaet/Day02.java deleted file mode 100644 index f5a2e9f..0000000 --- a/src/test/java/com/macasaet/Day02.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.macasaet; - -import java.util.Locale; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 2: Dive! --- - */ -public class Day02 { - - public enum Operation { - FORWARD { - public Position adjust(Position position, int magnitude) { - return new Position(position.horizontalPosition() + magnitude, position.depth()); - } - - public OrientedPosition adjust(OrientedPosition position, int magnitude) { - return new OrientedPosition(position.horizontalPosition() + magnitude, - position.depth() + (position.aim() * magnitude), - position.aim()); - } - }, - DOWN { - public Position adjust(Position position, int magnitude) { - return new Position(position.horizontalPosition(), position.depth() + magnitude); - } - - public OrientedPosition adjust(OrientedPosition position, int magnitude) { - return new OrientedPosition(position.horizontalPosition(), - position.depth(), - position.aim() + magnitude); - } - }, - UP { - public Position adjust(Position position, int magnitude) { - return new Position(position.horizontalPosition(), position.depth() - magnitude); - } - - public OrientedPosition adjust(OrientedPosition position, int magnitude) { - return new OrientedPosition(position.horizontalPosition(), - position.depth(), - position.aim() - magnitude); - } - }; - - public abstract Position adjust(Position position, int magnitude); - - public abstract OrientedPosition adjust(OrientedPosition position, int magnitude); - } - - public record Command(Operation operation, int magnitude) { - public static Command parse(final String string) { - final String[] components = string.split(" "); - final var operation = Operation.valueOf(components[0].toUpperCase(Locale.US)); - final int magnitude = Integer.parseInt(components[1]); - return new Command(operation, magnitude); - } - - public Position adjust(final Position position) { - return operation().adjust(position, magnitude()); - } - - public OrientedPosition adjust(final OrientedPosition position) { - return operation().adjust(position, magnitude()); - } - } - - public record Position(int horizontalPosition, int depth) { - public int result() { - return horizontalPosition() * depth(); - } - } - - public record OrientedPosition(int horizontalPosition, int depth, int aim) { - public int result() { - return horizontalPosition() * depth(); - } - } - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-02.txt"), - false) - .map(Command::parse); - } - - @Test - public final void part1() { - var position = new Position(0, 0); - for (final var i = getInput().iterator(); i.hasNext(); ) { - final var operation = i.next(); - position = operation.adjust(position); - } - System.out.println("Part 1: " + position.result()); - } - - @Test - public final void part2() { - var position = new OrientedPosition(0, 0, 0); - for (final var i = getInput().iterator(); i.hasNext(); ) { - final var operation = i.next(); - position = operation.adjust(position); - } - System.out.println("Part 2: " + position.result()); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day03.java b/src/test/java/com/macasaet/Day03.java deleted file mode 100644 index 47f6ec5..0000000 --- a/src/test/java/com/macasaet/Day03.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.macasaet; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 3: Binary Diagnostic --- - */ -public class Day03 { - - /** - * @return "a list of binary numbers which, when decoded properly, can tell you many useful things about the - * conditions of the submarine" - */ - protected Stream getDiagnosticReport() { - return StreamSupport - .stream(new LineSpliterator("day-03.txt"), - false) - .map(string -> { - final var chars = string.toCharArray(); - final var bits = new byte[chars.length]; - for (int i = chars.length; --i >= 0; bits[i] = chars[i] == '0' ? (byte) 0 : (byte) 1) ; - return bits; - }); - } - - protected int toUnsignedInt(final byte[] bits) { - int result = 0; - for (int i = bits.length; --i >= 0; result += bits[i] * Math.pow(2, bits.length - i - 1)) ; - return result; - } - - @Test - public final void part1() { - final var list = getDiagnosticReport().collect(Collectors.toList()); - final int width = list.get(0).length; - final int[] zeroCounts = new int[width]; - for (int i = zeroCounts.length; --i >= 0; zeroCounts[i] = 0) ; - final int[] oneCounts = new int[width]; - for (int i = oneCounts.length; --i >= 0; oneCounts[i] = 0) ; - for (final var array : list) { - for (int j = 0; j < width; j++) { - if (array[j] == 0) { - zeroCounts[j] += 1; - } else { - oneCounts[j] += 1; - } - } - } - final byte[] gammaArray = new byte[width]; - final byte[] epsilonArray = new byte[width]; - for (int i = gammaArray.length; --i >= 0; ) { - gammaArray[i] = zeroCounts[i] > oneCounts[i] ? (byte) 0 : (byte) 1; - epsilonArray[i] = zeroCounts[i] > oneCounts[i] ? (byte) 1 : (byte) 0; - } - - final int gammaRate = toUnsignedInt(gammaArray); - final int epsilonRate = toUnsignedInt(epsilonArray); - System.out.println("Part 1: " + (gammaRate * epsilonRate)); - } - - @Test - public final void part2() { - final var list = getDiagnosticReport().collect(Collectors.toList()); - final int width = list.get(0).length; - List oxygenCandidates = new ArrayList<>(list); - for (int i = 0; i < width && oxygenCandidates.size() > 1; i++) { - int zeros = 0; - int ones = 0; - for (final var value : oxygenCandidates) { - if (value[i] == 0) { - zeros++; - } else { - ones++; - } - } - final int index = i; - if (ones >= zeros) { - oxygenCandidates = oxygenCandidates.stream().filter(value -> value[index] == 1).collect(Collectors.toList()); - } else { - oxygenCandidates = oxygenCandidates.stream().filter(value -> value[index] == 0).collect(Collectors.toList()); - } - } - if (oxygenCandidates.size() > 1) { - throw new IllegalStateException("Too many oxygen candidates"); - } - List co2Candidates = new ArrayList<>(list); - for (int i = 0; i < width && co2Candidates.size() > 1; i++) { - int zeros = 0; - int ones = 0; - for (final var value : co2Candidates) { - if (value[i] == 0) { - zeros++; - } else { - ones++; - } - } - final int index = i; - if (zeros <= ones) { - co2Candidates = co2Candidates.stream().filter(value -> value[index] == 0).collect(Collectors.toList()); - } else { - co2Candidates = co2Candidates.stream().filter(value -> value[index] == 1).collect(Collectors.toList()); - } - } - if (co2Candidates.size() > 1) { - throw new IllegalStateException("Too many CO2 candidates"); - } - final byte[] oxyArray = oxygenCandidates.get(0); - final byte[] co2Array = co2Candidates.get(0); - final int oxygenGeneratorRating = toUnsignedInt(oxyArray); - final int co2ScrubberRating = toUnsignedInt(co2Array); - System.out.println("Part 2: " + (oxygenGeneratorRating * co2ScrubberRating)); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day04.java b/src/test/java/com/macasaet/Day04.java deleted file mode 100644 index d69541d..0000000 --- a/src/test/java/com/macasaet/Day04.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.macasaet; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 4: Giant Squid --- - */ -public class Day04 { - - /** - * The number of rows and columns on each square Bingo card - */ - static final int EDGE_LENGTH = 5; - - public static class Board { - private final int[][] grid; - private final boolean[][] marked; - - protected Board(final int[][] grid, final boolean[][] marked) { - this.grid = grid; - this.marked = marked; - } - - public Board(final int[][] grid) { - this(grid, new boolean[grid.length][]); - for (int i = grid.length; --i >= 0; this.marked[i] = new boolean[grid.length]) ; - } - - public boolean isWinner() { - // check rows - for (int i = marked.length; --i >= 0; ) { - final var row = marked[i]; - boolean complete = true; - for (int j = row.length; --j >= 0 && complete; complete = row[j]) ; - if (complete) { - return true; - } - } - // check columns - for (int j = marked.length; --j >= 0; ) { - boolean complete = true; - for (int i = marked.length; --i >= 0 && complete; complete = marked[i][j]) ; - if (complete) { - return true; - } - } - return false; - } - - public int score(final int lastDrawn) { - int sum = 0; - for (int i = grid.length; --i >= 0; ) { - final var row = grid[i]; - for (int j = row.length; --j >= 0; ) { - if (!marked[i][j]) { - sum += row[j]; - } - } - } - return sum * lastDrawn; - } - - public void mark(final int drawn) { - for (int i = grid.length; --i >= 0; ) { - final var row = grid[i]; - for (int j = row.length; --j >= 0; ) { - if (row[j] == drawn) { - marked[i][j] = true; - } - } - } - } - } - - public record Game(List boards, List numbers) { - - public int countBoards() { - return boards().size(); - } - - public void removeBoard(final int index) { - this.boards().remove(index); - } - - } - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-04.txt"), - false); - } - - protected Game getGame() { - final var input = getInput().iterator(); - final var moves = input.next(); - - List boards = new ArrayList<>(); - int[][] grid = null; - int gridIndex = -1; - while (input.hasNext()) { - final var line = input.next(); - if (line.isBlank()) { - if (grid != null) { - boards.add(new Board(grid)); - } - grid = new int[EDGE_LENGTH][]; - for (int i = EDGE_LENGTH; --i >= 0; grid[i] = new int[EDGE_LENGTH]) ; - gridIndex = 0; - continue; - } - final var cells = line.split("\\s"); - if (cells.length > 0) { - final var values = Arrays.stream(cells) - .filter(candidate -> !candidate.isBlank()) - .mapToInt(Integer::parseInt) - .toArray(); - if (values.length > 0) { - grid[gridIndex++] = values; - } - } - } - if (grid != null) { - boards.add(new Board(grid)); - } - final var moveArray = Arrays.stream(moves.split(",")) - .mapToInt(Integer::parseInt) - .collect(ArrayList::new, List::add, List::addAll); - return new Game(boards, moveArray); - } - - @Test - public final void part1() { - final var game = getGame(); - for (final var number : game.numbers()) { - for (final var board : game.boards()) { - board.mark(number); - if (board.isWinner()) { - final int score = board.score(number); - System.out.println("Part 1: " + score); - return; - } - } - } - throw new IllegalStateException("No winners"); - } - - @Test - public final void part2() { - final var game = getGame(); - for (final var number : game.numbers()) { - if (game.countBoards() == 1) { - final var lastWinner = game.boards().get(0); - lastWinner.mark(number); - if (!lastWinner.isWinner()) { - continue; - } - System.out.println("Part 2: " + lastWinner.score(number)); - return; - } - final List idsToRemove = new ArrayList<>(); - for (int i = game.boards().size(); --i >= 0; ) { - final var board = game.boards().get(i); - board.mark(number); - if (board.isWinner()) { - idsToRemove.add(i); - } - } - for (final var id : idsToRemove) { - game.removeBoard(id); - } - } - throw new IllegalStateException("Tie for last place"); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day05.java b/src/test/java/com/macasaet/Day05.java deleted file mode 100644 index bb23695..0000000 --- a/src/test/java/com/macasaet/Day05.java +++ /dev/null @@ -1,188 +0,0 @@ -package com.macasaet; - -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 5: Hydrothermal Venture --- - */ -public class Day05 { - - /** - * A point on the ocean floor - */ - public static record Point(int x, int y) { - public static Point parse(final String string) { - final var components = string.split(","); - return new Point(Integer.parseInt(components[0]), Integer.parseInt(components[1])); - } - - } - - /** - * A portion of the ocean floor on which there are hydrothermal vents, which produce large, opaque clouds - */ - public static record LineSegment(Point start, Point end) { - public static LineSegment parse(final String string) { - final var components = string.split(" -> "); - return new LineSegment(Point.parse(components[0]), Point.parse(components[1])); - } - - /** - * Highlight the location of this line segment on the diagram. Each cell that this segment covers will have its - * value incremented. The higher the number, the more vents cover the cell. - * - * @param diagram A map of the ocean floor which will be updated by this call. - */ - public void update(final int[][] diagram) { - /* - "Because of the limits of the hydrothermal vent mapping system, the lines in your list will only ever be - horizontal, vertical, or a diagonal line at exactly 45 degrees." - */ - final int horizontalStep = start().x() == end().x() - ? 0 - : start().x() < end().x() - ? 1 - : -1; - final int verticalStep = start().y() == end().y() - ? 0 - : start().y() < end().y() - ? 1 - : -1; - final Predicate xTester = start().x() == end().x() - ? x -> true - : start().x() < end().x() - ? x -> x <= end().x() - : x -> x >= end().x(); - final Predicate yTester = start().y() == end().y() - ? y -> true - : start().y() < end().y() - ? y -> y <= end().y() - : y -> y >= end().y(); - - for (int i = start().x(), j = start().y(); - xTester.test(i) && yTester.test(j); - i += horizontalStep, j += verticalStep) { - diagram[i][j]++; - } - } - - public int lowestX() { - return Math.min(start().x(), end().x()); - } - - public int highestX() { - return Math.max(start().x(), end().x()); - } - - public int lowestY() { - return Math.min(start().y(), end().y()); - } - - public int highestY() { - return Math.max(start().y(), end().y()); - } - - public String toString() { - return start() + " -> " + end(); - } - } - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-05.txt"), - false) - .map(LineSegment::parse); - } - - protected record Extremes(int lowestX, int lowestY, int highestX, int highestY) { - public Extremes combine(final LineSegment segment) { - return new Extremes(Math.min(lowestX(), segment.lowestX()), - Math.min(lowestY(), segment.lowestY()), - Math.max(highestX(), segment.highestX()), - Math.max(highestY(), segment.highestY())); - } - - public Extremes combine(final Extremes other) { - return new Extremes(Math.min(lowestX(), other.lowestX()), - Math.min(lowestY(), other.lowestY()), - Math.max(highestX(), other.highestX()), - Math.max(highestY(), other.highestY())); - } - - public int[][] createBlankDiagram() { - final int[][] result = new int[highestX() + 1][]; - for (int i = result.length; --i >= 0; result[i] = new int[highestY() + 1]) ; - return result; - } - } - - @Test - public final void part1() { - final var segments = getInput() - // "For now, only consider horizontal and vertical lines: lines where either x1 = x2 or y1 = y2." - .filter(segment -> segment.start().x() == segment.end().x() || segment.start().y() == segment.end().y()) - .collect(Collectors.toList()); - - final var extremes = segments - .stream() - .reduce(new Extremes(0, 0, 0, 0), - Extremes::combine, - Extremes::combine); - // there are no negative values - // Note, we could save a little bit of space and time by using a smaller map since none of the line segments - // need point 0,0. However, the savings are likely negligible. - final int[][] diagram = extremes.createBlankDiagram(); - - for (final var segment : segments) { - segment.update(diagram); - } - int sum = 0; - for (int i = diagram.length; --i >= 0; ) { - final var row = diagram[i]; - for (int j = row.length; --j >= 0; ) { - if (row[j] >= 2) { - sum++; - } - } - } - System.out.println("Part 1: " + sum); - } - - @Test - public final void part2() { - /* - "Unfortunately, considering only horizontal and vertical lines doesn't give you the full picture; you need to - also consider diagonal lines." - */ - final var segments = getInput() - .collect(Collectors.toList()); - final var extremes = segments - .stream() - .reduce(new Extremes(0, 0, 0, 0), - Extremes::combine, - Extremes::combine); - // there are no negative values - // Note, we could save a little bit of space and time by using a smaller map since none of the line segments - // need point 0,0. However, the savings are likely negligible. - final int[][] diagram = extremes.createBlankDiagram(); - for (final var segment : segments) { - segment.update(diagram); - } - int sum = 0; - for (int i = diagram.length; --i >= 0; ) { - final var row = diagram[i]; - for (int j = row.length; --j >= 0; ) { - if (row[j] >= 2) { - sum++; - } - } - } - System.out.println("Part 2: " + sum); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day06.java b/src/test/java/com/macasaet/Day06.java deleted file mode 100644 index 10a59b3..0000000 --- a/src/test/java/com/macasaet/Day06.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.macasaet; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 6: Lanternfish --- - */ -public class Day06 { - - /** - * A glowing fish that spawns very quickly. Their population grows exponentially. - */ - public static class Lanternfish { - - private int daysToSpawn; - - /** - * @param daysToSpawn the number of days until it creates a new {@link Lanternfish} - */ - public Lanternfish(final int daysToSpawn) { - setDaysToSpawn(daysToSpawn); - } - - /** - * Simulate the passage of one day - * - * @return either a new Lanternfish or nothing depending on whether the fish spawned - */ - public Optional tick() { - final var timer = getDaysToSpawn() - 1; - if (timer < 0) { - setDaysToSpawn(6); - return Optional.of(new Lanternfish(8)); - } else { - setDaysToSpawn(timer); - return Optional.empty(); - } - } - - /** - * @return the number of days until the fish spawns - */ - public int getDaysToSpawn() { - return this.daysToSpawn; - } - - /** - * Update this fish's days to spawn - * - * @param daysToSpawn the number of days until the fish spawns, must be non-negative - */ - protected void setDaysToSpawn(final int daysToSpawn) { - if (daysToSpawn < 0) { - throw new IllegalArgumentException("\"days to spawn\" must be non-negative"); - } - this.daysToSpawn = daysToSpawn; - } - } - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-06.txt"), - false); - } - - public List parseInput() { - final var list = getInput().toList(); - final var line = list.get(0); - final var components = line.split(","); - return Arrays.stream(components) - .mapToInt(Integer::parseInt) - .mapToObj(Lanternfish::new) - .collect(Collectors.toList()); - } - - @Test - public final void part1() { - var population = parseInput(); - for (int _i = 80; --_i >= 0; ) { - final List list = new ArrayList<>(population); - for (final var fish : population) { - final var result = fish.tick(); - result.ifPresent(list::add); - } - population = list; - } - System.out.println("Part 1: " + population.size()); - } - - @Test - public final void part2() { - final var initial = parseInput(); - var map = new long[9]; - for (final var fish : initial) { - map[fish.getDaysToSpawn()]++; - } - for (int _i = 256; --_i >= 0; ) { - final var temp = new long[map.length]; - for (int daysToSpawn = map.length; --daysToSpawn >= 0; ) { - final var count = map[daysToSpawn]; - final var prototype = new Lanternfish(daysToSpawn); - final var result = prototype.tick(); - temp[prototype.getDaysToSpawn()] += count; - result.ifPresent(spawn -> temp[spawn.getDaysToSpawn()] = temp[spawn.getDaysToSpawn()] + count); - } - map = temp; - } - final var result = Arrays.stream(map).reduce(0L, Long::sum); - System.out.println("Part 2: " + result); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day07.java b/src/test/java/com/macasaet/Day07.java deleted file mode 100644 index 4393d46..0000000 --- a/src/test/java/com/macasaet/Day07.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.macasaet; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 7: The Treachery of Whales --- - */ -public class Day07 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-07.txt"), - false); - } - - /** - * @return the horizontal position of each crab submarine in the swarm - */ - protected List getCrabPositions() { - final var list = getInput().collect(Collectors.toList()); - final var line = list.get(0); - return Arrays.stream(line.split(",")) - .mapToInt(Integer::parseInt) - .collect(ArrayList::new, List::add, List::addAll); - } - - /** - * Assuming a constant fuel consumption rate, calculate the fuel required for the swarm to reach alignmentPoint. - * - * @param positions the starting position of each crab submarine - * @param alignmentPoint a potential point for the crabs to gather in order to blast a hole in the ocean floor - * @return the fuel required to reach the alignment point - */ - protected int calculateConstantFuel(final Iterable positions, final int alignmentPoint) { - int sum = 0; - for (final var position : positions) { - sum += Math.abs(alignmentPoint - position); - } - return sum; - } - - /** - * Calculate the fuel required for the swarm to reach alignmentPoint - * - * @param positions the starting position for each crab submarine - * @param alignmentPoint a potential point for the crabs to gather in order to blast a hole in the ocean floor - * @return the fuel required to reach the alignment point - */ - protected int calculateFuel(final Iterable positions, final int alignmentPoint) { - int sum = 0; - for (final var position : positions) { - sum += calculateFuel(position, alignmentPoint); - } - return sum; - } - - /** - * Calculate the fuel required for a single crab submarine to travel from one horizontal position to the next. - * - * @param start the starting position (inclusive) - * @param end the ending position (inclusive) - * @return the amount of fuel consumed in the journey - */ - protected int calculateFuel(final int start, final int end) { - final int target = Math.abs(end - start); - int sum = 0; - for (int i = target; --i >= 0; ) { - sum += i + 1; - } - return sum; - } - - @Test - public final void part1() { - int min = Integer.MAX_VALUE; - int max = Integer.MIN_VALUE; - final var positions = getCrabPositions(); - for (final var position : positions) { - min = Math.min(min, position); - max = Math.max(max, position); - } - final int result = IntStream.range(min, max) - .map(alignmentPoint -> calculateConstantFuel(positions, alignmentPoint)) - .min() - .getAsInt(); - System.out.println("Part 1: " + result); - } - - @Test - public final void part2() { - int min = Integer.MAX_VALUE; - int max = Integer.MIN_VALUE; - final var positions = getCrabPositions(); - for (final var position : positions) { - min = Math.min(min, position); - max = Math.max(max, position); - } - final int result = IntStream.range(min, max) - .map(alignmentPoint -> calculateFuel(positions, alignmentPoint)) - .min() - .getAsInt(); - 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 deleted file mode 100644 index 7ac8b58..0000000 --- a/src/test/java/com/macasaet/Day08.java +++ /dev/null @@ -1,225 +0,0 @@ -package com.macasaet; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 8: Seven Segment Search --- - */ -public class Day08 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-08.txt"), - false); - } - - public record Digit(List segments) { - public Digit decode(final Map map) { - return new Digit(segments().stream() - .map(map::get) - .collect(Collectors.toUnmodifiableList())); - } - - public int asInt() { - if (segments().size() == 6 && hasSegments('a', 'b', 'c', 'e', 'f', 'g')) { // FIXME use on/off mechanism - return 0; - } else if (segments().size() == 2 && hasSegments('c', 'f')) { - return 1; - } else if (segments().size() == 5 && hasSegments('a', 'c', 'd', 'e', 'g')) { - return 2; - } else if (segments().size() == 5 && hasSegments('a', 'c', 'd', 'f', 'g')) { - return 3; - } else if (segments().size() == 4 && hasSegments('b', 'c', 'd', 'f')) { - return 4; - } else if (segments().size() == 5 && hasSegments('a', 'b', 'd', 'f', 'g')) { - return 5; - } else if (segments().size() == 6 && hasSegments('a', 'b', 'd', 'e', 'f', 'g')) { - return 6; - } else if (segments().size() == 3 && hasSegments('a', 'c', 'f')) { - return 7; - } else if (segments().size() == 7 && hasSegments('a', 'b', 'c', 'd', 'e', 'f', 'g')) { - return 8; - } else if (segments().size() == 6 && hasSegments('a', 'b', 'c', 'd', 'f', 'g')) { - return 9; - } - throw new IllegalStateException("Invalid Digit: " + this); - } - - public boolean hasSegments(final char... segments) { - for (final var segment : segments) { - if (!hasSegment(segment)) { - return false; - } - } - return true; - } - - public boolean hasSegment(final char segment) { - return segments().contains(segment); - } - - public static Digit parse(final String string) { - final var array = string.toCharArray(); - final var list = new ArrayList(array.length); - for (final var c : array) { - list.add(c); - } - return new Digit(Collections.unmodifiableList(list)); - } - } - - public static record Entry(List uniqueSignalPatterns, List outputValue) { - - public int decodeOutput() { - final var map = buildDecodingMap(); - final StringBuilder builder = new StringBuilder(); - for (final var outputDigit : outputValue()) { - final var decodedDigit = outputDigit.decode(map); - final int digit = decodedDigit.asInt(); - builder.append(digit); - } - final String stringInt = builder.toString(); - return Integer.parseInt(stringInt); - } - - protected SortedSet getDigitSegmentsWithCount(final int n) { - return uniqueSignalPatterns().stream() - .filter(digit -> digit.segments().size() == n) - .findFirst() - .get() - .segments() - .stream() - .collect(TreeSet::new, SortedSet::add, SortedSet::addAll); - } - - protected Set getDigitsWithCount(final int n) { // TODO return stream - return uniqueSignalPatterns() - .stream() - .filter(digit -> digit.segments().size() == n).collect(Collectors.toUnmodifiableSet()); - } - - public Map buildDecodingMap() { - final var encodingMap = buildEncodingMap(); - final var result = new HashMap(); - for(final var entry : encodingMap.entrySet()) { - result.put(entry.getValue(), entry.getKey()); - } - return Collections.unmodifiableMap(result); - } - - public Map buildEncodingMap() { - final var map = new HashMap(); - final var oneSegments = getDigitSegmentsWithCount(2); - final var sevenSegments = getDigitSegmentsWithCount(3); - final var fourSegments = getDigitSegmentsWithCount(4); - final var eightSegments = getDigitSegmentsWithCount(7); - final var aMapping = sevenSegments.stream().filter(c -> !oneSegments.contains(c)).findFirst().get(); - map.put('a', aMapping); - - final var zeroSixNine = getDigitsWithCount(6); - var zsnSegments = zeroSixNine.stream().flatMap(digit -> digit.segments().stream()).collect(Collectors.toList()); - zsnSegments.removeIf(sevenSegments::contains); - zsnSegments.removeIf(fourSegments::contains); - final var sssMap = new HashMap(); - for (final var c : zsnSegments) { - sssMap.compute(c, (_key, old) -> old == null ? 1 : old + 1); - } - if(sssMap.size() != 2) { - throw new IllegalStateException("More segments for 0, 6, 9 encountered: " + sssMap); - } - for (final var entry : sssMap.entrySet()) { - if (entry.getValue() == 3) { - map.put('g', entry.getKey()); - } else if (entry.getValue() == 2) { - map.put('e', entry.getKey()); - } else { - throw new IllegalStateException(); - } - } - - final var twoFiveThree = getDigitsWithCount(5); - var tftSegments = twoFiveThree.stream().flatMap(digit -> digit.segments.stream()).collect(Collectors.toList()); - tftSegments.removeIf(sevenSegments::contains); - tftSegments.removeIf(candidate -> candidate.equals(map.get('e'))); - tftSegments.removeIf(candidate -> candidate.equals(map.get('g'))); - final var tftCounts = new HashMap(); - for(final var c : tftSegments) { - tftCounts.compute(c, (_key, old) -> old == null ? 1 : old + 1); - } - for(final var entry : tftCounts.entrySet()) { - if(entry.getValue() == 3) { - map.put('d', entry.getKey()); - } else if(entry.getValue() == 1) { - map.put('b', entry.getKey()); - } else { - throw new IllegalStateException(); - } - } - - zsnSegments = zeroSixNine.stream().flatMap(digit -> digit.segments().stream()).collect(Collectors.toList()); - zsnSegments.removeIf(candidate -> candidate.equals(map.get('a'))); - zsnSegments.removeIf(candidate -> candidate.equals(map.get('b'))); - zsnSegments.removeIf(candidate -> candidate.equals(map.get('d'))); - zsnSegments.removeIf(candidate -> candidate.equals(map.get('e'))); - zsnSegments.removeIf(candidate -> candidate.equals(map.get('g'))); - final var zsnCounts = new HashMap(); - for(final var c : zsnSegments) { - zsnCounts.compute(c, (_key, old) -> old == null ? 1 : old + 1); - } - for(final var entry : zsnCounts.entrySet()) { - if(entry.getValue() == 2) { - map.put('c', entry.getKey()); - } else if( entry.getValue() == 3) { - map.put('f', entry.getKey()); - } else { - throw new IllegalStateException(); - } - } - - return map; - } - - public static Entry parse(final String string) { - final var components = string.split(" \\| "); - final var uniqueSignalPatterns = components[0].split(" "); - final var outputValue = components[1].split(" "); - - return new Entry(Arrays.stream(uniqueSignalPatterns) - .map(Digit::parse) - .collect(Collectors.toUnmodifiableList()), - Arrays.stream(outputValue) - .map(Digit::parse) - .collect(Collectors.toUnmodifiableList())); - } - - } - - @Test - public final void part1() { - final var result = getInput() - .map(Entry::parse) - .flatMap(entry -> entry.outputValue().stream()) - .filter(digit -> { - final var segments = digit.segments(); - final var numSegments = segments.size(); - return numSegments == 2 || numSegments == 4 || numSegments == 3 || numSegments == 7; - }) - .count(); - System.out.println("Part 1: " + result); - } - - @Test - public final void part2() { - final var result = getInput() - .map(Entry::parse) - .mapToInt(Entry::decodeOutput).sum(); - - 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 deleted file mode 100644 index abd2f4c..0000000 --- a/src/test/java/com/macasaet/Day09.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.macasaet; - -import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 9: Smoke Basin --- - */ -public class Day09 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-09.txt"), - false); - } - - public HeightMap getHeightMap() { - final var list = getInput().map(line -> { - final var chars = line.toCharArray(); - final var ints = new int[chars.length]; - for (int i = chars.length; --i >= 0; ints[i] = chars[i] - '0') ; - return ints; - }).collect(Collectors.toList()); - final int[][] grid = new int[list.size()][]; - for (int i = list.size(); --i >= 0; grid[i] = list.get(i)) ; - return new HeightMap(grid); - } - - /** - * A height map of the floor of the nearby caves generated by the submarine - */ - public record HeightMap(int[][] grid) { // FIXME use bytes - - public Stream points() { - return IntStream.range(0, grid().length) - .boxed() - .flatMap(i -> IntStream.range(0, grid()[i].length) - .mapToObj(j -> new Point(i, j))); - } - - /** - * A location on the floor of a nearby cave - */ - public class Point { - final int x; - final int y; - - public Point(final int x, final int y) { - this.x = x; - this.y = y; - } - - public int x() { - return this.x; - } - - public int y() { - return this.y; - } - - public int getBasinSize() { - return getBasinPoints().size(); - } - - /** - * Identify all the higher points that are also part of the same basin, assuming this location is part of a - * basin. - * - * @return all the higher points, if any, that are part of the same basin. - */ - public Set getBasinPoints() { - if (getHeight() >= 9) { - return Collections.emptySet(); - } - final var result = new HashSet(); - result.add(this); - final Function> basinPointRetriever = neighbour -> { - if (neighbour.getHeight() >= 9 || neighbour.getHeight() <= getHeight() || result.contains(neighbour)) { - return Stream.empty(); - } - return neighbour.getBasinPoints().stream(); - }; - above().stream().flatMap(basinPointRetriever).forEach(result::add); - below().stream().flatMap(basinPointRetriever).forEach(result::add); - left().stream().flatMap(basinPointRetriever).forEach(result::add); - right().stream().flatMap(basinPointRetriever).forEach(result::add); - return Collections.unmodifiableSet(result); - } - - /** - * @return true if and only if this location is lower than all of its adjacent locations (up to four, - * diagonals do not count) - */ - public boolean isLowPoint() { - final var compareTo = new ArrayList(4); - above().ifPresent(compareTo::add); - below().ifPresent(compareTo::add); - left().ifPresent(compareTo::add); - right().ifPresent(compareTo::add); - return compareTo.stream().allMatch(neighbour -> neighbour.getHeight() > getHeight()); - } - - /** - * @return an assessment of the risk from smoke flowing through the cave - */ - public int getRiskLevel() { - return getHeight() + 1; - } - - /** - * @return the height of this particular location, from 0-9 - */ - public int getHeight() { - return grid()[x()][y()]; - } - - public Optional above() { - return x() > 0 ? Optional.of(new Point(x() - 1, y())) : Optional.empty(); - } - - public Optional below() { - return x() < grid().length - 1 ? Optional.of(new Point(x() + 1, y())) : Optional.empty(); - } - - public Optional left() { - return y() > 0 ? Optional.of(new Point(x(), y() - 1)) : Optional.empty(); - } - - public Optional right() { - return y() < grid()[x()].length - 1 ? Optional.of(new Point(x(), y() + 1)) : Optional.empty(); - } - - public int hashCode() { - return Objects.hash(x(), y()); - } - - public boolean equals(final Object o) { - try { - final Point other = (Point) o; - return this.x() == other.x() && this.y() == other.y(); - } catch (final ClassCastException cce) { - return false; - } - } - } - - } - - @Test - public final void part1() { - final var map = getHeightMap(); - final int sum = map.points() - .filter(HeightMap.Point::isLowPoint) - .mapToInt(HeightMap.Point::getRiskLevel) - .sum(); - System.out.println("Part 1: " + sum); - } - - @Test - public final void part2() { - final var map = getHeightMap(); - final var basinSizes = map.points() - .filter(HeightMap.Point::isLowPoint) - .mapToInt(HeightMap.Point::getBasinSize) - .collect(() -> new TreeSet(Comparator.reverseOrder()), SortedSet::add, SortedSet::addAll); - final var iterator = basinSizes.iterator(); - final var result = iterator.next() * iterator.next() * iterator.next(); - 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 deleted file mode 100644 index a7723f4..0000000 --- a/src/test/java/com/macasaet/Day10.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.macasaet; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 10: Syntax Scoring --- - */ -public class Day10 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-10.txt"), - false); - } - - /** - * The type of open and closing delimiter for a chunk in the navigation subsystem - */ - public enum BracketType { - PARENTHESIS('(', ')', 3, 1), - SQUARE('[', ']', 57, 2), - CURLY('{', '}', 1197, 3), - ANGLED('<', '>', 25137, 4); - - private final char open; - private final char close; - private final int corruptionPoints; - private final int autocompletePoints; - - BracketType(char open, char close, int corruptionPoints, final int autocompletePoints) { - this.open = open; - this.close = close; - this.corruptionPoints = corruptionPoints; - this.autocompletePoints = autocompletePoints; - } - - public static BracketType forOpen(final char c) { - return switch (c) { - case '(' -> PARENTHESIS; - case '[' -> SQUARE; - case '{' -> CURLY; - case '<' -> ANGLED; - default -> throw new IllegalStateException("Unexpected value: " + c); - }; - } - - public static BracketType forClose(final char c) { - return switch (c) { - case ')' -> PARENTHESIS; - case ']' -> SQUARE; - case '}' -> CURLY; - case '>' -> ANGLED; - default -> throw new IllegalStateException("Unexpected value: " + c); - }; - } - } - - /** - * @param line a line in the navigation subsystem - * @return a score of how corrupt the line is. A score of zero means it is not corrupt. The higher the value, the - * more corrupt the line is. - */ - public int calculateCorruptionScore(final char[] line) { - final var stack = new LinkedList(); - for (int i = 0; i < line.length; i++) { - final var c = line[i]; - if (c == '(' || c == '[' || c == '{' || c == '<') { - stack.push(BracketType.forOpen(c)); - } else if (c == ')' || c == ']' || c == '}' || c == '>') { - if (stack.peek().close == c) { - stack.pop(); - } else { - // corrupt - return BracketType.forClose(c).corruptionPoints; - } - } - } - // if stack is not empty, it's incomplete - return 0; - } - - /** - * @param line a non-corrupt line in the navigation subsystem. Behaviour is undefined for corrupt lines. - * @return the score for the suffix required to complete the line - */ - public long calculateCompletionScore(final char[] line) { - final var stack = new LinkedList(); - for (int i = 0; i < line.length; i++) { - final var c = line[i]; - if (c == '(' || c == '[' || c == '{' || c == '<') { - stack.push(BracketType.forOpen(c)); - } else if (c == ')' || c == ']' || c == '}' || c == '>') { - if (stack.peek().close == c) { - stack.pop(); - } else { - throw new IllegalArgumentException("Corrupt: " + new String(line)); - } - } - } - long result = 0; - while (!stack.isEmpty()) { - final var unclosed = stack.pop(); - result = result * 5 + unclosed.autocompletePoints; - } - return result; - } - - @Test - public final void part1() { - final var result = getInput() - .map(String::toCharArray) - .filter(line -> line.length > 0) - .mapToInt(this::calculateCorruptionScore) - .sum(); - System.out.println("Part 1: " + result); - } - - @Test - public final void part2() { - final var list = getInput() - .map(String::toCharArray) - .filter(line -> line.length > 0) - .filter(line -> calculateCorruptionScore(line) <= 0) // discard corrupted lines - .mapToLong(this::calculateCompletionScore) - .sorted() - .collect(ArrayList::new, List::add, List::addAll); - final var result = list.get(list.size() / 2); - System.out.println("Part 2: " + result); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day11.java b/src/test/java/com/macasaet/Day11.java deleted file mode 100644 index f566fd3..0000000 --- a/src/test/java/com/macasaet/Day11.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.macasaet; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 11: Dumbo Octopus --- - */ -public class Day11 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-11.txt"), - false); - } - - /** - * @return a spatial grid of the cavern indicating the location of the octopuses - */ - protected Octopus[][] getOctopusGrid() { - final var list = getInput().map(line -> { - final var chars = line.toCharArray(); - final byte[] result = new byte[chars.length]; - for (int i = chars.length; --i >= 0; result[i] = (byte) (chars[i] - '0')) ; - return result; - }).collect(ArrayList::new, List::add, List::addAll); - final var result = new Octopus[list.size()][]; - for (int i = list.size(); --i >= 0; ) { - final var row = list.get(i); - result[i] = new Octopus[row.length]; - for (int j = row.length; --j >= 0; result[i][j] = new Octopus(i, j, row[j])) ; - } - return result; - } - - /** - * A rare bioluminescent dumbo octopus - */ - public static class Octopus { - private final int x, y; - private byte energyLevel; - - public Octopus(final int x, final int y, final byte energyLevel) { - this.x = x; - this.y = y; - this.energyLevel = energyLevel; - } - - /** - * Increase the octopus' energy level and, if appropriate, propagate side effects to its neighbours. - * - * @param population the full population of octopuses - */ - public void prepareStep(final Population population) { - if (this.energyLevel > 9) { - // "An octopus can only flash at most once per step." - return; - } - // "First, the energy level of each octopus increases by 1." - this.energyLevel++; - if (this.energyLevel > 9) { - // "Then, any octopus with an energy level greater than 9 flashes. This increases the energy level of - // all adjacent octopuses by 1, including octopuses that are diagonally adjacent." - final var grid = population.grid(); - final var hasRowAbove = x > 0; - final var hasRowBelow = x < grid.length - 1; - final var hasColumnToLeft = y > 0; - final var hasColumnToRight = y < grid[x].length - 1; - - if (hasRowAbove) { - grid[x - 1][y].prepareStep(population); - if (hasColumnToLeft) { - grid[x - 1][y - 1].prepareStep(population); - } - if (hasColumnToRight) { - grid[x - 1][y + 1].prepareStep(population); - } - } - if (hasColumnToLeft) { - grid[x][y - 1].prepareStep(population); - } - if (hasColumnToRight) { - grid[x][y + 1].prepareStep(population); - } - if (hasRowBelow) { - grid[x + 1][y].prepareStep(population); - if (hasColumnToLeft) { - grid[x + 1][y - 1].prepareStep(population); - } - if (hasColumnToRight) { - grid[x + 1][y + 1].prepareStep(population); - } - } - } - } - - /** - * Complete the step and finalise any side effects. - * - * @return true if and only if the octopus flashed during this step. - */ - public boolean finishStep() { - if (this.energyLevel > 9) { - // "Finally, any octopus that flashed during this step has its energy level set to 0, as it used all of - // its energy to flash." - this.energyLevel = 0; - return true; - } - return false; - } - } - - /** - * The full population of dumbo octopuses. The population members will be modified with each step. - */ - public record Population(Octopus[][] grid) { - public int step() { - for (int i = grid.length; --i >= 0; ) { - final var row = grid[i]; - for (int j = row.length; --j >= 0; ) { - row[j].prepareStep(this); - } - } - int flashes = 0; - for (int i = grid.length; --i >= 0; ) { - final var row = grid[i]; - for (int j = row.length; --j >= 0; ) { - if (row[j].finishStep()) flashes++; - } - } - return flashes; - } - - } - - @Test - public final void part1() { - final var energyLevels = getOctopusGrid(); - final var population = new Population(energyLevels); - - int flashes = 0; - - for (int step = 0; step < 100; step++) { - flashes += population.step(); - } - - System.out.println("Part 1: " + flashes); - } - - @Test - public final void part2() { - final var energyLevels = getOctopusGrid(); - final var population = new Population(energyLevels); - int step = 0; - while(true) { - final int flashes = population.step(); - step++; - if(flashes == 100) { - System.out.println("Part 2: " + step); - break; - } - } - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day12.java b/src/test/java/com/macasaet/Day12.java deleted file mode 100644 index af7de96..0000000 --- a/src/test/java/com/macasaet/Day12.java +++ /dev/null @@ -1,182 +0,0 @@ -package com.macasaet; - -import java.util.*; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 12: Passage Pathing --- - */ -public class Day12 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-12.txt"), - false); - } - - /** - * @return a map of the connected caves - */ - protected Map getMap() { - final var map = new HashMap(); - getInput().forEach(line -> { - final var components = line.split("-"); - final var sourceLabel = components[0]; - final var targetLabel = components[1]; - final var source = map.computeIfAbsent(sourceLabel, Node::new); - final var target = map.computeIfAbsent(targetLabel, Node::new); - source.connected.add(target); - target.connected.add(source); - }); - return Collections.unmodifiableMap(map); - } - - public Node getStartingPoint() { - return getMap().get("start"); - } - - /** - * A distinct path through the cave system - */ - public record Path(List nodes, Node specialCave, int specialCaveVisits) { - - public int hashCode() { - int result = 0; - for (final var node : nodes()) { - result = result * 31 + node.hashCode(); - } - return result; - } - - public boolean equals(final Object o) { - if (o == null) { - return false; - } - try { - final var other = (Path) o; - return nodes().equals(other.nodes()); - } catch (final ClassCastException cce) { - return false; - } - } - } - - public static class Node { - private final boolean isStart; - private final boolean isEnd; - private final boolean isSmallCave; - private final String label; - - private final Set connected = new HashSet<>(); - - public Node(final String label) { - this("start".equalsIgnoreCase(label), "end".equalsIgnoreCase(label), - label.toLowerCase(Locale.ROOT).equals(label), label); - } - - protected Node(boolean isStart, boolean isEnd, boolean isSmallCave, final String label) { - this.isStart = isStart; - this.isEnd = isEnd; - this.isSmallCave = isSmallCave; - this.label = label; - } - - public int hashCode() { - int result = 0; - result += result * 31 + label.hashCode(); - return result; - } - - public boolean equals(final Object o) { - if (o == null) { - return false; - } - try { - final Node other = (Node) o; - return label.equals(other.label); - } catch (final ClassCastException cce) { - return false; - } - } - - } - - protected Set getPaths(final Node node, final Path pathSoFar) { - final var result = new HashSet(); - - if (node.isStart && pathSoFar.nodes.size() > 1) { - // "once you leave the start cave, you may not return to it" - return Collections.emptySet(); - } - - final var nodes = new ArrayList<>(pathSoFar.nodes()); - if (node.isEnd) { - // "once you reach the end cave, the path must end immediately" - nodes.add(node); - return Collections.singleton(new Path(Collections.unmodifiableList(nodes), pathSoFar.specialCave(), pathSoFar.specialCaveVisits())); - } - int specialCaveVisits = pathSoFar.specialCaveVisits(); - if (node.isSmallCave) { - if (node.equals(pathSoFar.specialCave())) { - // "a single small cave can be visited at most twice" - if (pathSoFar.specialCaveVisits() < 1) { - specialCaveVisits++; - } else { - return Collections.emptySet(); - } - } else { - if (pathSoFar.nodes().contains(node)) { - // "the remaining small caves can be visited at most once" - return Collections.emptySet(); - } - } - } - nodes.add(node); - for (final var neighbour : node.connected) { - if (neighbour.isSmallCave && pathSoFar.specialCave() == null) { - result.addAll(getPaths(neighbour, new Path(Collections.unmodifiableList(nodes), null, 0))); - result.addAll(getPaths(neighbour, new Path(Collections.unmodifiableList(nodes), neighbour, 0))); - } else { - result.addAll(getPaths(neighbour, new Path(Collections.unmodifiableList(nodes), pathSoFar.specialCave(), specialCaveVisits))); - } - } - return Collections.unmodifiableSet(result); - } - - protected int countPaths(final Node node, final Set visitedSmallCaves) { - int result = 0; - if (node.isEnd) { - return 1; - } - if (visitedSmallCaves.contains(node)) { - // invalid path - return 0; - } - if (node.isSmallCave) { - visitedSmallCaves.add(node); - } - for (final var connected : node.connected) { - final var set = new HashSet<>(visitedSmallCaves); - result += countPaths(connected, set); - } - return result; - } - - @Test - public final void part1() { - final var start = getStartingPoint(); - final int result = countPaths(start, new HashSet<>()); - System.out.println("Part 1: " + result); - } - - @Test - public final void part2() { - final var start = getStartingPoint(); - final var paths = getPaths(start, new Path(Collections.emptyList(), null, 0)); - System.out.println("Part 2: " + paths.size()); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day13.java b/src/test/java/com/macasaet/Day13.java deleted file mode 100644 index b60355f..0000000 --- a/src/test/java/com/macasaet/Day13.java +++ /dev/null @@ -1,187 +0,0 @@ -package com.macasaet; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 13: Transparent Origami --- - */ -public class Day13 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-13.txt"), - false); - } - - /** - * A point on the translucent sheet of paper. Note that x and y correspond to a particular - * {@link Axis}. - */ - public record Point(int x, int y) { - } - - /** - * An axis of the translucent sheet of paper - */ - public enum Axis { - /** - * The axis that increases to the right - */ - X, - - /** - * The axis that increases downward - */ - Y, - } - - /** - * An equation for a line - */ - public record Line(Axis axis, int value) { - public String toString() { - return switch (axis()) { - case X -> "x=" + value; - case Y -> "y=" + value; - }; - } - } - - public record Input(Collection points, List folds, int maxX, int maxY) { - public Sheet getSheet() { - final boolean[][] grid = new boolean[maxY + 1][]; - for (int i = grid.length; --i >= 0; ) { - grid[i] = new boolean[maxX + 1]; - } - for (final var point : points) { - /* The first value, x, increases to the right. The second value, y, increases downward. */ - grid[point.y()][point.x()] = true; - } - return new Sheet(grid); - } - } - - /** - * A sheet of translucent paper - */ - public record Sheet(boolean[][] grid) { - - public int countDots() { - int result = 0; - final var grid = grid(); - for (int i = grid.length; --i >= 0; ) { - for (int j = grid[i].length; --j >= 0; ) { - if (grid[i][j]) { - result++; - } - } - } - return result; - } - - public String toString() { - final var builder = new StringBuilder(); - for (final var row : grid) { - for (final var cell : row) { - builder.append(cell ? '#' : '.'); - } - builder.append('\n'); - } - return builder.toString(); - } - - public Sheet fold(final Line line) { - // note, value is always positive - return switch (line.axis()) { - case X -> { - // fold along the x-axis (vertical line) - final var newGrid = new boolean[grid.length][]; - for (int i = newGrid.length; --i >= 0; ) { - final var newRow = new boolean[line.value() + 1]; - for (int j = newRow.length; --j >= 0; newRow[j] = grid[i][j]) ; - for(int j = grid[i].length - line.value(); --j > 0; ) { - if(grid[i][line.value() + j]) { - newRow[line.value() - j] = true; - } - } - newGrid[i] = newRow; - } - yield new Sheet(newGrid); - } - case Y -> { - // fold along the y-axis (horizontal line) - final var newGrid = new boolean[line.value()][]; - for (int i = newGrid.length; --i >= 0; ) { - final var newRow = new boolean[grid[i].length]; - for (int j = grid[i].length; --j >= 0; newRow[j] = grid[i][j]) ; - newGrid[i] = newRow; - } - for (int i = grid.length - line.value(); --i > 0; ) { - final var oldRow = grid[line.value() + i]; - for (int j = oldRow.length; - --j >= 0; - newGrid[line.value() - i][j] |= oldRow[j]) - ; - } - yield new Sheet(newGrid); - } - }; - } - } - - public Input parseInput() { - int section = 0; - final var points = new HashSet(); - final var folds = new ArrayList(); - int maxX = Integer.MIN_VALUE; - int maxY = Integer.MIN_VALUE; - for (final var line : getInput().collect(Collectors.toList())) { - if (line.isBlank()) { - section++; - continue; - } - if (section == 0) { // points - final var components = line.split(","); - final var x = Integer.parseInt(components[0]); - maxX = Math.max(x, maxX); - final var y = Integer.parseInt(components[1]); - maxY = Math.max(y, maxY); - final var point = new Point(x, y); - points.add(point); - } else { // commands - final var equation = line.replaceFirst("fold along ", ""); - final var components = equation.split("="); - final var axis = Axis.valueOf(components[0].toUpperCase(Locale.ROOT)); - final var value = Integer.parseInt(components[1]); - final var fold = new Line(axis, value); - folds.add(fold); - } - } - return new Input(points, folds, maxX, maxY); - } - - @Test - public final void part1() { - final var input = parseInput(); - final var sheet = input.getSheet(); - final var firstFold = input.folds().get(0); - final var result = sheet.fold(firstFold); - System.out.println("Part 1: " + result.countDots()); - } - - @Test - public final void part2() { - final var input = parseInput(); - var sheet = input.getSheet(); - for (final var fold : input.folds()) { - sheet = sheet.fold(fold); - } - System.out.println("Part 2:\n" + sheet); - } - -} diff --git a/src/test/java/com/macasaet/Day14.java b/src/test/java/com/macasaet/Day14.java deleted file mode 100644 index c44d50b..0000000 --- a/src/test/java/com/macasaet/Day14.java +++ /dev/null @@ -1,166 +0,0 @@ -package com.macasaet; - -import java.math.BigInteger; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 14: Extended Polymerization --- - */ -public class Day14 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-14.txt"), - false); - } - - public record Polymer(Map pairCounts, char firstElement, char lastElement) { - public static Polymer forTemplate(final String templateString) { - final var firstElement = templateString.charAt(0); - final var lastElement = templateString.charAt(templateString.length() - 1); - final var map = new HashMap(); - for (int i = 1; i < templateString.length(); i++) { - map.merge(new ElementPair(templateString.charAt(i - 1), templateString.charAt(i)), - BigInteger.ONE, - BigInteger::add); - } - return new Polymer(Collections.unmodifiableMap(map), firstElement, lastElement); - } - - /** - * Apply the pair insertion process one time. - * - * @param rules pair insertion rules for generating a new polymer - * @return the new polymer that results - */ - public Polymer applyRules(final Map rules) { - final var map = new HashMap(); - for (final var entry : pairCounts().entrySet()) { - final var key = entry.getKey(); - final var count = entry.getValue(); - final var rule = rules.get(key); - final var left = new ElementPair(key.start(), rule.insert()); - final var right = new ElementPair(rule.insert(), key.end()); - - map.compute(left, (_key, oldCount) -> oldCount == null ? count : oldCount.add(count)); - map.compute(right, (_key, oldCount) -> oldCount == null ? count : oldCount.add(count)); - } - return new Polymer(Collections.unmodifiableMap(map), firstElement(), lastElement()); - } - - /** - * Determine how many times each element appears in the polymer - * - * @return the number of times each element appears in the polymer - */ - public SortedMap> histogram() { - final var map = new HashMap(); - for (final var entry : pairCounts().entrySet()) { - final var pair = entry.getKey(); - final var count = entry.getValue(); - map.compute(pair.start(), - (_key, oldValue) -> oldValue == null ? count : oldValue.add(count)); - map.compute(pair.end(), - (_key, oldValue) -> oldValue == null ? count : oldValue.add(count)); - } - for (final var entry : map.entrySet()) { - final var element = entry.getKey(); - final var count = entry.getValue(); - if (element.equals(firstElement()) || element.equals(lastElement())) { - entry.setValue(count.divide(BigInteger.TWO).add(BigInteger.ONE)); - } else { - entry.setValue(count.divide(BigInteger.TWO)); - } - } - final var result = new TreeMap>(); - for (final var entry : map.entrySet()) { - final var target = result.computeIfAbsent(entry.getValue(), _key -> new HashSet<>()); - target.add(entry.getKey()); - } - return Collections.unmodifiableSortedMap(result); - } - } - - /** - * A pair of elements that appear adjacent to each other. This may be used in the context of a pair insertion rule - * definition or a polymer. - * - * @see Polymer - * @see PairInsertionRule - */ - protected record ElementPair(char start, char end) { - } - - /** - * A single instruction to aid in finding the optimal polymer formula - */ - public record PairInsertionRule(char start, char end, char insert) { - - public static PairInsertionRule parse(final String string) { - final var components = string.split(" -> "); - final var match = components[0].toCharArray(); - return new PairInsertionRule(match[0], match[1], components[1].toCharArray()[0]); - } - - } - - protected record Input(Polymer polymerTemplate, List rules) { - } - - protected Input parseInput() { - final var list = getInput().collect(Collectors.toList()); - int mode = 0; - Polymer polymer = null; - final var rules = new ArrayList(); - for (final var line : list) { - if (line.isBlank()) { - mode++; - continue; - } - if (mode == 0) { - polymer = Polymer.forTemplate(line); - } else { - rules.add(PairInsertionRule.parse(line)); - } - } - return new Input(polymer, rules); - } - - @Test - public final void part1() { - final var input = parseInput(); - var polymer = input.polymerTemplate(); - final var rules = input.rules(); - final var ruleMap = new HashMap(); - for (final var rule : rules) { - ruleMap.put(new ElementPair(rule.start(), rule.end()), rule); - } - for (int _i = 0; _i < 10; _i++) { - polymer = polymer.applyRules(ruleMap); - } - final var histogram = polymer.histogram(); - System.out.println("Part 1: " + histogram.lastKey().subtract(histogram.firstKey())); - } - - @Test - public final void part2() { - final var input = parseInput(); - var polymer = input.polymerTemplate(); - final var rules = input.rules(); - final var ruleMap = new HashMap(); - for (final var rule : rules) { - ruleMap.put(new ElementPair(rule.start(), rule.end()), rule); - } - for (int _i = 0; _i < 40; _i++) { - polymer = polymer.applyRules(ruleMap); - } - final var histogram = polymer.histogram(); - System.out.println("Part 2: " + histogram.lastKey().subtract(histogram.firstKey())); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day15.java b/src/test/java/com/macasaet/Day15.java deleted file mode 100644 index e93e89f..0000000 --- a/src/test/java/com/macasaet/Day15.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.macasaet; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 15: Chiton --- - */ -public class Day15 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-15.txt"), - false); - } - - protected int[][] getGrid() { - final var list = getInput().collect(Collectors.toList()); - final int[][] grid = new int[list.size()][]; - for (int i = 0; i < grid.length; i++) { - final var chars = list.get(i).toCharArray(); - final var row = new int[chars.length]; - for (int j = chars.length; --j >= 0; row[j] = chars[j] - '0') ; - grid[i] = row; - } - return grid; - } - - public record Point(int x, int y) { - - public int risk(int[][] risks) { - return risks[x][y]; - } - - } - - public record Cavern(int[][] grid) { - - public Set predecessors(final Point source) { - final var result = new HashSet(); - if (source.x() > 0) { - result.add(new Point(source.x() - 1, source.y())); - } - if (source.y() > 0) { - result.add(new Point(source.x(), source.y() - 1)); - } - return Collections.unmodifiableSet(result); - } - - public Set successors(final Point source) { - final var result = new HashSet(); - if (source.x() < grid().length - 1) { - result.add(new Point(source.x() + 1, source.y())); - } - if (source.y() < grid()[source.x()].length - 1) { - result.add(new Point(source.x(), source.y() + 1)); - } - return Collections.unmodifiableSet(result); - } - - public Cavern explode() { - final int[][] largeGrid = new int[grid.length * 5][]; - for (int i = largeGrid.length; --i >= 0; ) { - largeGrid[i] = new int[grid.length * 5]; - } - for (int i = grid.length; --i >= 0; ) { - for (int j = grid.length; --j >= 0; ) { - largeGrid[i][j] = grid[i][j]; - } - } - for (int tileRow = 0; tileRow < 5; tileRow++) { - for (int tileColumn = 0; tileColumn < 5; tileColumn++) { - if (tileRow > 0) { - for (int i = grid.length; --i >= 0; ) { - for (int j = grid.length; --j >= 0; ) { - // copy from row above - int value = largeGrid[(tileRow - 1) * grid.length + i][tileColumn * grid.length + j] + 1; - if (value == 10) { - value = 1; - } - largeGrid[tileRow * grid.length + i][tileColumn * grid.length + j] = value; - } - } - } else if (tileColumn > 0) { - for (int i = grid.length; --i >= 0; ) { - for (int j = grid.length; --j >= 0; ) { - // copy from column to the left - int value = largeGrid[tileRow * grid.length + i][(tileColumn - 1) * grid.length + j] + 1; - if (value == 10) { - value = 1; - } - largeGrid[tileRow * grid.length + i][tileColumn * grid.length + j] = value; - } - } - } - } - } - return new Cavern(largeGrid); - } - - public long[][] calculateCumulativeRisk() { - final var cumulative = new long[grid().length][]; - for (int i = cumulative.length; --i >= 0; cumulative[i] = new long[grid()[i].length]) ; - final var visited = new HashSet(); - final var queue = new LinkedList(); - final var destination = new Point(grid().length - 1, grid()[grid().length - 1].length - 1); - queue.add(destination); - visited.add(destination); - - while (!queue.isEmpty()) { - final var node = queue.remove(); - final var successors = successors(node); - if (successors.isEmpty()) { - // destination - cumulative[node.x][node.y] = node.risk(grid()); - } else { - var minSuccessorRisk = Long.MAX_VALUE; - for (final var successor : successors) { - if (!visited.contains(successor)) { - throw new IllegalStateException("Successor has not been visited"); - } - minSuccessorRisk = Math.min(minSuccessorRisk, cumulative[successor.x][successor.y]); - } - cumulative[node.x][node.y] = node.risk(grid()) + minSuccessorRisk; - } - - for (final var predecessor : predecessors(node)) { - if (!visited.contains(predecessor)) { - queue.add(predecessor); - visited.add(predecessor); - } - } - } - return cumulative; - } - - /** - * @return the risk level associated with the path through the cavern that avoids the most chitons - */ - public int lowestRiskThroughTheCavern() { - // the lowest known risk from origin to a given node - final var lowestRiskToNode = new HashMap(); - // the estimated risk from origin to destination if it goes through a given node - final var estimatedRiskThroughNode = new HashMap(); - final var openSet = new PriorityQueue(Comparator.comparing(estimatedRiskThroughNode::get)); - - for (int i = grid().length; --i >= 0; ) { - final var row = grid()[i]; - for (int j = row.length; --j >= 0; ) { - final var point = new Point(i, j); - if (i == 0 && j == 0) { - lowestRiskToNode.put(point, 0); - estimatedRiskThroughNode.put(point, manhattanDistance(point)); - openSet.add(point); - } else { - lowestRiskToNode.put(point, Integer.MAX_VALUE); - estimatedRiskThroughNode.put(point, Integer.MAX_VALUE); - } - } - } - - while(!openSet.isEmpty()) { - final var current = openSet.poll(); - if(current.x() == grid().length - 1 && current.y() == grid()[grid().length - 1].length - 1) { - return lowestRiskToNode.get(current); - } - final var lowestRiskToCurrent = lowestRiskToNode.get(current); - for(final var neighbour : neighbours(current)) { - final var tentativeRisk = lowestRiskToCurrent + neighbour.risk(grid()); - if(tentativeRisk < lowestRiskToNode.get(neighbour)) { - lowestRiskToNode.put(neighbour, tentativeRisk); - estimatedRiskThroughNode.put(neighbour, tentativeRisk + manhattanDistance(neighbour)); - if(!openSet.contains(neighbour)) { - openSet.add(neighbour); - } - } - } - } - throw new IllegalStateException("No path out of the cavern!"); - } - - /** - * @param point - * @return - */ - protected int manhattanDistance(Point point) { - return Math.abs(point.x() - (grid().length - 1)) - + Math.abs(point.y() - (grid()[grid().length - 1].length - 1)); - } - - public Set neighbours(final Point point) { - final var result = new HashSet(); - if (point.x() > 0) { - result.add(new Point(point.x() - 1, point.y())); - } - if (point.x() < grid().length - 1) { - result.add(new Point(point.x() + 1, point.y())); - } - if (point.y() > 0) { - result.add(new Point(point.x(), point.y() - 1)); - } - if (point.y() < grid()[point.x()].length - 1) { - result.add(new Point(point.x(), point.y() + 1)); - } - return Collections.unmodifiableSet(result); - } - - } - - @Test - public final void part1() { - final var grid = getGrid(); - final var cavern = new Cavern(grid); - System.out.println("Part 1: " + cavern.lowestRiskThroughTheCavern()); - } - - @Test - public final void part2() { - final var grid = getGrid(); - final var cavern = new Cavern(grid).explode(); - System.out.println("Part 2: " + cavern.lowestRiskThroughTheCavern()); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day16.java b/src/test/java/com/macasaet/Day16.java deleted file mode 100644 index b3eb4b0..0000000 --- a/src/test/java/com/macasaet/Day16.java +++ /dev/null @@ -1,352 +0,0 @@ -package com.macasaet; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -/** - * --- Day 16: Packet Decoder --- - */ -public class Day16 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-16.txt"), - false); - } - - public interface Packet { - long version(); - - void accept(PacketVisitor packetVisitor); - - long evaluate(); - } - - public record Literal(long version, long value) implements Packet { - - public void accept(PacketVisitor packetVisitor) { - packetVisitor.visit(this); - } - - public long evaluate() { - return value(); - } - } - - public enum OperatorType { - SUM { - public long evaluate(List operands) { - return operands.stream().mapToLong(Packet::evaluate).sum(); - } - }, - PRODUCT { - public long evaluate(List operands) { - return operands.stream().mapToLong(Packet::evaluate).reduce(1, (x, y) -> x * y); - } - }, - MINIMUM { - public long evaluate(List operands) { - return operands.stream().mapToLong(Packet::evaluate).min().orElseThrow(); - } - }, - MAXIMUM { - public long evaluate(List operands) { - return operands.stream().mapToLong(Packet::evaluate).max().orElseThrow(); - } - }, - GREATER_THAN { - public long evaluate(List operands) { - if (operands.size() != 2) { - throw new IllegalArgumentException("Invalid operand list for \"greater than\" operator: " + operands); - } - final var x = operands.get(0).evaluate(); - final var y = operands.get(1).evaluate(); - return x > y ? 1 : 0; - } - }, - LESS_THAN { - public long evaluate(List operands) { - if (operands.size() != 2) { - throw new IllegalStateException("Invalid operand list for \"less than\" operator: " + operands); - } - final var x = operands.get(0).evaluate(); - final var y = operands.get(1).evaluate(); - return x < y ? 1 : 0; - } - }, - EQUAL_TO { - public long evaluate(List operands) { - if (operands.size() != 2) { - throw new IllegalStateException("Invalid operand list for \"equal to\" operator: " + operands); - } - final var x = operands.get(0).evaluate(); - final var y = operands.get(1).evaluate(); - return x == y ? 1 : 0; - } - }; - - public abstract long evaluate(List operands); - - public static OperatorType forId(final int typeId) { - return switch (typeId) { - case 0 -> SUM; - case 1 -> PRODUCT; - case 2 -> MINIMUM; - case 3 -> MAXIMUM; - case 5 -> GREATER_THAN; - case 6 -> LESS_THAN; - case 7 -> EQUAL_TO; - default -> throw new IllegalArgumentException("Invalid operator type ID: " + typeId); - }; - } - } - - public record Operator(long version, OperatorType operatorType, List operands) implements Packet { - - public void accept(PacketVisitor packetVisitor) { - packetVisitor.enter(this); - for (final var subPacket : operands()) { - subPacket.accept(packetVisitor); - } - packetVisitor.exit(this); - } - - public long evaluate() { - return operatorType().evaluate(operands()); - } - } - - public interface PacketVisitor { - void visit(Literal literal); - - void enter(Operator operator); - - void exit(Operator operator); - } - - public static class PacketBuilder { - - private long version; - private long typeId; - private OptionalLong literalValue = OptionalLong.empty(); - private final List subPackets = new ArrayList<>(); - - public Packet readHex(final String hexString) { - final var hexDigits = hexString.toCharArray(); - final var bits = hexToBits(hexDigits); - read(bits, 0); - return toPacket(); - } - - public int read(final List bits, int transmissionCursor) { - final var versionBits = bits.subList(transmissionCursor, transmissionCursor + 3); - transmissionCursor += 3; - this.version = toLong(versionBits); - - final var typeBits = bits.subList(transmissionCursor, transmissionCursor + 3); - transmissionCursor += 3; - this.typeId = toLong(typeBits); - - // TODO consider adding methods to parse each type specifically - if (this.typeId == 4) { - boolean finalGroup = false; - final var literalBits = new ArrayList(); - while (!finalGroup) { - final var groupBits = bits.subList(transmissionCursor, transmissionCursor + 5); - transmissionCursor += 5; - finalGroup = groupBits.get(0) == 0; - literalBits.addAll(groupBits.subList(1, 5)); - } - if (literalBits.size() > 63) { - throw new IllegalArgumentException("Literal is too large for an long: " + literalBits.size()); - } - literalValue = OptionalLong.of(toLong(literalBits)); - return transmissionCursor; - } else { - final var lengthTypeIdBits = bits.subList(transmissionCursor, transmissionCursor + 1); - transmissionCursor += 1; - final var lengthTypeId = toLong(lengthTypeIdBits); - if (lengthTypeId == 0) { - final var lengthOfSubPacketsBits = bits.subList(transmissionCursor, transmissionCursor + 15); - transmissionCursor += 15; - final var lengthOfSubPackets = toLong(lengthOfSubPacketsBits); - int bitsRead = 0; - while (bitsRead < lengthOfSubPackets) { - final var subPacketBuilder = new PacketBuilder(); - final var newCursor = subPacketBuilder.read(bits, transmissionCursor); - final var subPacketSize = newCursor - transmissionCursor; // size of sub-packet in bits - transmissionCursor = newCursor; - - subPackets.add(subPacketBuilder.toPacket()); - bitsRead += subPacketSize; - } - return transmissionCursor; - } else if (lengthTypeId == 1) { - final var numSubPacketsBits = bits.subList(transmissionCursor, transmissionCursor + 11); - transmissionCursor += 11; - final var numSubPackets = toLong(numSubPacketsBits); - for (int packetsRead = 0; packetsRead < numSubPackets; packetsRead++) { - final var subPacketBuilder = new PacketBuilder(); - transmissionCursor = subPacketBuilder.read(bits, transmissionCursor); - subPackets.add(subPacketBuilder.toPacket()); - } - return transmissionCursor; - } else { - throw new IllegalArgumentException("Invalid length type ID: " + lengthTypeId); - } - } - } - - public Packet toPacket() { - if (typeId == 4) { - return new Literal(version, literalValue.orElseThrow()); - } else { - return new Operator(version, OperatorType.forId((int) typeId), subPackets); - } - } - - protected long toLong(final List bits) { - long result = 0; - for (int i = 0; i < bits.size(); i++) { - final var bit = bits.get(i); - if (bit == 1) { - final long shiftDistance = bits.size() - i - 1; - result |= 1L << shiftDistance; - } else if (bit != 0) { - throw new IllegalArgumentException("Invalid bit representation of an integer: " + bits); - } - } - return result; - } - - protected List hexToBits(final char[] hexDigits) { - final var result = new ArrayList(hexDigits.length * 4); - for (final var digit : hexDigits) { - final var bits = switch (digit) { - case '0' -> Arrays.asList((byte) 0, (byte) 0, (byte) 0, (byte) 0); - case '1' -> Arrays.asList((byte) 0, (byte) 0, (byte) 0, (byte) 1); - case '2' -> Arrays.asList((byte) 0, (byte) 0, (byte) 1, (byte) 0); - case '3' -> Arrays.asList((byte) 0, (byte) 0, (byte) 1, (byte) 1); - case '4' -> Arrays.asList((byte) 0, (byte) 1, (byte) 0, (byte) 0); - case '5' -> Arrays.asList((byte) 0, (byte) 1, (byte) 0, (byte) 1); - case '6' -> Arrays.asList((byte) 0, (byte) 1, (byte) 1, (byte) 0); - case '7' -> Arrays.asList((byte) 0, (byte) 1, (byte) 1, (byte) 1); - case '8' -> Arrays.asList((byte) 1, (byte) 0, (byte) 0, (byte) 0); - case '9' -> Arrays.asList((byte) 1, (byte) 0, (byte) 0, (byte) 1); - case 'A', 'a' -> Arrays.asList((byte) 1, (byte) 0, (byte) 1, (byte) 0); - case 'B', 'b' -> Arrays.asList((byte) 1, (byte) 0, (byte) 1, (byte) 1); - case 'C', 'c' -> Arrays.asList((byte) 1, (byte) 1, (byte) 0, (byte) 0); - case 'D', 'd' -> Arrays.asList((byte) 1, (byte) 1, (byte) 0, (byte) 1); - case 'E', 'e' -> Arrays.asList((byte) 1, (byte) 1, (byte) 1, (byte) 0); - case 'F', 'f' -> Arrays.asList((byte) 1, (byte) 1, (byte) 1, (byte) 1); - default -> throw new IllegalStateException("Unexpected value: " + digit); - }; - result.addAll(bits); - } - return Collections.unmodifiableList(result); - } - } - - @Test - public final void testParseLiteral() { - // given - final var input = "D2FE28"; - final var builder = new PacketBuilder(); - - // when - final var result = builder.readHex(input); - - // then - assertTrue(result instanceof Literal); - final var literal = (Literal) result; - assertEquals(2021, literal.value); - } - - @Test - public final void testOperatorWithTwoSubPackets() { - // given - final var input = "38006F45291200"; - final var builder = new PacketBuilder(); - - // when - final var result = builder.readHex(input); - - // then - assertTrue(result instanceof Operator); - final var operator = (Operator) result; - assertEquals(1, operator.version()); - assertEquals(OperatorType.LESS_THAN, operator.operatorType()); - assertEquals(2, operator.operands().size()); - final var a = (Literal) operator.operands().get(0); - assertEquals(10, a.value()); - final var b = (Literal) operator.operands().get(1); - assertEquals(20, b.value()); - } - - @Test - public final void part1() { - final var line = getInput().collect(Collectors.toList()).get(0); - final var builder = new PacketBuilder(); - final var packet = builder.readHex(line); - class VersionSummer implements PacketVisitor { - - int sum = 0; - - public void visit(Literal literal) { - sum += literal.version(); - } - - public void enter(Operator operator) { - } - - public void exit(Operator operator) { - sum += operator.version(); - } - } - final var summer = new VersionSummer(); - packet.accept(summer); - - System.out.println("Part 1: " + summer.sum); - } - - @Test - public final void part2() { - final var line = getInput().collect(Collectors.toList()).get(0); - final var builder = new PacketBuilder(); - final var packet = builder.readHex(line); - System.out.println("Part 2: " + packet.evaluate()); - } - - @Nested - public class PacketBuilderTest { - @Test - public void testToInt() { - // given - final var builder = new PacketBuilder(); - // when - // then - assertEquals(2021, builder.toLong(Arrays.asList((byte) 0, (byte) 1, (byte) 1, (byte) 1, (byte) 1, (byte) 1, (byte) 1, (byte) 0, (byte) 0, (byte) 1, (byte) 0, (byte) 1))); - } - - @Test - public final void testMaths() { - assertEquals(3, new PacketBuilder().readHex("C200B40A82").evaluate()); - assertEquals(54, new PacketBuilder().readHex("04005AC33890").evaluate()); - assertEquals(7, new PacketBuilder().readHex("880086C3E88112").evaluate()); - assertEquals(9, new PacketBuilder().readHex("CE00C43D881120").evaluate()); - assertEquals(1, new PacketBuilder().readHex("D8005AC2A8F0").evaluate()); - assertEquals(0, new PacketBuilder().readHex("F600BC2D8F").evaluate()); - assertEquals(0, new PacketBuilder().readHex("9C005AC2F8F0").evaluate()); - assertEquals(1, new PacketBuilder().readHex("9C0141080250320F1802104A08").evaluate()); - } - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day17.java b/src/test/java/com/macasaet/Day17.java deleted file mode 100644 index 66dc6f1..0000000 --- a/src/test/java/com/macasaet/Day17.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.macasaet; - -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 17: Trick Shot --- - */ -public class Day17 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-17.txt"), - false); - } - - /** - * The target area in a large ocean trench - */ - public record Target(int minX, int maxX, int minY, int maxY) { - } - - /** - * A probe at a given point in time - */ - public record Probe(int xVelocity, int yVelocity, int x, int y) { - - /** - * Launch a probe from the origin - * - * @param xVelocity the starting horizontal velocity - * @param yVelocity the starting vertical velocity - * @return the initial state of the probe at the origin - */ - public static Probe launch(final int xVelocity, final int yVelocity) { - return new Probe(xVelocity, yVelocity, 0, 0); - } - - public Optional step() { - if(x > 0 && x + xVelocity < 0) { - return Optional.empty(); - } - if(y < 0 && y + yVelocity > 0) { - return Optional.empty(); - } - final int newX = x + xVelocity; - final int newY = y + yVelocity; - final int newXVelocity = xVelocity > 0 - ? xVelocity - 1 - : xVelocity < 0 - ? xVelocity + 1 - : xVelocity; - return Optional.of(new Probe(newXVelocity, - yVelocity - 1, - newX, - newY)); - } - - public Optional peak(final Target target) { - var peak = Integer.MIN_VALUE; - var p = Optional.of(this); - while (p.isPresent()) { - final var probe = p.get(); - peak = Math.max(peak, probe.y()); - if (probe.x() < target.minX() && probe.y() < target.minY()) { - // short - return Optional.empty(); - } else if (probe.x() > target.maxX()) { - // long - return Optional.empty(); - } else if (probe.x() >= target.minX() && probe.x() <= target.maxX() - && probe.y() >= target.minY() && probe.y() <= target.maxY()) { - return Optional.of(peak); - } - p = probe.step(); - } - return Optional.empty(); - } - - } - - @Test - public final void part1() { - final var line = getInput().collect(Collectors.toList()).get(0); - final var bounds = line.replaceFirst("target area: ", "").split(", "); - final var xBounds = bounds[0].replaceFirst("x=", "").split("\\.\\."); - final var yBounds = bounds[1].replaceFirst("y=", "").split("\\.\\."); - final int minX = Integer.parseInt(xBounds[0]); - final int maxX = Integer.parseInt(xBounds[1]); - final int minY = Integer.parseInt(yBounds[0]); - final int maxY = Integer.parseInt(yBounds[1]); - final var target = new Target(minX, maxX, minY, maxY); - - final var max = IntStream.range(0, 50) - .parallel() - .mapToObj(x -> IntStream.range(-50, 50) - .parallel() - .mapToObj(y -> Probe.launch(x, y)) - ).flatMap(probes -> probes) - .flatMapToInt(probe -> probe.peak(target) - .stream() - .mapToInt(peak -> peak)) - .max(); - - - System.out.println("Part 1: " + max.getAsInt()); - } - - @Test - public final void part2() { - final var line = getInput().collect(Collectors.toList()).get(0); - final var bounds = line.replaceFirst("target area: ", "").split(", "); - final var xBounds = bounds[0].replaceFirst("x=", "").split("\\.\\."); - final var yBounds = bounds[1].replaceFirst("y=", "").split("\\.\\."); - final int minX = Integer.parseInt(xBounds[0]); - final int maxX = Integer.parseInt(xBounds[1]); - final int minY = Integer.parseInt(yBounds[0]); - final int maxY = Integer.parseInt(yBounds[1]); - final var target = new Target(minX, maxX, minY, maxY); - int count = 0; - for (int x = 1; x <= 400; x++) { - for (int y = -400; y <= 400; y++) { - final var probe = Probe.launch(x, y); - if (probe.peak(target).isPresent()) { - count++; - } - } - } - - System.out.println("Part 2: " + count); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day18.java b/src/test/java/com/macasaet/Day18.java deleted file mode 100644 index afd2133..0000000 --- a/src/test/java/com/macasaet/Day18.java +++ /dev/null @@ -1,432 +0,0 @@ -package com.macasaet; - -import static com.macasaet.Day18.SnailfishNumber.parse; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -/** - * --- Day 18: Snailfish --- - */ -public class Day18 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-18.txt"), - false); - } - - /** - * An element of a {@link SnailfishNumber} - */ - public interface Token { - } - - /** - * A symbol in a {@link SnailfishNumber} - */ - public enum Symbol implements Token { - START_PAIR, - END_PAIR, - SEPARATOR, - - } - - /** - * An integer in a {@link SnailfishNumber} - */ - public record Number(int value) implements Token { - } - - /** - * "Snailfish numbers aren't like regular numbers. Instead, every snailfish number is a pair - an ordered list of - * two elements. Each element of the pair can be either a regular number or another pair." - */ - public record SnailfishNumber(List expression) { - - public SnailfishNumber(final String string) { - this(parse(string)); - } - - /** - * "The magnitude of a pair is 3 times the magnitude of its left element plus 2 times the magnitude of its right - * element. The magnitude of a regular number is just that number." - * - * @return the snailfish number distilled into a single value - */ - public int magnitude() { - var stack = new LinkedList(); - for (final var token : expression) { - if (token.equals(Symbol.START_PAIR)) { - } else if (token instanceof final Number number) { - stack.push(number.value()); - } else if (token.equals(Symbol.END_PAIR)) { - final var rightValue = stack.pop(); - final var leftValue = stack.pop(); - stack.push(leftValue * 3 + rightValue * 2); - } - } - if (stack.size() != 1) { - throw new IllegalStateException("Invalid stack: " + stack); - } - return stack.get(0); - } - - /** - * Repeatedly explode or split this snailfish number until those operations can no longer be performed. - * - * @return a representation of this snailfish number that cannot be reduced any further - */ - public SnailfishNumber reduce() { - var newExpression = expression; - while (true) { - var explosionIndex = getExplosionIndex(newExpression); - var splitIndex = getSplitIndex(newExpression); - if (explosionIndex > 0) { - newExpression = explode(newExpression, explosionIndex); - } else if (splitIndex > 0) { - newExpression = split(newExpression, splitIndex); - } else { - break; - } - } - return new SnailfishNumber(newExpression); - } - - /** - * Add a snailfish number. Note, this operation is *not commutative*: `x.add(y)` is not the same as `y.add(x)`. - * Also note that the process of addition may yield a snailfish number that needs to be reduced. - * - * @param addend the number to add to this snailfish number - * @return the sum of the snailfish numbers (may need to be reduced - * @see SnailfishNumber#reduce() - */ - public SnailfishNumber add(final SnailfishNumber addend) { - final var tokens = new ArrayList(); - tokens.add(Symbol.START_PAIR); - tokens.addAll(expression()); - tokens.add(Symbol.SEPARATOR); - tokens.addAll(addend.expression()); - tokens.add(Symbol.END_PAIR); - return new SnailfishNumber(Collections.unmodifiableList(tokens)); - } - - static List parse(final String expression) { - final var result = new ArrayList(); - for (int i = 0; i < expression.length(); i++) { - final var c = expression.charAt(i); - if (c == '[') { - result.add(Symbol.START_PAIR); - } else if (c == ']') { - result.add(Symbol.END_PAIR); - } else if (c == ',') { - result.add(Symbol.SEPARATOR); - } else if (c >= '0' && c <= '9') { - int endExclusive = i + 1; - while (endExclusive < expression.length()) { - final var d = expression.charAt(endExclusive); - if (d < '0' || d > '9') { - break; - } - endExclusive++; - } - final int value = Integer.parseInt(expression.substring(i, endExclusive)); - result.add(new Number(value)); - i = endExclusive - 1; - } - } - return Collections.unmodifiableList(result); - } - - /** - * Split a regular number. "To split a regular number, replace it with a pair; the left element of the pair - * should be the regular number divided by two and rounded down, while the right element of the pair should be - * the regular number divided by two and rounded up." - * - * @param expression a raw representation of a snailfish number - * @param index the index of a regular number to split. The caller is responsible for ensuring that this number - * can be split and that it is the most appropriate action to take. - * @return the reduced snailfish number in raw tokens - */ - List split(final List expression, final int index) { - final var result = new ArrayList(); - if (index > 0) { - result.addAll(expression.subList(0, index)); - } - final var regularNumber = (Number) expression.get(index); - - final var left = Math.floorDiv(regularNumber.value(), 2); - final var right = (int) Math.ceil(regularNumber.value() / 2.0d); - - result.add(Symbol.START_PAIR); - result.add(new Number(left)); - result.add(Symbol.SEPARATOR); - result.add(new Number(right)); - result.add(Symbol.END_PAIR); - if (index + 1 < expression.size()) { - result.addAll(expression.subList(index + 1, expression.size())); - } - return Collections.unmodifiableList(result); - } - - /** - * Determine whether any of the regular numbers can be split and if so, the highest-priority number to split. - * - * @param expression a raw representation of a snailfish number - * @return the index of the best regular number to split or -1 if none can be split - */ - int getSplitIndex(final List expression) { - for (int i = 0; i < expression.size(); i++) { - final var token = expression.get(i); - if (token instanceof final Number number) { - if (number.value() >= 10) { - return i; - - } - } - } - return -1; - } - - /** - * Explode the pair starting at `index`. "To explode a pair, the pair's left value is added to the first regular - * number to the left of the exploding pair (if any), and the pair's right value is added to the first regular - * number to the right of the exploding pair (if any). Exploding pairs will always consist of two regular - * numbers. Then, the entire exploding pair is replaced with the regular number 0." - * - * @param expression a raw representation of a snailfish number - * @param index the index of the opening brace of the pair to explode. The caller must ensure that an explosion - * operation is valid at the index and that the index represents the most appropriate pair to - * explode. - * @return the reduced expression in raw format - */ - List explode(final List expression, final int index) { - final var result = new ArrayList<>(expression); - final int leftNumberIndex = index + 1; - final int rightNumberIndex = index + 3; - final int left = ((Number) expression.get(leftNumberIndex)).value(); - final int right = ((Number) expression.get(rightNumberIndex)).value(); - int leftIndex = -1; - int rightIndex = -1; - - for (int i = index; --i >= 0; ) { - final var c = expression.get(i); - if (c instanceof Number) { - leftIndex = i; - break; - } - } - for (int i = rightNumberIndex + 1; i < expression.size(); i++) { - final var c = expression.get(i); - if (c instanceof Number) { - rightIndex = i; - break; - } - } - if (leftIndex < 0 && rightIndex < 0) { - throw new IllegalArgumentException("Cannot be exploded: " + expression); - } - // "the pair's left value is added to the first regular number to the left of the exploding pair (if any)" - if (leftIndex > 0) { - final int leftOperand = ((Number) expression.get(leftIndex)).value(); - final int replacement = leftOperand + left; - result.set(leftIndex, new Number(replacement)); - } - // "the pair's right value is added to the first regular number to the right of the exploding pair (if any)" - if (rightIndex > 0) { - final int rightOperand = ((Number) expression.get(rightIndex)).value(); - final int replacement = rightOperand + right; - result.set(rightIndex, new Number(replacement)); - } - // "Exploding pairs will always consist of two regular numbers. Then, the entire exploding pair is replaced - // with the regular number 0." - result.set(index, new Number(0)); - result.remove(index + 1); - result.remove(index + 1); - result.remove(index + 1); - result.remove(index + 1); - return Collections.unmodifiableList(result); - } - - /** - * @param expression a raw representation of a snailfish number - * @return the index of the most appropriate pair to explode (opening brace) or -1 if no explosion is appropriate - */ - int getExplosionIndex(final List expression) { - int depth = -1; - int maxDepth = Integer.MIN_VALUE; - int result = -1; - for (int i = 0; i < expression.size(); i++) { - final var token = expression.get(i); - if (token == Symbol.START_PAIR) { - depth++; - } else if (token == Symbol.END_PAIR) { - depth--; - } - if (depth > maxDepth) { - maxDepth = depth; - result = i; - } - } - return result > 3 ? result : -1; - } - - } - - @Nested - public class SnailfishNumberTest { - - @Test - public final void testAdd() { - assertEquals(new SnailfishNumber("[[[[0,7],4],[[7,8],[6,0]]],[8,1]]"), - new SnailfishNumber("[[[[4,3],4],4],[7,[[8,4],9]]]") - .add(new SnailfishNumber("[1,1]")) - .reduce()); - // either this example is broken or my bug is not triggered in the real puzzle input T_T -// assertEquals(new SnailfishNumber("[[[[7,8],[6,6]],[[6,0],[7,7]]],[[[7,8],[8,8]],[[7,9],[0,6]]]]"), -// new SnailfishNumber("[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]") -// .add(new SnailfishNumber("[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]")) -// .reduce()); - } - - @Test - public final void testAddList() { - // given - final var lines = """ - [[[0,[4,5]],[0,0]],[[[4,5],[2,6]],[9,5]]] - [7,[[[3,7],[4,3]],[[6,3],[8,8]]]] - [[2,[[0,8],[3,4]]],[[[6,7],1],[7,[1,6]]]] - [[[[2,4],7],[6,[0,5]]],[[[6,8],[2,8]],[[2,1],[4,5]]]] - [7,[5,[[3,8],[1,4]]]] - [[2,[2,2]],[8,[8,1]]] - [2,9] - [1,[[[9,3],9],[[9,0],[0,7]]]] - [[[5,[7,4]],7],1] - [[[[4,2],2],6],[8,7]]"""; - final var list = Arrays.stream(lines.split("\n")) - .map(SnailfishNumber::new) - .collect(Collectors.toList()); - - // when - var sum = list.get(0); - for (final var addend : list.subList(1, list.size())) { - sum = sum.add(addend).reduce(); - } - - // then - assertEquals(new SnailfishNumber("[[[[8,7],[7,7]],[[8,6],[7,7]]],[[[0,7],[6,6]],[8,7]]]"), - sum); - } - - @Test - public final void testSplit() { - final var instance = new SnailfishNumber(Collections.emptyList()); - - assertEquals(parse("[5, 5]"), - instance.split(parse("10"), 0)); - assertEquals(parse("[5, 6]"), - instance.split(parse("11"), 0)); - assertEquals(parse("[6, 6]"), - instance.split(parse("12"), 0)); - } - - @Test - public final void testExplosionIndex() { - final var instance = new SnailfishNumber(Collections.emptyList()); - assertEquals(4, - instance.getExplosionIndex(parse("[[[[[9,8],1],2],3],4]"))); - assertEquals(12, - instance.getExplosionIndex(parse("[7,[6,[5,[4,[3,2]]]]]"))); - assertEquals(10, - instance.getExplosionIndex(parse("[[6,[5,[4,[3,2]]]],1]"))); - assertEquals(10, - instance.getExplosionIndex(parse("[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]"))); - assertEquals(24, - instance.getExplosionIndex(parse("[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]"))); - } - - @Test - public final void testExplode() { - final var instance = new SnailfishNumber(Collections.emptyList()); - assertEquals(parse("[[[[0,9],2],3],4]"), - instance - .explode(parse("[[[[[9,8],1],2],3],4]"), 4)); - assertEquals(parse("[7,[6,[5,[7,0]]]]"), - instance - .explode(parse("[7,[6,[5,[4,[3,2]]]]]"), 12)); - assertEquals(parse("[[6,[5,[7,0]]],3]"), - instance - .explode(parse("[[6,[5,[4,[3,2]]]],1]"), 10)); - assertEquals(parse("[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]"), - instance - .explode(parse("[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]"), 10)); - assertEquals(parse("[[3,[2,[8,0]]],[9,[5,[7,0]]]]"), - instance - .explode(parse("[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]"), 24)); - } - - @Test - public final void testMagnitude() { - assertEquals(29, new SnailfishNumber("[9,1]").magnitude()); - assertEquals(21, new SnailfishNumber("[1,9]").magnitude()); - assertEquals(143, new SnailfishNumber("[[1,2],[[3,4],5]]").magnitude()); - assertEquals(1384, new SnailfishNumber("[[[[0,7],4],[[7,8],[6,0]]],[8,1]]").magnitude()); - assertEquals(445, new SnailfishNumber("[[[[1,1],[2,2]],[3,3]],[4,4]]").magnitude()); - assertEquals(791, new SnailfishNumber("[[[[3,0],[5,3]],[4,4]],[5,5]]").magnitude()); - assertEquals(1137, new SnailfishNumber("[[[[5,0],[7,4]],[5,5]],[6,6]]").magnitude()); - assertEquals(3488, new SnailfishNumber("[[[[8,7],[7,7]],[[8,6],[7,7]]],[[[0,7],[6,6]],[8,7]]]").magnitude()); - assertEquals(3993, new SnailfishNumber("[[[[7,8],[6,6]],[[6,0],[7,7]]],[[[7,8],[8,8]],[[7,9],[0,6]]]]").magnitude()); - } - - @Test - public final void verifyStableState() { - // given - final var original = new SnailfishNumber("[[[[7,8],[6,6]],[[6,0],[7,7]]],[[[7,8],[8,8]],[[7,9],[0,6]]]]"); - - // when - final var reduced = original.reduce(); - - // then - assertEquals(original, reduced); - } - } - - - @Test - public final void part1() { - final var list = - getInput().map(SnailfishNumber::new).collect(Collectors.toList()); - var sum = list.get(0); - for (final var addend : list.subList(1, list.size())) { - sum = sum.add(addend).reduce(); - } - System.out.println("Part 1: " + sum.magnitude()); - } - - @Test - public final void part2() { - final var list = - getInput().map(SnailfishNumber::new).collect(Collectors.toList()); - int max = Integer.MIN_VALUE; - for (final var x : list) { - for (final var y : list) { - if (x.equals(y)) { - continue; - } - final var sum = x.add(y).reduce(); - final var magnitude = sum.magnitude(); - if (magnitude > max) { - max = magnitude; - } - } - } - System.out.println("Part 2: " + max); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day19.java b/src/test/java/com/macasaet/Day19.java deleted file mode 100644 index a11c202..0000000 --- a/src/test/java/com/macasaet/Day19.java +++ /dev/null @@ -1,395 +0,0 @@ -package com.macasaet; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -/** - * --- Day 19: Beacon Scanner --- - */ -public class Day19 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-19.txt"), - false); - } - - protected List getScanners() { - final var list = getInput().toList(); - final var result = new ArrayList(); - var observations = new HashSet(); - int id = -1; - for (final var line : list) { - if (line.startsWith("--- scanner ")) { - id = Integer.parseInt(line - .replaceFirst("^--- scanner ", "") - .replaceFirst(" ---$", "")); - } else if (!line.isBlank()) { - observations.add(Position.parse(line)); - } else { // line is blank - result.add(new Scanner(id, Collections.unmodifiableSet(observations))); - observations = new HashSet<>(); - id = -1; - } - } - result.add(new Scanner(id, Collections.unmodifiableSet(observations))); - return Collections.unmodifiableList(result); - } - - public enum Direction { - POSITIVE_X { - public Position face(Position position) { - return position; - } - }, - NEGATIVE_X { - public Position face(Position position) { - return new Position(-position.x(), position.y(), -position.z()); - } - }, - POSITIVE_Y { - public Position face(Position position) { - return new Position(position.y(), -position.x(), position.z()); - } - }, - NEGATIVE_Y { - public Position face(Position position) { - return new Position(-position.y(), position.x(), position.z()); - } - }, - POSITIVE_Z { - public Position face(Position position) { - return new Position(position.z(), position.y(), -position.x()); - } - }, - NEGATIVE_Z { - public Position face(Position position) { - return new Position(-position.z(), position.y(), position.x()); - } - }; - - public abstract Position face(final Position position); - - } - - public enum Rotation { - r0 { - public Position rotate(final Position position) { - return position; - } - }, - r90 { - public Position rotate(Position position) { - return new Position(position.x(), -position.z(), position.y()); - } - }, - r180 { - public Position rotate(Position position) { - return new Position(position.x(), -position.y(), -position.z()); - } - }, - r270 { - public Position rotate(Position position) { - return new Position(position.x(), position.z(), -position.y()); - } - }; - - public abstract Position rotate(final Position position); - } - - public record Transformation(Direction direction, Rotation rotation) { - /** - * Look at a position from a specific orientation - * - * @param position a position relative to one point of view - * @return the same position relative to a different point of view - */ - public Position reorient(final Position position) { - return rotation.rotate(direction.face(position)); - } - - } - - public record Position(int x, int y, int z) { - public static Position parse(final String line) { - final var components = line.split(","); - return new Position(Integer.parseInt(components[0]), - Integer.parseInt(components[1]), - Integer.parseInt(components[2])); - } - - public Position plus(Position amount) { - return new Position(x() + amount.x(), y() + amount.y(), z() + amount.z()); - } - - public Position minus(final Position other) { - return new Position(x() - other.x(), y() - other.y(), z() - other.z()); - } - } - - public interface OverlapResult { - } - - public record Overlap(Position distance, Transformation transformation, - Set overlappingBeacons) implements OverlapResult { - } - - public record None() implements OverlapResult { - } - - public record Scanner(int id, Set observations) { - - public OverlapResult getOverlappingBeacons(final Scanner other) { - for (final var direction : Direction.values()) { - for (final var rotation : Rotation.values()) { - final var transformation = new Transformation(direction, rotation); - final var distances = observations().stream() - .flatMap(a -> other.observations() - .stream() - .map(transformation::reorient) - .map(a::minus)) - .collect(Collectors.toList()); - for (final var offset : distances) { - final var intersection = other.observations() - .stream() - .map(transformation::reorient) - .map(observation -> observation.plus(offset)) - .filter(observations()::contains) - .collect(Collectors.toUnmodifiableSet()); - if (intersection.size() >= 12) { - return new Overlap(offset, transformation, intersection); - } - } - } - } - return new None(); - } - - } - - @Nested - public class ScannerTest { - @Test - public final void testOverlapWithOrigin() { - // given - final var scanner0Observations = """ - 404,-588,-901 - 528,-643,409 - -838,591,734 - 390,-675,-793 - -537,-823,-458 - -485,-357,347 - -345,-311,381 - -661,-816,-575 - -876,649,763 - -618,-824,-621 - 553,345,-567 - 474,580,667 - -447,-329,318 - -584,868,-557 - 544,-627,-890 - 564,392,-477 - 455,729,728 - -892,524,684 - -689,845,-530 - 423,-701,434 - 7,-33,-71 - 630,319,-379 - 443,580,662 - -789,900,-551 - 459,-707,401 - """; - final var scanner1Observations = """ - 686,422,578 - 605,423,415 - 515,917,-361 - -336,658,858 - 95,138,22 - -476,619,847 - -340,-569,-846 - 567,-361,727 - -460,603,-452 - 669,-402,600 - 729,430,532 - -500,-761,534 - -322,571,750 - -466,-666,-811 - -429,-592,574 - -355,545,-477 - 703,-491,-529 - -328,-685,520 - 413,935,-424 - -391,539,-444 - 586,-435,557 - -364,-763,-893 - 807,-499,-711 - 755,-354,-619 - 553,889,-390 - """; - final var scanner0 = new Scanner(0, - Arrays.stream(scanner0Observations.split("\n")) - .map(Position::parse) - .collect(Collectors.toUnmodifiableSet())); - final var scanner1 = new Scanner(0, - Arrays.stream(scanner1Observations.split("\n")) - .map(Position::parse) - .collect(Collectors.toUnmodifiableSet())); - - // when - final var result = scanner0.getOverlappingBeacons(scanner1); - - // then - assertTrue(result instanceof Overlap); - final var overlap = (Overlap) result; - assertEquals(12, overlap.overlappingBeacons().size()); - } - - @Test - public final void testOverlapWithNonOrigin() { - // given - final var scanner1Observations = """ - 686,422,578 - 605,423,415 - 515,917,-361 - -336,658,858 - 95,138,22 - -476,619,847 - -340,-569,-846 - 567,-361,727 - -460,603,-452 - 669,-402,600 - 729,430,532 - -500,-761,534 - -322,571,750 - -466,-666,-811 - -429,-592,574 - -355,545,-477 - 703,-491,-529 - -328,-685,520 - 413,935,-424 - -391,539,-444 - 586,-435,557 - -364,-763,-893 - 807,-499,-711 - 755,-354,-619 - 553,889,-390 - """; - final var scanner4Observations = """ - 727,592,562 - -293,-554,779 - 441,611,-461 - -714,465,-776 - -743,427,-804 - -660,-479,-426 - 832,-632,460 - 927,-485,-438 - 408,393,-506 - 466,436,-512 - 110,16,151 - -258,-428,682 - -393,719,612 - -211,-452,876 - 808,-476,-593 - -575,615,604 - -485,667,467 - -680,325,-822 - -627,-443,-432 - 872,-547,-609 - 833,512,582 - 807,604,487 - 839,-516,451 - 891,-625,532 - -652,-548,-490 - 30,-46,-14 - """; - final var scanner1 = new Scanner(0, - Arrays.stream(scanner1Observations.split("\n")) - .map(Position::parse) - .collect(Collectors.toUnmodifiableSet())); - final var scanner4 = new Scanner(0, - Arrays.stream(scanner4Observations.split("\n")) - .map(Position::parse) - .collect(Collectors.toUnmodifiableSet())); - - // when - final var result = scanner1.getOverlappingBeacons(scanner4); - - // then - assertTrue(result instanceof Overlap); - final var overlap = (Overlap) result; - assertEquals(12, overlap.overlappingBeacons().size()); - } - } - - @Test - public final void part1() { - final var scanners = getScanners(); - final var knownBeacons = new HashSet(); - final var origin = new Scanner(-1, knownBeacons); - final var remaining = new ArrayList<>(scanners); - while (!remaining.isEmpty()) { - final var other = remaining.remove(0); - if (knownBeacons.isEmpty()) { - knownBeacons.addAll(other.observations()); - continue; - } - final var result = origin.getOverlappingBeacons(other); - if (result instanceof final Overlap overlap) { - knownBeacons.addAll(other.observations() - .stream() - .map(overlap.transformation()::reorient) - .map(observation -> observation.plus(overlap.distance())) - .collect(Collectors.toList())); - } else { - remaining.add(other); - } - } - System.out.println("Part 1: " + knownBeacons.size()); - } - - @Test - public final void part2() { - final var scanners = getScanners(); - final var knownBeacons = new HashSet(); - final var origin = new Scanner(-1, knownBeacons); - final var remaining = new ArrayList<>(scanners); - final var distances = new HashSet(); - while (!remaining.isEmpty()) { - final var other = remaining.remove(0); - if (knownBeacons.isEmpty()) { - knownBeacons.addAll(other.observations()); - continue; - } - final var result = origin.getOverlappingBeacons(other); - if (result instanceof final Overlap overlap) { - knownBeacons.addAll(other.observations() - .stream() - .map(overlap.transformation()::reorient) - .map(observation -> observation.plus(overlap.distance())) - .collect(Collectors.toList())); - distances.add(overlap.distance()); - } else { - remaining.add(other); - } - } - int maxDistance = Integer.MIN_VALUE; - for (final var x : distances) { - for (final var y : distances) { - final int distance = Math.abs(x.x() - y.x()) - + Math.abs(x.y() - y.y()) - + Math.abs(x.z() - y.z()); - maxDistance = Math.max(maxDistance, distance); - } - } - System.out.println("Part 2: " + maxDistance); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day20.java b/src/test/java/com/macasaet/Day20.java deleted file mode 100644 index 85115e9..0000000 --- a/src/test/java/com/macasaet/Day20.java +++ /dev/null @@ -1,233 +0,0 @@ -package com.macasaet; - -import static org.junit.jupiter.api.Assertions.*; - -import java.util.*; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -/** - * --- Day 20: Trench Map --- - */ -public class Day20 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-20.txt"), - false); - } - - public record ImageEnhancementAlgorithm(boolean[] map) { - - public boolean isPixelLit(final int code) { - return map()[code]; - } - - public static ImageEnhancementAlgorithm parse(final String line) { - final var map = new boolean[line.length()]; - for (int i = line.length(); --i >= 0; map[i] = line.charAt(i) == '#') ; - return new ImageEnhancementAlgorithm(map); - } - } - - protected record Coordinate(int x, int y) { - } - - public record Image(SortedMap> pixels, int minX, int maxX, int minY, - int maxY, boolean isEven) { - public Image enhance(final ImageEnhancementAlgorithm algorithm) { - final var enhancedPixelMap = new TreeMap>(); - int enhancedMinX = minX; - int enhancedMaxX = maxX; - int enhancedMinY = minY; - int enhancedMaxY = maxY; - for (int i = minX - 1; i <= maxX + 1; i++) { - final var targetRow = new TreeMap(); - for (int j = minY - 1; j <= maxY + 1; j++) { - final int replacementId = decode(i, j, !isEven && algorithm.isPixelLit(0)); - final var shouldLight = algorithm.isPixelLit(replacementId); - if (shouldLight) { - // save space by only storing an entry when lit - targetRow.put(j, true); - enhancedMinY = Math.min(enhancedMinY, j); - enhancedMaxY = Math.max(enhancedMaxY, j); - } - } - if (!targetRow.isEmpty()) { - // save space by only storing a row if at least one cell is lit - enhancedPixelMap.put(i, Collections.unmodifiableSortedMap(targetRow)); - enhancedMinX = Math.min(enhancedMinX, i); - enhancedMaxX = Math.max(enhancedMaxX, i); - } - } - return new Image(Collections.unmodifiableSortedMap(enhancedPixelMap), enhancedMinX, enhancedMaxX, enhancedMinY, enhancedMaxY, !isEven); - } - - int decode(final int x, final int y, boolean voidIsLit) { - final var list = getNeighbouringCoordinates(x, y).stream() - .map(coordinate -> isBitSet(coordinate, voidIsLit)) - .toList(); - int result = 0; - for (int i = list.size(); --i >= 0; ) { - if (list.get(i)) { - final int shiftDistance = list.size() - i - 1; - result |= 1 << shiftDistance; - } - } - if (result < 0 || result > 512) { - throw new IllegalStateException("Unable to decode pixel at " + x + ", " + y); - } - return result; - } - - boolean isBitSet(final Coordinate coordinate, boolean voidIsLit) { - final var row = pixels().get(coordinate.x()); - if((coordinate.x() < minX || coordinate.x() > maxX) && row == null) { - return voidIsLit; - } - else if(row == null) { - return false; - } - return row.getOrDefault(coordinate.y(), (coordinate.y() < minY || coordinate.y() > maxY) && voidIsLit); - } - - List getNeighbouringCoordinates(int x, int y) { - return Arrays.asList( - new Coordinate(x - 1, y - 1), - new Coordinate(x - 1, y), - new Coordinate(x - 1, y + 1), - new Coordinate(x, y - 1), - new Coordinate(x, y), - new Coordinate(x, y + 1), - new Coordinate(x + 1, y - 1), - new Coordinate(x + 1, y), - new Coordinate(x + 1, y + 1) - ); - } - - public long countLitPixels() { - return pixels().values() - .stream() - .flatMap(row -> row.values().stream()) - .filter(isLit -> isLit) - .count(); - } - - public int width() { - return maxX - minX + 1; - } - - public int height() { - return maxY - minY + 1; - } - - public String toString() { - final var builder = new StringBuilder(); - builder.append(width()).append('x').append(height()).append('\n'); - for (int i = minX; i <= maxX; i++) { - final var row = pixels.getOrDefault(i, Collections.emptySortedMap()); - for (int j = minY; j <= maxY; j++) { - final var value = row.getOrDefault(j, false); - builder.append(value ? '#' : '.'); - } - builder.append('\n'); - } - return builder.toString(); - } - - public static Image parse(final List lines) { - final var pixels = new TreeMap>(); - final int minX = 0; - final int minY = 0; - int maxX = 0; - int maxY = 0; - for (int i = lines.size(); --i >= 0; ) { - final var line = lines.get(i); - final var row = new TreeMap(); - for (int j = line.length(); --j >= 0; ) { - final var pixel = line.charAt(j); - row.put(j, pixel == '#'); - if (pixel == '#') { - row.put(j, true); - maxY = Math.max(maxY, j); - } - } - if (!row.isEmpty()) { - maxX = Math.max(maxX, i); - pixels.put(i, Collections.unmodifiableSortedMap(row)); - } - } - return new Image(Collections.unmodifiableSortedMap(pixels), minX, maxX, minY, maxY, true); - } - - } - - @Nested - public class ImageTest { - @Test - public final void testToInt() { - final var string = """ - #..#. - #.... - ##..# - ..#.. - ..### - """; - final var image = Image.parse(Arrays.asList(string.split("\n"))); - assertEquals(34, image.decode(2, 2, false)); - } - - @Test - public final void flipAllOn() { - final var template = "#........"; - final var imageString = """ - ... - ... - ... - """; - final var image = Image.parse(Arrays.asList(imageString.split("\n"))); - final var result = image.enhance(ImageEnhancementAlgorithm.parse(template)); - assertTrue(result.pixels().get(1).get(1)); - } - - @Test - public final void turnOffPixel() { - final var templateBuilder = new StringBuilder(); - for (int i = 511; --i >= 0; templateBuilder.append('#')) ; - templateBuilder.append('.'); - final var template = templateBuilder.toString(); - final var imageString = """ - ### - ### - ### - """; - final var image = Image.parse(Arrays.asList(imageString.split("\n"))); - final var result = image.enhance(ImageEnhancementAlgorithm.parse(template)); - final var middleRow = result.pixels().get(1); - assertFalse(middleRow.containsKey(1)); - } - } - - @Test - public final void part1() { - final var list = getInput().toList(); - final var algorithm = ImageEnhancementAlgorithm.parse(list.get(0)); - final var image = Image.parse(list.subList(2, list.size())) - .enhance(algorithm) - .enhance(algorithm); - System.out.println("Part 1: " + image.countLitPixels()); - } - - @Test - public final void part2() { - final var list = getInput().toList(); - final var algorithm = ImageEnhancementAlgorithm.parse(list.get(0)); - var image = Image.parse(list.subList(2, list.size())); - for(int _i = 50; --_i >= 0; image = image.enhance(algorithm)); - System.out.println("Part 2: " + image.countLitPixels()); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day21.java b/src/test/java/com/macasaet/Day21.java deleted file mode 100644 index 1223048..0000000 --- a/src/test/java/com/macasaet/Day21.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.macasaet; - -import java.math.BigInteger; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Test; - -/** - * --- Day 21: Dirac Dice --- - */ -public class Day21 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-21.txt"), - false); - } - - public static class DeterministicDie { - int value; - int totalRolls = 0; - - protected DeterministicDie(final int startingValue) { - this.value = startingValue; - } - - public DeterministicDie() { - this(1); - } - - public int roll() { - final int result = value; - value += 1; - if (value > 100) { - value -= 100; - } - totalRolls++; - return result; - } - } - - public record Pawn(int position, int score) { - public Pawn fork() { - return new Pawn(position(), score()); - } - - public Pawn move(final int distance) { - int newPosition = (position() + distance) % 10; - if (newPosition == 0) { - newPosition = 10; - } - final int newScore = score() + newPosition; - return new Pawn(newPosition, newScore); - } - - public Pawn takeTurn(final DeterministicDie die) { - final int distance = die.roll() + die.roll() + die.roll(); - return move(distance); - } - } - - public record Game(Pawn player1, Pawn player2, boolean playerOnesTurn) { - - } - - public record ScoreCard(BigInteger playerOneWins, BigInteger playerTwoWins) { - public ScoreCard add(final ScoreCard other) { - return new ScoreCard(playerOneWins().add(other.playerOneWins()), playerTwoWins().add(other.playerTwoWins())); - } - } - - public static class QuantumDie { - private final Map cache = new HashMap<>(); - - public ScoreCard play(final Game game) { - if (cache.containsKey(game)) { - return cache.get(game); - } - final var reverseScenario = new Game(game.player2(), game.player1(), !game.playerOnesTurn()); - if (cache.containsKey(reverseScenario)) { - final var reverseResult = cache.get(reverseScenario); - return new ScoreCard(reverseResult.playerTwoWins(), reverseResult.playerOneWins()); - } - - if (game.player1().score() >= 21) { - final var result = new ScoreCard(BigInteger.ONE, BigInteger.ZERO); - cache.put(game, result); - return result; - } else if (game.player2().score() >= 21) { - final var result = new ScoreCard(BigInteger.ZERO, BigInteger.ONE); - cache.put(game, result); - return result; - } - - var result = new ScoreCard(BigInteger.ZERO, BigInteger.ZERO); - for (int i = 1; i <= 3; i++) { - for (int j = 1; j <= 3; j++) { - for (int k = 1; k <= 3; k++) { - final int movementDistance = i + j + k; - final var forkResult = game.playerOnesTurn() - ? play(new Game(game.player1().move(movementDistance), game.player2(), false)) - : play(new Game(game.player1(), game.player2().fork().move(movementDistance), true)); - result = result.add(forkResult); - } - } - } - cache.put(game, result); - return result; - } - } - - @Test - public final void part1() { - final var lines = getInput().toList(); - final int playerOnePosition = - Integer.parseInt(lines.get(0).replaceAll("Player . starting position: ", "")); - final int playerTwoPosition = - Integer.parseInt(lines.get(1).replaceAll("Player . starting position: ", "")); - var playerOne = new Pawn(playerOnePosition, 0); - var playerTwo = new Pawn(playerTwoPosition, 0); - final var die = new DeterministicDie(); - while (true) { - // player 1 - playerOne = playerOne.takeTurn(die); - if (playerOne.score() >= 1000) { - break; - } - - // player 2 - playerTwo = playerTwo.takeTurn(die); - if (playerTwo.score() >= 1000) { - break; - } - } - int losingScore = Math.min(playerOne.score(), playerTwo.score()); - - System.out.println("Part 1: " + (losingScore * die.totalRolls)); - } - - @Test - public final void part2() { - final var lines = getInput().toList(); - final int playerOnePosition = - Integer.parseInt(lines.get(0).replaceAll("Player . starting position: ", "")); - final int playerTwoPosition = - Integer.parseInt(lines.get(1).replaceAll("Player . starting position: ", "")); - final var playerOne = new Pawn(playerOnePosition, 0); - final var playerTwo = new Pawn(playerTwoPosition, 0); - final var die = new QuantumDie(); - final var game = new Game(playerOne, playerTwo, true); - final var result = die.play(game); - final var winningScore = result.playerOneWins().max(result.playerTwoWins()); - System.out.println("Part 2: " + winningScore); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day22.java b/src/test/java/com/macasaet/Day22.java deleted file mode 100644 index 333be59..0000000 --- a/src/test/java/com/macasaet/Day22.java +++ /dev/null @@ -1,202 +0,0 @@ -package com.macasaet; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.math.BigInteger; -import java.util.*; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -/** - * --- Day 22: Reactor Reboot --- - */ -public class Day22 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-22.txt"), - false); - } - - public record ReactorCore( - SortedMap>> cubes) { - - public long count(final long xMin, final long xMax, final long yMin, final long yMax, final long zMin, final long zMax) { - long sum = 0; - for (var i = xMin; i <= xMax; i++) { - final var xDimension = cubes().getOrDefault(i, Collections.emptySortedMap()); - for (var j = yMin; j <= yMax; j++) { - final var yDimension = xDimension.getOrDefault(j, Collections.emptySortedMap()); - for (var k = zMin; k <= zMax; k++) { - if (yDimension.getOrDefault(k, false)) { - sum++; - } - } - } - } - return sum; - } - - public void process(final Instruction instruction) { - final var block = instruction.block(); - final var on = instruction.on(); - for (var i = block.xMin(); i <= block.xMax(); i++) { - final var xDimension = cubes().computeIfAbsent(i, _key -> new TreeMap<>()); - for (var j = block.yMin(); j <= block.yMax(); j++) { - final var yDimension = xDimension.computeIfAbsent(j, _key -> new TreeMap<>()); - for (var k = block.zMin(); k <= block.zMax(); k++) { - yDimension.put(k, on); - } - } - } - } - - } - - public record Instruction(boolean on, Block block) { - public static Instruction parse(final String string) { - final var components = string.split(" "); - final boolean on = "on".equalsIgnoreCase(components[0]); - final var block = Block.parse(components[1]); - return new Instruction(on, block); - } - - } - - public record Block(long xMin, long xMax, long yMin, long yMax, long zMin, long zMax) { - - public BigInteger volume() { - return (BigInteger.valueOf(xMax).subtract(BigInteger.valueOf(xMin)).add(BigInteger.ONE)) - .multiply(BigInteger.valueOf(yMax).subtract(BigInteger.valueOf(yMin)).add(BigInteger.ONE)) - .multiply(BigInteger.valueOf(zMax).subtract(BigInteger.valueOf(zMin)).add(BigInteger.ONE)); - } - - public static Block parse(final String string) { - final var ranges = string.split(","); - final var xRange = ranges[0].split("\\.\\."); - final var xMin = Long.parseLong(xRange[0].replaceAll("x=", "")); - final var xMax = Long.parseLong((xRange[1])); - final var yRange = ranges[1].split("\\.\\."); - final var yMin = Long.parseLong((yRange[0].replaceAll("y=", ""))); - final var yMax = Long.parseLong((yRange[1])); - final var zRange = ranges[2].split("\\.\\."); - final var zMin = Long.parseLong((zRange[0].replaceAll("z=", ""))); - final var zMax = Long.parseLong((zRange[1])); - return new Block(xMin, xMax, yMin, yMax, zMin, zMax); - } - - public boolean overlaps(final Block other) { - return intersection(other).isPresent(); - } - - public Optional intersection(final Block other) { - if (xMin > other.xMax() || xMax < other.xMin() - || yMin > other.yMax() || yMax < other.yMin() - || zMin > other.zMax() || zMax < other.zMin()) { - return Optional.empty(); - } - final var result = new Block(Math.max(xMin, other.xMin()), Math.min(xMax, other.xMax()), - Math.max(yMin, other.yMin()), Math.min(yMax, other.yMax()), - Math.max(zMin, other.zMin()), Math.min(zMax, other.zMax())); - return Optional.of(result); - } - } - - @Nested - public class BlockTest { - @Test - public final void verifyEqualBlocksOverlap() { - // given - final var x = new Block(-2, 2, -2, 2, -2, 2); - final var y = new Block(-2, 2, -2, 2, -2, 2); - - // when - - // then - assertTrue(x.overlaps(y)); - assertTrue(y.overlaps(x)); - } - - @Test - public final void verifyNestedBlocksOverlap() { - final var inner = new Block(-2, 2, -2, 2, -2, 2); - final var outer = new Block(-4, 4, -4, 4, -4, 4); - - assertTrue(inner.overlaps(outer)); - assertTrue(outer.overlaps(inner)); - } - - @Test - public final void verifyIntersectingBlocksOverlap() { - final var x = new Block(10, 12, 10, 12, 10, 12); - final var y = new Block(11, 13, 11, 13, 11, 13); - - assertTrue(x.overlaps(y)); - assertTrue(y.overlaps(x)); - } - - @Test - public final void testIntersection() { - final var x = new Block(10, 12, 10, 12, 10, 12); - final var y = new Block(11, 13, 11, 13, 11, 13); - - assertTrue(x.intersection(y).isPresent()); - assertEquals(BigInteger.valueOf(8), x.intersection(y).orElseThrow().volume()); - assertTrue(y.intersection(x).isPresent()); - assertEquals(BigInteger.valueOf(8), y.intersection(x).orElseThrow().volume()); - assertEquals(x.intersection(y).orElseThrow(), y.intersection(x).orElseThrow()); - } - } - - @Test - public final void part1() { - final var core = new ReactorCore(new TreeMap<>()); - getInput().map(Instruction::parse).map(fullInstruction -> { - final var fullBlock = fullInstruction.block(); - final var truncatedBlock = new Block(Math.max(fullBlock.xMin(), -50), Math.min(fullBlock.xMax(), 50), - Math.max(fullBlock.yMin(), -50), Math.min(fullBlock.yMax(), 50), - Math.max(fullBlock.zMin(), -50), Math.min(fullBlock.zMax(), 50)); - return new Instruction(fullInstruction.on(), truncatedBlock); - }).forEach(core::process); - System.out.println("Part 1: " + core.count(-50, 50, -50, 50, -50, 50)); - } - - @Test - public final void part2() { - final var originalList = getInput().map(Instruction::parse).toList(); - final var appliedInstructions = new ArrayList(); - for (final var instruction : originalList) { - final var modifiedInstructions = new ArrayList(); - if (instruction.on()) { - // only add initial instructions that turn ON cubes - modifiedInstructions.add(instruction); - } - // override any previous instructions - for (final var previousInstruction : appliedInstructions) { - // add compensating instructions to handle the overlaps - instruction.block() - .intersection(previousInstruction.block()) - .map(intersection -> new Instruction(!previousInstruction.on(), intersection)) - .ifPresent(modifiedInstructions::add); - } - - appliedInstructions.addAll(modifiedInstructions); - } - - final var sum = appliedInstructions.stream() - .map(instruction -> instruction.block() - .volume() - .multiply(instruction.on() - ? BigInteger.ONE - : BigInteger.valueOf(-1))) - .reduce(BigInteger.ZERO, - BigInteger::add); - - System.out.println("Part 2: " + sum); - } - -} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day23.java b/src/test/java/com/macasaet/Day23.java deleted file mode 100644 index c545b82..0000000 --- a/src/test/java/com/macasaet/Day23.java +++ /dev/null @@ -1,900 +0,0 @@ -package com.macasaet; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.PriorityBlockingQueue; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * --- Day 23: Amphipod --- - */ -public class Day23 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-23.txt"), - false); - } - - protected Tile[][] parseGrid(List lines) { - final var result = new Tile[lines.size()][]; - for (int i = lines.size(); --i >= 0; ) { - final var line = lines.get(i); - result[i] = new Tile[line.length()]; - for (int j = line.length(); --j >= 0; ) { - final AmphipodType targetType = AmphipodType.forDestinationColumn(j); - final var c = line.charAt(j); - result[i][j] = switch (c) { - case '.' -> new Tile(new Point(i, j), null, null); - case 'A' -> new Tile(new Point(i, j), targetType, AmphipodType.AMBER); - case 'B' -> new Tile(new Point(i, j), targetType, AmphipodType.BRONZE); - case 'C' -> new Tile(new Point(i, j), targetType, AmphipodType.COPPER); - case 'D' -> new Tile(new Point(i, j), targetType, AmphipodType.DESERT); - default -> null; - }; - } - } - - return result; - } - - public enum AmphipodType { - AMBER(1, 3), - BRONZE(10, 5), - COPPER(100, 7), - DESERT(1000, 9); - - private final int energyPerStep; - private final int destinationColumn; - - AmphipodType(final int energyPerStep, final int destinationColumn) { - this.energyPerStep = energyPerStep; - this.destinationColumn = destinationColumn; - } - - public static AmphipodType forDestinationColumn(final int destinationColumn) { - for (final var candidate : values()) { - if (candidate.destinationColumn == destinationColumn) { - return candidate; - } - } - return null; - } - } - - public record Move(Point from, Point to) { - } - - public record BranchResult(Node node, int cost) { - } - - public record Node(Tile[][] tiles, Map, BranchResult> branchCache) { - - private static final Map estimatedDistanceCache = new ConcurrentHashMap<>(); - private static final Map> branchesCache = new ConcurrentHashMap<>(); - private static final Map solutionCache = new ConcurrentHashMap<>(); - - public static Node createInitialNode(final Tile[][] tiles) { - return new Node(tiles, new ConcurrentHashMap<>()); - } - - public BranchResult branch(final List moves) { - if (moves.size() == 0) { - System.err.println("How is this empty?"); - return new BranchResult(this, 0); - } - if (branchCache.containsKey(moves)) { - return branchCache.get(moves); - } - final var copy = new Tile[tiles.length][]; - for (int i = tiles.length; --i >= 0; ) { - final var row = new Tile[tiles[i].length]; - System.arraycopy(tiles[i], 0, row, 0, tiles[i].length); - copy[i] = row; - } - final var source = moves.get(0).from(); - final var destination = moves.get(moves.size() - 1).to(); - final var sourceTile = source.getTile(tiles); - final var destinationTile = destination.getTile(tiles); - final var amphipod = sourceTile.amphipodType(); - if (amphipod == null) { - System.err.println("source amphipod is missing :-("); - } - source.setTile(copy, sourceTile.updateType(null)); - destination.setTile(copy, destinationTile.updateType(amphipod)); - final var cost = moves.size() * amphipod.energyPerStep; - final var result = new BranchResult(new Node(copy, new ConcurrentHashMap<>()), cost); - branchCache.put(moves, result); - return result; - } - - boolean isSideRoom(final Point point) { - final var x = point.x(); - final var y = point.y(); - return x > 1 && (y == 3 || y == 5 || y == 7 || y == 9); - } - - boolean isCorridor(final Point point) { - return point.x() == 1; - } - - public int hashCode() { - // equality based on layout of the burrow regardless of how the amphipods got to that state - // FNV hash - long result = 2166136261L; - final Function rowHasher = row -> { - long rowHash = 2166136261L; - for (final var tile : row) { - rowHash = (16777619L * rowHash) ^ (tile == null ? 0L : (long) Objects.hashCode(tile.amphipodType())); - } - return rowHash; - }; - for (final var row : tiles()) { - result = (16777619L * result) ^ rowHasher.apply(row); - } - - return Long.hashCode(result); - // Bob Jenkins' One-at-a-Time hash -// int result = 0; -// final Function rowHasher = row -> { -// int rowHash = 0; -// for(final var tile : row) { -// final var tileHash = tile != null ? Objects.hashCode(tile.amphipodType()) : 0; -// rowHash += tileHash; -// rowHash += rowHash << 10; -// rowHash ^= rowHash >> 6; -// } -// rowHash += rowHash << 3; -// rowHash ^= rowHash >> 11; -// rowHash += rowHash << 15; -// return rowHash; -// }; -// for(final var row : tiles()) { -// result += rowHasher.apply(row); -// result += (result << 10); -// result ^= (result >> 6); -// } -// result += result << 3; -// result ^= result >> 11; -// result += result << 15; -// return result; - } - - public boolean equals(final Object o) { - if (o == null) { - return false; - } - if (this == o) { - return true; - } - try { - final var other = (Node) o; - // equality based on layout of the burrow regardless of how the amphipods got to that state - if (tiles().length != other.tiles().length) { - return false; - } - for (int i = tiles().length; --i >= 0; ) { - final var xRow = tiles()[i]; - final var yRow = other.tiles()[i]; - if (xRow.length != yRow.length) { - return false; - } - for (int j = xRow.length; --j >= 0; ) { - if (xRow[j] != yRow[j]) { - if (xRow[j] == null - || yRow[j] == null - || !Objects.equals(xRow[j].amphipodType(), yRow[j].amphipodType())) { - return false; - } - } - } - } - return true; - } catch (final ClassCastException cce) { - return false; - } - } - - public String toString() { - final var builder = new StringBuilder(); - for (final var row : tiles()) { - for (final var cell : row) { - if (cell == null) { - builder.append('#'); - } else if (cell.amphipodType() == null) { - builder.append('.'); - } else { - builder.append(switch (cell.amphipodType()) { - case AMBER -> 'A'; - case BRONZE -> 'B'; - case COPPER -> 'C'; - case DESERT -> 'D'; - }); - } - } - builder.append('\n'); - } - return builder.toString(); - } - - public boolean isSolution() { - if (solutionCache.containsKey(this)) { - return solutionCache.get(this); - } - final var result = Arrays.stream(tiles()) - .flatMap(Arrays::stream) - .filter(Objects::nonNull) - .allMatch(Tile::hasTargetType); - solutionCache.put(this, result); - return result; - } - - public Stream getBranches() { - if (branchesCache.containsKey(this)) { - return branchesCache.get(this).stream(); - } - final var branchResults = new Vector(); - return Arrays.stream(tiles()) - .parallel() - .flatMap(row -> Arrays.stream(row) - .parallel() - .filter(Objects::nonNull) - .filter(tile -> !tile.isVacant()) - .flatMap(this::getMoves)) - .map(this::branch) - .peek(branchResults::add) - .onClose(() -> branchesCache.put(this, Collections.unmodifiableList(branchResults))); - } - -// @Deprecated -// public Set> getMoves() { -// final var result = new HashSet>(); -// for (final var row : tiles()) { -// for (final var tile : row) { -// if (tile != null && !tile.isVacant()) { -// result.addAll(getMoves(tile).collect(Collectors.toSet())); -// } -// } -// } -// return Collections.unmodifiableSet(result); -// } - - /** - * Find all the actions (series of moves) that can be taken for a single amphipod. - * - * @param occupiedTile a tile with an amphipod - * @return All the moves that can be applied starting with occupiedTile that end in a valid temporary - * destination for the amphipod - */ - Stream> getMoves(final Tile occupiedTile) { - if (isSideRoom(occupiedTile.location()) && occupiedTile.hasTargetType()) { - var roomComplete = true; - for (var cursor = tiles[occupiedTile.location().x() + 1][occupiedTile.location().y()]; - cursor != null; - cursor = tiles[cursor.location().x() + 1][cursor.location().y()]) { - if (!cursor.hasTargetType()) { - // one of the amphipods in this room is destined elsewhere - // so the amphipod from the original tile will need to move out of the way - roomComplete = false; - break; - } - } - if (roomComplete) { - // all the amphipods in this room have this as their intended destination - return Stream.empty(); - } - } - - var paths = iterateThroughPaths(occupiedTile.amphipodType(), - occupiedTile, - Collections.singletonList(occupiedTile.location())); - if (isCorridor(occupiedTile.location())) { - /* - * "Once an amphipod stops moving in the hallway, it will stay in that spot until it can move into a - * room. (That is, once any amphipod starts moving, any other amphipods currently in the hallway are - * locked in place and will not move again until they can move fully into a room.)" - */ - paths = paths - // only select paths that end in a room - .filter(path -> isSideRoom(path.get(path.size() - 1) - .getTile(tiles()) - .location())); - } else { - // reduce the search space by only considering rooms from rooms into hallways - // prune any path that starts from a room and ends in a room -// paths = paths -// .filter(path -> isCorridor(path.get(path.size() - 1) -// .getTile(tiles()) -// .location())); - } - // convert tiles to moves - return paths - .filter(path -> path.size() > 1) // filter out paths in which the amphipod does not move - .map(points -> { - final var moves = new ArrayList(points.size() - 1); - for (int i = 1; i < points.size(); i++) { - moves.add(new Move(points.get(i - 1), points.get(i))); - } - return Collections.unmodifiableList(moves); - }); - } - - Stream> iterateThroughPaths(final AmphipodType amphipodType, final Tile current, final List pathSoFar) { - // TODO store `pathSoFar` as a stack so checking for node becomes O(1) instead of O(n) - final int x = current.location.x(); - final int y = current.location.y(); - final var up = tiles[x - 1][y]; - final var down = tiles[x + 1][y]; - final var left = tiles[x][y - 1]; - final var right = tiles[x][y + 1]; - final var suppliers = new ArrayList>>>(4); - if (up != null && up.isVacant() && !pathSoFar.contains(up.location())) { - suppliers.add(() -> streamUpPaths(amphipodType, current, pathSoFar)); - } - if (down != null - && down.isVacant() - && !pathSoFar.contains(down.location()) - // don't enter side room unless it is the ultimate destination - && down.targetType == amphipodType) { - suppliers.add(() -> streamDownPaths(amphipodType, current, pathSoFar)); - } - if (left != null && left.isVacant() && !pathSoFar.contains(left.location())) { - suppliers.add(() -> streamLeftPaths(amphipodType, current, pathSoFar)); - } - if (right != null && right.isVacant() && !pathSoFar.contains(right.location())) { - suppliers.add(() -> streamRightPaths(amphipodType, current, pathSoFar)); - } - if (suppliers.isEmpty()) { - // dead end, emit the path so far - suppliers.add(() -> Stream.of(Collections.unmodifiableList(pathSoFar))); - } - - return suppliers.stream() - .flatMap(Supplier::get); - } - - Stream> streamUpPaths(final AmphipodType amphipodType, final Tile current, final List pathSoFar) { - final int x = current.location.x(); - final int y = current.location.y(); - final var up = tiles[x - 1][y]; - // amphipod is in a side room - if (isSideRoom(up.location()) && current.targetType == amphipodType) { - // amphipod is in the back of the room in which it belongs, stop here - return Stream.of(pathSoFar); - } - final var incrementalPath = new ArrayList<>(pathSoFar); - incrementalPath.add(up.location()); - // whether "up" is the front of the room or the corridor outside the room, we have to keep moving - return iterateThroughPaths(amphipodType, up, incrementalPath); - } - - Stream> streamDownPaths(final AmphipodType amphipodType, final Tile current, final List pathSoFar) { - final int x = current.location.x(); - final int y = current.location.y(); - final var down = tiles[x + 1][y]; - final var incrementalPath = new ArrayList<>(pathSoFar); - incrementalPath.add(down.location()); - if ((isCorridor(current.location()) && canEnterRoom(amphipodType, down)) || isSideRoom(current.location())) { - // go as for back into the room as possible, don't just stop at the entrance - return iterateThroughPaths(amphipodType, down, incrementalPath); - } - return Stream.empty(); - } - - Stream> streamLeftPaths(final AmphipodType amphipodType, final Tile current, final List pathSoFar) { - final int x = current.location.x(); - final int y = current.location.y(); - final var left = tiles[x][y - 1]; - Stream> result = Stream.empty(); - final var incrementalPath = new ArrayList<>(pathSoFar); - incrementalPath.add(left.location()); - if (tiles[left.location().x() + 1][left.location().y()] == null || !isSideRoom(tiles[left.location().x() + 1][left.location().y()].location())) { - // this is not in front of a side room, - // we can stop here while other amphipods move - result = Stream.concat(result, Stream.of(Collections.unmodifiableList(incrementalPath))); - } - result = Stream.concat(result, iterateThroughPaths(amphipodType, left, incrementalPath)); - return result; - } - - Stream> streamRightPaths(final AmphipodType amphipodType, final Tile current, final List pathSoFar) { - final int x = current.location.x(); - final int y = current.location.y(); - final var right = tiles[x][y + 1]; - Stream> result = Stream.empty(); - final var incrementalPath = new ArrayList<>(pathSoFar); - incrementalPath.add(right.location()); - if (tiles[right.location().x() + 1][right.location().y()] == null || !isSideRoom(tiles[right.location().x() + 1][right.location().y()].location())) { - // this is not in front of a side room, - // we can stop here while other amphipods move - result = Stream.concat(result, Stream.of(Collections.unmodifiableList(incrementalPath))); - } - result = Stream.concat(result, iterateThroughPaths(amphipodType, right, incrementalPath)); - return result; - } - -// /** -// * Find all the paths an amphipod can take -// * -// * @param amphipodType the type of amphipod that is moving -// * @param current a tile through which the amphipod will take -// * @param pathSoFar the full path of the amphipod so far, *must* include _current_ -// * @return all the paths (start to finish) the amphipod can take -// */ -// @Deprecated -// List> getPaths(final AmphipodType amphipodType, final Tile current, final List pathSoFar) { -// final int x = current.location.x(); -// final int y = current.location.y(); -// final var up = tiles[x - 1][y]; -// final var down = tiles[x + 1][y]; -// final var left = tiles[x][y - 1]; -// final var right = tiles[x][y + 1]; -// -// final var result = new ArrayList>(); -// if (up != null && up.isVacant() && !pathSoFar.contains(up.location())) { -// // amphipod is in a side room -// if (isSideRoom(up.location()) && current.targetType == amphipodType) { -// // amphipod is in the back of the room in which it belongs, stop here -// return Collections.singletonList(pathSoFar); -// } -// final var incrementalPath = new ArrayList<>(pathSoFar); -// incrementalPath.add(up.location()); -// // whether "up" is the front of the room or the corridor outside the room, we have to keep moving -// result.addAll(getPaths(amphipodType, up, incrementalPath)); -// } -// if (down != null -// && down.isVacant() -// && !pathSoFar.contains(down.location()) -// // don't enter side room unless it is the ultimate destination -// && down.targetType == amphipodType) { -// // either entering a room or moving to the back of the room -// final var incrementalPath = new ArrayList<>(pathSoFar); -// incrementalPath.add(down.location()); -// if ((isCorridor(current.location()) && canEnterRoom(amphipodType, down)) || isSideRoom(current.location())) { -// // go as for back into the room as possible, don't just stop at the entrance -// result.addAll(getPaths(amphipodType, down, incrementalPath)); -// } -// } -// if (left != null && left.isVacant() && !pathSoFar.contains(left.location())) { -// final var incrementalPath = new ArrayList<>(pathSoFar); -// incrementalPath.add(left.location()); -// if (tiles[left.location().x() + 1][left.location().y()] == null || !isSideRoom(tiles[left.location().x() + 1][left.location().y()].location())) { -// // this is not in front of a side room, -// // we can stop here while other amphipods move -// result.add(Collections.unmodifiableList(incrementalPath)); -// } -// result.addAll(getPaths(amphipodType, left, incrementalPath)); -// } -// if (right != null && right.isVacant() && !pathSoFar.contains(right.location())) { -// final var incrementalPath = new ArrayList<>(pathSoFar); -// incrementalPath.add(right.location()); -// if (tiles[right.location().x() + 1][right.location().y()] == null || !isSideRoom(tiles[right.location().x() + 1][right.location().y()].location())) { -// // this is not in front of a side room, -// // we can stop here while other amphipods move -// result.add(Collections.unmodifiableList(incrementalPath)); -// } -// result.addAll(getPaths(amphipodType, right, incrementalPath)); -// } -// if (result.isEmpty() && pathSoFar.size() > 1) { -// // dead end, emit the path so far -// result.add(pathSoFar); -// } -// return Collections.unmodifiableList(result); -// } - - boolean canEnterRoom(final AmphipodType amphipodType, final Tile frontOfRoom) { - if (!isSideRoom(frontOfRoom.location())) { - throw new IllegalArgumentException("Not a side room: " + frontOfRoom); - } - if (frontOfRoom.targetType() != amphipodType) { - // this is not the destination room - return false; - } - // ensure all occupants have this as their destination - boolean hasOccupants = false; - for (var roomTile = tiles()[frontOfRoom.location().x() + 1][frontOfRoom.location().y()]; - roomTile != null; - roomTile = tiles()[roomTile.location().x() + 1][roomTile.location().y()]) { - if (roomTile.amphipodType() == null) { - if (hasOccupants) { - System.err.println("***There is a gap in the room***"); - return false; // there is a gap that shouldn't be here - } - continue; - } else { - hasOccupants = true; - } - if (!roomTile.hasTargetType()) { - return false; - } - } - return true; - } - - /** - * Estimate the energy required for all the amphipods to get to their side rooms. This strictly underestimates - * the amount of energy required. It assumes: each amphipod has an unobstructed path to their room, they only - * need to get to the front of the room, not all the way to the back, they do not need to take any detours to - * let other amphipods pass (e.g. they don't need to leave the room and then come back in). - * - * @return an underestimate of the energy required for the amphipods of the burrow to self-organise - */ - public int estimatedDistanceToSolution() { - if (estimatedDistanceCache.containsKey(this)) { - return estimatedDistanceCache.get(this); - } - int result = 0; - for (int i = tiles.length; --i >= 0; ) { - final var row = tiles[i]; - for (int j = row.length; --j >= 0; ) { - final var tile = row[j]; - if (tile != null && tile.amphipodType != null) { - final int horizontalDistance = - Math.abs(tile.amphipodType.destinationColumn - tile.location().y()); - int verticalDistance = 0; - if (horizontalDistance != 0) { - // get to the corridor - verticalDistance = tile.location().x() - 1; - // enter the side room - verticalDistance += 1; - } else if (isCorridor(tile.location())) { - // it's implied that horizontal distance is 0 - // we're right outside the target room - // enter the side room - verticalDistance = 1; - } - final int distance = verticalDistance + horizontalDistance; - result += distance * tile.amphipodType().energyPerStep; - } - } - } - estimatedDistanceCache.put(this, result); - return result; - } - - } - - protected record Point(int x, int y) { - - public Tile getTile(final Tile[][] tiles) { - return tiles[x()][y()]; - } - - public void setTile(final Tile[][] tiles, final Tile tile) { - if (tile.location().x() != x() || tile.location().y() != y()) { - throw new IllegalArgumentException("Tile and location do not match"); - } - tiles[x()][y()] = tile; - } - } - - public record Tile(Point location, AmphipodType targetType, AmphipodType amphipodType) { - - public Tile updateType(final AmphipodType newType) { - return new Tile(location, targetType, newType); - } - - public boolean isVacant() { - return amphipodType == null; - } - - public boolean hasTargetType() { - return Objects.equals(amphipodType(), targetType()); - } - - } - -// public int lwst(final Node start) { -// final var lowestCostToNode = new ConcurrentHashMap(); -// final var estimatedCostThroughNode = new ConcurrentHashMap(); -// final var openSet = new PriorityBlockingQueue(100000, Comparator.comparing(estimatedCostThroughNode::get)); -// -// // add the starting node, getting there is free -// lowestCostToNode.put(start, 0); -// estimatedCostThroughNode.put(start, start.estimatedDistanceToSolution()); -// openSet.add(start); -// -// final var executor = ForkJoinPool.commonPool(); -// final var stateModifiers = new LinkedBlockingDeque>(); -// final var complete = new AtomicBoolean(false); -// executor.execute(() -> { -// while (!complete.get()) { -// Thread.yield(); -// try { -// final var current = openSet.take(); -// if (current.isSolution()) { -// stateModifiers.addFirst(() -> lowestCostToNode.get(current)); -// } -// final var lowestCostToCurrent = lowestCostToNode.get(current); -// executor.execute(() -> current.getBranches().map(branchResult -> { -// final var tentativeBranchCost = lowestCostToCurrent + branchResult.cost(); -// final var branchNode = branchResult.node(); -// final Supplier updater = () -> { -// if (tentativeBranchCost < lowestCostToNode.getOrDefault(branchNode, Integer.MAX_VALUE)) { -// // either we've never visited this node before, -// // or the last time we did, we took a more expensive route -// lowestCostToNode.put(branchNode, tentativeBranchCost); -// -// // update the cost through this branch -// // need to remove and re-add to get correct ordering in the open set -// estimatedCostThroughNode.put(branchNode, tentativeBranchCost + branchNode.estimatedDistanceToSolution()); -// -// openSet.remove(branchNode); // O(n) -// openSet.add(branchNode); // O(log(n)) -// } -// return (Integer)null; -// }; -// return updater; -// }).forEach(stateModifiers::addLast)); -// } catch (InterruptedException e) { -// e.printStackTrace(); -// complete.set(true); -// Thread.currentThread().interrupt(); -// throw new RuntimeException(e.getMessage(), e); -// } -// } -// }); -// // process updates sequentially -// while (!complete.get()) { -// Thread.yield(); -// try { -// final var updater = stateModifiers.takeFirst(); -// final var cost = updater.get(); -// if (cost != null) { -// complete.set(true); -// return cost; -// } -// } catch (InterruptedException e) { -// e.printStackTrace(); -// complete.set(true); -// Thread.currentThread().interrupt(); -// throw new RuntimeException(e.getMessage(), e); -// } -// } -// throw new IllegalStateException("An error occurred"); -// } -// -// protected String id(Node node) { -// final var outputStream = new ByteArrayOutputStream(); -// outputStream.write(node.hashCode()); -// return Base64.getEncoder().encodeToString(outputStream.toByteArray()); -// } - - public int lowest(final Node start) { - final var lowestCostToNode = new ConcurrentHashMap(); - final var estimatedCostThroughNode = new ConcurrentHashMap(); - final var openSet = new PriorityBlockingQueue(100000, Comparator.comparing(estimatedCostThroughNode::get)); - - // add the starting node, getting there is free - lowestCostToNode.put(start, 0); - estimatedCostThroughNode.put(start, start.estimatedDistanceToSolution()); - openSet.add(start); - - while (!openSet.isEmpty()) { - if(Node.solutionCache.size() % 10000 == 0) { - System.err.println(openSet.size() + " branches left to check in the open set"); - System.err.println("Appraised the energy cost to " + lowestCostToNode.size() + " nodes."); - System.err.println("Estimated the energy cost through " + estimatedCostThroughNode.size() + " nodes."); - System.err.println("Lowest estimated cost so far: " + estimatedCostThroughNode.get(openSet.peek())); - } - final var current = openSet.poll(); // O(log(n)) - if (current.isSolution()) { - System.err.println("Found solution:\n" + current); - return lowestCostToNode.get(current); - } - final var lowestCostToCurrent = lowestCostToNode.get(current); - current.getBranches() - .parallel() - .filter(branchResult -> { - final var tentativeBranchCost = lowestCostToCurrent + branchResult.cost(); - final var branchNode = branchResult.node(); - return tentativeBranchCost < lowestCostToNode.getOrDefault(branchNode, Integer.MAX_VALUE); - }) - .forEach(branchResult -> { - final var tentativeBranchCost = lowestCostToCurrent + branchResult.cost(); - final var branchNode = branchResult.node(); - // either we've never visited this node before, - // or the last time we did, we took a more expensive route - lowestCostToNode.put(branchNode, tentativeBranchCost); - - // update the cost through this branch - // need to remove and re-add to get correct ordering in the open set -// openSet.remove(branchNode); // O(n) - openSet.removeIf(node -> node.equals(branchNode)); - estimatedCostThroughNode.put(branchNode, tentativeBranchCost + branchNode.estimatedDistanceToSolution()); - openSet.add(branchNode); // O(log(n)) - }); -// current.getBranches().forEach(branchResult -> { -// final var tentativeBranchCost = lowestCostToCurrent + branchResult.cost(); -// final var branchNode = branchResult.node(); -// if (tentativeBranchCost < lowestCostToNode.getOrDefault(branchNode, Integer.MAX_VALUE)) { -// // either we've never visited this node before, -// // or the last time we did, we took a more expensive route -// lowestCostToNode.put(branchNode, tentativeBranchCost); -// -// // update the cost through this branch -// // need to remove and re-add to get correct ordering in the open set -// openSet.remove(branchNode); // O(n) -// estimatedCostThroughNode.put(branchNode, tentativeBranchCost + branchNode.estimatedDistanceToSolution()); -// openSet.add(branchNode); // O(log(n)) -// } -// }); - } - throw new IllegalStateException("Amphipods are gridlocked :-("); - } - - @Nested - public class NodeTest { - - @Test - public final void verifyEquality() { - // given - final var string = """ - ############# - #.....D.D.A.# - ###.#B#C#.### - #A#B#C#.# - ######### - """; - final var x = Node.createInitialNode(parseGrid(string.lines().toList())); - final var y = Node.createInitialNode(parseGrid(string.lines().toList())); - - // when - - // then - assertEquals(x.hashCode(), y.hashCode()); - assertEquals(x, y); - } - - @Test - public final void verifyBranchEquality() { - // given - final var string = """ - ############# - #.....D.D.A.# - ###.#B#C#.### - #A#B#C#.# - ######### - """; - final var original = Node.createInitialNode(parseGrid(string.lines().toList())); - - // when - final var x = original.branch(Collections.singletonList(new Move(new Point(2, 5), new Point(1, 2)))); - final var y = original.branch(Collections.singletonList(new Move(new Point(2, 5), new Point(1, 2)))); - - // then - assertEquals(x.hashCode(), y.hashCode()); - assertEquals(x, y); - } - - @Test - public final void verifyEstimatedDistanceIsZero() { - // given - final var string = """ - ############# - #...........# - ###A#B#C#D### - #A#B#C#D# - ######### - """; - final var initial = Node.createInitialNode(parseGrid(string.lines().toList())); - - // when - final var result = initial.estimatedDistanceToSolution(); - - // then - assertEquals(0, result); - } - - @Disabled - @Test - public final void verifyEstimationOrdering() { - // given - final var states = new String[]{ - """ - ############# - #...........# - ###B#C#B#D### - #A#D#C#A# - ######### - """, - """ - ############# - #...B.......# - ###B#C#.#D### - #A#D#C#A# - ######### - """, - """ - ############# - #...B.......# - ###B#.#C#D### - #A#D#C#A# - ######### - """, - """ - ############# - #.....D.....# - ###B#.#C#D### - #A#B#C#A# - ######### - """, - """ - ############# - #.....D.....# - ###.#B#C#D### - #A#B#C#A# - ######### - """, - """ - ############# - #.....D.D.A.# - ###.#B#C#.### - #A#B#C#.# - ######### - """, - """ - ############# - #.........A.# - ###.#B#C#D### - #A#B#C#D# - ######### - """, - """ - ############# - #...........# - ###A#B#C#D### - #A#B#C#D# - ######### - """ - }; - final var nodes = Arrays.stream(states) - .map(String::lines) - .map(Stream::toList) - .map(Day23.this::parseGrid) - .map(Node::createInitialNode) - .toList(); - - // when - - // then - for (int i = 1; i < nodes.size(); i++) { - final var previous = nodes.get(i - 1); - final var current = nodes.get(i); - assertTrue(previous.estimatedDistanceToSolution() >= current.estimatedDistanceToSolution(), - "Previous state has a lower estimated distance. Previous:\n" + previous + "\n(cost: " + previous.estimatedDistanceToSolution() + ")\nCurrent:\n" + current + "\n(cost: " + current.estimatedDistanceToSolution() + ")"); - } - } - } - - @Test - public final void part1() { - final var initial = Node.createInitialNode(parseGrid(getInput().toList())); - - System.out.println("Part 1: " + lowest(initial)); - } - - @Disabled - @Test - public final void part2() { - final var lines = getInput().collect(Collectors.toList()); - lines.add(3, " #D#B#A#C# "); - lines.add(3, " #D#C#B#A# "); - final var initial = Node.createInitialNode(parseGrid(lines)); - System.err.println("Initial state:\n" + initial); - - System.out.println("Part 2: " + lowest(initial)); - } - -} \ 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 100755 index 51eea06..0000000 --- a/src/test/java/com/macasaet/Day24.java +++ /dev/null @@ -1,284 +0,0 @@ -package com.macasaet; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; -import java.util.PrimitiveIterator; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * --- Day 24: Arithmetic Logic Unit --- - */ -public class Day24 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-24.txt"), - false); - } - - public static class ArithmeticLogicUnit { - public BigInteger getW() { - return w; - } - - public void setW(BigInteger w) { - this.w = w; - } - - public BigInteger getX() { - return x; - } - - public void setX(BigInteger x) { - this.x = x; - } - - public BigInteger getY() { - return y; - } - - public void setY(BigInteger y) { - this.y = y; - } - - public BigInteger getZ() { - return z; - } - - public void setZ(BigInteger z) { - this.z = z; - } - - private BigInteger w = BigInteger.ZERO, x = BigInteger.ZERO, y = BigInteger.ZERO, z = BigInteger.ZERO; - - public List getInstructions() { - return instructions; - } - - public void setInstructions(List instructions) { - this.instructions = instructions; - } - - private List instructions = new ArrayList<>(); - - public boolean isValid(final String modelNumber) { - final var iterator = modelNumber.chars().map(Character::getNumericValue).iterator(); - for (final var instruction : getInstructions()) { - instruction.evaluate(iterator); - } - return BigInteger.ZERO.equals(getZ()); - } - - public static ArithmeticLogicUnit parse(final Stream lines) { - final var result = new ArithmeticLogicUnit(); - final List instructions = lines.map(line -> { - final var components = line.split(" "); - - final Consumer resultSetter = switch (components[1]) { - case "w" -> result::setW; - case "x" -> result::setX; - case "y" -> result::setY; - case "z" -> result::setZ; - default -> throw new IllegalArgumentException("Invalid instruction, invalid l-value: " + line); - }; - final Supplier xSupplier = switch (components[1]) { - case "w" -> result::getW; - case "x" -> result::getX; - case "y" -> result::getY; - case "z" -> result::getZ; - default -> throw new IllegalArgumentException("Invalid instruction, invalid l-value: " + line); - }; - final Supplier ySupplier = components.length > 2 ? switch (components[2]) { - case "w" -> result::getW; - case "x" -> result::getX; - case "y" -> result::getY; - case "z" -> result::getZ; - default -> () -> new BigInteger(components[2]); - } : () -> { - throw new IllegalStateException(); - }; - return switch (components[0]) { - case "inp" -> new Input(resultSetter); - case "add" -> new Add(resultSetter, xSupplier, ySupplier); - case "mul" -> new Multiply(resultSetter, xSupplier, ySupplier); - case "div" -> new Divide(resultSetter, xSupplier, ySupplier); - case "mod" -> new Modulo(resultSetter, xSupplier, ySupplier); - case "eql" -> new Equals(resultSetter, xSupplier, ySupplier); - default -> throw new IllegalArgumentException("Invalid instruction: " + line); - }; - }).toList(); - result.setInstructions(instructions); - return result; - } - - public void reset() { - setW(BigInteger.ZERO); - setX(BigInteger.ZERO); - setY(BigInteger.ZERO); - setZ(BigInteger.ZERO); - } - } - - public interface Instruction { - void evaluate(PrimitiveIterator.OfInt input); - } - - public record Input(Consumer setter) implements Instruction { - - public void evaluate(PrimitiveIterator.OfInt input) { - setter.accept(BigInteger.valueOf(input.nextInt())); - } - } - - public record Add(Consumer resultSetter, Supplier xSupplier, - Supplier ySupplier) implements Instruction { - public void evaluate(PrimitiveIterator.OfInt _input) { - resultSetter.accept(xSupplier.get().add(ySupplier.get())); - } - } - - public record Multiply(Consumer resultSetter, Supplier xSupplier, - Supplier ySupplier) implements Instruction { - public void evaluate(PrimitiveIterator.OfInt _input) { - resultSetter.accept(xSupplier.get().multiply(ySupplier.get())); - } - } - - public record Divide(Consumer resultSetter, Supplier xSupplier, - Supplier ySupplier) implements Instruction { - public void evaluate(PrimitiveIterator.OfInt _input) { - resultSetter.accept(xSupplier.get().divide(ySupplier.get())); - } - } - - public record Modulo(Consumer resultSetter, Supplier xSupplier, - Supplier ySupplier) implements Instruction { - public void evaluate(PrimitiveIterator.OfInt _input) { - resultSetter.accept(xSupplier.get().mod(ySupplier.get())); - } - } - - public record Equals(Consumer resultSetter, Supplier xSupplier, - Supplier ySupplier) implements Instruction { - public void evaluate(PrimitiveIterator.OfInt _input) { - resultSetter.accept(xSupplier.get().equals(ySupplier.get()) ? BigInteger.ONE : BigInteger.ZERO); - } - } - - @Nested - public class ArithmeticLogicUnitTest { - @Disabled - @Test - public void testNegation() { - // given - final var input = """ - inp x - mul x -1 - """; - final var alu = ArithmeticLogicUnit.parse(input.lines()); - - // when - alu.isValid("7"); - - // then - assertEquals(-7, alu.getX()); - } - - @Disabled - @Test - public final void testThreeTimes() { - // given - final var input = """ - inp z - inp x - mul z 3 - eql z x - """; - final var alu = ArithmeticLogicUnit.parse(input.lines()); - - // when - alu.isValid("39"); - - // then - assertEquals(1, alu.getZ()); - } - - @Disabled - @Test - public final void testBinaryConversion() { - // given - final var input = """ - inp w - add z w - mod z 2 - div w 2 - add y w - mod y 2 - div w 2 - add x w - mod x 2 - div w 2 - mod w 2 - """; - final var alu = ArithmeticLogicUnit.parse(input.lines()); - - // when - alu.isValid("9"); - - // then - assertEquals(1, alu.getW()); - assertEquals(0, alu.getX()); - assertEquals(0, alu.getY()); - assertEquals(1, alu.getZ()); - } - - @Test - public final void testIsValid() { - // given - final var alu = ArithmeticLogicUnit.parse(getInput()); - - // when - final var result = alu.isValid("13579246899999"); - - // then - System.err.println("z=" + alu.getZ()); - } - } - - @Disabled - @Test - public final void part1() { - final var monad = getInput().toList(); - final var counter = new AtomicInteger(0); - final var result = Stream.iterate(new BigInteger("99999999999999"), previous -> previous.subtract(BigInteger.ONE)) - .parallel() - .filter(candidate -> { - final int count = counter.updateAndGet(previous -> previous + 1); - if(count % 10000 == 0) { - System.err.println("Testing: " + candidate); - } - final var alu = ArithmeticLogicUnit.parse(monad.stream()); - return alu.isValid(candidate.toString()); - }).findFirst(); - System.out.println("Part 1: " + result.orElseThrow()); - } - - @Disabled - @Test - public final void part2() { - - System.out.println("Part 2: " + null); - } - -} \ 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 100755 index d9e5335..0000000 --- a/src/test/java/com/macasaet/Day25.java +++ /dev/null @@ -1,159 +0,0 @@ -package com.macasaet; - -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -/** - * - */ -public class Day25 { - - protected Stream getInput() { - return StreamSupport - .stream(new LineSpliterator("day-25.txt"), - false); - } - - public record Herd() { - - } - - public record SeaCucumber() { - - } - - public record OceanFloor(char[][] grid) { - public static OceanFloor parse(final Stream lines) { - final var list = lines.toList(); - final var grid = new char[list.size()][]; - for (int i = list.size(); --i >= 0; ) { - final var line = list.get(i); - grid[i] = new char[line.length()]; - for (int j = line.length(); --j >= 0; grid[i][j] = line.charAt(j)) ; - } - return new OceanFloor(grid); - } - - public OceanFloor step() { - return stepEast().stepSouth(); - } - - boolean isOccupied(final int x, final int y) { - return grid[x][y] != '.'; - } - - char[][] createBlankGrid() { - final char[][] result = new char[grid().length][]; - for (int i = grid().length; --i >= 0; ) { - final var originalRow = grid()[i]; - final var newRow = new char[originalRow.length]; - for (int j = originalRow.length; --j >= 0; newRow[j] = '.') ; - result[i] = newRow; - } - return result; - } - - OceanFloor stepEast() { - final char[][] copy = createBlankGrid(); - for (int i = grid().length; --i >= 0; ) { - final var originalRow = grid()[i]; - for (int j = originalRow.length; --j >= 0; ) { - final var nextIndex = (j + 1) % originalRow.length; - if (originalRow[j] == '>') { - if (!isOccupied(i, nextIndex)) { - copy[i][nextIndex] = '>'; - } else { - copy[i][j] = '>'; - } - } else if (originalRow[j] != '.') { - copy[i][j] = originalRow[j]; - } - } - } - return new OceanFloor(copy); - } - - OceanFloor stepSouth() { - final char[][] copy = createBlankGrid(); - for (int i = grid().length; --i >= 0; ) { - final var originalRow = grid()[i]; - final var nextIndex = (i + 1) % grid().length; - for (int j = originalRow.length; --j >= 0; ) { - if (originalRow[j] == 'v') { - if (!isOccupied(nextIndex, j)) { - copy[nextIndex][j] = 'v'; - } else { - copy[i][j] = 'v'; - } - } else if (originalRow[j] != '.') { - copy[i][j] = originalRow[j]; - } - } - } - return new OceanFloor(copy); - } - - public String toString() { - final var builder = new StringBuilder(); - for (final var row : grid()) { - builder.append(row).append('\n'); - } - return builder.toString(); - } - - public int hashCode() { - int result = 1; - for (final var row : grid()) { - result += 31 * result + Arrays.hashCode(row); - } - return result; - } - - public boolean equals(final Object o) { - if (o == null) { - return false; - } else if (this == o) { - return true; - } - try { - final OceanFloor other = (OceanFloor) o; - if (grid().length != other.grid().length) { - return false; - } - for (int i = grid.length; --i >= 0; ) { - final var mine = grid()[i]; - final var theirs = other.grid()[i]; - if (!Arrays.equals(mine, theirs)) { - return false; - } - } - return true; - } catch (final ClassCastException _cce) { - return false; - } - } - } - - @Test - public final void part1() { - var oceanFloor = OceanFloor.parse(getInput()); - for (int i = 1; ; i++) { - final var next = oceanFloor.step(); - if (next.equals(oceanFloor)) { - System.out.println("Part 1: " + i); - break; - } - oceanFloor = next; - } - } - - @Test - public final void part2() { - - System.out.println("Part 2: " + null); - } - -} \ No newline at end of file diff --git a/src/test/resources/sample/day-01.txt b/src/test/resources/sample/day-01.txt deleted file mode 100644 index 167e291..0000000 --- a/src/test/resources/sample/day-01.txt +++ /dev/null @@ -1,10 +0,0 @@ -199 -200 -208 -210 -200 -207 -240 -269 -260 -263 diff --git a/src/test/resources/sample/day-02.txt b/src/test/resources/sample/day-02.txt deleted file mode 100644 index b7172ac..0000000 --- a/src/test/resources/sample/day-02.txt +++ /dev/null @@ -1,6 +0,0 @@ -forward 5 -down 5 -forward 8 -up 3 -down 8 -forward 2 diff --git a/src/test/resources/sample/day-03.txt b/src/test/resources/sample/day-03.txt deleted file mode 100644 index a6366a8..0000000 --- a/src/test/resources/sample/day-03.txt +++ /dev/null @@ -1,12 +0,0 @@ -00100 -11110 -10110 -10111 -10101 -01111 -00111 -11100 -10000 -11001 -00010 -01010 diff --git a/src/test/resources/sample/day-04.txt b/src/test/resources/sample/day-04.txt deleted file mode 100644 index 669a51d..0000000 --- a/src/test/resources/sample/day-04.txt +++ /dev/null @@ -1,19 +0,0 @@ -7,4,9,5,11,17,23,2,0,14,21,24,10,16,13,6,15,25,12,22,18,20,8,19,3,26,1 - -22 13 17 11 0 - 8 2 23 4 24 -21 9 14 16 7 - 6 10 3 18 5 - 1 12 20 15 19 - - 3 15 0 2 22 - 9 18 13 17 5 -19 8 7 25 23 -20 11 10 24 4 -14 21 16 12 6 - -14 21 17 24 4 -10 16 15 9 19 -18 8 23 26 20 -22 11 13 6 5 - 2 0 12 3 7 diff --git a/src/test/resources/sample/day-05.txt b/src/test/resources/sample/day-05.txt deleted file mode 100644 index b258f68..0000000 --- a/src/test/resources/sample/day-05.txt +++ /dev/null @@ -1,10 +0,0 @@ -0,9 -> 5,9 -8,0 -> 0,8 -9,4 -> 3,4 -2,2 -> 2,1 -7,0 -> 7,4 -6,4 -> 2,0 -0,9 -> 2,9 -3,4 -> 1,4 -0,0 -> 8,8 -5,5 -> 8,2 diff --git a/src/test/resources/sample/day-06.txt b/src/test/resources/sample/day-06.txt deleted file mode 100644 index 55129f1..0000000 --- a/src/test/resources/sample/day-06.txt +++ /dev/null @@ -1 +0,0 @@ -3,4,3,1,2 diff --git a/src/test/resources/sample/day-07.txt b/src/test/resources/sample/day-07.txt deleted file mode 100644 index 18bd32a..0000000 --- a/src/test/resources/sample/day-07.txt +++ /dev/null @@ -1 +0,0 @@ -16,1,2,0,4,2,7,1,2,14 diff --git a/src/test/resources/sample/day-08.txt b/src/test/resources/sample/day-08.txt deleted file mode 100644 index c9f629b..0000000 --- a/src/test/resources/sample/day-08.txt +++ /dev/null @@ -1,10 +0,0 @@ -be cfbegad cbdgef fgaecd cgeb fdcge agebfd fecdb fabcd edb | fdgacbe cefdb cefbgd gcbe -edbfga begcd cbg gc gcadebf fbgde acbgfd abcde gfcbed gfec | fcgedb cgb dgebacf gc -fgaebd cg bdaec gdafb agbcfd gdcbef bgcad gfac gcb cdgabef | cg cg fdcagb cbg -fbegcd cbd adcefb dageb afcb bc aefdc ecdab fgdeca fcdbega | efabcd cedba gadfec cb -aecbfdg fbg gf bafeg dbefa fcge gcbea fcaegb dgceab fcbdga | gecf egdcabf bgf bfgea -fgeab ca afcebg bdacfeg cfaedg gcfdb baec bfadeg bafgc acf | gebdcfa ecba ca fadegcb -dbcfg fgd bdegcaf fgec aegbdf ecdfab fbedc dacgb gdcebf gf | cefg dcbef fcge gbcadfe -bdfegc cbegaf gecbf dfcage bdacg ed bedf ced adcbefg gebcd | ed bcgafe cdgba cbgef -egadfb cdbfeg cegd fecab cgb gbdefca cg fgcdab egfdb bfceg | gbdfcae bgc cg cgb -gcafb gcf dcaebfg ecagb gf abcdeg gaef cafbge fdbac fegbdc | fgae cfgab fg bagce diff --git a/src/test/resources/sample/day-09.txt b/src/test/resources/sample/day-09.txt deleted file mode 100644 index 6dee4a4..0000000 --- a/src/test/resources/sample/day-09.txt +++ /dev/null @@ -1,5 +0,0 @@ -2199943210 -3987894921 -9856789892 -8767896789 -9899965678 diff --git a/src/test/resources/sample/day-10.txt b/src/test/resources/sample/day-10.txt deleted file mode 100644 index b1518d9..0000000 --- a/src/test/resources/sample/day-10.txt +++ /dev/null @@ -1,10 +0,0 @@ -[({(<(())[]>[[{[]{<()<>> -[(()[<>])]({[<{<<[]>>( -{([(<{}[<>[]}>{[]{[(<()> -(((({<>}<{<{<>}{[]{[]{} -[[<[([]))<([[{}[[()]]] -[{[{({}]{}}([{[{{{}}([] -{<[[]]>}<{[{[{[]{()[[[] -[<(<(<(<{}))><([]([]() -<{([([[(<>()){}]>(<<{{ -<{([{{}}[<[[[<>{}]]]>[]] diff --git a/src/test/resources/sample/day-11.txt b/src/test/resources/sample/day-11.txt deleted file mode 100644 index 03743f6..0000000 --- a/src/test/resources/sample/day-11.txt +++ /dev/null @@ -1,10 +0,0 @@ -5483143223 -2745854711 -5264556173 -6141336146 -6357385478 -4167524645 -2176841721 -6882881134 -4846848554 -5283751526 diff --git a/src/test/resources/sample/day-12.txt b/src/test/resources/sample/day-12.txt deleted file mode 100644 index 6fd8c41..0000000 --- a/src/test/resources/sample/day-12.txt +++ /dev/null @@ -1,7 +0,0 @@ -start-A -start-b -A-c -A-b -b-d -A-end -b-end diff --git a/src/test/resources/sample/day-13.txt b/src/test/resources/sample/day-13.txt deleted file mode 100644 index 282114c..0000000 --- a/src/test/resources/sample/day-13.txt +++ /dev/null @@ -1,21 +0,0 @@ -6,10 -0,14 -9,10 -0,3 -10,4 -4,11 -6,0 -6,12 -4,1 -0,13 -10,12 -3,4 -3,0 -8,4 -1,10 -2,14 -8,10 -9,0 - -fold along y=7 -fold along x=5 diff --git a/src/test/resources/sample/day-14.txt b/src/test/resources/sample/day-14.txt deleted file mode 100644 index b5594dd..0000000 --- a/src/test/resources/sample/day-14.txt +++ /dev/null @@ -1,18 +0,0 @@ -NNCB - -CH -> B -HH -> N -CB -> H -NH -> C -HB -> C -HC -> B -HN -> C -NN -> C -BH -> H -NC -> B -NB -> B -BN -> B -BB -> N -BC -> B -CC -> N -CN -> C diff --git a/src/test/resources/sample/day-15.txt b/src/test/resources/sample/day-15.txt deleted file mode 100644 index ab80887..0000000 --- a/src/test/resources/sample/day-15.txt +++ /dev/null @@ -1,10 +0,0 @@ -1163751742 -1381373672 -2136511328 -3694931569 -7463417111 -1319128137 -1359912421 -3125421639 -1293138521 -2311944581 diff --git a/src/test/resources/sample/day-16.txt b/src/test/resources/sample/day-16.txt deleted file mode 100644 index 3f0eda1..0000000 --- a/src/test/resources/sample/day-16.txt +++ /dev/null @@ -1 +0,0 @@ -D2FE28 diff --git a/src/test/resources/sample/day-17.txt b/src/test/resources/sample/day-17.txt deleted file mode 100644 index a07e02d..0000000 --- a/src/test/resources/sample/day-17.txt +++ /dev/null @@ -1 +0,0 @@ -target area: x=20..30, y=-10..-5 diff --git a/src/test/resources/sample/day-18.txt b/src/test/resources/sample/day-18.txt deleted file mode 100644 index 1368dc4..0000000 --- a/src/test/resources/sample/day-18.txt +++ /dev/null @@ -1,10 +0,0 @@ -[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]] -[[[5,[2,8]],4],[5,[[9,9],0]]] -[6,[[[6,2],[5,6]],[[7,6],[4,7]]]] -[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]] -[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]] -[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]] -[[[[5,4],[7,7]],8],[[8,3],8]] -[[9,3],[[9,9],[6,[4,9]]]] -[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]] -[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]] diff --git a/src/test/resources/sample/day-19.txt b/src/test/resources/sample/day-19.txt deleted file mode 100644 index 4e496e9..0000000 --- a/src/test/resources/sample/day-19.txt +++ /dev/null @@ -1,136 +0,0 @@ ---- scanner 0 --- -404,-588,-901 -528,-643,409 --838,591,734 -390,-675,-793 --537,-823,-458 --485,-357,347 --345,-311,381 --661,-816,-575 --876,649,763 --618,-824,-621 -553,345,-567 -474,580,667 --447,-329,318 --584,868,-557 -544,-627,-890 -564,392,-477 -455,729,728 --892,524,684 --689,845,-530 -423,-701,434 -7,-33,-71 -630,319,-379 -443,580,662 --789,900,-551 -459,-707,401 - ---- scanner 1 --- -686,422,578 -605,423,415 -515,917,-361 --336,658,858 -95,138,22 --476,619,847 --340,-569,-846 -567,-361,727 --460,603,-452 -669,-402,600 -729,430,532 --500,-761,534 --322,571,750 --466,-666,-811 --429,-592,574 --355,545,-477 -703,-491,-529 --328,-685,520 -413,935,-424 --391,539,-444 -586,-435,557 --364,-763,-893 -807,-499,-711 -755,-354,-619 -553,889,-390 - ---- scanner 2 --- -649,640,665 -682,-795,504 --784,533,-524 --644,584,-595 --588,-843,648 --30,6,44 --674,560,763 -500,723,-460 -609,671,-379 --555,-800,653 --675,-892,-343 -697,-426,-610 -578,704,681 -493,664,-388 --671,-858,530 --667,343,800 -571,-461,-707 --138,-166,112 --889,563,-600 -646,-828,498 -640,759,510 --630,509,768 --681,-892,-333 -673,-379,-804 --742,-814,-386 -577,-820,562 - ---- scanner 3 --- --589,542,597 -605,-692,669 --500,565,-823 --660,373,557 --458,-679,-417 --488,449,543 --626,468,-788 -338,-750,-386 -528,-832,-391 -562,-778,733 --938,-730,414 -543,643,-506 --524,371,-870 -407,773,750 --104,29,83 -378,-903,-323 --778,-728,485 -426,699,580 --438,-605,-362 --469,-447,-387 -509,732,623 -647,635,-688 --868,-804,481 -614,-800,639 -595,780,-596 - ---- scanner 4 --- -727,592,562 --293,-554,779 -441,611,-461 --714,465,-776 --743,427,-804 --660,-479,-426 -832,-632,460 -927,-485,-438 -408,393,-506 -466,436,-512 -110,16,151 --258,-428,682 --393,719,612 --211,-452,876 -808,-476,-593 --575,615,604 --485,667,467 --680,325,-822 --627,-443,-432 -872,-547,-609 -833,512,582 -807,604,487 -839,-516,451 -891,-625,532 --652,-548,-490 -30,-46,-14 diff --git a/src/test/resources/sample/day-20.txt b/src/test/resources/sample/day-20.txt deleted file mode 100644 index 8fa4bd4..0000000 --- a/src/test/resources/sample/day-20.txt +++ /dev/null @@ -1,7 +0,0 @@ -..#.#..#####.#.#.#.###.##.....###.##.#..###.####..#####..#....#..#..##..###..######.###...####..#..#####..##..#.#####...##.#.#..#.##..#.#......#.###.######.###.####...#.##.##..#..#..#####.....#.#....###..#.##......#.....#..#..#..##..#...##.######.####.####.#.#...#.......#..#.#.#...####.##.#......#..#...##.#.##..#...##.#.##..###.#......#.#.......#.#.#.####.###.##...#.....####.#..#..#.##.#....##..#.####....##...##..#...#......#.#.......#.......##..####..#...#.#.#...##..#.#..###..#####........#..####......#..# - -#..#. -#.... -##..# -..#.. -..### diff --git a/src/test/resources/sample/day-21.txt b/src/test/resources/sample/day-21.txt deleted file mode 100644 index 3f69194..0000000 --- a/src/test/resources/sample/day-21.txt +++ /dev/null @@ -1,2 +0,0 @@ -Player 1 starting position: 4 -Player 2 starting position: 8 diff --git a/src/test/resources/sample/day-22.txt b/src/test/resources/sample/day-22.txt deleted file mode 100644 index 2790bed..0000000 --- a/src/test/resources/sample/day-22.txt +++ /dev/null @@ -1,60 +0,0 @@ -on x=-5..47,y=-31..22,z=-19..33 -on x=-44..5,y=-27..21,z=-14..35 -on x=-49..-1,y=-11..42,z=-10..38 -on x=-20..34,y=-40..6,z=-44..1 -off x=26..39,y=40..50,z=-2..11 -on x=-41..5,y=-41..6,z=-36..8 -off x=-43..-33,y=-45..-28,z=7..25 -on x=-33..15,y=-32..19,z=-34..11 -off x=35..47,y=-46..-34,z=-11..5 -on x=-14..36,y=-6..44,z=-16..29 -on x=-57795..-6158,y=29564..72030,z=20435..90618 -on x=36731..105352,y=-21140..28532,z=16094..90401 -on x=30999..107136,y=-53464..15513,z=8553..71215 -on x=13528..83982,y=-99403..-27377,z=-24141..23996 -on x=-72682..-12347,y=18159..111354,z=7391..80950 -on x=-1060..80757,y=-65301..-20884,z=-103788..-16709 -on x=-83015..-9461,y=-72160..-8347,z=-81239..-26856 -on x=-52752..22273,y=-49450..9096,z=54442..119054 -on x=-29982..40483,y=-108474..-28371,z=-24328..38471 -on x=-4958..62750,y=40422..118853,z=-7672..65583 -on x=55694..108686,y=-43367..46958,z=-26781..48729 -on x=-98497..-18186,y=-63569..3412,z=1232..88485 -on x=-726..56291,y=-62629..13224,z=18033..85226 -on x=-110886..-34664,y=-81338..-8658,z=8914..63723 -on x=-55829..24974,y=-16897..54165,z=-121762..-28058 -on x=-65152..-11147,y=22489..91432,z=-58782..1780 -on x=-120100..-32970,y=-46592..27473,z=-11695..61039 -on x=-18631..37533,y=-124565..-50804,z=-35667..28308 -on x=-57817..18248,y=49321..117703,z=5745..55881 -on x=14781..98692,y=-1341..70827,z=15753..70151 -on x=-34419..55919,y=-19626..40991,z=39015..114138 -on x=-60785..11593,y=-56135..2999,z=-95368..-26915 -on x=-32178..58085,y=17647..101866,z=-91405..-8878 -on x=-53655..12091,y=50097..105568,z=-75335..-4862 -on x=-111166..-40997,y=-71714..2688,z=5609..50954 -on x=-16602..70118,y=-98693..-44401,z=5197..76897 -on x=16383..101554,y=4615..83635,z=-44907..18747 -off x=-95822..-15171,y=-19987..48940,z=10804..104439 -on x=-89813..-14614,y=16069..88491,z=-3297..45228 -on x=41075..99376,y=-20427..49978,z=-52012..13762 -on x=-21330..50085,y=-17944..62733,z=-112280..-30197 -on x=-16478..35915,y=36008..118594,z=-7885..47086 -off x=-98156..-27851,y=-49952..43171,z=-99005..-8456 -off x=2032..69770,y=-71013..4824,z=7471..94418 -on x=43670..120875,y=-42068..12382,z=-24787..38892 -off x=37514..111226,y=-45862..25743,z=-16714..54663 -off x=25699..97951,y=-30668..59918,z=-15349..69697 -off x=-44271..17935,y=-9516..60759,z=49131..112598 -on x=-61695..-5813,y=40978..94975,z=8655..80240 -off x=-101086..-9439,y=-7088..67543,z=33935..83858 -off x=18020..114017,y=-48931..32606,z=21474..89843 -off x=-77139..10506,y=-89994..-18797,z=-80..59318 -off x=8476..79288,y=-75520..11602,z=-96624..-24783 -on x=-47488..-1262,y=24338..100707,z=16292..72967 -off x=-84341..13987,y=2429..92914,z=-90671..-1318 -off x=-37810..49457,y=-71013..-7894,z=-105357..-13188 -off x=-27365..46395,y=31009..98017,z=15428..76570 -off x=-70369..-16548,y=22648..78696,z=-1892..86821 -on x=-53470..21291,y=-120233..-33476,z=-44150..38147 -off x=-93533..-4276,y=-16170..68771,z=-104985..-24507 \ No newline at end of file diff --git a/src/test/resources/sample/day-23.txt b/src/test/resources/sample/day-23.txt deleted file mode 100644 index 6a7120d..0000000 --- a/src/test/resources/sample/day-23.txt +++ /dev/null @@ -1,5 +0,0 @@ -############# -#...........# -###B#C#B#D### - #A#D#C#A# - ######### diff --git a/src/test/resources/sample/day-24.txt b/src/test/resources/sample/day-24.txt deleted file mode 100755 index a41747d..0000000 --- a/src/test/resources/sample/day-24.txt +++ /dev/null @@ -1,2 +0,0 @@ -inp x -mul x -1 diff --git a/src/test/resources/sample/day-25.txt b/src/test/resources/sample/day-25.txt deleted file mode 100755 index 0f3c0cd..0000000 --- a/src/test/resources/sample/day-25.txt +++ /dev/null @@ -1,9 +0,0 @@ -v...>>.vv> -.vv>>.vv.. ->>.>v>...v ->>v>>.>.v. -v>v.vv.v.. ->.>>..v... -.vv..>.>v. -v.v..>>v.v -....v..v.> From e6d94df73ee37862d75eaea0e3821173fdf7934f Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Wed, 30 Nov 2022 21:28:53 -0800 Subject: [PATCH 03/21] Day 1 --- .github/workflows/ci.yml | 2 +- src/test/java/com/macasaet/Day01.java | 63 ++++++++++++++++++++------- src/test/resources/sample/day-01.txt | 14 ++++++ 3 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 src/test/resources/sample/day-01.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e789d7..007257b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: distribution: 'temurin' java-version: '19' check-latest: true - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: ~/.m2 key: m2-${{ runner.os }}-19-${{ hashFiles('**/pom.xml') }} diff --git a/src/test/java/com/macasaet/Day01.java b/src/test/java/com/macasaet/Day01.java index 7af31fc..d70e9ed 100644 --- a/src/test/java/com/macasaet/Day01.java +++ b/src/test/java/com/macasaet/Day01.java @@ -1,43 +1,76 @@ package com.macasaet; -import org.junit.jupiter.api.Disabled; 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.stream.StreamSupport; /** - * --- Day 1: --- + * --- Day 1: Calorie Counting --- */ public class Day01 { - /** - * - * - * @return - */ - protected List getInput() { + protected Iterator getInput() { return StreamSupport .stream(new LineSpliterator("day-01.txt"), false) - .collect(ArrayList::new, List::add, List::addAll); + .iterator(); + } + + protected List getElves() { + var calories = new ArrayList(); + final var elves = new ArrayList(); + for (final var i = getInput(); i.hasNext(); ) { + final var line = i.next(); + if (line.isBlank()) { + elves.add(new Elf(Collections.unmodifiableList(calories))); + calories = new ArrayList<>(); + } else { + calories.add(new BigInteger(line.strip())); + } + } + if (!calories.isEmpty()) { + elves.add(new Elf(Collections.unmodifiableList(calories))); + } + return Collections.unmodifiableList(elves); } - @Disabled @Test public final void part1() { - final var list = getInput(); + final var elves = getElves(); + final var elf = elves.stream() + .max(Comparator.comparing(Elf::totalCaloriesCarried)) + .get(); - System.out.println("Part 1: " + null); + System.out.println("Part 1: " + elf.totalCaloriesCarried()); } - @Disabled @Test public final void part2() { - final var list = getInput(); + final var elves = getElves(); + final var list = elves.stream() + .sorted(Comparator.comparing(Elf::totalCaloriesCarried).reversed()) + .toList(); - System.out.println("Part 2: " + null); + 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/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 From 64274d9e645cccfe5077dc496a11d57fb6eedc5d Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Thu, 1 Dec 2022 21:34:48 -0800 Subject: [PATCH 04/21] Day 2 --- src/test/java/com/macasaet/Day02.java | 177 ++++++++++++++++++++++++++ src/test/resources/sample/day-02.txt | 3 + 2 files changed, 180 insertions(+) create mode 100644 src/test/java/com/macasaet/Day02.java create mode 100644 src/test/resources/sample/day-02.txt diff --git a/src/test/java/com/macasaet/Day02.java b/src/test/java/com/macasaet/Day02.java new file mode 100644 index 0000000..875f433 --- /dev/null +++ b/src/test/java/com/macasaet/Day02.java @@ -0,0 +1,177 @@ +package com.macasaet; + +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 { + + protected Stream getInput() { + return StreamSupport + .stream(new LineSpliterator("day-02.txt"), + false) + .map(line -> { + final var components = line.strip().split(" "); + return new Round(Shape.forChar(components[0].charAt(0)), + Shape.forChar(components[1].charAt(0)), + ResponseStrategy.forChar(components[1].charAt(0))); + }); + } + + @Test + public final void part1() { + final var result = getInput().mapToInt(Round::naiveScore).sum(); + + System.out.println("Part 1: " + result); + } + + @Test + public final void part2() { + final var result = getInput().mapToInt(Round::score).sum(); + + System.out.println("Part 2: " + result); + } + + /** + * A shape that a contestant can play in a round + */ + public enum Shape { + Rock { + public int score() { + return 1; + } + + public Shape beatenBy() { + return Paper; + } + + public 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) { + switch (c) { + case 'X': + case 'A': + return Shape.Rock; + case 'Y': + case 'B': + return Shape.Paper; + case 'Z': + case 'C': + return Shape.Scissors; + } + throw new IllegalArgumentException(); + } + + /** + * @return the inherent value of this shape + */ + public abstract int score(); + + /** + * @return the shape that beats this one + */ + public abstract Shape beatenBy(); + + /** + * @return the shape this one beats + */ + public abstract Shape beats(); + } + + /** + * 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) { + switch (c) { + case 'X': + return Lose; + case 'Y': + return Draw; + case 'Z': + return Win; + } + throw new IllegalArgumentException(); + } + + 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(); + } + + /** + * @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/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 From d2f3c99dff39e387d8ce6c6d198c4ac321092581 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Thu, 1 Dec 2022 22:29:30 -0800 Subject: [PATCH 05/21] Day 2 - simplify switch statements --- src/test/java/com/macasaet/Day02.java | 28 +++++++++++++-------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/test/java/com/macasaet/Day02.java b/src/test/java/com/macasaet/Day02.java index 875f433..424b241 100644 --- a/src/test/java/com/macasaet/Day02.java +++ b/src/test/java/com/macasaet/Day02.java @@ -83,18 +83,19 @@ public Shape beats() { }; public static Shape forChar(final int c) { - switch (c) { + return switch (c) { case 'X': case 'A': - return Shape.Rock; + yield Shape.Rock; case 'Y': case 'B': - return Shape.Paper; + yield Shape.Paper; case 'Z': case 'C': - return Shape.Scissors; - } - throw new IllegalArgumentException(); + yield Shape.Scissors; + default: + throw new IllegalArgumentException("Invalid shape: " + c); + }; } /** @@ -134,15 +135,12 @@ public Shape respond(Shape opponent) { }; public static ResponseStrategy forChar(final char c) { - switch (c) { - case 'X': - return Lose; - case 'Y': - return Draw; - case 'Z': - return Win; - } - throw new IllegalArgumentException(); + return switch (c) { + case 'X' -> Lose; + case 'Y' -> Draw; + case 'Z' -> Win; + default -> throw new IllegalArgumentException("Invalid strategy: " + c); + }; } public abstract Shape respond(final Shape opponent); From 67f7de6ee5c7520964c9b19b6daed7d3fc3d49ab Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Fri, 2 Dec 2022 21:31:41 -0800 Subject: [PATCH 06/21] Day 3 --- src/test/java/com/macasaet/Day03.java | 117 ++++++++++++++++++++++++++ src/test/resources/sample/day-03.txt | 6 ++ 2 files changed, 123 insertions(+) create mode 100644 src/test/java/com/macasaet/Day03.java create mode 100644 src/test/resources/sample/day-03.txt diff --git a/src/test/java/com/macasaet/Day03.java b/src/test/java/com/macasaet/Day03.java new file mode 100644 index 0000000..af6e98a --- /dev/null +++ b/src/test/java/com/macasaet/Day03.java @@ -0,0 +1,117 @@ +package com.macasaet; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.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 { + + 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 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/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 From 21710bbe5a42786bf712c4f69139e48ece87e9c0 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Sat, 3 Dec 2022 21:17:43 -0800 Subject: [PATCH 07/21] Day 4 --- src/test/java/com/macasaet/Day04.java | 69 +++++++++++++++++++++++++++ src/test/resources/sample/day-04.txt | 6 +++ 2 files changed, 75 insertions(+) create mode 100644 src/test/java/com/macasaet/Day04.java create mode 100644 src/test/resources/sample/day-04.txt diff --git a/src/test/java/com/macasaet/Day04.java b/src/test/java/com/macasaet/Day04.java new file mode 100644 index 0000000..8b3d13d --- /dev/null +++ b/src/test/java/com/macasaet/Day04.java @@ -0,0 +1,69 @@ +package com.macasaet; + +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 { + + /** + * 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/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 From bf35b0ff7c2174c138a4d6f2f50595cbc9eab975 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Tue, 6 Dec 2022 22:15:24 -0800 Subject: [PATCH 08/21] Days 5-7 --- src/test/java/com/macasaet/Day05.java | 139 +++++++++++++++ src/test/java/com/macasaet/Day06.java | 55 ++++++ src/test/java/com/macasaet/Day07.java | 241 ++++++++++++++++++++++++++ src/test/resources/sample/day-05.txt | 9 + src/test/resources/sample/day-06.txt | 1 + src/test/resources/sample/day-07.txt | 23 +++ 6 files changed, 468 insertions(+) create mode 100644 src/test/java/com/macasaet/Day05.java create mode 100644 src/test/java/com/macasaet/Day06.java create mode 100644 src/test/java/com/macasaet/Day07.java create mode 100644 src/test/resources/sample/day-05.txt create mode 100644 src/test/resources/sample/day-06.txt create mode 100644 src/test/resources/sample/day-07.txt diff --git a/src/test/java/com/macasaet/Day05.java b/src/test/java/com/macasaet/Day05.java new file mode 100644 index 0000000..97c43c4 --- /dev/null +++ b/src/test/java/com/macasaet/Day05.java @@ -0,0 +1,139 @@ +package com.macasaet; + +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 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; + } + } + } 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; + } + } + } 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); + + } + +} \ No newline at end of file diff --git a/src/test/java/com/macasaet/Day06.java b/src/test/java/com/macasaet/Day06.java new file mode 100644 index 0000000..769fb1b --- /dev/null +++ b/src/test/java/com/macasaet/Day06.java @@ -0,0 +1,55 @@ +package com.macasaet; + +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * --- Day 6: --- + * https://adventofcode.com/2022/day/6 + */ +public class Day06 { + + 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(set.size() >= 14) { + final var result = i; + System.out.println("Part 2: " + result); + return; + } + } + 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 new file mode 100644 index 0000000..5aee1bc --- /dev/null +++ b/src/test/java/com/macasaet/Day07.java @@ -0,0 +1,241 @@ +package com.macasaet; + +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 { + + static class Session { + private final Directory root = new Directory("/", new HashMap<>()); + private Directory workingDirectory = root; + private final Map parentMap = new HashMap<>(); + } + + static abstract class File { + abstract int size(); + } + + static class Directory extends File { + private final String name; + private final Map files; + + public Directory(String name, Map files) { + this.name = name; + this.files = files; + } + + int size() { + int result = 0; + for(final var file : files.values()) { + result += file.size(); + } + return result; + } + + public String toString() { + return "Directory{" + + "name='" + name + '\'' + + '}'; + } + + final Set findDirectoriesSmallerThan(final int maxSize) { + final var result = new HashSet(); + for(final var file : files.values()) { + try { + final var directory = (Directory)file; + result.addAll(directory.findDirectoriesSmallerThan(maxSize)); + } catch(final ClassCastException ignored) { + } + } + if(size() < maxSize) { // FIXME duplicated traversal + result.add(this); + } + return Collections.unmodifiableSet(result); + } + final Set findDirectoriesLargerThan(final int minSize) { + final var result = new HashSet(); + if(size() >= minSize) { + for (final var file : files.values()) { // FIXME duplicated traversal + try { + final var directory = (Directory) file; + result.addAll(directory.findDirectoriesLargerThan(minSize)); + } catch (final ClassCastException ignored) { + } + } + result.add(this); + } + return Collections.unmodifiableSet(result); + } + } + + static class Leaf extends File { + private final String name; + private final int size; + + public Leaf(String name, int size) { + this.name = name; + this.size = size; + } + + int size() { + return size; + } + + @Override + public String toString() { + return "Leaf{" + + "name='" + name + '\'' + + ", size=" + size + + '}'; + } + } + + static abstract class Line { + static Line parse(final String line) { + if(line.startsWith("$")) { + return Command.parse(line); + } + return Output.parse(line); + } + abstract void execute(Session session); + } + + static abstract class Command extends Line { + static Command parse(final String line) { + if(line.startsWith("$ cd")) { + return ChangeDirectory.parse(line); + } + return ListContents.parse(line); + } + } + + static class ListContents extends Command { + void execute(final Session session) { + } + static Command parse(final String ignored) { + return new ListContents(); + } + } + + static class ChangeDirectory extends Command { + private final String argument; + + public ChangeDirectory(String argument) { + this.argument = argument; + } + + void execute(Session session) { + if("..".equals(argument)) { + final var parent = session.parentMap.get(session.workingDirectory); + if(parent == null) { + throw new IllegalArgumentException("Working directory has no parent: " + session.workingDirectory); + } + session.workingDirectory = parent; + } else if( "/".equals(argument)) { + session.workingDirectory = session.root; + } else { + final var target = (Directory) session.workingDirectory.files.get(argument); + if(target == null) { + throw new IllegalArgumentException("No directory named \"" + argument + "\" inside \"" + session.workingDirectory + "\""); + } + session.workingDirectory = target; + } + } + + static ChangeDirectory parse(final String line) { + return new ChangeDirectory(line.split(" ")[2]); + } + } + + static abstract class Output extends Line { + static Output parse(final String line) { + if(line.startsWith("dir")) { + return DirectoryListing.parse(line); + } + 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/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 From a685df3bf0cd69b51be96c592122326a717ced63 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Wed, 7 Dec 2022 21:38:18 -0800 Subject: [PATCH 09/21] Day 8 --- src/test/java/com/macasaet/Day08.java | 161 ++++++++++++++++++++++++++ src/test/resources/sample/day-08.txt | 5 + 2 files changed, 166 insertions(+) create mode 100644 src/test/java/com/macasaet/Day08.java create mode 100644 src/test/resources/sample/day-08.txt diff --git a/src/test/java/com/macasaet/Day08.java b/src/test/java/com/macasaet/Day08.java new file mode 100644 index 0000000..98c498e --- /dev/null +++ b/src/test/java/com/macasaet/Day08.java @@ -0,0 +1,161 @@ +package com.macasaet; + +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 { + + 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++; + } + } + } + return result; + } + + 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; + } + } + int southScore = 0; + for(int i = x + 1; i < grid().length; i++) { + final var height = grid()[i][y]; + southScore += 1; + if(height >= treeHeight) { + break; + } + } + 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; + } + + 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; + } + + 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; + } + + private boolean isObstructedFromTheWest(int x, int y, int treeHeight) { + for(int j = y; --j >= 0; ) { + if(grid()[x][j] >= treeHeight) { + return true; + } + } + return false; + } + + 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; + } + + private boolean isObstructedFromTheNorth(int x, int y, int treeHeight) { + for(int i = x; --i >= 0; ) { + if(grid()[i][y] >= treeHeight) { + return true; + } + } + return false; + } + } + + protected Forest getInput() { + final var list = StreamSupport + .stream(new LineSpliterator("day-08.txt"), + false) + .map(line -> { + final var chars = line.toCharArray(); + final var row = new int[chars.length]; + for(int i = chars.length; --i >= 0; row[i] = chars[i] - '0'); + return row; + }) + .collect(Collectors.toList()); + final var grid = new int[list.size()][]; + for(int i = list.size(); --i >= 0; grid[i] = list.get(i)); + return new Forest(grid); + } + + @Test + public final void part1() { + final var 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/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 From bfe31dcc95148210198d9a1b0945742d7548a583 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Thu, 8 Dec 2022 22:09:50 -0800 Subject: [PATCH 10/21] Day 9 --- src/test/java/com/macasaet/Day09.java | 170 ++++++++++++++++++++++++++ src/test/resources/sample/day-09.txt | 8 ++ 2 files changed, 178 insertions(+) create mode 100644 src/test/java/com/macasaet/Day09.java create mode 100644 src/test/resources/sample/day-09.txt diff --git a/src/test/java/com/macasaet/Day09.java b/src/test/java/com/macasaet/Day09.java new file mode 100644 index 0000000..f334501 --- /dev/null +++ b/src/test/java/com/macasaet/Day09.java @@ -0,0 +1,170 @@ +package com.macasaet; + +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 { + + 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); + // NOT 7017 + } + + @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/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 From 2f0370287de6721e306714fabc716492e129b0d0 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Fri, 9 Dec 2022 22:18:23 -0800 Subject: [PATCH 11/21] Day 10 --- src/test/java/com/macasaet/Day09.java | 1 - src/test/java/com/macasaet/Day10.java | 154 ++++++++++++++++++++++++++ src/test/resources/sample/day-10.txt | 146 ++++++++++++++++++++++++ 3 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/macasaet/Day10.java create mode 100644 src/test/resources/sample/day-10.txt diff --git a/src/test/java/com/macasaet/Day09.java b/src/test/java/com/macasaet/Day09.java index f334501..b6bcb00 100644 --- a/src/test/java/com/macasaet/Day09.java +++ b/src/test/java/com/macasaet/Day09.java @@ -156,7 +156,6 @@ public final void part1() { getInput().forEach(rope::process); final var result = rope.countVisited(); System.out.println("Part 1: " + result); - // NOT 7017 } @Test diff --git a/src/test/java/com/macasaet/Day10.java b/src/test/java/com/macasaet/Day10.java new file mode 100644 index 0000000..2d0d47e --- /dev/null +++ b/src/test/java/com/macasaet/Day10.java @@ -0,0 +1,154 @@ +package com.macasaet; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * --- Day 10: Cathode-Ray Tube --- + * https://adventofcode.com/2022/day/10 + */ +public class Day10 { + + public enum Instruction { + noop { + public int cycles() { + return 0; + } + }, + addx { + public int cycles() { + return 2; + } + }; + + public abstract int cycles(); + + public static Instruction parse(final String string) { + return Instruction.valueOf(string); + } + } + + public record Operation(Instruction instruction, Integer argument) { + public List execute(int cycle, int register) { + return switch (instruction()) { + case noop -> Collections.singletonList(new CycleSnapshot(cycle + 1, register)); + case addx -> + List.of(new CycleSnapshot(cycle + 1, register), + new CycleSnapshot(cycle + 2, register + argument)); + }; + } + + public static 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); + } + } + + 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); + } + + public List execute(final Operation operation) { + final var result = operation.execute(cycle, register); + final var last = result.get(result.size() - 1); + cycle = last.cycle(); + register = last.register(); + return result; + } + } + + public static class Display { + final char[][] pixels = new char[6][]; + + { + for(int i = pixels.length; --i >= 0; pixels[i] = new char[40]); + } + + public void update(final CycleSnapshot snapshot) { + final var pixelIndex = snapshot.cycle() - 1; + final var spritePositions = + Arrays.asList(snapshot.register() - 1, snapshot.register(), snapshot.register() + 1); + final int row = pixelIndex / 40; + final int column = pixelIndex % 40; + if(row >= pixels.length) { + return; + } + if(spritePositions.contains(column)) { + pixels[row][column] = '#'; + } else { + pixels[row][column] = '.'; + } + } + 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)) { +// System.err.println("During cycle " + sideEffect.cycle() + ", register X has the value " + sideEffect.register() + ", so the signal strength is " + sideEffect.signalStrength()); + 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/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 From 4b56955f58ed282b898a0a2bbbad5961f1420ee2 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Fri, 9 Dec 2022 23:29:12 -0800 Subject: [PATCH 12/21] Day 10 - cleanup --- src/test/java/com/macasaet/Day10.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/macasaet/Day10.java b/src/test/java/com/macasaet/Day10.java index 2d0d47e..a289ed3 100644 --- a/src/test/java/com/macasaet/Day10.java +++ b/src/test/java/com/macasaet/Day10.java @@ -133,7 +133,6 @@ public final void part1() { final var sideEffects = state.execute(instruction); for(final var sideEffect : sideEffects) { if(interestingCycles.contains(sideEffect.cycle)) { -// System.err.println("During cycle " + sideEffect.cycle() + ", register X has the value " + sideEffect.register() + ", so the signal strength is " + sideEffect.signalStrength()); accumulator.addAndGet(sideEffect.signalStrength()); } } From 088a3c250b2d5842aa379c57eadfe7d60d477a17 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Sat, 10 Dec 2022 23:28:18 -0800 Subject: [PATCH 13/21] Day 11 --- src/test/java/com/macasaet/Day11.java | 189 ++++++++++++++++++++++++++ src/test/resources/sample/day-11.txt | 27 ++++ 2 files changed, 216 insertions(+) create mode 100644 src/test/java/com/macasaet/Day11.java create mode 100644 src/test/resources/sample/day-11.txt diff --git a/src/test/java/com/macasaet/Day11.java b/src/test/java/com/macasaet/Day11.java new file mode 100644 index 0000000..45db617 --- /dev/null +++ b/src/test/java/com/macasaet/Day11.java @@ -0,0 +1,189 @@ +package com.macasaet; + +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 enum Operator implements BiFunction { + ADD { + public BigInteger apply(BigInteger x, BigInteger y) { + return x.add(y); + } + }, + MULTIPLY { + public BigInteger apply(BigInteger x, BigInteger y) { + return x.multiply(y); + } + }; + + public static Operator parse(final String string) { + return switch(string) { + case "*" -> MULTIPLY; + case "+" -> ADD; + default -> throw new IllegalArgumentException("Invalid operator: " + string); + }; + } + } + + public record Operation(Operator operator, + Function lValueSupplier, + Function rValueSupplier) implements Function { + + public BigInteger apply(final BigInteger oldValue) { + Objects.requireNonNull(oldValue); + final var lValue = lValueSupplier.apply(oldValue); + final var rValue = rValueSupplier.apply(oldValue); + return operator.apply(lValue, rValue); + } + + public static Operation parse(String line) { + line = line.strip(); + if(!line.trim().startsWith("Operation:")) { + throw new IllegalArgumentException("Not an operation: " + line); + } + final var components = line.split(" "); + final var lValueExpression = components[3]; + final Function lValueSupplier = "old".equalsIgnoreCase(lValueExpression) + ? old -> old + : ignored -> new BigInteger(lValueExpression); + final var operator = Operator.parse(components[4]); + final var rValueExpression = components[5]; + final Function rValueSupplier = "old".equalsIgnoreCase(rValueExpression) + ? old -> old + : ignored -> new BigInteger(rValueExpression); + return new Operation(operator, lValueSupplier, rValueSupplier); + } + } + + /** + * 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)); + } + + public BigInteger countItemsInspected() { + return itemsInspected.get(); + } + + 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); + } + + 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(); + } + + @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 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()); + } + } + } + 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/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 From 0927ae765a77c74f10e3e9696897865fd93bd64b Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Sun, 11 Dec 2022 22:08:57 -0800 Subject: [PATCH 14/21] Day 12 --- src/test/java/com/macasaet/Day12.java | 164 ++++++++++++++++++++++++++ src/test/resources/sample/day-12.txt | 5 + 2 files changed, 169 insertions(+) create mode 100644 src/test/java/com/macasaet/Day12.java create mode 100644 src/test/resources/sample/day-12.txt diff --git a/src/test/java/com/macasaet/Day12.java b/src/test/java/com/macasaet/Day12.java new file mode 100644 index 0000000..3257259 --- /dev/null +++ b/src/test/java/com/macasaet/Day12.java @@ -0,0 +1,164 @@ +package com.macasaet; + +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 { + + 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); + } + } + } + } + return Integer.MAX_VALUE; + } + + public List getPotentialTrailHeads() { + final var list = new ArrayList(); + for(int i = this.grid().length; --i >= 0; ) { + final var row = this.grid()[i]; + for(int j = row.length; --j >= 0; ) { + if(row[j] == 0) { + list.add(new Coordinate(i, j)); + } + } + } + return Collections.unmodifiableList(list); + } + + 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); + } + + 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)); + } + + } + + 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); + } + + @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); + } + +} \ 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 From 8baf857c4e8fab3f7393ef4fbf186ebd323ca4a8 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Mon, 12 Dec 2022 22:00:00 -0800 Subject: [PATCH 15/21] Day 13 --- src/test/java/com/macasaet/Day13.java | 161 ++++++++++++++++++++++++++ src/test/resources/sample/day-13.txt | 23 ++++ 2 files changed, 184 insertions(+) create mode 100644 src/test/java/com/macasaet/Day13.java create mode 100644 src/test/resources/sample/day-13.txt diff --git a/src/test/java/com/macasaet/Day13.java b/src/test/java/com/macasaet/Day13.java new file mode 100644 index 0000000..457145f --- /dev/null +++ b/src/test/java/com/macasaet/Day13.java @@ -0,0 +1,161 @@ +package com.macasaet; + +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 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"); + } + return compareToLiteral((Literal) other); + } + } + } + + public record ListItem(List items) implements Item { + public static ListItem parse(final String string) { + final var stack = new ArrayDeque(); + StringBuilder numberBuffer = new StringBuilder(); + for(final char c : string.toCharArray()) { + if(c == '[') { + stack.push(new ListItem(new ArrayList<>())); + } else if(c == ']') { + if(!numberBuffer.isEmpty()) { + final var numberString = numberBuffer.toString(); + numberBuffer.delete(0, numberBuffer.length()); + final var number = Integer.parseInt(numberString); + stack.peek().items().add(new Literal(number)); + } + if(stack.size() > 1) { + final var completed = stack.pop(); + stack.peek().items().add(completed); + } + } else if(c == ',') { + if(!numberBuffer.isEmpty()) { + final var numberString = numberBuffer.toString(); + numberBuffer.delete(0, numberBuffer.length()); + final var number = Integer.parseInt(numberString); + stack.peek().items().add(new Literal(number)); + } + } else { + numberBuffer.append(c); + } + } + return stack.pop(); + } + + 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()); + } + + } + + public record Literal(int item) implements Item { + public int compareToList(ListItem other) { + return asList().compareToList(other); + } + + public int compareToLiteral(Literal other) { + return Integer.compare(item(), other.item()); + } + + public ListItem asList() { + return new ListItem(Collections.singletonList(this)); + } + } + + protected List getInput() { + final var lines = StreamSupport.stream(new LineSpliterator("day-13.txt"), false) + .collect(Collectors.joining("\n")); + final var blocks = lines.split("\n\n"); + return Arrays.stream(blocks).map(block -> Pair.parse(block)).toList(); + } + + @Test + public final void part1() { + final var pairs = getInput(); + var result = 0; + for(int i = 0; i < pairs.size(); i++) { + final var pair = pairs.get(i); + if(pair.isInOrder()) { + result += i + 1; + } + } + System.out.println("Part 1: " + result); + } + + @Test + public final void part2() { + final var 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/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 From 4aa6730baccf08d3da995f91175b9a5032ef01c5 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Tue, 13 Dec 2022 22:33:37 -0800 Subject: [PATCH 16/21] Day 14 --- src/test/java/com/macasaet/Day14.java | 212 ++++++++++++++++++++++++++ src/test/resources/sample/day-14.txt | 2 + 2 files changed, 214 insertions(+) create mode 100644 src/test/java/com/macasaet/Day14.java create mode 100644 src/test/resources/sample/day-14.txt diff --git a/src/test/java/com/macasaet/Day14.java b/src/test/java/com/macasaet/Day14.java new file mode 100644 index 0000000..7f7d777 --- /dev/null +++ b/src/test/java/com/macasaet/Day14.java @@ -0,0 +1,212 @@ +package com.macasaet; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.StreamSupport; + +/** + * --- Day 14: Regolith Reservoir --- + * https://adventofcode.com/2022/day/14 + */ +public class Day14 { + + 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; + } + } + } + } + + 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; + } + } + } + } + + 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 lines) { + int maxDepth = 0; + int maxHorizontalOffset = Integer.MIN_VALUE; + int minHorizontalOffset = Integer.MAX_VALUE; + + final var grid = new HashMap>(); + for(final var line : lines) { + final var rockPath = parseRockPaths(line); + var last = rockPath.get(0); + if(last.verticalDepth() > maxDepth) { + maxDepth = last.verticalDepth(); + } + if(last.horizontalOffset() < minHorizontalOffset) { + minHorizontalOffset = last.horizontalOffset(); + } + if(last.horizontalOffset() > maxHorizontalOffset) { + maxHorizontalOffset = last.horizontalOffset(); + } + for(int i = 1; i < rockPath.size(); i++) { + final var current = rockPath.get(i); + if(last.verticalDepth() == current.verticalDepth()) { + // horizontal line + int start; + int end; + if(last.horizontalOffset() < current.horizontalOffset()) { + start = last.horizontalOffset(); + end = current.horizontalOffset(); + } else { + start = current.horizontalOffset(); + end = last.horizontalOffset(); + } + final var row = grid.computeIfAbsent(last.verticalDepth(), key -> new HashMap<>()); + for(int y = start; y <= end; y++) { + row.put(y, Cell.ROCK); + } + } else { + if(last.horizontalOffset() != current.horizontalOffset()) { + throw new IllegalStateException("Line segments are not on the same vertical axis"); + } + // vertical line + int start; + int end; + if(last.verticalDepth() < current.verticalDepth()) { + start = last.verticalDepth(); + end = current.verticalDepth(); + } else { + start = current.verticalDepth(); + end = last.verticalDepth(); + } + for(int x = start; x <= end; x++) { + final var row = grid.computeIfAbsent(x, key -> new HashMap<>()); + row.put(last.horizontalOffset(), Cell.ROCK); + } + } + if(current.verticalDepth() > maxDepth) { + maxDepth = current.verticalDepth(); + } + if(current.horizontalOffset() < minHorizontalOffset) { + minHorizontalOffset = current.horizontalOffset(); + } + if(current.horizontalOffset() > maxHorizontalOffset) { + maxHorizontalOffset = current.horizontalOffset(); + } + last = current; + } + } + return new Cave(grid, maxDepth, minHorizontalOffset, maxHorizontalOffset); + } + + static List parseRockPaths(final String line) { + return Arrays.stream(line.split(" -> ")).map(Coordinate::parse).toList(); + } + + @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(); + } + } + + protected Cave getInput() { + final var lines = StreamSupport.stream(new LineSpliterator("day-14.txt"), false) + .toList(); + return Cave.parse(lines); + } + + @Test + public final void part1() { + final var cave = getInput(); + final var result = cave.pourSandIntoAbyss(); + + System.out.println("Part 1: " + result); + } + + @Test + public final void part2() { + final var cave = getInput(); + final var result = cave.fillAperture(); + + System.out.println("Part 2: " + result); + } + +} \ No newline at end of file diff --git a/src/test/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 From 128d4217f45fa2bebda24224d427a84bcb1405e6 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Thu, 15 Dec 2022 18:01:19 -0800 Subject: [PATCH 17/21] Day 15 --- src/test/java/com/macasaet/Day15.java | 205 ++++++++++++++++++++++++++ src/test/resources/sample/day-15.txt | 14 ++ 2 files changed, 219 insertions(+) create mode 100644 src/test/java/com/macasaet/Day15.java create mode 100644 src/test/resources/sample/day-15.txt diff --git a/src/test/java/com/macasaet/Day15.java b/src/test/java/com/macasaet/Day15.java new file mode 100644 index 0000000..2fea62c --- /dev/null +++ b/src/test/java/com/macasaet/Day15.java @@ -0,0 +1,205 @@ +package com.macasaet; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.IntPredicate; +import java.util.stream.StreamSupport; + +/** + * --- Day 15: Beacon Exclusion Zone --- + * https://adventofcode.com/2022/day/15 + */ +public class Day15 { + + 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 sensors, IntPredicate includeRow, IntPredicate includeColumn) { + int minX = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE; + int minY = Integer.MAX_VALUE; + int maxY = Integer.MIN_VALUE; + final var grid = new HashMap>(); + for(final var sensor : sensors) { + minX = Math.min(minX, sensor.location().x() - sensor.distanceToBeacon()); + maxX = Math.max(maxX, sensor.location().x() + sensor.distanceToBeacon()); + minY = Math.min(minY, sensor.location().y() - sensor.distanceToBeacon()); + maxY = Math.max(maxY, sensor.location().y() + sensor.distanceToBeacon()); + + sensor.setCoverageArea(grid, includeRow, includeColumn); + sensor.setBeacon(grid, includeRow, includeColumn); + sensor.setSensor(grid, includeRow, includeColumn); + } + + return new CaveMap(grid, minX, maxX, minY, maxY); + } + + public 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; + } + } + } + } + throw new IllegalStateException("No uncovered point found"); + } + +} \ 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 From d6550291acaac5e2be90218d74bd5247c027d319 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Sun, 18 Dec 2022 13:32:27 -0800 Subject: [PATCH 18/21] Day 18 --- src/test/java/com/macasaet/Day18.java | 176 ++++++++++++++++++++++++++ src/test/resources/sample/day-18.txt | 13 ++ 2 files changed, 189 insertions(+) create mode 100644 src/test/java/com/macasaet/Day18.java create mode 100644 src/test/resources/sample/day-18.txt diff --git a/src/test/java/com/macasaet/Day18.java b/src/test/java/com/macasaet/Day18.java new file mode 100644 index 0000000..3c29e60 --- /dev/null +++ b/src/test/java/com/macasaet/Day18.java @@ -0,0 +1,176 @@ +package com.macasaet; + +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 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 cubes; + private final CubeType[][][] grid; + + public Droplet(final Collection 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); + } + } + 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); + } + } + cube.setType(grid, CubeType.Water); + } + } + + protected Collection 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)); + } + 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 getCubes() { + return cubes; + } + + public CubeType[][][] getGrid() { + return grid; + } + } + +} \ 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 From 5cbe0aa9b2e2ffae22ddbe96937a96ca67ed7c38 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Mon, 19 Dec 2022 22:37:28 -0800 Subject: [PATCH 19/21] Day 20 --- src/test/java/com/macasaet/Day20.java | 106 ++++++++++++++++++++++++++ src/test/resources/sample/day-20.txt | 7 ++ 2 files changed, 113 insertions(+) create mode 100644 src/test/java/com/macasaet/Day20.java create mode 100644 src/test/resources/sample/day-20.txt diff --git a/src/test/java/com/macasaet/Day20.java b/src/test/java/com/macasaet/Day20.java new file mode 100644 index 0000000..4125a47 --- /dev/null +++ b/src/test/java/com/macasaet/Day20.java @@ -0,0 +1,106 @@ +package com.macasaet; + +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.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * --- Day 20: Grove Positioning System --- + * https://adventofcode.com/2022/day/20 + */ +public class Day20 { + + public record Number(int originalIndex, int value, BigInteger decryptedValue) { + Number(int originalIndex, int value) { + this(originalIndex, value, BigInteger.valueOf(value).multiply(BigInteger.valueOf(811_589_153))); + } + } + + protected static List getInput() { + final List numbers = StreamSupport.stream(new LineSpliterator("day-20.txt"), false) + .mapToInt(Integer::parseInt) + .collect(ArrayList::new, (x, y) -> x.add(y), (x, y) -> x.addAll(y)); + final var result = new ArrayList(numbers.size()); + for(int i = 0; i < numbers.size(); i++) { + result.add(new Number(i, numbers.get(i))); + } + return Collections.unmodifiableList(result); + } + + @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; + } + } + final var workingSet = new ArrayList<>(numbers); + + for(final var number : numbers) { + final var originalIndex = workingSet.indexOf(number); + workingSet.remove(originalIndex); + var newIndex = (originalIndex + number.value()) % (numbers.size() - 1); + if(newIndex < 0) { + newIndex += numbers.size() - 1; + } + workingSet.add(newIndex, number); + } + + 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 result = (long)x + (long)y + (long)z; + + System.out.println("Part 1: " + result); + } + + @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; + } + workingSet.add(newIndex, number); + } + } + + 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(); + + final var result = x.add(y).add(z); + + System.out.println("Part 2: " + result); + } + +} \ 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 From bc07f57b685f5f5982d37fe0264250462d904982 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Fri, 23 Dec 2022 19:27:47 -0800 Subject: [PATCH 20/21] Day 23 --- src/test/java/com/macasaet/Day23.java | 644 ++++++++++++++++++++++++++ src/test/resources/sample/day-23.txt | 7 + 2 files changed, 651 insertions(+) create mode 100644 src/test/java/com/macasaet/Day23.java create mode 100644 src/test/resources/sample/day-23.txt diff --git a/src/test/java/com/macasaet/Day23.java b/src/test/java/com/macasaet/Day23.java new file mode 100644 index 0000000..b701a77 --- /dev/null +++ b/src/test/java/com/macasaet/Day23.java @@ -0,0 +1,644 @@ +package com.macasaet; + +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 { + + 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); + } + }; + + public abstract Set relativeCoordinates(Coordinate reference); + + public abstract Coordinate adjacent(Coordinate reference); + } + + 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 Crater(final Map> grid, int minX, int maxX, int minY, int maxY) { + this.grid = grid; + setMinX(minX); + setMinY(minY); + setMaxX(maxX); + setMaxY(maxY); + } + + 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 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() { + 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/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 From afd3588095dc3660039e84f8c3fd56d66cc6cce4 Mon Sep 17 00:00:00 2001 From: Carlos Macasaet Date: Fri, 23 Dec 2022 19:37:29 -0800 Subject: [PATCH 21/21] Day 21 --- src/test/java/com/macasaet/Day21.java | 323 ++++++++++++++++++++++++++ src/test/resources/sample/day-21.txt | 15 ++ 2 files changed, 338 insertions(+) create mode 100644 src/test/java/com/macasaet/Day21.java create mode 100644 src/test/resources/sample/day-21.txt diff --git a/src/test/java/com/macasaet/Day21.java b/src/test/java/com/macasaet/Day21.java new file mode 100644 index 0000000..b2fbfc8 --- /dev/null +++ b/src/test/java/com/macasaet/Day21.java @@ -0,0 +1,323 @@ +package com.macasaet; + +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.StreamSupport; + +/** + * --- Day 21: Monkey Math --- + * https://adventofcode.com/2022/day/21 + */ +public class Day21 { + + public record Monkey(String name, Job job) { + public long yell(final Map monkeys, final Map results) { + return job().yell(monkeys, results); + } + + public Simplification simplify(final Map monkeys, final Map results) { + return job().simplify(monkeys, results); + } + + public static Monkey parse(final String line) { + final var components = line.split(": "); + final var name = components[0].trim(); + final var job = Job.parse(components[1].trim()); + return new Monkey(name, job); + } + } + + public interface Simplification { + Simplification simplify(); + } + + 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()); + } + // cannot simplify further + return this; + } else if (x() instanceof final Expression expression && y() instanceof final Value constant) { + // e.g. 5/x=2 or x/5=2 + final var inverse = expression.operation().inverse(); + if (expression.x() instanceof Value) { + // e.g. 5/x=2 or x/5=2 + if (expression.operation().isSymmetric()) { + // e.g. 2x=5 -> x=5*2 -> x=10 + final var rValue = new Expression(constant, inverse, expression.x()).simplify(); + return new Expression(expression.y(), Operation.IS_EQUAL, rValue); + } else { + // e.g. 5/x=2 -> 5=2x + final var rValue = new Expression(constant, inverse, expression.y()); + return new Expression(expression.x(), Operation.IS_EQUAL, rValue); + } + } else if (expression.y() instanceof Value) { + // e.g. x/5=2 -> x=2*5 -> x=10 + final var rValue = new Expression(constant, inverse, expression.y()).simplify(); + return new Expression(expression.x(), Operation.IS_EQUAL, rValue); + } + // cannot simplify further + return this; + } + } + return this; + } + + public String toString() { + return "(" + x() + ") " + operation() + " (" + y() + ")"; + } + } + + record Value(long value) implements Simplification { + public String toString() { + return "" + value(); + } + + public Simplification simplify() { + return this; + } + } + + 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/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