“Oh, we had to stop the water because we ran out of sand to filter it with! Can’t make snow with dirty water. Don’t worry, I’m sure we’ll get more sand soon; we only turned off the water a few days… weeks… oh no.” His face sinks into a look of horrified realization.
Solution in Java
Full source can be found: in GitHub
Part 1
1private record Seed(long seed, long additional, Seed original) {
2 private Seed {
3 if (additional < 0) {
4 throw new IllegalArgumentException("Additional must be positive");
5 }
6 }
7
8 Seed before(long position) {
9 return new Seed(
10 seed,
11 position - seed - 1,
12 this);
13 }
14
15 Seed bounded(long start, long end) {
16 return new Seed(
17 Math.max(seed, start),
18 Math.min(additional, end - seed),
19 this);
20 }
21
22 Seed after(long position) {
23 return new Seed(
24 position + 1,
25 additional - (position - seed) - 1,
26 this);
27 }
28
29 long last() {
30 return seed + additional;
31 }
32}
33
34private record Mutation(long start, long destination, long size) {
35 boolean within(Seed seed) {
36 return (start <= seed.seed())
37 && (start + size >= seed.seed() + seed.additional());
38 }
39
40 boolean overlap(Seed seed) {
41 var endAfterStart = seed.seed() + seed.additional() >= start;
42 var startBeforeEnd = seed.seed() <= start + size;
43
44 return endAfterStart && startBeforeEnd;
45 }
46
47 Seed mutate(Seed seed) {
48 var location = destination + seed.seed() - start;
49 return new Seed(location, seed.additional(), seed);
50 }
51
52 long last() {
53 return start + size;
54 }
55}
With the method parseMutations
to process the input string into a list of Mutation
instances. As well as the parseSeeds
method to process the seed from the input string.
1private List<Mutation> parseMutations(String input) {
2 var mutationInstruction = input.split("\\n");
3
4 var mappingPattern = Pattern.compile("(\\d+)\\s(\\d+)\\s(\\d+)");
5
6 return IntStream.range(1, mutationInstruction.length)
7 .mapToObj(i -> mappingPattern.matcher(mutationInstruction[i]))
8 .filter(Matcher::matches)
9 .map(matcher -> new Mutation(
10 Long.parseLong(matcher.group(2)),
11 Long.parseLong(matcher.group(1)),
12 Long.parseLong(matcher.group(3)) - 1))
13 .toList();
14}
15
16private List<Seed> parseSeeds(String input) {
17 return Arrays.stream(input.substring(7).split("\\s"))
18 .map(Long::parseLong)
19 .map(n -> new Seed(n, 0, null))
20 .toList();
21}
Using the parsed mutations we can then for each of them change the set of Seed
instances. We do this by calling the mutate
method for each seed and every mutation in the mutations.
1public void part1() {
2 var sections = inputLoader.string()
3 .split("\\n\\n");
4
5 var seeds = parseSeeds(sections[0]);
6 for (var mutationSet = 1; mutationSet < sections.length; mutationSet++) {
7 var mutations = parseMutations(sections[mutationSet]);
8
9 seeds = seeds.stream()
10 .flatMap(s -> mutate(s, mutations).stream())
11 .toList();
12 }
13
14 var answer = seeds.stream()
15 .mapToLong(Seed::seed)
16 .min()
17 .getAsLong();
18
19 validator.part1(answer);
20}
The actual method to mutate is described below. This loops over the mutations and then checks if the seed is within the mutation or overlapping. Depending on the state either the mutation is applied on the seed, or the seed is split.
1private List<Seed> mutate(Seed seed, List<Mutation> mutations) {
2 logger.trace("Processing seed {}.", seed);
3
4 for (var mutation : mutations) {
5 if (mutation.within(seed)) {
6 logger.debug("Seed {} within with mutation {}", seed, mutation);
7 return List.of(mutation.mutate(seed));
8 } else if (mutation.overlap(seed)) {
9 logger.debug("Seed {} overlaps with mutation {}", seed, mutation);
10
11 List<Seed> splitSeeds = new ArrayList<>();
12 if (seed.seed() < mutation.start()) {
13 splitSeeds.add(seed.before(mutation.start()));
14 }
15 splitSeeds.add(seed.bounded(mutation.start(), mutation.last()));
16 if (seed.last() > mutation.last()) {
17 splitSeeds.add(seed.after(mutation.last()));
18 }
19
20 logger.debug("Split seeds: {}", splitSeeds);
21 return mutate(splitSeeds, mutations);
22 }
23 }
24
25 return List.of(seed);
26}
27
28private List<Seed> mutate(List<Seed> seeds, List<Mutation> mutations) {
29 return seeds.stream()
30 .flatMap(s -> mutate(s, mutations).stream())
31 .toList();
32}
Part 2
For part 2 of today we directly call the mutate
method with the list of mutations and the list of seeds, rather than the intermediate version per seed.
1public void part2() {
2 var sections = inputLoader.string()
3 .split("\\n\\n");
4
5 var seeds = parseUpdated(sections[0]);
6 for (var mutationSet = 1; mutationSet < sections.length; mutationSet++) {
7 var mutations = parseMutations(sections[mutationSet]);
8
9 seeds = mutate(seeds, mutations);
10 }
11
12 var answer = seeds.stream()
13 .mapToLong(Seed::seed)
14 .min()
15 .getAsLong();
16 validator.part2(answer);
17}