From dbee715e231a1aabcf105557e52855a23a964207 Mon Sep 17 00:00:00 2001 From: Andreas Grois Date: Wed, 6 Dec 2023 19:20:03 +0100 Subject: Day5, part 1 --- Day5.lean | 271 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 Day5.lean (limited to 'Day5.lean') diff --git a/Day5.lean b/Day5.lean new file mode 100644 index 0000000..18f9aeb --- /dev/null +++ b/Day5.lean @@ -0,0 +1,271 @@ +import Common.DayPart +import Common.List + +namespace Day5 + +structure Seed where + id : Nat + deriving BEq, Ord, Repr + +structure Soil where + id : Nat + deriving BEq, Ord, Repr + +structure Fertilizer where + id : Nat + deriving BEq, Ord, Repr + +structure Water where + id : Nat + deriving BEq, Ord, Repr + +structure Light where + id : Nat + deriving BEq, Ord, Repr + +structure Temperature where + id : Nat + deriving BEq, Ord, Repr + +structure Humidity where + id : Nat + deriving BEq, Ord, Repr + +structure Location where + id : Nat + deriving BEq, Ord, Repr + +private class NatId (α : Type) where + fromNat : Nat → α + toNat : α → Nat + +private instance : NatId Seed where + fromNat := Seed.mk + toNat := Seed.id + +private instance : NatId Soil where + fromNat := Soil.mk + toNat := Soil.id + +private instance : NatId Fertilizer where + fromNat := Fertilizer.mk + toNat := Fertilizer.id + +private instance : NatId Water where + fromNat := Water.mk + toNat := Water.id + +private instance : NatId Light where + fromNat := Light.mk + toNat := Light.id + +private instance : NatId Temperature where + fromNat := Temperature.mk + toNat := Temperature.id + +private instance : NatId Humidity where + fromNat := Humidity.mk + toNat := Humidity.id + +private instance : NatId Location where + fromNat := Location.mk + toNat := Location.id + +private instance : Min Location where + min a b := if Ord.compare a b == Ordering.lt then a else b + +structure Mapping (α β : Type) where + inputStart : α + outputStart : β + length : Nat + deriving Repr + +structure Mappings (α β : Type) where + mappings : List $ Mapping α β + deriving Repr + +private def Mapping.apply? {α β : Type} [NatId α] [NatId β] (mapping : Mapping α β) (input : α) : Option β := + let input := NatId.toNat input + let fromStart := NatId.toNat mapping.inputStart + let toStart := NatId.toNat mapping.outputStart + if input ≥ fromStart ∧ input < fromStart + mapping.length then + some $ NatId.fromNat $ toStart + input - fromStart + else + none + +private def Mappings.apply {α β : Type} [NatId α] [NatId β] (mappings : Mappings α β) (input : α) : β := + let applied := mappings.mappings.findSome? $ flip Mapping.apply? input + applied.getD $ NatId.fromNat $ NatId.toNat input + +structure Almanach where + seedsToSoil : Mappings Seed Soil + soilToFertilizer : Mappings Soil Fertilizer + fertilizerToWater : Mappings Fertilizer Water + waterToLight : Mappings Water Light + lightToTemperature : Mappings Light Temperature + temperatureToHumidity : Mappings Temperature Humidity + humidityToLocation : Mappings Humidity Location + deriving Repr + +private def parseSeeds (input : String) : Option (List Seed) := + if input.startsWith "seeds: " then + let input := input.drop 7 + let input := String.trim <$> input.split Char.isWhitespace + let numbers := input.mapM String.toNat? + List.map NatId.fromNat <$> numbers + else + none + +private def parseMapping {α β : Type} [NatId α] [NatId β] (input : String) : Option $ Mapping α β := do + let input := String.trim <$> input.split Char.isWhitespace + let nums ← input.mapM String.toNat? + match nums with + | [a,b,c] => some $ {inputStart := NatId.fromNat b, outputStart := NatId.fromNat a, length := c} + | _ => none + +private def parseMappings (α β : Type) [NatId α] [NatId β] (input : String) (header : String) : Option $ Mappings α β := + if input.startsWith header then + let lines := String.trim <$> input.splitOn "\n" |> List.drop 1 |> List.filter (not ∘ String.isEmpty) + let mappings := lines.mapM parseMapping + Mappings.mk <$> mappings + else + none + +def parse (input : String) : Option ((List Seed) × Almanach) := do + let blocks := input.splitOn "\n\n" |> List.filter (not ∘ String.isEmpty) + let blocks := String.trim <$> blocks + if let [seeds, seedToSoil, soilToFertilizer, fertilizerToWater, waterToLight, lightToTemperature, temperatureToHumidity, humidityToLocation] := blocks then + let seeds ← parseSeeds seeds + let seedToSoil ← parseMappings Seed Soil seedToSoil "seed-to-soil map:" + let soilToFertilizer ← parseMappings Soil Fertilizer soilToFertilizer "soil-to-fertilizer map:" + let fertilizerToWater ← parseMappings Fertilizer Water fertilizerToWater "fertilizer-to-water map:" + let waterToLight ← parseMappings Water Light waterToLight "water-to-light map:" + let lightToTemperature ← parseMappings Light Temperature lightToTemperature "light-to-temperature map:" + let temperatureToHumidity ← parseMappings Temperature Humidity temperatureToHumidity "temperature-to-humidity map:" + let humidityToLocation ← parseMappings Humidity Location humidityToLocation "humidity-to-location map:" + (seeds, { + seedsToSoil := seedToSoil + soilToFertilizer := soilToFertilizer + fertilizerToWater := fertilizerToWater + waterToLight := waterToLight + lightToTemperature := lightToTemperature + temperatureToHumidity := temperatureToHumidity + humidityToLocation := humidityToLocation + : Almanach}) + else + none + +def part1 (input : ((List Seed) × Almanach)) : Option Nat := + let a := input.snd + let seedToLocation := a.humidityToLocation.apply + ∘ a.temperatureToHumidity.apply + ∘ a.lightToTemperature.apply + ∘ a.waterToLight.apply + ∘ a.fertilizerToWater.apply + ∘ a.soilToFertilizer.apply + ∘ a.seedsToSoil.apply + let locations := input.fst.map seedToLocation + NatId.toNat <$> locations.minimum? + + +-- Part 2 seems unmanageable by brute force. +-- So, let's rather combine the mappings in a smart way, and then invert them. +-- Then we can do binary search if any seed is in the domain of the combined mapping. +-- if not, the answer is just the smallest seed. + +private def Mappings.combine {α β γ : Type} [NatId α] [NatId β] [NatId γ] (a : Mappings α β) (b : Mappings β γ) : Mappings α γ := + -- the combined mapping contains all ranges in b that aren't "shielded" by ranges of a. + -- then it contains all ranges of a, but their codomain needs to be checked if it overlaps with + -- any ranges in b, and if yes, those ranges need to be split accordingly. + let domain := λ {δ ε : Type} [NatId δ] [NatId ε] (e :Mapping δ ε) ↦ (NatId.toNat e.inputStart, NatId.toNat e.inputStart + e.length) + let codomain := λ {δ ε : Type} [NatId δ] [NatId ε] (e :Mapping δ ε) ↦ (NatId.toNat e.outputStart, NatId.toNat e.outputStart + e.length) + let unshieldedB : List (Mapping α γ) := b.mappings.bind λ bm ↦ + let bmd := domain bm + let bmcd := codomain bm + let shielded := a.mappings.filterMap λ am ↦ + let amd := domain am + let s := Nat.max amd.fst bmd.fst + let e := Nat.min amd.snd bmd.snd + if e > s then some (s,e) else none + -- add a range before and after to shielded, then we can take those between + -- we anyhow need to sort shielded, sooo + let shielded := (bmd.fst, bmd.fst) :: (bmd.snd, bmd.snd) :: shielded + let shielded := shielded.quicksortBy (·.fst < ·.fst) + let rs := shielded.zip (shielded.drop 1) + let rs : List (Nat × Nat):= rs.map λ x ↦ (x.fst.snd, x.snd.fst) + let rs := rs.filter λ l ↦ l.snd > l.fst -- skip emtpy, they are just noise + rs.map λ (x : Nat × Nat) ↦ + let is : α := NatId.fromNat x.fst + let os : γ := NatId.fromNat $ bmcd.fst + x.fst - bmd.fst + let l : Nat := (x.snd - x.fst) + { inputStart := is, outputStart := os, length := l} + --TODO: Unify the onlyAffectedByA and affectedByBoth computations. + let onlyAffectedByA : List (Mapping α γ) := a.mappings.bind λ am ↦ + let amd := domain am + let amcd := codomain am + let overlapping := b.mappings.filterMap λ bm ↦ + let bmd := domain bm + let s := Nat.max amcd.fst bmd.fst + let e := Nat.min amcd.snd bmd.snd + if e > s then some (s,e) else none + -- overlapping is in the **codomain** of a. Transform to domain: + let overlapping := overlapping.map λ p ↦ (p.fst + amd.fst - amcd.fst, p.snd + amd.fst - amcd.fst) + let overlapping := (amd.fst, amd.fst) :: (amd.snd, amd.snd) :: overlapping + let overlapping := overlapping.quicksortBy (·.fst < ·.fst) + let rs := overlapping.zip (overlapping.drop 1) + let rs : List (Nat × Nat):= rs.map λ x ↦ (x.fst.snd, x.snd.fst) + let rs := rs.filter λ l ↦ l.snd > l.fst + rs.map λ (x : Nat × Nat) ↦ + let is : α := NatId.fromNat x.fst + let os : γ := NatId.fromNat $ amcd.fst + x.fst - amd.fst + let l : Nat := (x.snd - x.fst) + { inputStart := is, outputStart := os, length := l} + let affectedByBoth : List (Mapping α γ) := sorry + sorry + + +open DayPart +instance : Parse ⟨5, by simp⟩ (ι := (List Seed) × Almanach) where + parse := parse + +instance : Part ⟨5, _⟩ Parts.One (ι := (List Seed) × Almanach) (ρ := Nat) where + run := part1 + + +private def TestInput := "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 +" + +#eval parse TestInput +#eval parse TestInput >>= part1 -- cgit v1.2.3