From 50e2671e71e098254322efb46930a2f5700be8a7 Mon Sep 17 00:00:00 2001
From: Kapil Gain <69730236+kapilgain@users.noreply.github.com>
Date: Tue, 19 Dec 2023 11:15:55 +0530
Subject: [PATCH] Add Day 19
---
.idea/runConfigurations/Jacoco.xml | 4 +-
.idea/runConfigurations/Run_tests.xml | 3 +
src/main/java/org/advent/Main.java | 4 +-
.../org/advent/{utils => day17}/Crucible.java | 4 +-
.../java/org/advent/day17/Day17Part1.java | 1 -
.../java/org/advent/day17/Day17Part2.java | 1 -
src/main/java/org/advent/day19/Day19.java | 14 ++
.../java/org/advent/day19/Day19Part1.java | 81 ++++++++++
.../java/org/advent/day19/Day19Part2.java | 83 ++++++++++
src/main/java/org/advent/day19/Part.java | 18 +++
src/main/java/org/advent/day19/Workflow.java | 19 +++
.../java/org/advent/day19/WorkflowRule.java | 22 +++
src/main/java/org/advent/utils/MathUtils.java | 11 ++
src/main/java/org/advent/utils/Pointer.java | 6 +-
.../advent/{utils => day17}/CrucibleTest.java | 6 +-
.../java/org/advent/day18/Day18Part2Test.java | 3 +
.../java/org/advent/day19/Day19Part1Test.java | 146 ++++++++++++++++++
.../java/org/advent/day19/Day19Part2Test.java | 41 +++++
src/test/java/org/advent/day19/PartTest.java | 29 ++++
.../org/advent/day19/WorkflowRuleTest.java | 45 ++++++
.../java/org/advent/day19/WorkflowTest.java | 20 +++
.../java/org/advent/utils/MathUtilsTest.java | 8 +
22 files changed, 559 insertions(+), 10 deletions(-)
rename src/main/java/org/advent/{utils => day17}/Crucible.java (91%)
create mode 100644 src/main/java/org/advent/day19/Day19.java
create mode 100644 src/main/java/org/advent/day19/Day19Part1.java
create mode 100644 src/main/java/org/advent/day19/Day19Part2.java
create mode 100644 src/main/java/org/advent/day19/Part.java
create mode 100644 src/main/java/org/advent/day19/Workflow.java
create mode 100644 src/main/java/org/advent/day19/WorkflowRule.java
rename src/test/java/org/advent/{utils => day17}/CrucibleTest.java (89%)
create mode 100644 src/test/java/org/advent/day19/Day19Part1Test.java
create mode 100644 src/test/java/org/advent/day19/Day19Part2Test.java
create mode 100644 src/test/java/org/advent/day19/PartTest.java
create mode 100644 src/test/java/org/advent/day19/WorkflowRuleTest.java
create mode 100644 src/test/java/org/advent/day19/WorkflowTest.java
diff --git a/.idea/runConfigurations/Jacoco.xml b/.idea/runConfigurations/Jacoco.xml
index 56900c5..f30fce7 100644
--- a/.idea/runConfigurations/Jacoco.xml
+++ b/.idea/runConfigurations/Jacoco.xml
@@ -18,7 +18,9 @@
+
+
+
diff --git a/src/main/java/org/advent/Main.java b/src/main/java/org/advent/Main.java
index 0beb711..99d410d 100644
--- a/src/main/java/org/advent/Main.java
+++ b/src/main/java/org/advent/Main.java
@@ -1,11 +1,11 @@
package org.advent;
-import org.advent.day18.Day18;
+import org.advent.day19.Day19;
public class Main {
public static void main(String[] args) {
- new Day18().run();
+ new Day19().run();
}
}
diff --git a/src/main/java/org/advent/utils/Crucible.java b/src/main/java/org/advent/day17/Crucible.java
similarity index 91%
rename from src/main/java/org/advent/utils/Crucible.java
rename to src/main/java/org/advent/day17/Crucible.java
index e8d95e6..4bb17d5 100644
--- a/src/main/java/org/advent/utils/Crucible.java
+++ b/src/main/java/org/advent/day17/Crucible.java
@@ -1,4 +1,6 @@
-package org.advent.utils;
+package org.advent.day17;
+
+import org.advent.utils.Pointer;
import static org.apache.commons.lang3.builder.EqualsBuilder.reflectionEquals;
import static org.apache.commons.lang3.builder.HashCodeBuilder.reflectionHashCode;
diff --git a/src/main/java/org/advent/day17/Day17Part1.java b/src/main/java/org/advent/day17/Day17Part1.java
index 611d9f1..b4b5eb4 100644
--- a/src/main/java/org/advent/day17/Day17Part1.java
+++ b/src/main/java/org/advent/day17/Day17Part1.java
@@ -1,7 +1,6 @@
package org.advent.day17;
import io.vavr.collection.PriorityQueue;
-import org.advent.utils.Crucible;
import org.advent.utils.Direction;
import org.advent.utils.Location;
import org.advent.utils.Pointer;
diff --git a/src/main/java/org/advent/day17/Day17Part2.java b/src/main/java/org/advent/day17/Day17Part2.java
index 2b00a60..9df6831 100644
--- a/src/main/java/org/advent/day17/Day17Part2.java
+++ b/src/main/java/org/advent/day17/Day17Part2.java
@@ -1,7 +1,6 @@
package org.advent.day17;
import io.vavr.collection.PriorityQueue;
-import org.advent.utils.Crucible;
import java.util.List;
diff --git a/src/main/java/org/advent/day19/Day19.java b/src/main/java/org/advent/day19/Day19.java
new file mode 100644
index 0000000..8f5b5dc
--- /dev/null
+++ b/src/main/java/org/advent/day19/Day19.java
@@ -0,0 +1,14 @@
+package org.advent.day19;
+
+import org.advent.utils.ClasspathFileReader;
+
+public class Day19 implements Runnable {
+
+ @Override
+ public void run() {
+ var lines = new ClasspathFileReader().readAllLines("day19.input");
+ System.out.println(new Day19Part1().solve(lines));
+ System.out.println(new Day19Part2().solve(lines));
+ }
+
+}
diff --git a/src/main/java/org/advent/day19/Day19Part1.java b/src/main/java/org/advent/day19/Day19Part1.java
new file mode 100644
index 0000000..e7a96b1
--- /dev/null
+++ b/src/main/java/org/advent/day19/Day19Part1.java
@@ -0,0 +1,81 @@
+package org.advent.day19;
+
+import io.vavr.Tuple;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static io.vavr.collection.List.ofAll;
+import static java.util.Objects.requireNonNull;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+import static org.advent.utils.StringUtils.parseSections;
+import static org.advent.utils.StringUtils.readLines;
+
+public class Day19Part1 {
+
+ public Number solve(List lines) {
+ var sections = parseSections(lines);
+ var workflowMap = createWorkflowMap(sections.getFirst());
+ var parts = readLines(sections.getLast()).stream()
+ .map(Part::parse)
+ .toList();
+
+ return ofAll(parts.stream()
+ .map(part -> Tuple.of(part, runPartThroughWorkflows(part, workflowMap)))
+ .filter(tuple -> tuple._2.contains("A"))
+ .map(tuple -> tuple._1.calculateChristmasRating()))
+ .sum();
+ }
+
+ public Map createWorkflowMap(String lines) {
+ return readLines(lines).stream()
+ .map(Workflow::parse)
+ .collect(toMap(Workflow::name, identity()));
+ }
+
+ public List runPartThroughWorkflows(Part part, Map workflowMap) {
+ var workflowName = "in";
+ var path = new ArrayList();
+ path.add(workflowName);
+
+ while (true) {
+ var workflow = requireNonNull(workflowMap.get(workflowName));
+ for (var rule : workflow.rules()) {
+ if (evaluateCondition(rule.condition(), part)) {
+ if ("A".equals(rule.destination()) || "R".equals(rule.destination())) {
+ path.add(rule.destination());
+ return path;
+
+ } else {
+ workflowName = rule.destination();
+ path.add(workflowName);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ public boolean evaluateCondition(String condition, Part part) {
+ if (StringUtils.isBlank(condition)) {
+ return true;
+ }
+
+ var isLt = condition.contains("<");
+ String[] parts = condition.split(isLt ? "<" : ">");
+ String lhs = parts[0];
+ int rhs = Integer.parseInt(parts[1]);
+
+ return switch (lhs) {
+ case "x" -> isLt ? part.x() < rhs : part.x() > rhs;
+ case "m" -> isLt ? part.m() < rhs : part.m() > rhs;
+ case "a" -> isLt ? part.a() < rhs : part.a() > rhs;
+ case "s" -> isLt ? part.s() < rhs : part.s() > rhs;
+ default -> throw new IllegalArgumentException("Invalid condition format");
+ };
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/advent/day19/Day19Part2.java b/src/main/java/org/advent/day19/Day19Part2.java
new file mode 100644
index 0000000..22bb15b
--- /dev/null
+++ b/src/main/java/org/advent/day19/Day19Part2.java
@@ -0,0 +1,83 @@
+package org.advent.day19;
+
+import io.vavr.Tuple;
+import io.vavr.Tuple2;
+import org.advent.utils.MathUtils;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import static io.vavr.collection.HashMap.of;
+import static org.advent.utils.StringUtils.parseSections;
+
+public class Day19Part2 extends Day19Part1 {
+
+ public Number solve(List lines) {
+ var sections = parseSections(lines);
+ var workflowMap = createWorkflowMap(sections.getFirst());
+ return countAcceptedCombinations(
+ of(
+ 'x', Tuple.of(1, 4000),
+ 'm', Tuple.of(1, 4000),
+ 'a', Tuple.of(1, 4000),
+ 's', Tuple.of(1, 4000)
+ ),
+ workflowMap,
+ "in"
+ );
+ }
+
+ public long countAcceptedCombinations(
+ io.vavr.collection.Map> christmasRanges,
+ Map workflowMap,
+ String startingWorkflowName) {
+ if ("R".equals(startingWorkflowName)) {
+ return 0L;
+ }
+
+ if ("A".equals(startingWorkflowName)) {
+ return christmasRanges.values()
+ .map(range -> range._2 - range._1 + 1)
+ .product()
+ .longValue();
+ }
+
+ var workflow = workflowMap.get(startingWorkflowName);
+ var count = 0L;
+ for (var rule : workflow.rules()) {
+ var range = christmasRanges.get(rule.conditionLHS()).getOrNull();
+
+ // Last rule, no condition, just go to destination
+ if (rule.conditionOperator() == null) {
+ count += countAcceptedCombinations(christmasRanges, workflowMap, rule.destination());
+ continue;
+ }
+
+ char operator = rule.conditionOperator();
+ var rhs = Optional.ofNullable(rule.conditionRHS());
+ if (operator == '<') {
+ var matchedRange = MathUtils.intersectRange(range, Tuple.of(range._1, rhs.orElse(0) - 1));
+ var unmatchedRange = Tuple.of(matchedRange._2 + 1, range._2);
+ count += countAcceptedCombinations(
+ christmasRanges.put(rule.conditionLHS(), matchedRange),
+ workflowMap,
+ rule.destination());
+ christmasRanges = christmasRanges.put(rule.conditionLHS(), unmatchedRange);
+ continue;
+ }
+
+ // operator == '>'
+ var matchedRange = MathUtils.intersectRange(range, Tuple.of(rhs.orElse(0) + 1, range._2));
+ var unmatchedRange = Tuple.of(range._1, matchedRange._1 - 1);
+ count += countAcceptedCombinations(
+ christmasRanges.put(rule.conditionLHS(), matchedRange),
+ workflowMap,
+ rule.destination());
+ christmasRanges = christmasRanges.put(rule.conditionLHS(), unmatchedRange);
+ }
+
+ return count;
+ }
+
+}
diff --git a/src/main/java/org/advent/day19/Part.java b/src/main/java/org/advent/day19/Part.java
new file mode 100644
index 0000000..25f9d1c
--- /dev/null
+++ b/src/main/java/org/advent/day19/Part.java
@@ -0,0 +1,18 @@
+package org.advent.day19;
+
+public record Part(int x, int m, int a, int s) {
+
+ public int calculateChristmasRating() {
+ return x + m + a + s; // 🎄
+ }
+
+ public static Part parse(String line) {
+ var values = line.substring(1, line.length() - 1).split(",");
+ var x = Integer.parseInt(values[0].split("=")[1]);
+ var m = Integer.parseInt(values[1].split("=")[1]);
+ var a = Integer.parseInt(values[2].split("=")[1]);
+ var s = Integer.parseInt(values[3].split("=")[1]);
+ return new Part(x, m, a, s);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/advent/day19/Workflow.java b/src/main/java/org/advent/day19/Workflow.java
new file mode 100644
index 0000000..6b2a27f
--- /dev/null
+++ b/src/main/java/org/advent/day19/Workflow.java
@@ -0,0 +1,19 @@
+package org.advent.day19;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static java.util.stream.Collectors.toList;
+
+public record Workflow(String name, List rules) {
+
+ public static Workflow parse(String line) {
+ var parts = line.split("\\{");
+ var name = parts[0];
+ var rules = Arrays.stream(parts[1].substring(0, parts[1].length() - 1).split(","))
+ .map(WorkflowRule::parse)
+ .collect(toList());
+ return new Workflow(name, rules);
+ }
+
+}
diff --git a/src/main/java/org/advent/day19/WorkflowRule.java b/src/main/java/org/advent/day19/WorkflowRule.java
new file mode 100644
index 0000000..76763aa
--- /dev/null
+++ b/src/main/java/org/advent/day19/WorkflowRule.java
@@ -0,0 +1,22 @@
+package org.advent.day19;
+
+public record WorkflowRule(String condition, String destination) {
+
+ public static WorkflowRule parse(String rule) {
+ var ruleParts = rule.split(":");
+ return new WorkflowRule(ruleParts.length > 1 ? ruleParts[0] : null, ruleParts[ruleParts.length - 1]);
+ }
+
+ public Character conditionLHS() {
+ return condition == null ? null : condition.charAt(0);
+ }
+
+ public Character conditionOperator() {
+ return condition == null ? null : condition.charAt(1);
+ }
+
+ public Integer conditionRHS() {
+ return condition == null ? null : Integer.parseInt(condition.substring(2));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/advent/utils/MathUtils.java b/src/main/java/org/advent/utils/MathUtils.java
index 602a629..3e39f5d 100644
--- a/src/main/java/org/advent/utils/MathUtils.java
+++ b/src/main/java/org/advent/utils/MathUtils.java
@@ -47,4 +47,15 @@ public static long manhattanDistance(Tuple2 first, Tuple2 intersectRange(
+ Tuple2 firstRange, Tuple2 secondRange
+ ) {
+ var returnVal = Tuple.of(Math.max(firstRange._1, secondRange._1), Math.min(firstRange._2, secondRange._2));
+ if (returnVal._1 > returnVal._2) {
+ return Tuple.of(0, 0);
+ }
+
+ return returnVal;
+ }
+
}
diff --git a/src/main/java/org/advent/utils/Pointer.java b/src/main/java/org/advent/utils/Pointer.java
index 317c668..bd9ee19 100644
--- a/src/main/java/org/advent/utils/Pointer.java
+++ b/src/main/java/org/advent/utils/Pointer.java
@@ -3,15 +3,15 @@
public record Pointer(Location location, Direction direction) {
public Pointer move() {
- return new Pointer(location().move(direction), direction);
+ return new Pointer(location.move(direction), direction);
}
public Pointer turnLeft() {
- return new Pointer(location(), direction.turnLeft());
+ return new Pointer(location, direction.turnLeft());
}
public Pointer turnRight() {
- return new Pointer(location(), direction.turnRight());
+ return new Pointer(location, direction.turnRight());
}
}
\ No newline at end of file
diff --git a/src/test/java/org/advent/utils/CrucibleTest.java b/src/test/java/org/advent/day17/CrucibleTest.java
similarity index 89%
rename from src/test/java/org/advent/utils/CrucibleTest.java
rename to src/test/java/org/advent/day17/CrucibleTest.java
index 12e8dca..23f0df2 100644
--- a/src/test/java/org/advent/utils/CrucibleTest.java
+++ b/src/test/java/org/advent/day17/CrucibleTest.java
@@ -1,5 +1,9 @@
-package org.advent.utils;
+package org.advent.day17;
+import org.advent.day17.Crucible;
+import org.advent.utils.Direction;
+import org.advent.utils.Location;
+import org.advent.utils.Pointer;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
diff --git a/src/test/java/org/advent/day18/Day18Part2Test.java b/src/test/java/org/advent/day18/Day18Part2Test.java
index 8d45096..6631974 100644
--- a/src/test/java/org/advent/day18/Day18Part2Test.java
+++ b/src/test/java/org/advent/day18/Day18Part2Test.java
@@ -3,6 +3,7 @@
import org.advent.utils.Direction;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
import static org.advent.day18.Day18Part1Test.TEST_DATA;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -31,6 +32,8 @@ void testParseDirection() {
}
@Test
+ // This test is slow so disabling it in local environment
+ @DisabledIfEnvironmentVariable(named = "local", matches = "true")
void solvesForTestInput() {
assertEquals(952_408_144_115L, underTest.solve(TEST_DATA));
}
diff --git a/src/test/java/org/advent/day19/Day19Part1Test.java b/src/test/java/org/advent/day19/Day19Part1Test.java
new file mode 100644
index 0000000..b4c1557
--- /dev/null
+++ b/src/test/java/org/advent/day19/Day19Part1Test.java
@@ -0,0 +1,146 @@
+package org.advent.day19;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class Day19Part1Test {
+
+ public static final List TEST_DATA = Arrays.stream("""
+ px{a<2006:qkq,m>2090:A,rfg}
+ pv{a>1716:R,A}
+ lnx{m>1548:A,A}
+ rfg{s<537:gd,x>2440:R,A}
+ qs{s>3448:A,lnx}
+ qkq{x<1416:A,crn}
+ crn{x>2662:A,R}
+ in{s<1351:px,qqz}
+ qqz{s>2770:qs,m<1801:hdj,R}
+ gd{a>3333:R,R}
+ hdj{m>838:A,pv}
+
+ {x=787,m=2655,a=1222,s=2876}
+ {x=1679,m=44,a=2067,s=496}
+ {x=2036,m=264,a=79,s=2244}
+ {x=2461,m=1339,a=466,s=291}
+ {x=2127,m=1623,a=2188,s=1013}
+ """
+ .trim().split("\n")).toList();
+
+ public static final String TEST_WORKFLOW_DATA = """
+ px{a<2006:qkq,m>2090:A,rfg}
+ pv{a>1716:R,A}
+ lnx{m>1548:A,A}
+ rfg{s<537:gd,x>2440:R,A}
+ qs{s>3448:A,lnx}
+ qkq{x<1416:A,crn}
+ crn{x>2662:A,R}
+ in{s<1351:px,qqz}
+ qqz{s>2770:qs,m<1801:hdj,R}
+ gd{a>3333:R,R}
+ hdj{m>838:A,pv}
+ """
+ .trim();
+
+ private Day19Part1 underTest;
+
+ @BeforeEach
+ void setup() {
+ underTest = new Day19Part1();
+ }
+
+ @Test
+ void throwsExceptionWhenEvaluatingInvalidCondition() {
+ assertThrows(
+ Exception.class,
+ () -> underTest.evaluateCondition("hello", new Part(0, 0, 0, 0))
+ );
+
+ assertThrows(
+ Exception.class,
+ () -> underTest.evaluateCondition("q>10", new Part(0, 0, 0, 0))
+ );
+ }
+
+ @Test
+ void blankConditionEvaluatesToTrue() {
+ assertTrue(underTest.evaluateCondition(null, new Part(11, 0, 0, 0)));
+ }
+
+ @Test
+ void testEvaluateCondition() {
+ assertTrue(underTest.evaluateCondition("x>10", new Part(11, 0, 0, 0)));
+ assertFalse(underTest.evaluateCondition("x>10", new Part(10, 0, 0, 0)));
+ assertFalse(underTest.evaluateCondition("x>10", new Part(9, 0, 0, 0)));
+ assertTrue(underTest.evaluateCondition("x<10", new Part(9, 0, 0, 0)));
+
+ assertTrue(underTest.evaluateCondition("m>10", new Part(0, 11, 0, 0)));
+ assertFalse(underTest.evaluateCondition("m>10", new Part(0, 10, 0, 0)));
+ assertFalse(underTest.evaluateCondition("m>10", new Part(0, 9, 0, 0)));
+ assertFalse(underTest.evaluateCondition("m<9", new Part(0, 9, 0, 0)));
+
+ assertTrue(underTest.evaluateCondition("a>10", new Part(0, 0, 11, 0)));
+ assertFalse(underTest.evaluateCondition("a>10", new Part(0, 0, 10, 0)));
+ assertFalse(underTest.evaluateCondition("a>10", new Part(0, 0, 9, 0)));
+ assertTrue(underTest.evaluateCondition("a<10", new Part(0, 0, 9, 0)));
+
+ assertTrue(underTest.evaluateCondition("s>10", new Part(0, 0, 0, 11)));
+ assertFalse(underTest.evaluateCondition("s>10", new Part(0, 0, 0, 10)));
+ assertFalse(underTest.evaluateCondition("s>10", new Part(0, 0, 0, 9)));
+ assertTrue(underTest.evaluateCondition("s<10", new Part(0, 0, 0, 9)));
+ }
+
+ @Test
+ void testRunPartThroughWorkflowsForSmallerInput() {
+ var workflowMap = underTest.createWorkflowMap("in{A}");
+ assertEquals(
+ List.of("in", "A"),
+ underTest.runPartThroughWorkflows(Part.parse("{x=787,m=2655,a=1222,s=2876}"), workflowMap)
+ );
+
+ workflowMap = underTest.createWorkflowMap("in{R}");
+ assertEquals(
+ List.of("in", "R"),
+ underTest.runPartThroughWorkflows(Part.parse("{x=787,m=2655,a=1222,s=2876}"), workflowMap)
+ );
+ }
+
+ @Test
+ void testRunPartThroughWorkflows() {
+ var workflowMap = underTest.createWorkflowMap(TEST_WORKFLOW_DATA);
+ assertEquals(
+ Arrays.asList("in", "qqz", "qs", "lnx", "A"),
+ underTest.runPartThroughWorkflows(Part.parse("{x=787,m=2655,a=1222,s=2876}"), workflowMap)
+ );
+
+ assertEquals(
+ Arrays.asList("in", "px", "rfg", "gd", "R"),
+ underTest.runPartThroughWorkflows(Part.parse("{x=1679,m=44,a=2067,s=496}"), workflowMap)
+ );
+
+ assertEquals(
+ Arrays.asList("in", "qqz", "hdj", "pv", "A"),
+ underTest.runPartThroughWorkflows(Part.parse("{x=2036,m=264,a=79,s=2244}"), workflowMap)
+ );
+
+ assertEquals(
+ Arrays.asList("in", "px", "qkq", "crn", "R"),
+ underTest.runPartThroughWorkflows(Part.parse("{x=2461,m=1339,a=466,s=291}"), workflowMap)
+ );
+
+ assertEquals(
+ Arrays.asList("in", "px", "rfg", "A"),
+ underTest.runPartThroughWorkflows(Part.parse("{x=2127,m=1623,a=2188,s=1013}"), workflowMap)
+ );
+ }
+
+ @Test
+ void solvesForTestInput() {
+ assertEquals(19_114L, underTest.solve(TEST_DATA));
+ }
+
+}
diff --git a/src/test/java/org/advent/day19/Day19Part2Test.java b/src/test/java/org/advent/day19/Day19Part2Test.java
new file mode 100644
index 0000000..60408c4
--- /dev/null
+++ b/src/test/java/org/advent/day19/Day19Part2Test.java
@@ -0,0 +1,41 @@
+package org.advent.day19;
+
+import io.vavr.Tuple;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.advent.day19.Day19Part1Test.TEST_DATA;
+import static org.advent.day19.Day19Part1Test.TEST_WORKFLOW_DATA;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class Day19Part2Test {
+
+ private Day19Part2 underTest;
+
+ @BeforeEach
+ void setup() {
+ underTest = new Day19Part2();
+ }
+
+ @Test
+ void testCountForTestInput() {
+ var workflowMap = underTest.createWorkflowMap(TEST_WORKFLOW_DATA);
+ var ranges = io.vavr.collection.HashMap.of(
+ 'x', Tuple.of(1, 4000),
+ 'm', Tuple.of(1, 4000),
+ 'a', Tuple.of(1, 4000),
+ 's', Tuple.of(1, 4000)
+ );
+
+ assertEquals(
+ 167_409_079_868_000L,
+ underTest.countAcceptedCombinations(ranges, workflowMap, "in")
+ );
+ }
+
+ @Test
+ void solvesForTestInput() {
+ assertEquals(167_409_079_868_000L, underTest.solve(TEST_DATA));
+ }
+
+}
diff --git a/src/test/java/org/advent/day19/PartTest.java b/src/test/java/org/advent/day19/PartTest.java
new file mode 100644
index 0000000..54c8dee
--- /dev/null
+++ b/src/test/java/org/advent/day19/PartTest.java
@@ -0,0 +1,29 @@
+package org.advent.day19;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class PartTest {
+
+ @Test
+ void testInitialisation() {
+ var underTest = new Part(1, 2, 3, 4);
+ assertEquals(1, underTest.x());
+ assertEquals(2, underTest.m());
+ assertEquals(3, underTest.a());
+ assertEquals(4, underTest.s());
+ }
+
+ @Test
+ void testCalculateChristmasRating() {
+ var underTest = new Part(1, 2, 3, 4);
+ assertEquals(10, underTest.calculateChristmasRating());
+ }
+
+ @Test
+ void testParse() {
+ assertEquals(new Part(1, 2, 3, 4), Part.parse("{x=1,m=2,a=3,s=4}"));
+ }
+
+}
diff --git a/src/test/java/org/advent/day19/WorkflowRuleTest.java b/src/test/java/org/advent/day19/WorkflowRuleTest.java
new file mode 100644
index 0000000..687aba6
--- /dev/null
+++ b/src/test/java/org/advent/day19/WorkflowRuleTest.java
@@ -0,0 +1,45 @@
+package org.advent.day19;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class WorkflowRuleTest {
+
+ @Test
+ void testInitialisation() {
+ var underTest = new WorkflowRule("x>10", "one");
+ assertEquals("x>10", underTest.condition());
+ assertEquals("one", underTest.destination());
+ }
+
+ @Test
+ void testParse() {
+ assertEquals(new WorkflowRule("x>10", "one"), WorkflowRule.parse("x>10:one"));
+ assertEquals(new WorkflowRule(null, "A"), WorkflowRule.parse("A"));
+ assertEquals(new WorkflowRule(null, "R"), WorkflowRule.parse("R"));
+ }
+
+ @Test
+ void testConditionLHS() {
+ assertEquals('x', WorkflowRule.parse("x>10:one").conditionLHS());
+ assertNull(WorkflowRule.parse("A").conditionLHS());
+ assertNull(WorkflowRule.parse("R").conditionLHS());
+ }
+
+ @Test
+ void testConditionOperator() {
+ assertEquals('>', WorkflowRule.parse("x>10:one").conditionOperator());
+ assertNull(WorkflowRule.parse("A").conditionOperator());
+ assertNull(WorkflowRule.parse("R").conditionOperator());
+ }
+
+ @Test
+ void testConditionRHS() {
+ assertEquals(10, WorkflowRule.parse("x>10:one").conditionRHS());
+ assertNull(WorkflowRule.parse("A").conditionRHS());
+ assertNull(WorkflowRule.parse("R").conditionRHS());
+ }
+
+}
diff --git a/src/test/java/org/advent/day19/WorkflowTest.java b/src/test/java/org/advent/day19/WorkflowTest.java
new file mode 100644
index 0000000..7624539
--- /dev/null
+++ b/src/test/java/org/advent/day19/WorkflowTest.java
@@ -0,0 +1,20 @@
+package org.advent.day19;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class WorkflowTest {
+
+ @Test
+ void testParse() {
+ var underTest = Workflow.parse("ex{x>10:one,m<20:two,a>30:R,A}");
+ assertEquals("ex", underTest.name());
+ assertEquals(4, underTest.rules().size());
+ assertEquals(new WorkflowRule("x>10", "one"), underTest.rules().getFirst());
+ assertEquals(new WorkflowRule("m<20", "two"), underTest.rules().get(1));
+ assertEquals(new WorkflowRule("a>30", "R"), underTest.rules().get(2));
+ assertEquals(new WorkflowRule(null, "A"), underTest.rules().getLast());
+ }
+
+}
diff --git a/src/test/java/org/advent/utils/MathUtilsTest.java b/src/test/java/org/advent/utils/MathUtilsTest.java
index e9b2b9f..cf006c8 100644
--- a/src/test/java/org/advent/utils/MathUtilsTest.java
+++ b/src/test/java/org/advent/utils/MathUtilsTest.java
@@ -46,4 +46,12 @@ void calculatesManhattanDistanceBetweenTwoPoints() {
assertEquals(4, MathUtils.manhattanDistance(Tuple.of(2, 2), Tuple.of(3, 5)));
}
+ @Test
+ void calculatesIntersectionOfTwoRanges() {
+ assertEquals(Tuple.of(5, 10), MathUtils.intersectRange(Tuple.of(1, 10), Tuple.of(5, 15)));
+ assertEquals(Tuple.of(5, 10), MathUtils.intersectRange(Tuple.of(5, 15), Tuple.of(1, 10)));
+ assertEquals(Tuple.of(5, 5), MathUtils.intersectRange(Tuple.of(5, 10), Tuple.of(1, 5)));
+ assertEquals(Tuple.of(0, 0), MathUtils.intersectRange(Tuple.of(1, 4), Tuple.of(5, 10)));
+ }
+
}