Skip to content

Commit a66da56

Browse files
committed
Add day 21 solution
1 parent 89a0848 commit a66da56

File tree

14 files changed

+3320
-0
lines changed

14 files changed

+3320
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package eu.happycoders.adventofcode2022.day21;
2+
3+
import java.util.Map;
4+
5+
/**
6+
* Advent of Code 2022 – Object-Oriented Solutions in Java.
7+
*
8+
* <p>Puzzle solver for day 21.
9+
*
10+
* @author <a href="mailto:sven@happycoders.eu">Sven Woltmann</a>
11+
*/
12+
class Day21Solver {
13+
14+
static long solveTask1(String input) {
15+
Map<String, Job> jobMap = JobsParser.parse(input);
16+
Jobs jobs = new Jobs(jobMap);
17+
return jobs.solve();
18+
}
19+
20+
static long solveTask2(String input) {
21+
Map<String, Job> jobMap = JobsParser.parse(input);
22+
Jobs jobs = new Jobs(jobMap);
23+
return jobs.findNumberForHumanIfRootOperandsMustBeEqual();
24+
}
25+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package eu.happycoders.adventofcode2022.day21;
2+
3+
/**
4+
* Advent of Code 2022 – Object-Oriented Solutions in Java.
5+
*
6+
* <p>Abstract base class for a job given to a monkey.
7+
*
8+
* @author <a href="mailto:sven@happycoders.eu">Sven Woltmann</a>
9+
*/
10+
abstract sealed class Job permits NumberJob, MathOperationJob {
11+
private final String monkeyName;
12+
13+
Job(String monkeyName) {
14+
this.monkeyName = monkeyName;
15+
}
16+
17+
String monkeyName() {
18+
return monkeyName;
19+
}
20+
21+
abstract Long result();
22+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package eu.happycoders.adventofcode2022.day21;
2+
3+
import java.util.List;
4+
import java.util.Map;
5+
6+
class Jobs {
7+
8+
private static final String ROOT = "root";
9+
private static final String HUMAN = "humn";
10+
11+
private final List<Job> allJobs;
12+
private final Job rootJob;
13+
private final Job humanJob;
14+
15+
Jobs(Map<String, Job> monkeyNameToJob) {
16+
this.allJobs = List.copyOf(monkeyNameToJob.values());
17+
this.rootJob = monkeyNameToJob.get(ROOT);
18+
this.humanJob = monkeyNameToJob.get(HUMAN);
19+
}
20+
21+
long solve() {
22+
return solve(rootJob);
23+
}
24+
25+
private long solve(Job job) {
26+
return switch (job) {
27+
case NumberJob numberJob -> numberJob.result();
28+
case MathOperationJob mathOperationJob -> solveMathOperationJob(mathOperationJob);
29+
};
30+
}
31+
32+
private long solveMathOperationJob(MathOperationJob mathOperationJob) {
33+
Long cachedResult = mathOperationJob.result();
34+
if (cachedResult != null) {
35+
return cachedResult;
36+
}
37+
38+
long operand1 = solve(mathOperationJob.operand1Job());
39+
long operand2 = solve(mathOperationJob.operand2Job());
40+
41+
long result = mathOperationJob.apply(operand1, operand2);
42+
43+
mathOperationJob.setCachedResult(result);
44+
45+
return result;
46+
}
47+
48+
long findNumberForHumanIfRootOperandsMustBeEqual() {
49+
solve();
50+
removeCachedResultsFromRootToHuman();
51+
return applyReverseOperationsFromRootToHuman();
52+
}
53+
54+
private void removeCachedResultsFromRootToHuman() {
55+
while (true) {
56+
for (Job job : allJobs) {
57+
if (isOperationWithResultAndOneOperandHavingNoResultOrBeingHuman(job)) {
58+
((MathOperationJob) job).setCachedResult(null);
59+
if (job == rootJob) {
60+
return;
61+
}
62+
}
63+
}
64+
}
65+
}
66+
67+
private boolean isOperationWithResultAndOneOperandHavingNoResultOrBeingHuman(Job job) {
68+
return job.result() != null
69+
&& job instanceof MathOperationJob operation
70+
&& (getResultOrNullIfHuman(operation.operand1Job()) == null
71+
|| getResultOrNullIfHuman(operation.operand2Job()) == null);
72+
}
73+
74+
private Long getResultOrNullIfHuman(Job job) {
75+
return job == humanJob ? null : job.result();
76+
}
77+
78+
private long applyReverseOperationsFromRootToHuman() {
79+
return applyReverseOperationsDownToHuman((MathOperationJob) rootJob, 0);
80+
}
81+
82+
private long applyReverseOperationsDownToHuman(MathOperationJob job, long lastValue) {
83+
Long operand1 = getResultOrNullIfHuman(job.operand1Job());
84+
Long operand2 = getResultOrNullIfHuman(job.operand2Job());
85+
86+
// Either go down the left path...
87+
if (operand1 == null && operand2 != null) {
88+
return applyReverseOperationsDownToHumanViaOperand1(job, lastValue, operand2);
89+
}
90+
91+
// ... or the right path
92+
if (operand1 != null && operand2 == null) {
93+
return applyReverseOperationsDownToHumanViaOperand2(job, lastValue, operand1);
94+
}
95+
96+
throw new IllegalStateException();
97+
}
98+
99+
private long applyReverseOperationsDownToHumanViaOperand1(
100+
MathOperationJob job, long lastValue, Long operand2) {
101+
Long operand1;
102+
if (job == rootJob) {
103+
operand1 = operand2;
104+
} else {
105+
operand1 = job.applyReverseToFindOperand1(lastValue, operand2);
106+
}
107+
108+
if (job.operand1Job() == humanJob) {
109+
return operand1;
110+
}
111+
112+
return applyReverseOperationsDownToHuman((MathOperationJob) job.operand1Job(), operand1);
113+
}
114+
115+
private long applyReverseOperationsDownToHumanViaOperand2(
116+
MathOperationJob job, long lastValue, Long operand1) {
117+
Long operand2;
118+
if (job == rootJob) {
119+
operand2 = operand1;
120+
} else {
121+
operand2 = job.applyReverseToFindOperand2(lastValue, operand1);
122+
}
123+
124+
if (job.operand2Job() == humanJob) {
125+
return operand2;
126+
}
127+
128+
return applyReverseOperationsDownToHuman((MathOperationJob) job.operand2Job(), operand2);
129+
}
130+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package eu.happycoders.adventofcode2022.day21;
2+
3+
import java.util.Map;
4+
import java.util.regex.Matcher;
5+
import java.util.regex.Pattern;
6+
import java.util.stream.Collectors;
7+
8+
class JobsParser {
9+
10+
private static final Pattern PATTERN_NUMBER_JOB = Pattern.compile("([a-z]{4}): (\\d+)");
11+
private static final Pattern PATTERN_MATH_OPERATION_JOB =
12+
Pattern.compile("([a-z]{4}): ([a-z]{4}) ([+-/*]) ([a-z]{4})");
13+
14+
static Map<String, Job> parse(String input) {
15+
Map<String, Job> monkeyNameToJob =
16+
input
17+
.lines()
18+
.map(JobsParser::parseJob)
19+
.collect(Collectors.toMap(Job::monkeyName, job -> job));
20+
21+
for (Job value : monkeyNameToJob.values()) {
22+
if (value instanceof MathOperationJob op) {
23+
op.setOperand1Job(monkeyNameToJob.get(op.operand1MonkeyName()));
24+
op.setOperand2Job(monkeyNameToJob.get(op.operand2MonkeyName()));
25+
}
26+
}
27+
28+
return monkeyNameToJob;
29+
}
30+
31+
private static Job parseJob(String input) {
32+
Matcher matcher = PATTERN_NUMBER_JOB.matcher(input);
33+
if (matcher.matches()) {
34+
String monkeyName = matcher.group(1);
35+
int value = Integer.parseInt(matcher.group(2));
36+
return new NumberJob(monkeyName, value);
37+
}
38+
39+
matcher = PATTERN_MATH_OPERATION_JOB.matcher(input);
40+
if (matcher.matches()) {
41+
String monkeyName = matcher.group(1);
42+
String operand1MonkeyName = matcher.group(2);
43+
String operationSymbol = matcher.group(3);
44+
String operand2MonkeyName = matcher.group(4);
45+
46+
return switch (operationSymbol) {
47+
case "+" -> new MathOperationJobAddition(
48+
monkeyName, operand1MonkeyName, operand2MonkeyName);
49+
50+
case "-" -> new MathOperationJobSubtraction(
51+
monkeyName, operand1MonkeyName, operand2MonkeyName);
52+
53+
case "*" -> new MathOperationJobMultiplication(
54+
monkeyName, operand1MonkeyName, operand2MonkeyName);
55+
56+
case "/" -> new MathOperationJobDivision(
57+
monkeyName, operand1MonkeyName, operand2MonkeyName);
58+
59+
default -> throw new IllegalStateException("Unknown symbol: " + operationSymbol);
60+
};
61+
}
62+
63+
throw new IllegalStateException("Don't know how to parse: " + input);
64+
}
65+
}

0 commit comments

Comments
 (0)