Created
December 6, 2023 21:18
-
-
Save maartenpaauw/315e3ec3ab548f06c0510e379229e756 to your computer and use it in GitHub Desktop.
Advent of Code 2023 - Day 5
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Illuminate\Support\Collection; | |
use Illuminate\Support\Stringable; | |
use Illuminate\Support\Str; | |
$input = "seeds: 79 14 55 13 | |
seed-to-soil map: | |
50 98 2 | |
52 50 48 | |
soil-to-fertilizer map: | |
0 15 37 | |
37 52 2 | |
39 0 15 | |
fertilizer-to-water map: | |
49 53 8 | |
0 11 42 | |
42 0 7 | |
57 7 4 | |
water-to-light map: | |
88 18 7 | |
18 25 70 | |
light-to-temperature map: | |
45 77 23 | |
81 45 19 | |
68 64 13 | |
temperature-to-humidity map: | |
0 69 1 | |
1 0 69 | |
humidity-to-location map: | |
60 56 37 | |
56 93 4"; | |
final readonly class Mapper | |
{ | |
private Collection $ranges; | |
/** | |
* @param Collection<array-key, Range> $ranges | |
*/ | |
public function __construct(Collection $ranges) | |
{ | |
$this->ranges = $ranges | |
->sortBy(static fn (Range $range): int => $range->sourceStart()) | |
->values(); | |
} | |
/** | |
* @return Collection<array-key, Seed> | |
*/ | |
public function transform(Seed $seed): Collection | |
{ | |
foreach ($this->ranges->all() as $range) { | |
if ($seed->within($range)) { | |
return Collection::wrap(new Seed( | |
start: $seed->start() + $range->difference(), | |
amount: $seed->amount(), | |
)); | |
} | |
} | |
if ($this->ranges->every(static fn (Range $range): bool => $seed->outside($range))) { | |
return Collection::wrap($seed); | |
} | |
foreach ($this->ranges->all() as $range) { | |
if ($seed->overlapEnd($range)) { | |
$amount = $range->sourceEnd() - $seed->start() + 1; | |
return $this->transform(new Seed( | |
start: $seed->start() + $amount, | |
amount: $seed->amount() - $amount, | |
))->merge([new Seed( | |
start: $seed->start() + $range->difference(), | |
amount: $amount, | |
)]); | |
} else if ($seed->overlapStart($range)) { | |
$amount = $seed->end() - $range->sourceStart() + 1; | |
return $this->transform(new Seed( | |
start: $seed->end() - $seed->amount() + 1, | |
amount: $seed->amount() - $amount, | |
))->merge([new Seed( | |
start: $range->sourceStart(), | |
amount: $amount, | |
)]); | |
} | |
} | |
throw new LogicException(); | |
} | |
} | |
final readonly class Range | |
{ | |
public function __construct( | |
private int $source, | |
private int $destination, | |
private int $length, | |
) { | |
} | |
public function sourceStart(): int | |
{ | |
return $this->source; | |
} | |
public function sourceEnd(): int | |
{ | |
return $this->source + $this->length - 1; | |
} | |
public function difference(): int | |
{ | |
return $this->destination + -$this->sourceStart(); | |
} | |
} | |
final readonly class Seed | |
{ | |
public function __construct( | |
private int $start, | |
private int $amount, | |
) { | |
} | |
public function start(): int | |
{ | |
return $this->start; | |
} | |
public function end(): int | |
{ | |
return $this->start + $this->amount - 1; | |
} | |
public function amount(): int | |
{ | |
return $this->amount; | |
} | |
public function outside(Range $range): bool | |
{ | |
return $this->end() < $range->sourceStart() || $this->start() > $range->sourceEnd(); | |
} | |
public function within(Range $range): bool | |
{ | |
return $this->start() >= $range->sourceStart() && $this->end() <= $range->sourceEnd(); | |
} | |
public function overlapStart(Range $range): bool | |
{ | |
return $this->start() <= $range->sourceStart() && $this->end() >= $range->sourceStart(); | |
} | |
public function overlapEnd(Range $range): bool | |
{ | |
return $this->end() >= $range->sourceEnd() && $this->start() <= $range->sourceEnd(); | |
} | |
} | |
/** @var Collection<array-key, Stringable> $result */ | |
$result = Str::of($input) | |
->split('/\n\n/') | |
->mapInto(Stringable::class); | |
/** @var Collection<array-key, int> $seeds */ | |
$seeds = $result | |
->shift() | |
->matchAll('/\d+/') | |
->map(static fn (string $seed): int => intval($seed)); | |
$mappers = $result | |
->map(static fn (Stringable $notation) => new Mapper( | |
$notation | |
->split('/\n/') | |
->mapInto(Stringable::class) | |
->slice(1) | |
->map(static function (Stringable $range) { | |
return $range | |
->matchAll('/\d+/') | |
->map(static fn (string $number): int => intval($number)); | |
}) | |
->map(static fn (Collection $config) => new Range( | |
$config->get(1), | |
$config->get(0), | |
$config->get(2), | |
)) | |
->sortBy(static fn (Range $range): int => $range->sourceStart()) | |
->values(), | |
)); | |
$singleSeeds = $seeds | |
->map(static fn (int $seed) => new Seed($seed, 1)) | |
->sort(static fn (Seed $seed) => $seed->start()); | |
$part1 = $mappers | |
->reduce(static function (Collection $seeds, Mapper $mapper) { | |
return $seeds | |
->map(static fn (Seed $seed) => $mapper->transform($seed)) | |
->flatten(1); | |
}, $singleSeeds) | |
->map(static fn (Seed $seed): int => $seed->start()) | |
->min(); | |
$seedRanges = $seeds | |
->chunk(2) | |
->map(static fn (Collection $range) => new Seed( | |
$range->first(), | |
$range->last(), | |
)) | |
->sort(static fn (Seed $seed) => $seed->start()); | |
$part2 = $mappers | |
->reduce(static function (Collection $seeds, Mapper $mapper) { | |
return $seeds | |
->map(static fn (Seed $seed) => $mapper->transform($seed)) | |
->flatten(1); | |
}, $seedRanges) | |
->map(static fn (Seed $seed): int => $seed->start()) | |
->min(); | |
$result = [$part1, $part2]; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment