mirror of
https://github.com/Smaug123/AdventOfCode2022
synced 2025-10-10 03:58:41 +00:00
Day 16 (#26)
This commit is contained in:
15
.github/workflows/dotnet.yaml
vendored
15
.github/workflows/dotnet.yaml
vendored
@@ -33,8 +33,17 @@ jobs:
|
||||
run: dotnet build --no-restore --configuration ${{matrix.config}}
|
||||
- name: Test
|
||||
run: dotnet test --no-build --verbosity normal --configuration ${{matrix.config}}
|
||||
- name: Run app
|
||||
run: dotnet run --project AdventOfCode2022.App/AdventOfCode2022.App.fsproj --no-build --configuration ${{matrix.config}}
|
||||
|
||||
run-app:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
- name: Run app
|
||||
run: dotnet run --project AdventOfCode2022.App/AdventOfCode2022.App.fsproj --configuration Release
|
||||
|
||||
check-format:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -67,7 +76,7 @@ jobs:
|
||||
name: Run link checker
|
||||
|
||||
all-required-checks-complete:
|
||||
needs: [check-format, build, shellcheck, linkcheck]
|
||||
needs: [check-format, build, shellcheck, linkcheck, run-app]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "All required checks complete."
|
||||
|
@@ -25,6 +25,7 @@
|
||||
<EmbeddedResource Include="..\AdventOfCode2022.Test\Inputs\Day13.txt" />
|
||||
<EmbeddedResource Include="..\AdventOfCode2022.Test\Inputs\Day14.txt" />
|
||||
<EmbeddedResource Include="..\AdventOfCode2022.Test\Inputs\Day15.txt" />
|
||||
<EmbeddedResource Include="..\AdventOfCode2022.Test\Inputs\Day16.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -2,5 +2,5 @@ namespace AdventOfCode2022.App
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Inputs =
|
||||
let days = Array.init 15 (fun day -> Assembly.readResource $"Day%i{day + 1}.txt")
|
||||
let days = Array.init 16 (fun day -> Assembly.readResource $"Day%i{day + 1}.txt")
|
||||
let inline day (i : int) = days.[i - 1]
|
||||
|
@@ -57,6 +57,22 @@ type Benchmark11To15 () =
|
||||
[<GlobalCleanup>]
|
||||
member _.Cleanup () = Run.shouldWrite <- true
|
||||
|
||||
type Benchmark16To20 () =
|
||||
[<GlobalSetup>]
|
||||
member _.Setup () = Run.shouldWrite <- false
|
||||
|
||||
[<Params(16)>]
|
||||
member val Day = 0 with get, set
|
||||
|
||||
[<Params(false, true)>]
|
||||
member val IsPartOne = false with get, set
|
||||
|
||||
[<Benchmark>]
|
||||
member this.Benchmark () : unit =
|
||||
Run.allRuns.[this.Day - 1] (not this.IsPartOne) (Inputs.day this.Day)
|
||||
|
||||
[<GlobalCleanup>]
|
||||
member _.Cleanup () = Run.shouldWrite <- true
|
||||
|
||||
module Program =
|
||||
|
||||
@@ -72,6 +88,7 @@ module Program =
|
||||
let _summary = BenchmarkRunner.Run<Benchmark1To5> config
|
||||
let _summary = BenchmarkRunner.Run<Benchmark6To10> config
|
||||
let _summary = BenchmarkRunner.Run<Benchmark11To15> config
|
||||
let _summary = BenchmarkRunner.Run<Benchmark16To20> config
|
||||
0
|
||||
| _ ->
|
||||
|
||||
|
@@ -218,6 +218,20 @@ module Run =
|
||||
if shouldWrite then
|
||||
printfn "%i" output
|
||||
|
||||
let day16 (partTwo : bool) (input : string) =
|
||||
let day16 = input.Split '\n'
|
||||
|
||||
if not partTwo then
|
||||
let output = Day16.part1 day16
|
||||
|
||||
if shouldWrite then
|
||||
printfn "%i" output
|
||||
else
|
||||
let output = Day16.part2 day16
|
||||
|
||||
if shouldWrite then
|
||||
printfn "%i" output
|
||||
|
||||
let allRuns =
|
||||
[|
|
||||
day1
|
||||
@@ -235,4 +249,5 @@ module Run =
|
||||
day13
|
||||
day14
|
||||
day15
|
||||
day16
|
||||
|]
|
||||
|
@@ -23,6 +23,7 @@
|
||||
<Compile Include="Day13.fs" />
|
||||
<Compile Include="Day14.fs" />
|
||||
<Compile Include="Day15.fs" />
|
||||
<Compile Include="Day16.fs" />
|
||||
<EmbeddedResource Include="Inputs\Day1.txt" />
|
||||
<EmbeddedResource Include="Inputs\Day2.txt" />
|
||||
<EmbeddedResource Include="Inputs\Day3.txt" />
|
||||
@@ -38,6 +39,7 @@
|
||||
<EmbeddedResource Include="Inputs\Day13.txt" />
|
||||
<EmbeddedResource Include="Inputs\Day14.txt" />
|
||||
<EmbeddedResource Include="Inputs\Day15.txt" />
|
||||
<EmbeddedResource Include="Inputs\Day16.txt" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
49
AdventOfCode2022.Test/Day16.fs
Normal file
49
AdventOfCode2022.Test/Day16.fs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace AdventOfCode2022.Test
|
||||
|
||||
open NUnit.Framework
|
||||
open FsUnitTyped
|
||||
open AdventOfCode2022
|
||||
|
||||
[<TestFixture>]
|
||||
module TestDay16 =
|
||||
|
||||
let input =
|
||||
"""Valve AA has flow rate=0; tunnels lead to valves DD, II, BB
|
||||
Valve BB has flow rate=13; tunnels lead to valves CC, AA
|
||||
Valve CC has flow rate=2; tunnels lead to valves DD, BB
|
||||
Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE
|
||||
Valve EE has flow rate=3; tunnels lead to valves FF, DD
|
||||
Valve FF has flow rate=0; tunnels lead to valves EE, GG
|
||||
Valve GG has flow rate=0; tunnels lead to valves FF, HH
|
||||
Valve HH has flow rate=22; tunnel leads to valve GG
|
||||
Valve II has flow rate=0; tunnels lead to valves AA, JJ
|
||||
Valve JJ has flow rate=21; tunnel leads to valve II
|
||||
"""
|
||||
|
||||
[<Test>]
|
||||
let ``seq behaviour`` () =
|
||||
Day16.ofSeq [ 1 ; 2 ; 3 ; 16 ]
|
||||
|> Day16.toSeq
|
||||
|> List.ofSeq
|
||||
|> shouldEqual [ 1 ; 2 ; 3 ; 16 ]
|
||||
|
||||
[<Test>]
|
||||
let ``Part 1, given`` () =
|
||||
Day16.part1 (input.Split '\n') |> shouldEqual 1651
|
||||
|
||||
[<Test>]
|
||||
let ``Part 1`` () =
|
||||
let input = Assembly.readResource "Day16.txt"
|
||||
|
||||
Day16.part1 (input.Split '\n') |> shouldEqual 1751
|
||||
|
||||
|
||||
[<Test>]
|
||||
let ``Part 2, given`` () =
|
||||
Day16.part2 (input.Split '\n') |> shouldEqual 1707
|
||||
|
||||
[<Test>]
|
||||
let ``Part 2`` () =
|
||||
let input = Assembly.readResource "Day16.txt"
|
||||
|
||||
Day16.part2 (input.Split '\n') |> shouldEqual 2207
|
60
AdventOfCode2022.Test/Inputs/Day16.txt
Normal file
60
AdventOfCode2022.Test/Inputs/Day16.txt
Normal file
@@ -0,0 +1,60 @@
|
||||
Valve QP has flow rate=0; tunnels lead to valves IS, DG
|
||||
Valve MC has flow rate=0; tunnels lead to valves XX, QQ
|
||||
Valve OT has flow rate=7; tunnels lead to valves OE, BL, DJ, JS, LS
|
||||
Valve CZ has flow rate=0; tunnels lead to valves IC, ZL
|
||||
Valve GI has flow rate=0; tunnels lead to valves OM, GF
|
||||
Valve YB has flow rate=0; tunnels lead to valves DQ, MX
|
||||
Valve EJ has flow rate=0; tunnels lead to valves GB, ES
|
||||
Valve IS has flow rate=19; tunnels lead to valves AS, OB, QP
|
||||
Valve WI has flow rate=21; tunnels lead to valves SS, AK
|
||||
Valve JS has flow rate=0; tunnels lead to valves OT, HV
|
||||
Valve UR has flow rate=0; tunnels lead to valves OM, ZI
|
||||
Valve UC has flow rate=0; tunnels lead to valves QX, NG
|
||||
Valve BL has flow rate=0; tunnels lead to valves YW, OT
|
||||
Valve AK has flow rate=0; tunnels lead to valves WI, AL
|
||||
Valve QQ has flow rate=16; tunnels lead to valves MC, WH, MS, IY
|
||||
Valve PW has flow rate=0; tunnels lead to valves ZL, EK
|
||||
Valve AS has flow rate=0; tunnels lead to valves IS, MS
|
||||
Valve ZL has flow rate=9; tunnels lead to valves CD, QX, PW, CZ, PQ
|
||||
Valve OB has flow rate=0; tunnels lead to valves HS, IS
|
||||
Valve OE has flow rate=0; tunnels lead to valves IC, OT
|
||||
Valve AL has flow rate=0; tunnels lead to valves VX, AK
|
||||
Valve AM has flow rate=0; tunnels lead to valves OM, YW
|
||||
Valve QX has flow rate=0; tunnels lead to valves UC, ZL
|
||||
Valve DJ has flow rate=0; tunnels lead to valves OT, ST
|
||||
Valve ZI has flow rate=0; tunnels lead to valves VX, UR
|
||||
Valve PQ has flow rate=0; tunnels lead to valves ZL, YW
|
||||
Valve OM has flow rate=22; tunnels lead to valves GI, AM, EK, UR
|
||||
Valve NG has flow rate=13; tunnels lead to valves UC, HS, GF
|
||||
Valve AA has flow rate=0; tunnels lead to valves UJ, ES, JP, HY, ST
|
||||
Valve HY has flow rate=0; tunnels lead to valves GZ, AA
|
||||
Valve MS has flow rate=0; tunnels lead to valves AS, QQ
|
||||
Valve JK has flow rate=0; tunnels lead to valves YW, GB
|
||||
Valve JP has flow rate=0; tunnels lead to valves AA, PF
|
||||
Valve ST has flow rate=0; tunnels lead to valves AA, DJ
|
||||
Valve CD has flow rate=0; tunnels lead to valves SS, ZL
|
||||
Valve ES has flow rate=0; tunnels lead to valves EJ, AA
|
||||
Valve PF has flow rate=0; tunnels lead to valves JP, HV
|
||||
Valve RL has flow rate=0; tunnels lead to valves GB, IC
|
||||
Valve IY has flow rate=0; tunnels lead to valves QQ, SN
|
||||
Valve UJ has flow rate=0; tunnels lead to valves IC, AA
|
||||
Valve HS has flow rate=0; tunnels lead to valves NG, OB
|
||||
Valve WH has flow rate=0; tunnels lead to valves QQ, MX
|
||||
Valve YA has flow rate=0; tunnels lead to valves GB, HV
|
||||
Valve SN has flow rate=0; tunnels lead to valves IY, DG
|
||||
Valve GF has flow rate=0; tunnels lead to valves GI, NG
|
||||
Valve YW has flow rate=8; tunnels lead to valves GZ, JK, BL, PQ, AM
|
||||
Valve DG has flow rate=17; tunnels lead to valves QP, SN
|
||||
Valve MX has flow rate=11; tunnels lead to valves WH, YB
|
||||
Valve DQ has flow rate=0; tunnels lead to valves YB, HV
|
||||
Valve SS has flow rate=0; tunnels lead to valves CD, WI
|
||||
Valve HV has flow rate=4; tunnels lead to valves YA, DQ, TO, JS, PF
|
||||
Valve GB has flow rate=6; tunnels lead to valves LS, RL, JK, EJ, YA
|
||||
Valve EK has flow rate=0; tunnels lead to valves OM, PW
|
||||
Valve LS has flow rate=0; tunnels lead to valves GB, OT
|
||||
Valve IC has flow rate=5; tunnels lead to valves CZ, OE, UJ, TO, RL
|
||||
Valve XX has flow rate=0; tunnels lead to valves MC, FM
|
||||
Valve VX has flow rate=25; tunnels lead to valves ZI, AL
|
||||
Valve GZ has flow rate=0; tunnels lead to valves HY, YW
|
||||
Valve FM has flow rate=20; tunnel leads to valve XX
|
||||
Valve TO has flow rate=0; tunnels lead to valves IC, HV
|
@@ -25,6 +25,11 @@
|
||||
<Compile Include="Day13.fs" />
|
||||
<Compile Include="Day14.fs" />
|
||||
<Compile Include="Day15.fs" />
|
||||
<Compile Include="Day16.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FSharp.Collections.ParallelSeq" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -61,17 +61,6 @@ module Day15 =
|
||||
|
||||
let inline manhattan (p1 : Coordinate) (p2 : Coordinate) : int = abs (p1.X - p2.X) + abs (p1.Y - p2.Y)
|
||||
|
||||
let inline cantorBijection (x : int) (y : int) : int64 =
|
||||
let sum = x + y
|
||||
|
||||
let major =
|
||||
if sum % 2 = 0 then
|
||||
int64 (sum / 2) * int64 (sum + 1)
|
||||
else
|
||||
int64 sum * int64 ((sum + 1) / 2)
|
||||
|
||||
major + int64 y
|
||||
|
||||
let inline couldBeBeacon
|
||||
(sensors : ResizeArray<Coordinate>)
|
||||
(closestManhattans : int[])
|
||||
@@ -163,14 +152,14 @@ module Day15 =
|
||||
|
||||
let mutable xIndex = 0
|
||||
|
||||
while answer = -1L && xIndex < sensorXCoords.Length - 1 do
|
||||
while answer <> 1L && xIndex < sensorXCoords.Length - 1 do
|
||||
let xMin = sensorXCoords.[xIndex]
|
||||
xIndex <- xIndex + 1
|
||||
let xMax = sensorXCoords.[xIndex]
|
||||
|
||||
let mutable yIndex = 0
|
||||
|
||||
while answer = -1L && yIndex < sensorYCoords.Length - 1 do
|
||||
while answer <> 1L && yIndex < sensorYCoords.Length - 1 do
|
||||
let yMin = sensorYCoords.[yIndex]
|
||||
yIndex <- yIndex + 1
|
||||
let yMax = sensorYCoords.[yIndex]
|
||||
@@ -195,6 +184,42 @@ module Day15 =
|
||||
else
|
||||
plusXPlusYConstraint <- min plusXPlusYConstraint (-manhattan + sensor.X + sensor.Y - 1)
|
||||
|
||||
let yMax =
|
||||
if
|
||||
plusXPlusYConstraint <> Int32.MaxValue
|
||||
&& plusXMinusYConstraint <> Int32.MaxValue
|
||||
then
|
||||
min ((plusXPlusYConstraint - plusXMinusYConstraint) / 2) yMax
|
||||
else
|
||||
yMax
|
||||
|
||||
let xMax =
|
||||
if
|
||||
plusXPlusYConstraint <> Int32.MaxValue
|
||||
&& minusXPlusYConstraint <> Int32.MaxValue
|
||||
then
|
||||
min ((plusXPlusYConstraint - minusXPlusYConstraint) / 2) xMax
|
||||
else
|
||||
xMax
|
||||
|
||||
let xMin =
|
||||
if
|
||||
minusXMinusYConstraint <> Int32.MaxValue
|
||||
&& minusXPlusYConstraint <> Int32.MaxValue
|
||||
then
|
||||
max xMin (-(minusXMinusYConstraint + minusXPlusYConstraint) / 2)
|
||||
else
|
||||
xMin
|
||||
|
||||
let yMin =
|
||||
if
|
||||
minusXMinusYConstraint <> Int32.MaxValue
|
||||
&& plusXMinusYConstraint <> Int32.MaxValue
|
||||
then
|
||||
max yMin (-(minusXMinusYConstraint + plusXMinusYConstraint) / 2)
|
||||
else
|
||||
yMin
|
||||
|
||||
// (fst constraints).{x, y} <= (snd constraints), and also xMin <= x <= xMax and
|
||||
// yMin <= y <= yMax.
|
||||
let mutable falsified = false
|
||||
@@ -221,32 +246,6 @@ module Day15 =
|
||||
falsified <- true
|
||||
|
||||
if not falsified then
|
||||
// The most likely way for there to be no slack is if equality holds throughout both conjugate pairs
|
||||
// of constraints.
|
||||
// (It's also possible for there to be no slack by having one of the "x <= 33" constraints binding.
|
||||
// See below for the treatment of this case.)
|
||||
if
|
||||
minusXMinusYConstraint = -plusXPlusYConstraint
|
||||
&& plusXMinusYConstraint = -minusXPlusYConstraint
|
||||
then
|
||||
let y = (plusXPlusYConstraint - plusXMinusYConstraint) / 2
|
||||
let x = (plusXPlusYConstraint - minusXPlusYConstraint) / 2
|
||||
|
||||
if xMin <= x && x <= xMax && yMin <= y && y <= yMax then
|
||||
answer <- int64 x * 4000000L + int64 y
|
||||
|
||||
if answer = -1L then
|
||||
// In fact one of the xMax/xMin or yMax/yMin constraints binds.
|
||||
let mutable xIndex = 0
|
||||
|
||||
while answer = -1 && xIndex < sensorXCoords.Length do
|
||||
xIndex <- xIndex + 1
|
||||
// Try with this x.
|
||||
let _x = sensorXCoords.Length
|
||||
|
||||
for _sensor in sensors do
|
||||
// Construct the necessary inequalities, then solve them.
|
||||
failwith "I couldn't be bothered to implement this"
|
||||
|
||||
answer <- int64 xMax * 4000000L + int64 yMax
|
||||
|
||||
answer
|
||||
|
429
AdventOfCode2022/Day16.fs
Normal file
429
AdventOfCode2022/Day16.fs
Normal file
@@ -0,0 +1,429 @@
|
||||
namespace AdventOfCode2022
|
||||
|
||||
open System
|
||||
open FSharp.Collections.ParallelSeq
|
||||
|
||||
#if DEBUG
|
||||
open Checked
|
||||
#else
|
||||
#nowarn "9"
|
||||
#endif
|
||||
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Day16 =
|
||||
|
||||
type Node = int
|
||||
|
||||
/// Returns the nodes, and also the "AA" node.
|
||||
let parse (lines : string seq) : Map<Node, int * Node Set> * Node =
|
||||
let allNodes =
|
||||
lines
|
||||
|> Seq.filter (not << String.IsNullOrEmpty)
|
||||
|> Seq.mapi (fun i line ->
|
||||
let split = line.Split "tunne" |> Array.last |> (fun s -> s.Trim ())
|
||||
let split = split.Split(' ').[4..] |> Array.map (fun s -> s.Trim ',') |> Set.ofArray
|
||||
line.[6..7], (i, (Int32.Parse line.[line.IndexOf '=' + 1 .. line.IndexOf ';' - 1], split))
|
||||
)
|
||||
|> Map.ofSeq
|
||||
|
||||
let result =
|
||||
allNodes
|
||||
|> Map.toSeq
|
||||
|> Seq.map (fun (key, (node, (weight, outbound))) ->
|
||||
node, (weight, (Set.map (fun x -> fst (Map.find x allNodes)) outbound))
|
||||
)
|
||||
|> Map.ofSeq
|
||||
|
||||
result, fst allNodes.["AA"]
|
||||
|
||||
let inline tryMax< ^a when ^a : comparison> (s : seq<'a>) : 'a option =
|
||||
use enum = s.GetEnumerator ()
|
||||
|
||||
if not (enum.MoveNext ()) then
|
||||
None
|
||||
else
|
||||
let mutable answer = enum.Current
|
||||
|
||||
while enum.MoveNext () do
|
||||
answer <- max answer enum.Current
|
||||
|
||||
Some answer
|
||||
|
||||
let tryMin (s : seq<'a>) : 'a option =
|
||||
use enum = s.GetEnumerator ()
|
||||
|
||||
if not (enum.MoveNext ()) then
|
||||
None
|
||||
else
|
||||
let mutable answer = enum.Current
|
||||
|
||||
while enum.MoveNext () do
|
||||
answer <- min answer enum.Current
|
||||
|
||||
Some answer
|
||||
|
||||
let getShortestPathLength (valves : Map<_, _>) : Node -> Node -> int =
|
||||
let rec go (seenSoFar : Node Set) (v1 : Node) (v2 : Node) =
|
||||
let v2Neighbours = snd valves.[v2]
|
||||
|
||||
if v1 = v2 then
|
||||
Some 0
|
||||
elif Set.contains v1 v2Neighbours then
|
||||
Some 1
|
||||
elif Set.contains v2 seenSoFar then
|
||||
None
|
||||
else
|
||||
v2Neighbours
|
||||
|> Seq.choose (go (Set.add v2 seenSoFar) v1)
|
||||
|> tryMin
|
||||
|> Option.map ((+) 1)
|
||||
|
||||
fun v1 v2 -> go Set.empty v1 v2 |> Option.get
|
||||
|
||||
|
||||
type NodeSet = int64
|
||||
|
||||
let inline setNode (set : NodeSet) (nodeId : int) : NodeSet = set ||| (1L <<< nodeId)
|
||||
let inline getNode (set : NodeSet) (nodeId : int) : bool = set &&& (1L <<< nodeId) <> 0
|
||||
let ofSeq (nodes : Node seq) : NodeSet = (0L, nodes) ||> Seq.fold setNode
|
||||
|
||||
let toSeq (nodes : NodeSet) : Node seq =
|
||||
seq {
|
||||
let mutable nodes = nodes
|
||||
let mutable count = 0
|
||||
|
||||
while nodes > 0 do
|
||||
if nodes % 2L = 1L then
|
||||
yield count
|
||||
|
||||
nodes <- nodes >>> 1
|
||||
count <- count + 1
|
||||
}
|
||||
|
||||
let count (nodes : NodeSet) : int =
|
||||
let mutable nodes = nodes
|
||||
let mutable ans = 0
|
||||
|
||||
while nodes > 0 do
|
||||
if nodes % 2L = 1L then
|
||||
ans <- ans + 1
|
||||
|
||||
nodes <- nodes >>> 1
|
||||
|
||||
ans
|
||||
|
||||
|
||||
let first (nodes : NodeSet) : int =
|
||||
let mutable nodes = nodes
|
||||
let mutable count = 0
|
||||
let mutable ans = 0
|
||||
let mutable keepGoing = true
|
||||
|
||||
while keepGoing && nodes > 0 do
|
||||
if nodes % 2L = 1L then
|
||||
ans <- count
|
||||
keepGoing <- false
|
||||
|
||||
nodes <- nodes >>> 1
|
||||
count <- count + 1
|
||||
|
||||
ans
|
||||
|
||||
let part1 (lines : string seq) : int =
|
||||
let valves, aaNode = parse lines
|
||||
let allTaps = valves |> Map.filter (fun _ (x, _) -> x > 0) |> Map.keys |> ofSeq
|
||||
|
||||
let getShortestPathLength = getShortestPathLength valves
|
||||
|
||||
#if DEBUG
|
||||
let pathWeights = Arr2D.zeroCreate<int> valves.Count valves.Count
|
||||
#else
|
||||
let pathWeightsStorage = Array.zeroCreate (valves.Count * valves.Count)
|
||||
use ptr = fixed pathWeightsStorage
|
||||
let pathWeights = Arr2D.zeroCreate<int> ptr valves.Count valves.Count
|
||||
#endif
|
||||
for v1 in toSeq allTaps do
|
||||
for v2 in toSeq allTaps do
|
||||
let length = getShortestPathLength v1 v2
|
||||
Arr2D.set pathWeights v1 v2 length
|
||||
|
||||
let rec go
|
||||
(timeRemainingOnCurrentPath : int)
|
||||
(headingTo : Node)
|
||||
(alreadyOn : NodeSet)
|
||||
(currentWeight : int)
|
||||
(remaining : int)
|
||||
=
|
||||
if remaining <= 0 then
|
||||
currentWeight
|
||||
elif timeRemainingOnCurrentPath > 0 then
|
||||
go (timeRemainingOnCurrentPath - 1) headingTo alreadyOn currentWeight (remaining - 1)
|
||||
else
|
||||
|
||||
let alreadyOn = setNode alreadyOn headingTo
|
||||
|
||||
let mutable allTaps = allTaps &&& (~~~alreadyOn)
|
||||
let mutable count = 0
|
||||
let mutable max = ValueNone
|
||||
|
||||
while allTaps > 0 do
|
||||
if allTaps % 2L = 1 then
|
||||
let addToWeight = fst valves.[headingTo] * (remaining - 1)
|
||||
|
||||
let candidate =
|
||||
go
|
||||
(Arr2D.get pathWeights count headingTo)
|
||||
count
|
||||
alreadyOn
|
||||
(currentWeight + addToWeight)
|
||||
(remaining - 1)
|
||||
|
||||
match max with
|
||||
| ValueNone -> max <- ValueSome candidate
|
||||
| ValueSome existingMax ->
|
||||
if existingMax < candidate then
|
||||
max <- ValueSome candidate
|
||||
|
||||
allTaps <- allTaps >>> 1
|
||||
count <- count + 1
|
||||
|
||||
match max with
|
||||
| ValueSome v -> v
|
||||
| ValueNone -> currentWeight + (remaining - 1) * (fst valves.[headingTo])
|
||||
|
||||
let mutable startChoices = allTaps
|
||||
let mutable start = 0
|
||||
let mutable maxValue = Int32.MinValue
|
||||
|
||||
while startChoices > 0 do
|
||||
if startChoices % 2L = 1L then
|
||||
let distance = getShortestPathLength aaNode start
|
||||
// By inspecting the graph, I can see that AA is screened off from the rest
|
||||
// of the graph by the set of valves with distance at most 3 from it.
|
||||
// Assume that we're going to turn them on when we pass through - this isn't
|
||||
// actually fully general, but it is enough.
|
||||
if distance <= 3 then
|
||||
let candidate = go distance start 0 0 30
|
||||
maxValue <- max maxValue candidate
|
||||
|
||||
startChoices <- startChoices >>> 1
|
||||
start <- start + 1
|
||||
|
||||
maxValue
|
||||
|
||||
let part2 (lines : string seq) : int =
|
||||
let valves, aaNode = parse lines
|
||||
let valvesIndexed = valves |> Map.values |> Array.ofSeq
|
||||
|
||||
let allTaps = valves |> Map.filter (fun _ (x, _) -> x > 0) |> Map.keys |> ofSeq
|
||||
|
||||
let getShortestPathLength = getShortestPathLength valves
|
||||
|
||||
#if DEBUG
|
||||
let pathWeights = Arr2D.zeroCreate<int> valves.Count valves.Count
|
||||
#else
|
||||
let pathWeightsStorage = Array.zeroCreate (valves.Count * valves.Count)
|
||||
use ptr = fixed pathWeightsStorage
|
||||
let pathWeights = Arr2D.zeroCreate<int> ptr valves.Count valves.Count
|
||||
#endif
|
||||
|
||||
for v1 in toSeq allTaps do
|
||||
for v2 in toSeq allTaps do
|
||||
let length = getShortestPathLength v1 v2
|
||||
Arr2D.set pathWeights v1 v2 length
|
||||
|
||||
let rec go
|
||||
(journey1 : int)
|
||||
(journey2 : int)
|
||||
(headingTo1 : Node)
|
||||
(headingTo2 : Node)
|
||||
(alreadyOn : NodeSet)
|
||||
(currentWeight : int)
|
||||
(remaining : int)
|
||||
=
|
||||
if remaining <= 0 then
|
||||
currentWeight
|
||||
elif journey1 > 0 && journey2 > 0 then
|
||||
go (journey1 - 1) (journey2 - 1) headingTo1 headingTo2 alreadyOn currentWeight (remaining - 1)
|
||||
elif journey1 = 0 && journey2 > 0 then
|
||||
|
||||
let addToWeight =
|
||||
if getNode alreadyOn headingTo1 then
|
||||
0
|
||||
else
|
||||
(remaining - 1) * (fst valvesIndexed.[headingTo1])
|
||||
|
||||
let newWeight = addToWeight + currentWeight
|
||||
|
||||
let alreadyOn = setNode alreadyOn headingTo1
|
||||
|
||||
let mutable allTaps = allTaps &&& ~~~alreadyOn
|
||||
let mutable node = 0
|
||||
let mutable max = ValueNone
|
||||
|
||||
while allTaps > 0L do
|
||||
// Strictly speaking we might be able to overtake the other one
|
||||
// who is heading for their node, but it so happens that the
|
||||
// test cases don't care about that.
|
||||
if allTaps % 2L = 1L && node <> headingTo2 then
|
||||
let next =
|
||||
go
|
||||
(Arr2D.get pathWeights node headingTo1)
|
||||
(journey2 - 1)
|
||||
node
|
||||
headingTo2
|
||||
alreadyOn
|
||||
newWeight
|
||||
(remaining - 1)
|
||||
|
||||
match max with
|
||||
| ValueNone -> max <- ValueSome next
|
||||
| ValueSome existingMax ->
|
||||
if next > existingMax then
|
||||
max <- ValueSome next
|
||||
|
||||
allTaps <- allTaps >>> 1
|
||||
node <- node + 1
|
||||
|
||||
match max with
|
||||
| ValueSome v -> v
|
||||
| ValueNone -> go 1000000 (journey2 - 1) headingTo1 headingTo2 alreadyOn newWeight (remaining - 1)
|
||||
|
||||
elif journey2 = 0 && journey1 > 0 then
|
||||
let addToWeight =
|
||||
if getNode alreadyOn headingTo2 then
|
||||
0
|
||||
else
|
||||
fst valvesIndexed.[headingTo2] * (remaining - 1)
|
||||
|
||||
let newWeight = addToWeight + currentWeight
|
||||
|
||||
let alreadyOn = setNode alreadyOn headingTo2
|
||||
|
||||
let mutable allTaps = allTaps &&& ~~~alreadyOn
|
||||
let mutable node = 0
|
||||
let mutable max = ValueNone
|
||||
|
||||
while allTaps > 0L do
|
||||
// Strictly speaking we might be able to overtake the other one
|
||||
// who is heading for their node, but it so happens that the
|
||||
// test cases don't care about that.
|
||||
if allTaps % 2L = 1L && node <> headingTo1 then
|
||||
let next =
|
||||
go
|
||||
(journey1 - 1)
|
||||
(Arr2D.get pathWeights node headingTo2)
|
||||
headingTo1
|
||||
node
|
||||
alreadyOn
|
||||
newWeight
|
||||
(remaining - 1)
|
||||
|
||||
match max with
|
||||
| ValueNone -> max <- ValueSome next
|
||||
| ValueSome existingMax ->
|
||||
if next > existingMax then
|
||||
max <- ValueSome next
|
||||
|
||||
allTaps <- allTaps >>> 1
|
||||
node <- node + 1
|
||||
|
||||
match max with
|
||||
| ValueSome v -> v
|
||||
| ValueNone -> go (journey1 - 1) 1000000 headingTo1 headingTo2 alreadyOn newWeight (remaining - 1)
|
||||
|
||||
else
|
||||
// Both reached destination at same time
|
||||
let addToWeight1 =
|
||||
if getNode alreadyOn headingTo1 then
|
||||
0
|
||||
else
|
||||
(remaining - 1) * fst valvesIndexed.[headingTo1]
|
||||
|
||||
let addToWeight2 =
|
||||
if getNode alreadyOn headingTo2 then
|
||||
0
|
||||
else
|
||||
(remaining - 1) * fst valvesIndexed.[headingTo2]
|
||||
|
||||
let newWeight =
|
||||
if headingTo1 <> headingTo2 then
|
||||
addToWeight1 + addToWeight2 + currentWeight
|
||||
else
|
||||
addToWeight1 + currentWeight
|
||||
|
||||
let alreadyOn = setNode (setNode alreadyOn headingTo1) headingTo2
|
||||
|
||||
let nextChoices = allTaps &&& ~~~alreadyOn
|
||||
|
||||
if count nextChoices >= 2 then
|
||||
let mutable maxVal = Int32.MinValue
|
||||
|
||||
let mutable next1 = nextChoices
|
||||
let mutable count1 = 0
|
||||
|
||||
while next1 > 0 do
|
||||
if next1 % 2L = 1 then
|
||||
let mutable next2 = nextChoices
|
||||
let mutable count2 = 0
|
||||
|
||||
while next2 > 0 do
|
||||
// It's never correct for both to try and turn on the same node.
|
||||
if next2 % 2L = 1 && next1 <> next2 then
|
||||
let candidate =
|
||||
go
|
||||
(Arr2D.get pathWeights count1 headingTo1)
|
||||
(Arr2D.get pathWeights count2 headingTo2)
|
||||
count1
|
||||
count2
|
||||
alreadyOn
|
||||
newWeight
|
||||
(remaining - 1)
|
||||
|
||||
maxVal <- max maxVal candidate
|
||||
|
||||
next2 <- next2 >>> 1
|
||||
count2 <- count2 + 1
|
||||
|
||||
next1 <- next1 >>> 1
|
||||
count1 <- count1 + 1
|
||||
|
||||
maxVal
|
||||
|
||||
elif nextChoices = 0 then
|
||||
0
|
||||
else
|
||||
// nextChoices.Count = 1
|
||||
let next = first nextChoices
|
||||
go 100000 (Arr2D.get pathWeights next headingTo2) next next alreadyOn newWeight (remaining - 1)
|
||||
|
||||
let startChoices =
|
||||
allTaps
|
||||
|> toSeq
|
||||
|> Seq.map (fun startNode -> startNode, getShortestPathLength aaNode startNode)
|
||||
|> Map.ofSeq
|
||||
|
||||
let maxVal = ref Int32.MinValue
|
||||
|
||||
Seq.allPairs startChoices startChoices
|
||||
|> Seq.filter (fun (KeyValue (n1, distance1), KeyValue (n2, distance2)) ->
|
||||
// By inspecting the graph, I can see that AA is screened off from the rest
|
||||
// of the graph by the set of valves with distance at most 3 from it.
|
||||
// Assume that we're going to turn them on when we pass through - this isn't
|
||||
// actually fully general, but it is enough.
|
||||
n1 < n2 && distance1 <= 3 && distance2 <= 3
|
||||
)
|
||||
|> PSeq.map (fun (KeyValue (start1, distance1), KeyValue (start2, distance2)) ->
|
||||
go distance1 distance2 start1 start2 0 0 26
|
||||
)
|
||||
|> PSeq.iter (fun s ->
|
||||
lock
|
||||
maxVal
|
||||
(fun () ->
|
||||
if s > maxVal.Value then
|
||||
maxVal.Value <- s
|
||||
)
|
||||
)
|
||||
|
||||
maxVal.Value
|
Reference in New Issue
Block a user