aboutsummaryrefslogtreecommitdiff
path: root/aoc-2022-dotnet/Day19
diff options
context:
space:
mode:
authorTomasz Chojnacki <tomaszchojnacki2001@gmail.com>2023-01-01 22:18:29 +0100
committerTomasz Chojnacki <tomaszchojnacki2001@gmail.com>2023-01-01 22:18:29 +0100
commit806ca36af8773c71c6f4d2ac77fc35ac0aa3b5a0 (patch)
treeb47bd14bb94a615ce7a90f0b9a2a26fbd57c41b6 /aoc-2022-dotnet/Day19
parent353d185a1f89654f999dca7b75016ebef461a23c (diff)
downloadgleam_aoc2020-806ca36af8773c71c6f4d2ac77fc35ac0aa3b5a0.tar.gz
gleam_aoc2020-806ca36af8773c71c6f4d2ac77fc35ac0aa3b5a0.zip
Finish day 19
Diffstat (limited to 'aoc-2022-dotnet/Day19')
-rw-r--r--aoc-2022-dotnet/Day19/Day19.fsproj24
-rw-r--r--aoc-2022-dotnet/Day19/Program.fs148
2 files changed, 172 insertions, 0 deletions
diff --git a/aoc-2022-dotnet/Day19/Day19.fsproj b/aoc-2022-dotnet/Day19/Day19.fsproj
new file mode 100644
index 0000000..d723b37
--- /dev/null
+++ b/aoc-2022-dotnet/Day19/Day19.fsproj
@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>net7.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <Content Include="test.txt">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </Content>
+ <Content Include="input.txt">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </Content>
+ <Compile Include="Program.fs" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Update="FSharp.Core">
+ <TreatAsUsed>true</TreatAsUsed>
+ </PackageReference>
+ </ItemGroup>
+
+</Project>
diff --git a/aoc-2022-dotnet/Day19/Program.fs b/aoc-2022-dotnet/Day19/Program.fs
new file mode 100644
index 0000000..e8770b6
--- /dev/null
+++ b/aoc-2022-dotnet/Day19/Program.fs
@@ -0,0 +1,148 @@
+module Day19
+
+open System
+open System.IO
+open System.Text.RegularExpressions
+
+type Resource =
+ | Ore
+ | Clay
+ | Obsidian
+ | Geode
+
+ static member list = [ Ore; Clay; Obsidian; Geode ]
+
+type State =
+ { Robots: Map<Resource, int>
+ Gathered: Map<Resource, int>
+ TimeLeft: int }
+
+ static member initial time =
+ { Robots =
+ Map [ (Ore, 1)
+ (Clay, 0)
+ (Obsidian, 0)
+ (Geode, 0) ]
+ Gathered =
+ Map [ (Ore, 0)
+ (Clay, 0)
+ (Obsidian, 0)
+ (Geode, 0) ]
+ TimeLeft = time }
+
+ member inline state.optimisticGeodeCount =
+ state.Gathered[Geode]
+ + state.Robots[Geode] * state.TimeLeft
+ + state.TimeLeft * (state.TimeLeft - 1) / 2
+
+ static member inline tick state =
+ { state with
+ Gathered = Map.map (fun resource -> (+) state.Robots[resource]) state.Gathered
+ TimeLeft = state.TimeLeft - 1 }
+
+ member inline state.isImpossibleToBuy =
+ function
+ | Geode -> state.Robots[Obsidian] = 0
+ | Obsidian -> state.Robots[Clay] = 0
+ | _ -> false
+
+type Blueprint =
+ { Id: int
+ RobotCosts: Map<Resource, Map<Resource, int>> }
+
+ static member private regex = Regex(@"\d+", RegexOptions.Compiled)
+
+ static member parse input =
+ Blueprint.regex.Matches(input)
+ |> Seq.map (fun kv -> int kv.Value)
+ |> List.ofSeq
+ |> function
+ | [ id; oreOreCost; clayOreCost; obsidianOreCost; obsidianClayCost; geodeOreCost; geodeObsidianCost ] ->
+ { Id = id
+ RobotCosts =
+ Map [ (Ore,
+ Map [ (Ore, oreOreCost)
+ (Clay, 0)
+ (Obsidian, 0)
+ (Geode, 0) ])
+ (Clay,
+ Map [ (Ore, clayOreCost)
+ (Clay, 0)
+ (Obsidian, 0)
+ (Geode, 0) ])
+ (Obsidian,
+ Map [ (Ore, obsidianOreCost)
+ (Clay, obsidianClayCost)
+ (Obsidian, 0)
+ (Geode, 0) ])
+ (Geode,
+ Map [ (Ore, geodeOreCost)
+ (Clay, 0)
+ (Obsidian, geodeObsidianCost)
+ (Geode, 0) ]) ] }
+ | _ -> failwith "Invalid blueprint format!"
+
+ member inline private blueprint.canBuyRobot robotResource state =
+ Map.forall (fun resource -> (>=) state.Gathered[resource]) blueprint.RobotCosts[robotResource]
+
+ member inline private blueprint.buyRobot robotResource state =
+ { state with
+ Robots = state.Robots.Change(robotResource, Option.map <| (+) 1)
+ Gathered =
+ state.Gathered
+ |> Map.map (fun costResource previousCount ->
+ previousCount
+ - blueprint.RobotCosts[robotResource][costResource]) }
+
+ static member private robotCaps blueprint =
+ Resource.list
+ |> List.map (fun costResource ->
+ costResource,
+ Resource.list
+ |> List.map (fun robotResource -> blueprint.RobotCosts[robotResource][costResource])
+ |> List.max)
+ |> Map.ofList
+ |> Map.add Geode Int32.MaxValue
+
+ static member evaluate time blueprint =
+ let robotCaps = Blueprint.robotCaps blueprint
+
+ let rec solve bestScore robotGoal state =
+ if state.TimeLeft <= 0 then
+ state.Gathered[Geode]
+ elif state.Robots[robotGoal] >= robotCaps[robotGoal]
+ || state.isImpossibleToBuy robotGoal
+ || state.optimisticGeodeCount <= bestScore then
+ bestScore
+ elif blueprint.canBuyRobot robotGoal state then
+ let state =
+ state
+ |> State.tick
+ |> blueprint.buyRobot robotGoal
+
+ List.fold (fun bestScore nextRobotGoal -> solve bestScore nextRobotGoal state) bestScore Resource.list
+ else
+ state |> State.tick |> solve bestScore robotGoal
+
+ List.fold (fun bestScore robotGoal -> solve bestScore robotGoal (State.initial time)) 0 Resource.list
+
+ static member inline qualityLevel blueprint =
+ blueprint.Id * Blueprint.evaluate 24 blueprint
+
+let solution1 =
+ Seq.map Blueprint.parse
+ >> Seq.sumBy Blueprint.qualityLevel
+
+let solution2 =
+ Seq.map Blueprint.parse
+ >> Seq.truncate 3
+ >> Seq.map (Blueprint.evaluate 32)
+ >> Seq.reduce (*)
+
+let test = File.ReadLines("test.txt")
+assert (solution1 test = 33)
+assert (solution2 test = 56 * 62)
+
+let input = File.ReadLines("input.txt")
+printfn "%d" <| solution1 input
+printfn "%d" <| solution2 input