input = new ArrayDeque<>(2);
+ public RetentionPolicy policy;
+ private long[] program;
+ private int instructionCounter = 0;
+ private long lastInput;
+ private long relativeBase = 0;
+
+ public IntcodeComputer(RetentionPolicy policy, long[] program, long... input) {
+ super(0);
+ this.program = Arrays.copyOf(program, 10000);
+ setInput(input);
+ this.policy = policy;
+ }
+
+ public IntcodeComputer(int day, long... input) {
+ super(day);
+ this.program = dayNumbers(",");
+ this.program = Arrays.copyOf(this.program, 10000);
+ if (day == 2) {
+ this.program[1] = input[0];
+ this.program[2] = input[1];
+ } else if (day == 17) {
+ this.program[0] = input[0];
+ }
+ setInput(input);
+ this.policy = RetentionPolicy.EXIT_ON_OUTPUT;
+ }
+
+ public long run(long... input) {
+ setInput(input);
+ return run();
+ }
+
+ public long run() {
+ long result;
+ while ((result = executeInstruction(Math.toIntExact(program[instructionCounter]))) == CONTINUE_CODE) ;
+ return result;
+ }
+
+ private long executeInstruction(int instruction) {
+ if (instruction > 99)
+ return parseComplexInstruction(instruction);
+ return execute(instruction);
+ }
+
+ private long execute(int instruction) {
+ return execute(new int[3], instruction);
+ }
+
+ private long execute(int[] method, int instruction) {
+ int nParams = nParams(instruction);
+ long[] args = IntStream.range(1, nParams + 1).mapToLong(j -> program[instructionCounter + j]).toArray();
+ transformParameters(method, args, instruction);
+ return executeInstruction(args, instruction);
+ }
+
+ private void transformParameters(int[] method, long[] args, int instruction) {
+ IntStream.range(0, args.length).filter(i -> method[i] != 1).filter(i -> i + 1 != args.length || !Arrays.stream(DO_NOT_TRANSFORM_FINAL_ARGUMENT).anyMatch(j -> j == instruction))
+ .forEach(i -> args[i] = program[Math.toIntExact((method[i] == 2 ? relativeBase : 0) + args[i])]);
+ if (Arrays.stream(DO_NOT_TRANSFORM_FINAL_ARGUMENT).anyMatch(j -> j == instruction) && method[args.length - 1] == 2) {
+ args[args.length - 1] += relativeBase;
+ }
+ }
+
+ private long readInput() {
+ if (input.isEmpty())
+ return lastInput;
+ lastInput = input.poll();
+ return lastInput;
+ }
+
+ public void addInput(long... num) {
+ for (long n : num)
+ input.add(n);
+ }
+
+ public long firstElement() {
+ return program[0];
+ }
+
+ public void setInput(long... input) {
+ this.input.clear();
+ addInput(input);
+ }
+
+ private long executeInstruction(long[] args, int instruction) {
+ if (instruction == 3 && policy == RetentionPolicy.EXIT_ON_EMPTY_INPUT && input.size() == 0)
+ return STOP_CODE;
+ instructionCounter += nParams(instruction) + 1;
+ switch (instruction) {
+ case 1:
+ program[Math.toIntExact(args[2])] = args[0] + args[1];
+ break;
+ case 2:
+ program[Math.toIntExact(args[2])] = args[0] * args[1];
+ break;
+ case 3:
+ program[Math.toIntExact(args[0])] = readInput();
+ break;
+ case 4:
+ return args[0];
+ case 5:
+ if (args[0] != 0) instructionCounter = Math.toIntExact(args[1]);
+ break;
+ case 6:
+ if (args[0] == 0) instructionCounter = Math.toIntExact(args[1]);
+ break;
+ case 7:
+ program[Math.toIntExact(args[2])] = args[0] < args[1] ? 1 : 0;
+ break;
+ case 8:
+ program[Math.toIntExact(args[2])] = args[0] == args[1] ? 1 : 0;
+ break;
+ case 9:
+ relativeBase += Math.toIntExact(args[0]);
+ break;
+ case 99:
+ return STOP_CODE;
+ default:
+ throw new IllegalStateException("Something went wrong!");
+ }
+ return CONTINUE_CODE;
+ }
+
+ private long parseComplexInstruction(int instruction) {
+ int[] instructions = getInstructions(instruction);
+ int opcode = getOpCode(instructions);
+ return execute(new int[]{instructions[2], instructions[1], instructions[0]}, opcode);
+ }
+
+ private int getOpCode(int instruction) {
+ return getOpCode(getInstructions(instruction));
+ }
+
+ private int getOpCode(int[] instructions) {
+ return (instructions[3] * 10) + instructions[4];
+ }
+
+ private int[] getInstructions(int instruction) {
+ int[] instructions = new int[5];
+ for (int j = instructions.length - 1; instruction > 0; j--) {
+ instructions[j] = instruction % 10;
+ instruction /= 10;
+ }
+ return instructions;
+ }
+
+ private int nParams(int instruction) {
+ switch (instruction) {
+ case 99:
+ return -1;
+ case 3:
+ case 4:
+ case 9:
+ return 1;
+ case 5:
+ case 6:
+ return 2;
+ case 1:
+ case 2:
+ case 7:
+ case 8:
+ return 3;
+ default:
+ if (instruction > 99) return nParams(getOpCode(instruction));
+ else throw new IllegalStateException("Something went wrong! " + instruction);
+ }
+ }
+
+ public void setElement(int i, long j) {
+ program[i] = j;
+ }
+
+ public void setInput(String patterns) {
+ setInput(patterns.chars().mapToLong(e -> e).toArray());
+ }
+
+ @Override
+ public Object part1() {
+ return null;
+ }
+
+ @Override
+ public Object part2() {
+ return null;
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/intcode/RetentionPolicy.java b/src/main/java/com/sbaars/adventofcode/year19/intcode/RetentionPolicy.java
new file mode 100644
index 00000000..719d0b1f
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/intcode/RetentionPolicy.java
@@ -0,0 +1,5 @@
+package com.sbaars.adventofcode.year19.intcode;
+
+public enum RetentionPolicy {
+ EXIT_ON_EMPTY_INPUT, EXIT_ON_OUTPUT
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/pathfinding/CharGrid2d.java b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/CharGrid2d.java
new file mode 100644
index 00000000..56706ad7
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/CharGrid2d.java
@@ -0,0 +1,129 @@
+package com.sbaars.adventofcode.year19.pathfinding;
+
+import java.awt.*;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Creates nodes and neighbours from a 2d grid. Each point in the map has an
+ * integer value that specifies the cost of crossing that point. If this value
+ * is negative, the point is unreachable.
+ *
+ * If diagonal movement is allowed, the Chebyshev distance is used, else
+ * Manhattan distance is used.
+ *
+ * @author Ben Ruijl
+ */
+public class CharGrid2d {
+ private final char[][] map;
+ private final boolean allowDiagonal;
+ List collectedKeys;
+
+ public CharGrid2d(char[][] map, boolean allowDiagonal) {
+ this.map = map;
+ this.allowDiagonal = allowDiagonal;
+ }
+
+ public List findPath(Point start, Point end) {
+ return PathFinding.doAStar(new MapNode(start.x, start.y), new MapNode(end.x, end.y)).stream().map(MapNode::toPoint).collect(Collectors.toList());
+ }
+
+ /**
+ * A node in a 2d map. This is simply the coordinates of the point.
+ *
+ * @author Ben Ruijl
+ */
+ public class MapNode implements Node {
+ private final int x, y;
+
+ public MapNode(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public double getHeuristic(MapNode goal) {
+ if (allowDiagonal) {
+ return Math.max(Math.abs(x - goal.x), Math.abs(y - goal.y));
+ } else {
+ return Math.abs(x - goal.x) + Math.abs(y - goal.y);
+ }
+ }
+
+ public double getTraversalCost(MapNode neighbour) {
+ return 1 + map[neighbour.y][neighbour.x];
+ }
+
+ public Set getNeighbours() {
+ Set neighbours = new HashSet();
+
+ for (int i = x - 1; i <= x + 1; i++) {
+ for (int j = y - 1; j <= y + 1; j++) {
+ if ((i == x && j == y) || i < 0 || j < 0 || j >= map.length
+ || i >= map[j].length) {
+ continue;
+ }
+
+ if (!allowDiagonal &&
+ ((i < x && j < y) ||
+ (i < x && j > y) ||
+ (i > x && j > y) ||
+ (i > x && j < y))) {
+ continue;
+ }
+
+ if (map[j][i] == '#') {
+ continue;
+ }
+
+ // TODO: create cache instead of recreation
+ neighbours.add(new MapNode(i, j));
+ }
+ }
+
+ return neighbours;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + x + ", " + y + ")";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + getOuterType().hashCode();
+ result = prime * result + x;
+ result = prime * result + y;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ MapNode other = (MapNode) obj;
+ if (!getOuterType().equals(other.getOuterType()))
+ return false;
+ if (x != other.x)
+ return false;
+ return y == other.y;
+ }
+
+ public Point toPoint() {
+ return new Point(x, y);
+ }
+
+ private CharGrid2d getOuterType() {
+ return CharGrid2d.this;
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/pathfinding/Grid2d.java b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/Grid2d.java
new file mode 100644
index 00000000..b3d1b666
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/Grid2d.java
@@ -0,0 +1,129 @@
+package com.sbaars.adventofcode.year19.pathfinding;
+
+import com.sbaars.adventofcode.year19.days.Day15;
+import java.awt.*;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Creates nodes and neighbours from a 2d grid. Each point in the map has an
+ * integer value that specifies the cost of crossing that point. If this value
+ * is negative, the point is unreachable.
+ *
+ * If diagonal movement is allowed, the Chebyshev distance is used, else
+ * Manhattan distance is used.
+ *
+ * @author Ben Ruijl
+ */
+public class Grid2d {
+ private final int[][] map;
+ private final boolean allowDiagonal;
+
+ public Grid2d(int[][] map, boolean allowDiagonal) {
+ this.map = map;
+ this.allowDiagonal = allowDiagonal;
+ }
+
+ public List findPath(Point start, Point end) {
+ return PathFinding.doAStar(new MapNode(start.x, start.y), new MapNode(end.x, end.y)).stream().map(MapNode::toPoint).collect(Collectors.toList());
+ }
+
+ /**
+ * A node in a 2d map. This is simply the coordinates of the point.
+ *
+ * @author Ben Ruijl
+ */
+ public class MapNode implements Node {
+ private final int x, y;
+
+ public MapNode(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public double getHeuristic(MapNode goal) {
+ if (allowDiagonal) {
+ return Math.max(Math.abs(x - goal.x), Math.abs(y - goal.y));
+ } else {
+ return Math.abs(x - goal.x) + Math.abs(y - goal.y);
+ }
+ }
+
+ public double getTraversalCost(MapNode neighbour) {
+ return 1 + map[neighbour.y][neighbour.x];
+ }
+
+ public Set getNeighbours() {
+ Set neighbours = new HashSet();
+
+ for (int i = x - 1; i <= x + 1; i++) {
+ for (int j = y - 1; j <= y + 1; j++) {
+ if ((i == x && j == y) || i < 0 || j < 0 || j >= map.length
+ || i >= map[j].length) {
+ continue;
+ }
+
+ if (!allowDiagonal &&
+ ((i < x && j < y) ||
+ (i < x && j > y) ||
+ (i > x && j > y) ||
+ (i > x && j < y))) {
+ continue;
+ }
+
+ if (map[j][i] == Day15.WALL || map[j][i] == Day15.UNEXPLORED) {
+ continue;
+ }
+
+ // TODO: create cache instead of recreation
+ neighbours.add(new MapNode(i, j));
+ }
+ }
+
+ return neighbours;
+ }
+
+ @Override
+ public String toString() {
+ return "(" + x + ", " + y + ")";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + getOuterType().hashCode();
+ result = prime * result + x;
+ result = prime * result + y;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ MapNode other = (MapNode) obj;
+ if (!getOuterType().equals(other.getOuterType()))
+ return false;
+ if (x != other.x)
+ return false;
+ return y == other.y;
+ }
+
+ public Point toPoint() {
+ return new Point(x, y);
+ }
+
+ private Grid2d getOuterType() {
+ return Grid2d.this;
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/pathfinding/Node.java b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/Node.java
new file mode 100644
index 00000000..8bbccfeb
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/Node.java
@@ -0,0 +1,38 @@
+package com.sbaars.adventofcode.year19.pathfinding;
+
+import java.util.Set;
+
+/**
+ * A node in a graph, useful for pathfinding.
+ *
+ * @param Actual type of the node
+ * @author Ben Ruijl
+ */
+public interface Node {
+ /**
+ * The heuristic cost to move from the current node to the goal. In most
+ * cases this is the Manhattan distance or Chebyshev distance.
+ *
+ * @param goal
+ * @return
+ */
+ double getHeuristic(T goal);
+
+ /**
+ * The cost of moving from the current node to the neighbour. In most cases
+ * this is just the distance between the nodes, but additional terrain cost
+ * can be added as well (for example climbing a mountain is more expensive
+ * than walking on a road).
+ *
+ * @param neighbour Neighbour of current node
+ * @return Traversal cost
+ */
+ double getTraversalCost(T neighbour);
+
+ /**
+ * Gets the set of neighbouring nodes.
+ *
+ * @return Neighbouring nodes
+ */
+ Set getNeighbours();
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/pathfinding/PathFinding.java b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/PathFinding.java
new file mode 100644
index 00000000..792b9e6a
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/pathfinding/PathFinding.java
@@ -0,0 +1,85 @@
+package com.sbaars.adventofcode.year19.pathfinding;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Set;
+
+/**
+ * Helper class containing pathfinding algorithms.
+ *
+ * @author Ben Ruijl
+ */
+public class PathFinding {
+
+ /**
+ * A Star pathfinding. Note that the heuristic has to be monotonic:
+ * {@code h(x) <=
+ * d(x, y) + h(y)}.
+ *
+ * @param start Starting node
+ * @param goal Goal node
+ * @return Shortest path from start to goal, or null if none found
+ */
+ public static > List doAStar(T start, T goal) {
+ Set closed = new HashSet();
+ Map fromMap = new HashMap();
+ List route = new LinkedList();
+ Map gScore = new HashMap();
+ final Map fScore = new HashMap();
+ PriorityQueue open = new PriorityQueue(11, new Comparator() {
+
+ public int compare(T nodeA, T nodeB) {
+ return Double.compare(fScore.get(nodeA), fScore.get(nodeB));
+ }
+ });
+
+ gScore.put(start, 0.0);
+ fScore.put(start, start.getHeuristic(goal));
+ open.offer(start);
+
+ while (!open.isEmpty()) {
+ T current = open.poll();
+ if (current.equals(goal)) {
+ while (current != null) {
+ route.add(0, current);
+ current = fromMap.get(current);
+ }
+
+ return route;
+ }
+
+ closed.add(current);
+
+ for (T neighbour : current.getNeighbours()) {
+ if (closed.contains(neighbour)) {
+ continue;
+ }
+
+ double tentG = gScore.get(current)
+ + current.getTraversalCost(neighbour);
+
+ boolean contains = open.contains(neighbour);
+ if (!contains || tentG < gScore.get(neighbour)) {
+ gScore.put(neighbour, tentG);
+ fScore.put(neighbour, tentG + neighbour.getHeuristic(goal));
+
+ if (contains) {
+ open.remove(neighbour);
+ }
+
+ open.offer(neighbour);
+ fromMap.put(neighbour, current);
+ }
+ }
+ }
+
+ return new ArrayList<>();
+ }
+
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/util/CountMap.java b/src/main/java/com/sbaars/adventofcode/year19/util/CountMap.java
new file mode 100644
index 00000000..1498360d
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/util/CountMap.java
@@ -0,0 +1,58 @@
+package com.sbaars.adventofcode.year19.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class CountMap extends HashMap {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ public CountMap() {
+ }
+
+ public CountMap(int initialCapacity, float loadFactor) {
+ super(initialCapacity, loadFactor);
+ }
+
+ public CountMap(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ public CountMap(Map extends K, Integer> m) {
+ super(m);
+ }
+
+ public Integer increment(K key) {
+ return super.put(key, super.containsKey(key) ? super.get(key) + 1 : 1);
+ }
+
+ public Integer increment(K key, int amount) {
+ return super.put(key, super.containsKey(key) ? super.get(key) + amount : amount);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Integer get(Object key) {
+ if (!super.containsKey(key))
+ super.put((K) key, 0);
+ return super.get(key);
+ }
+
+ @Override
+ public String toString() {
+ return keySet().stream().sorted().map(e -> e + "\t" + get(e)).collect(Collectors.joining(System.lineSeparator()));
+ }
+
+ public void addAll(CountMap amountPerCloneClassSize) {
+ amountPerCloneClassSize.entrySet().stream().forEach(e -> this.increment(e.getKey(), e.getValue()));
+ }
+
+ public void incrementAll(CountMap input) {
+ for (Entry i : input.entrySet()) {
+ increment(i.getKey(), i.getValue());
+ }
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year19/util/LongCountMap.java b/src/main/java/com/sbaars/adventofcode/year19/util/LongCountMap.java
new file mode 100644
index 00000000..32dca808
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year19/util/LongCountMap.java
@@ -0,0 +1,69 @@
+package com.sbaars.adventofcode.year19.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.LongStream;
+
+public class LongCountMap extends HashMap {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ public LongCountMap() {
+ }
+
+ public LongCountMap(int initialCapacity, float loadFactor) {
+ super(initialCapacity, loadFactor);
+ }
+
+ public LongCountMap(int initialCapacity) {
+ super(initialCapacity);
+ }
+
+ public LongCountMap(Map extends K, Long> m) {
+ super(m);
+ }
+
+ public Long increment(K key) {
+ return super.put(key, super.containsKey(key) ? super.get(key) + 1 : 1);
+ }
+
+ public Long increment(K key, long amount) {
+ return super.put(key, super.containsKey(key) ? super.get(key) + amount : amount);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Long get(Object key) {
+ if (!super.containsKey(key))
+ super.put((K) key, 0L);
+ return super.get(key);
+ }
+
+ @Override
+ public String toString() {
+ return keySet().stream().sorted().map(e -> e + "\t" + get(e)).collect(Collectors.joining(System.lineSeparator()));
+ }
+
+ public void addAll(LongCountMap amountPerCloneClassSize) {
+ amountPerCloneClassSize.entrySet().stream().forEach(e -> this.increment(e.getKey(), e.getValue()));
+ }
+
+ public void incrementAll(LongCountMap input) {
+ for (Entry i : input.entrySet()) {
+ increment(i.getKey(), i.getValue());
+ }
+ }
+
+ public long sumValues() {
+ return values().stream().mapToLong(e -> e).sum();
+ }
+
+ public static LongCountMap ofFrequencies(LongStream frequencies) {
+ var lcm = new LongCountMap();
+ frequencies.forEach(lcm::increment);
+ return lcm;
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/Day2020.java b/src/main/java/com/sbaars/adventofcode/year20/Day2020.java
new file mode 100644
index 00000000..abc1429d
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/Day2020.java
@@ -0,0 +1,9 @@
+package com.sbaars.adventofcode.year20;
+
+import com.sbaars.adventofcode.common.Day;
+
+public abstract class Day2020 extends Day {
+ protected Day2020(int day) {
+ super(2020, day);
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/Main.java b/src/main/java/com/sbaars/adventofcode/year20/Main.java
new file mode 100644
index 00000000..eacfa11b
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/Main.java
@@ -0,0 +1,16 @@
+package com.sbaars.adventofcode.year20;
+
+import com.sbaars.adventofcode.common.Day;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+
+public class Main {
+ public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException, InvocationTargetException, NoSuchMethodException {
+ for (int day = 1; day <= 25; day++) {
+ System.out.println("Day " + day + ":");
+ Day instance = (Day) Class.forName("com.sbaars.adventofcode.year20.days.Day" + day).getDeclaredConstructor().newInstance();
+ instance.printParts();
+ System.out.println();
+ }
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/days/Day1.java b/src/main/java/com/sbaars/adventofcode/year20/days/Day1.java
new file mode 100644
index 00000000..7730f234
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/days/Day1.java
@@ -0,0 +1,37 @@
+package com.sbaars.adventofcode.year20.days;
+
+import com.sbaars.adventofcode.year20.Day2020;
+import java.util.Arrays;
+
+public class Day1 extends Day2020 {
+ public Day1() {
+ super(1);
+ }
+
+ public static void main(String[] args) {
+ new Day1().printParts();
+ }
+
+ @Override
+ public Object part1() {
+ long[] nums = dayNumbers();
+ return Arrays.stream(nums).flatMap(a ->
+ Arrays.stream(nums).filter(b -> a + b == 2020L).map(b -> a * b)
+ ).findAny().getAsLong();
+ }
+
+ @Override
+ public Object part2() {
+ long[] s = dayNumbers();
+ for (long a : s) {
+ for (long b : s) {
+ for (long c : s) {
+ if (a != b && b != c && a + b + c == 2020L) {
+ return a * b * c;
+ }
+ }
+ }
+ }
+ return 0;
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/days/Day10.java b/src/main/java/com/sbaars/adventofcode/year20/days/Day10.java
new file mode 100644
index 00000000..59f06af7
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/days/Day10.java
@@ -0,0 +1,51 @@
+package com.sbaars.adventofcode.year20.days;
+
+import static java.util.Arrays.sort;
+
+import com.sbaars.adventofcode.year19.util.CountMap;
+import com.sbaars.adventofcode.year19.util.LongCountMap;
+import com.sbaars.adventofcode.year20.Day2020;
+import org.apache.commons.lang3.ArrayUtils;
+
+public class Day10 extends Day2020 {
+ public Day10() {
+ super(10);
+ }
+
+ public static void main(String[] args) {
+ new Day10().printParts();
+ }
+
+ @Override
+ public Object part1() {
+ long[] input = getInput();
+ CountMap diffs = new CountMap<>();
+ for (int i = 1; i < input.length; i++) {
+ diffs.increment(input[i] - input[i - 1]);
+ }
+ return diffs.get(1L) * diffs.get(3L);
+ }
+
+ @Override
+ public Object part2() {
+ long[] input = getInput();
+ LongCountMap nRoutes = new LongCountMap<>();
+ nRoutes.increment(input[input.length - 1]);
+ for (int i = input.length - 2; i >= 0; i--) {
+ for (int j = i + 1; j < input.length && j <= i + 3; j++) {
+ if (input[j] - input[i] <= 3) {
+ nRoutes.increment(input[i], nRoutes.get(input[j]));
+ }
+ }
+ }
+ return nRoutes.get(0L);
+ }
+
+ private long[] getInput() {
+ long[] input = dayNumbers();
+ sort(input);
+ input = ArrayUtils.add(input, input[input.length - 1] + 3);
+ input = ArrayUtils.addFirst(input, 0L);
+ return input;
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/days/Day11.java b/src/main/java/com/sbaars/adventofcode/year20/days/Day11.java
new file mode 100644
index 00000000..54c91b22
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/days/Day11.java
@@ -0,0 +1,91 @@
+package com.sbaars.adventofcode.year20.days;
+
+import static java.util.Arrays.stream;
+
+import com.sbaars.adventofcode.common.Direction;
+import com.sbaars.adventofcode.year20.Day2020;
+import java.awt.*;
+
+public class Day11 extends Day2020 {
+ public Day11() {
+ super(11);
+ }
+
+ public static void main(String[] args) {
+ new Day11().printParts();
+ }
+
+ @Override
+ public Object part1() {
+ char[][] input = dayGrid();
+ int changes;
+ do {
+ changes = 0;
+ char[][] newGrid = new char[input.length][input[0].length];
+ for (int i = 0; i < input.length; i++) {
+ for (int j = 0; j < input[0].length; j++) {
+ Point p = new Point(i, j);
+ char[][] finalInput = input;
+ long occupied = stream(Direction.eightDirections()).filter(d ->
+ d.getInGrid(finalInput, d.move(p)) == '#'
+ ).count();
+ changes = getChanges(input, changes, newGrid, i, j, occupied, 4L);
+ }
+ }
+ input = newGrid;
+ } while (changes > 0);
+ return getCount(input, '#');
+ }
+
+ private int getChanges(char[][] input, int changes, char[][] newGrid, int i, int j, long occupied, long numOccupied) {
+ if (occupied == 0L && input[i][j] == 'L') {
+ newGrid[i][j] = '#';
+ changes++;
+ } else if (occupied >= numOccupied && input[i][j] == '#') {
+ newGrid[i][j] = 'L';
+ changes++;
+ } else {
+ newGrid[i][j] = input[i][j];
+ }
+ return changes;
+ }
+
+ private long getCount(char[][] input, char ch) {
+ return stream(input).flatMapToInt(c -> new String(c).chars()).filter(c -> c == ch).count();
+ }
+
+ @Override
+ public Object part2() {
+ char[][] input = dayGrid();
+ int changes;
+ do {
+ changes = 0;
+ char[][] newGrid = new char[input.length][input[0].length];
+ for (int i = 0; i < input.length; i++) {
+ for (int j = 0; j < input[0].length; j++) {
+ int occupied = countOccupied(input, i, j);
+ changes = getChanges(input, changes, newGrid, i, j, occupied, 5L);
+ }
+ }
+ input = newGrid;
+ } while (changes > 0);
+ return getCount(input, '#');
+ }
+
+ private int countOccupied(char[][] input, int i, int j) {
+ int occupied = 0;
+ Direction[] dirs = Direction.eightDirections();
+ for (Direction dir : dirs) {
+ Point p = new Point(i, j);
+ p = dir.move(p, 1);
+ while (p.x >= 0 && p.x < input.length && p.y >= 0 && p.y < input[0].length && input[p.x][p.y] != 'L') {
+ if (input[p.x][p.y] == '#') {
+ occupied++;
+ break;
+ }
+ p = dir.move(p, 1);
+ }
+ }
+ return occupied;
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/days/Day12.java b/src/main/java/com/sbaars/adventofcode/year20/days/Day12.java
new file mode 100644
index 00000000..226c2678
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/days/Day12.java
@@ -0,0 +1,61 @@
+package com.sbaars.adventofcode.year20.days;
+
+import com.sbaars.adventofcode.common.Direction;
+import com.sbaars.adventofcode.year20.Day2020;
+
+import java.awt.*;
+import java.util.List;
+
+import static com.sbaars.adventofcode.common.Direction.EAST;
+import static com.sbaars.adventofcode.common.Direction.turnDegrees;
+import static java.lang.Math.abs;
+import static java.util.stream.Collectors.toList;
+
+public class Day12 extends Day2020 {
+ public Day12() {
+ super(12);
+ }
+
+ public static void main(String[] args) {
+ new Day12().printParts();
+ }
+
+ @Override
+ public Object part1() {
+ List input = convertInput();
+ Direction face = EAST;
+ Point location = new Point(0, 0);
+ for (Flight f : input) {
+ switch (f.dir) {
+ case 'L', 'R' -> face = face.turnDegrees(f.distance, f.dir == 'R');
+ case 'F' -> location = face.move(location, f.distance);
+ default -> location = Direction.getByDir(f.dir).move(location, f.distance);
+ }
+ }
+ return abs(location.x) + abs(location.y);
+ }
+
+ private List convertInput() {
+ return dayStream().map(e -> new Flight(e.charAt(0), Integer.parseInt(e.substring(1)))).collect(toList());
+ }
+
+ @Override
+ public Object part2() {
+ List input = convertInput();
+ Point waypoint = new Point(10, -1);
+ Point location = new Point(0, 0);
+ for (Flight f : input) {
+ switch (f.dir) {
+ case 'L', 'R' -> waypoint = turnDegrees(waypoint, f.distance, f.dir == 'R');
+ case 'F' -> location = new Point(location.x + (waypoint.x * f.distance), location.y + (waypoint.y * f.distance));
+ default -> waypoint = Direction.getByDir(f.dir).move(waypoint, f.distance);
+ }
+ }
+ return abs(location.x) + abs(location.y);
+ }
+
+ record Flight(char dir, int distance) {
+ }
+
+
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/days/Day13.java b/src/main/java/com/sbaars/adventofcode/year20/days/Day13.java
new file mode 100644
index 00000000..c7b9c1cd
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/days/Day13.java
@@ -0,0 +1,52 @@
+package com.sbaars.adventofcode.year20.days;
+
+import static java.lang.Integer.parseInt;
+import static java.lang.Long.parseLong;
+import static java.util.Arrays.stream;
+import static java.util.stream.IntStream.range;
+
+import com.sbaars.adventofcode.year20.Day2020;
+
+public class Day13 extends Day2020 {
+ public Day13() {
+ super(13);
+ }
+
+ public static void main(String[] args) {
+ new Day13().printParts();
+ }
+
+ @Override
+ public Object part1() {
+ String[] day = dayStrings();
+ int timestamp = parseInt(day[0]);
+ int[] times = stream(day[1].replace(",x", "").split(","))
+ .mapToInt(Integer::parseInt).toArray();
+ for (int i = timestamp; true; i++) {
+ for (int j : times) {
+ if (i % j == 0) {
+ return j * (i - timestamp);
+ }
+ }
+ }
+ }
+
+ @Override
+ public Object part2() {
+ String[] s = dayStrings()[1].split(",");
+ long[][] nums = range(0, s.length).filter(i -> !s[i].equals("x"))
+ .mapToObj(i -> new long[]{parseLong(s[i]), i})
+ .toArray(long[][]::new);
+ long product = stream(nums).mapToLong(a -> a[0]).reduce((a, b) -> a * b).getAsLong();
+ long sum = stream(nums).mapToLong(a -> a[1] * (product / a[0]) * inverseModulo(product / a[0], a[0])).sum();
+ return product - sum % product;
+ }
+
+ long inverseModulo(long x, long y) {
+ if (x != 0) {
+ long modulo = y % x;
+ return modulo == 0 ? 1 : y - inverseModulo(modulo, x) * y / x;
+ }
+ return 0;
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/days/Day14.java b/src/main/java/com/sbaars/adventofcode/year20/days/Day14.java
new file mode 100644
index 00000000..9d617876
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/days/Day14.java
@@ -0,0 +1,97 @@
+package com.sbaars.adventofcode.year20.days;
+
+import static com.sbaars.adventofcode.common.ReadsFormattedString.readString;
+import static java.lang.Long.parseLong;
+import static java.lang.Long.toBinaryString;
+import static java.util.Optional.empty;
+import static java.util.Optional.of;
+
+import com.sbaars.adventofcode.year20.Day2020;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.commons.text.TextStringBuilder;
+
+public class Day14 extends Day2020 {
+ public Day14() {
+ super(14);
+ }
+
+ public static void main(String[] args) {
+ new Day14().printParts();
+ }
+
+ @Override
+ public Object part1() {
+ Instruction[] input = getInput();
+ Map memory = new HashMap<>();
+ final TextStringBuilder currentMask = new TextStringBuilder();
+ for (Instruction i : input) {
+ i.getMem().ifPresentOrElse(m -> memory.put(m.index, m.value | parseLong(currentMask.toString(), 2)),
+ () -> currentMask.set(i.value).replaceAll("X", "0"));
+ }
+ return memory.values().stream().mapToLong(e -> e).sum();
+ }
+
+ @Override
+ public Object part2() {
+ Instruction[] input = getInput();
+ Map memory = new HashMap<>();
+ String currentMask = "";
+ for (Instruction i : input) {
+ Optional mem = i.getMem();
+ if (mem.isPresent()) {
+ StringBuilder bin = binWithLength(mem.get().index, currentMask.length());
+ List floaters = applyMask(currentMask, bin);
+ fillMemory(memory, mem, bin, floaters);
+ } else {
+ currentMask = i.value;
+ }
+ }
+ return memory.values().stream().mapToLong(e -> e).sum();
+ }
+
+ private Instruction[] getInput() {
+ return dayStream().map(s -> readString(s, "%s = %s", Instruction.class)).toArray(Instruction[]::new);
+ }
+
+ private void fillMemory(Map memory, Optional mem, StringBuilder bin, List floaters) {
+ StringBuilder binary;
+ for (long j = 0; (binary = binWithLength(j, floaters.size())).length() == floaters.size(); j++) {
+ for (int k = 0; k < floaters.size(); k++) {
+ bin.setCharAt(floaters.get(k), binary.charAt(k));
+ }
+ memory.put(parseLong(bin.toString(), 2), mem.get().value);
+ }
+ }
+
+ private List applyMask(String currentMask, StringBuilder bin) {
+ List floaters = new ArrayList<>();
+ for (int j = 0; j < bin.length(); j++) {
+ char c = currentMask.charAt(j);
+ if (c == 'X') floaters.add(j);
+ else if (c == '1') bin.setCharAt(j, c);
+ }
+ return floaters;
+ }
+
+ private StringBuilder binWithLength(long val, int s) {
+ StringBuilder bin = new StringBuilder(toBinaryString(val));
+ while (bin.length() < s) {
+ bin.insert(0, '0');
+ }
+ return bin;
+ }
+
+ public static record Instruction(String mem, String value) {
+
+ public Optional getMem() {
+ return mem.startsWith("mem") ? of(readString(mem + value, "mem[%n]%n", Mem.class)) : empty();
+ }
+ }
+
+ public static record Mem(long index, long value) {
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/days/Day15.java b/src/main/java/com/sbaars/adventofcode/year20/days/Day15.java
new file mode 100644
index 00000000..3f928271
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/days/Day15.java
@@ -0,0 +1,40 @@
+package com.sbaars.adventofcode.year20.days;
+
+import static java.util.stream.IntStream.range;
+
+import com.sbaars.adventofcode.year20.Day2020;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Day15 extends Day2020 {
+ public Day15() {
+ super(15);
+ }
+
+ public static void main(String[] args) {
+ new Day15().printParts();
+ }
+
+ @Override
+ public Object part1() {
+ return getSolution(2020L);
+ }
+
+ @Override
+ public Object part2() {
+ return getSolution(30000000L);
+ }
+
+ private long getSolution(long offset) {
+ Map turnNumbers = new HashMap<>();
+ long[] nums = dayNumbers(",");
+ range(0, nums.length - 1).forEach(i -> turnNumbers.put(nums[i], (long) i));
+ long lastNumber = nums[nums.length - 1];
+ for (long turnNumber = turnNumbers.size(); turnNumber < offset - 1; turnNumber++) {
+ long newLastNumber = turnNumbers.containsKey(lastNumber) ? turnNumber - turnNumbers.get(lastNumber) : 0;
+ turnNumbers.put(lastNumber, turnNumber);
+ lastNumber = newLastNumber;
+ }
+ return lastNumber;
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/days/Day16.java b/src/main/java/com/sbaars/adventofcode/year20/days/Day16.java
new file mode 100644
index 00000000..4a46b37b
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/days/Day16.java
@@ -0,0 +1,82 @@
+package com.sbaars.adventofcode.year20.days;
+
+import static com.sbaars.adventofcode.common.ReadsFormattedString.readString;
+import static java.util.Arrays.stream;
+import static java.util.stream.Collectors.toList;
+import static org.apache.commons.lang3.ArrayUtils.subarray;
+
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import com.sbaars.adventofcode.year20.Day2020;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+public class Day16 extends Day2020 {
+ private final Rule[] rules;
+ private final long[] myTicket;
+ private final List> tickets;
+ public Day16() {
+ super(16);
+ String[] input = day().split("\n\n");
+ this.rules = stream(input[0].split("\n")).map(s -> readString(s, "%s: %n-%n or %n-%n", Rule.class)).toArray(Rule[]::new);
+ this.myTicket = stream(input[1].split("\n")[1].split(",")).mapToLong(Long::parseLong).toArray();
+ String[] ticketStrings = input[2].split("\n");
+ this.tickets = stream(subarray(ticketStrings, 1, ticketStrings.length)).map(s -> stream(s.split(",")).map(Long::parseLong).collect(toList())).collect(toList());
+ }
+
+ public static void main(String[] args) {
+ new Day16().printParts();
+ }
+
+ @Override
+ public Object part1() {
+ return tickets.stream().flatMapToLong(t -> t.stream().filter(n -> stream(rules).noneMatch(r -> r.check(n))).mapToLong(e -> e)).sum();
+ }
+
+ @Override
+ public Object part2() {
+ List> valid = tickets.stream().filter(t -> t.stream().allMatch(n -> stream(rules).anyMatch(r -> r.check(n)))).collect(toList());
+
+ Multimap ruleIndex = MultimapBuilder.hashKeys().arrayListValues().build();
+ for (Rule r : rules) {
+ for (int j = 0; j < valid.get(0).size(); j++) {
+ int finalJ = j;
+ if (valid.stream().allMatch(t -> r.check(t.get(finalJ)))) {
+ ruleIndex.put(j, r);
+ }
+ }
+ }
+
+ Optional>> rs;
+ Set indices = new HashSet<>();
+ while ((rs = ruleIndex.asMap().entrySet().stream().filter(e -> e.getValue().size() == 1 && !indices.contains(e.getKey())).findAny()).isPresent()) {
+ Map.Entry> r = rs.get();
+ int index = r.getKey();
+ Rule rule = ((List) r.getValue()).get(0);
+ for (int i = 0; i < rules.length; i++) {
+ Map.Entry> t = new ArrayList<>(ruleIndex.asMap().entrySet()).get(i);
+ if (t.getKey() != index) {
+ t.getValue().remove(rule);
+ }
+ }
+ indices.add(index);
+ }
+
+ return ruleIndex.asMap().entrySet().stream().filter(e -> e.getValue().stream().anyMatch(Rule::isDeparture)).mapToLong(e -> myTicket[e.getKey()]).reduce((a, b) -> a * b).getAsLong();
+ }
+
+ public record Rule(String name, long lower1, long upper1, long lower2, long upper2) {
+ public boolean check(long val) {
+ return (val >= lower1 && val <= upper1) || (val >= lower2 && val <= upper2);
+ }
+
+ public boolean isDeparture() {
+ return name.startsWith("departure");
+ }
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/days/Day17.java b/src/main/java/com/sbaars/adventofcode/year20/days/Day17.java
new file mode 100644
index 00000000..88440d65
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/days/Day17.java
@@ -0,0 +1,114 @@
+package com.sbaars.adventofcode.year20.days;
+
+import com.sbaars.adventofcode.common.HasRecursion;
+import com.sbaars.adventofcode.year20.Day2020;
+import java.util.HashSet;
+import java.util.Set;
+
+public class Day17 extends Day2020 implements HasRecursion {
+ public Day17() {
+ super(17);
+ }
+
+ public static void main(String[] args) {
+ new Day17().printParts();
+ }
+
+ @Override
+ public Object part1() {
+ Set pos = new HashSet<>();
+ char[][] input = dayGrid();
+ for (int i = 0; i < input.length; i++) {
+ for (int j = 0; j < input[i].length; j++) {
+ if (input[i][j] == '#') {
+ pos.add(new Pos(i, j, 0));
+ }
+ }
+ }
+ for (int i = 0; i < 6; i++) {
+ Set newPos = new HashSet<>();
+ Set checkedPos = new HashSet<>();
+ for (Pos p : pos) {
+ addNeighbors(pos, newPos, checkedPos, p, true);
+ }
+ pos = newPos;
+ }
+ return pos.size();
+ }
+
+ public void addNeighbors(Set pos, Set newPos, Set checkedPos, Pos p, boolean active) {
+ if (!checkedPos.contains(p)) {
+ long neighbours = active ? -1 : 0;
+ checkedPos.add(p);
+ for (int a = -1; a <= 1; a++) {
+ for (int b = -1; b <= 1; b++) {
+ for (int c = -1; c <= 1; c++) {
+ Pos x = new Pos(p.x + a, p.y + b, p.z + c);
+ if (pos.contains(x)) {
+ neighbours++;
+ } else if (active) {
+ addNeighbors(pos, newPos, checkedPos, x, false);
+ }
+ }
+ }
+ }
+ if ((active && (neighbours == 2 || neighbours == 3)) ||
+ (!active && neighbours == 3)) {
+ newPos.add(p);
+ }
+ }
+ }
+
+ @Override
+ public Object part2() {
+ Set pos = new HashSet<>();
+ char[][] input = dayGrid();
+ for (int i = 0; i < input.length; i++) {
+ for (int j = 0; j < input[i].length; j++) {
+ if (input[i][j] == '#') {
+ pos.add(new Pos4(i, j, 0, 0));
+ }
+ }
+ }
+ for (int i = 0; i < 6; i++) {
+ Set newPos = new HashSet<>();
+ Set checkedPos = new HashSet<>();
+ for (Pos4 p : pos) {
+ addNeighbors(pos, newPos, checkedPos, p, true);
+ }
+ pos = newPos;
+ }
+ return pos.size();
+ }
+
+ public void addNeighbors(Set pos, Set newPos, Set checkedPos, Pos4 p, boolean active) {
+ if (!checkedPos.contains(p)) {
+ long neighbours = active ? -1 : 0;
+ checkedPos.add(p);
+ for (int a = -1; a <= 1; a++) {
+ for (int b = -1; b <= 1; b++) {
+ for (int c = -1; c <= 1; c++) {
+ for (int d = -1; d <= 1; d++) {
+ Pos4 x = new Pos4(p.x + a, p.y + b, p.z + c, p.w + d);
+ if (pos.contains(x)) {
+ neighbours++;
+ } else if (active) {
+ addNeighbors(pos, newPos, checkedPos, x, false);
+ }
+ }
+ }
+ }
+ }
+ if ((active && (neighbours == 2 || neighbours == 3)) ||
+ (!active && neighbours == 3)) {
+ newPos.add(p);
+ }
+ }
+ }
+
+ public static record Pos(long x, long y, long z) {
+ }
+
+ public static record Pos4(long x, long y, long z, long w) {
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/days/Day18.java b/src/main/java/com/sbaars/adventofcode/year20/days/Day18.java
new file mode 100644
index 00000000..a6ec71b5
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/days/Day18.java
@@ -0,0 +1,73 @@
+package com.sbaars.adventofcode.year20.days;
+
+import com.sbaars.adventofcode.common.HasRecursion;
+import com.sbaars.adventofcode.year20.Day2020;
+import org.apache.commons.lang3.tuple.Pair;
+
+import static java.util.Arrays.stream;
+
+public class Day18 extends Day2020 implements HasRecursion {
+ public Day18() {
+ super(18);
+ }
+
+ public static void main(String[] args) {
+ new Day18().printParts();
+ }
+
+ @Override
+ public Object part1() {
+ return getSolution(true);
+ }
+
+ @Override
+ public Object part2() {
+ return getSolution(false);
+ }
+
+ public long resolveExpression(StringBuilder s, boolean part1) {
+ var a = solve(s, part1);
+ return getSolution(a.getRight(), s, a.getLeft(), part1);
+ }
+
+ private long getSolution(boolean part1) {
+ return stream(dayStrings()).mapToLong(i -> resolveExpression(new StringBuilder(i), part1)).sum();
+ }
+
+ private Pair solve(StringBuilder s, boolean part1) {
+ long leftHand;
+ int i = s.length() - 2;
+ if (s.charAt(s.length() - 1) == ')') {
+ for (int nBrackets = 1; nBrackets > 0; i--) {
+ if (s.charAt(i) == '(') nBrackets--;
+ else if (s.charAt(i) == ')') nBrackets++;
+ }
+ i++;
+ leftHand = resolveExpression(new StringBuilder(s.substring(i + 1, s.length() - 1)), part1);
+ } else {
+ leftHand = Long.parseLong(s.substring(s.length() - 1, s.length()));
+ i = s.length() - 1;
+ }
+ return Pair.of(leftHand, i);
+ }
+
+ private long getSolution(int i, StringBuilder s, long leftHand, boolean part1) {
+ if (i > 0) {
+ char operator = s.charAt(i - 2);
+ StringBuilder leftSide = new StringBuilder(s.substring(0, i - 3));
+ if (operator == '*') {
+ return resolveExpression(new StringBuilder(s.substring(0, i - 3)), part1) * leftHand;
+ } else if (operator == '+') {
+ if (part1) {
+ return resolveExpression(new StringBuilder(s.substring(0, i - 3)), true) + leftHand;
+ } else {
+ var sol = solve(leftSide, false);
+ return getSolution(sol.getRight(), leftSide, sol.getKey() + leftHand, false);
+ }
+ }
+ } else if (i == 0) {
+ return leftHand;
+ }
+ throw new IllegalStateException();
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/days/Day19.java b/src/main/java/com/sbaars/adventofcode/year20/days/Day19.java
new file mode 100644
index 00000000..919a835e
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/days/Day19.java
@@ -0,0 +1,135 @@
+package com.sbaars.adventofcode.year20.days;
+
+import static java.lang.Long.parseLong;
+import static java.util.Arrays.stream;
+import static java.util.Collections.singletonList;
+
+import com.sbaars.adventofcode.year20.Day2020;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class Day19 extends Day2020 {
+ Map> sol = new HashMap<>();
+
+ public Day19() {
+ super(19);
+ }
+
+ public static void main(String[] args) {
+ new Day19().printParts();
+ }
+
+ private static Optional getLetter(String rule) {
+ return rule.startsWith("\"") ? Optional.of(rule.charAt(1) + "") : Optional.empty();
+ }
+
+ private static long[] getRule(String rule, boolean num) {
+ if (rule.startsWith("\"")) {
+ return new long[]{};
+ }
+ String[] r = rule.split(" \\| ");
+ if (!num || r.length > 1) {
+ return Arrays.stream(r[num ? 1 : 0].split(" ")).mapToLong(Long::parseLong).toArray();
+ } else {
+ return new long[]{};
+ }
+ }
+
+ @Override
+ public Object part1() {
+ return getSolution(day());
+ }
+
+ private long getSolution(String inputFile) {
+ String[] input = inputFile.split("\n\n");
+ Map rules = Arrays.stream(input[0].split("\n"))
+ .map(e -> e.split(": "))
+ .collect(Collectors.toMap(e -> parseLong(e[0]), e -> new Rule(parseLong(e[0]), e[1])));
+
+ rules.values().forEach(e -> e.getPossibilities(rules, sol));
+ return stream(input[1].split("\n"))
+ .filter(e -> sol.values().stream().anyMatch(r -> r.contains(e)))
+ .count();
+ }
+
+ @Override
+ public Object part2() {
+ int maxDepth = 10;
+ String[] input = day().split("\n\n");
+ Set all = sol.values().stream().flatMap(Collection::stream).collect(Collectors.toUnmodifiableSet());
+ Set s42 = sol.get(42L);
+ Set s31 = sol.get(31L);
+ Set s11 = sol.get(11L);
+ for (int i = 2; i <= maxDepth; i++) {
+ Set add = new HashSet<>(s42.size() * s31.size());
+ Set add2 = new HashSet<>(s42.size() * s31.size() * s11.size());
+ for (String o : s42) {
+ for (String o1 : s42) {
+ add.add(o + o1);
+ }
+ for (String o2 : s31) {
+ for (String o3 : s11) {
+ add2.add(o + o3 + o2);
+ }
+ }
+ }
+ s42.addAll(add);
+ s11.addAll(add2);
+ }
+ return stream(input[1].split("\n"))
+ .filter(e -> sol.values().stream().anyMatch(r -> r.contains(e)))
+ .count();
+ }
+
+ public record Rule(long id, Optional letter, long[] rule1, long[] rule2) {
+ public Rule(long id, String rule) {
+ this(id, getLetter(rule), getRule(rule, false), getRule(rule, true));
+ }
+
+ public Set getPossibilities(Map m, Map> sol) {
+ if (sol.containsKey(id)) return sol.get(id);
+ if (letter.isEmpty()) {
+ Rule[] r = stream(rule1).mapToObj(m::get).toArray(Rule[]::new);
+ Rule[] orRule = stream(rule2).mapToObj(m::get).toArray(Rule[]::new);
+ Set output = r[0].getPossibilities(m, sol);
+ if (sol.containsKey(id)) return sol.get(id);
+ for (int i = 1; i < r.length; i++) {
+ Set output2 = r[i].getPossibilities(m, sol);
+ if (sol.containsKey(id)) return sol.get(id);
+ Set newOne = new HashSet<>();
+ for (String o : output) {
+ for (String o2 : output2) {
+ newOne.add(o + o2);
+ }
+ }
+ output = newOne;
+ }
+ if (orRule.length > 0) {
+ Set outputOr = orRule[0].getPossibilities(m, sol);
+ if (sol.containsKey(id)) return sol.get(id);
+ for (int i = 1; i < orRule.length; i++) {
+ Set outputOr2 = orRule[i].getPossibilities(m, sol);
+ if (sol.containsKey(id)) return sol.get(id);
+ Set newOne = new HashSet<>();
+ for (String o : outputOr) {
+ for (String o2 : outputOr2) {
+ newOne.add(o + o2);
+ }
+ }
+ outputOr = newOne;
+ }
+ output.addAll(outputOr);
+ }
+ sol.put(id, output);
+ return output;
+ }
+ return new HashSet<>(singletonList(letter.get()));
+ }
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/days/Day2.java b/src/main/java/com/sbaars/adventofcode/year20/days/Day2.java
new file mode 100644
index 00000000..293587d7
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/days/Day2.java
@@ -0,0 +1,49 @@
+package com.sbaars.adventofcode.year20.days;
+
+import static com.sbaars.adventofcode.common.ReadsFormattedString.readString;
+import static java.lang.Math.toIntExact;
+
+import com.sbaars.adventofcode.year20.Day2020;
+import java.util.function.Predicate;
+
+public class Day2 extends Day2020 {
+ public Day2() {
+ super(2);
+ }
+
+ public static void main(String[] args) {
+ new Day2().printParts();
+ }
+
+ public static Password mapPassword(String s) {
+ return readString(s, "%n-%n %c: %s", Password.class);
+ }
+
+ @Override
+ public Object part1() {
+ return pwCount(Password::isValid);
+ }
+
+ private long pwCount(Predicate validator) {
+ return dayStream().map(Day2::mapPassword).filter(validator).count();
+ }
+
+ @Override
+ public Object part2() {
+ return pwCount(Password::isValid2);
+ }
+
+ public record Password(long lower, long higher, char character, String password) {
+
+ public boolean isValid() {
+ long count = password.chars().filter(c -> c == character).count();
+ return count >= lower && count <= higher;
+ }
+
+ public boolean isValid2() {
+ int char1 = password.charAt(toIntExact(lower - 1));
+ int char2 = password.charAt(toIntExact(higher - 1));
+ return char1 != char2 && (char1 == character || char2 == character);
+ }
+ }
+}
diff --git a/src/main/java/com/sbaars/adventofcode/year20/days/Day20.java b/src/main/java/com/sbaars/adventofcode/year20/days/Day20.java
new file mode 100644
index 00000000..3ccff922
--- /dev/null
+++ b/src/main/java/com/sbaars/adventofcode/year20/days/Day20.java
@@ -0,0 +1,127 @@
+package com.sbaars.adventofcode.year20.days;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Streams;
+import com.sbaars.adventofcode.common.Direction;
+import com.sbaars.adventofcode.year20.Day2020;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.awt.*;
+import java.util.List;
+import java.util.*;
+
+import static com.sbaars.adventofcode.common.Direction.*;
+import static java.lang.Long.parseLong;
+import static java.util.Arrays.stream;
+
+public class Day20 extends Day2020 {
+ public Day20() {
+ super(20);
+ }
+
+ public static void main(String[] args) {
+ new Day20().printParts();
+ }
+
+ @Override
+ public Object part1() {
+ Grid[] input = stream(day().split("\n\n")).map(Grid::new).toArray(Grid[]::new);
+ Multimap