Compare commits

...

2 Commits

Author SHA1 Message Date
Smaug123
61c3f1c79a Tidy
Some checks failed
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/pr/build Pipeline failed
ci/woodpecker/push/all-checks-complete Pipeline was successful
ci/woodpecker/pr/all-checks-complete unknown status
2023-12-07 08:54:04 +00:00
Smaug123
cc5a0cec0d First hacky answer 2023-12-07 08:37:50 +00:00
3 changed files with 194 additions and 71 deletions

View File

@@ -1,86 +1,204 @@
namespace AdventOfCode2023 namespace AdventOfCode2023
open System open System
open System.Collections.Generic
type Hand =
| Five = 10
| Four = 9
| FullHouse = 8
| Three = 7
| TwoPairs = 6
| Pair = 5
| High = 4
type HandContents =
{
First : byte
Second : byte
Third : byte
Fourth : byte
Fifth : byte
}
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module Day7 = module Day7 =
let parse (s : string) = let inline toByte (adjustJoker : bool) (c : char) : byte =
use mutable lines = StringSplitEnumerator.make '\n' s if c <= '9' then byte c - byte '0'
elif c = 'T' then 10uy
elif c = 'J' then (if adjustJoker then 1uy else 11uy)
elif c = 'Q' then 12uy
elif c = 'K' then 13uy
elif c = 'A' then 14uy
else failwithf "could not parse: %c" c
let times = let inline private updateState (tallies : ResizeArray<_>) newNum =
lines.MoveNext () |> ignore let mutable isAdded = false
use mutable line = StringSplitEnumerator.make' ' ' lines.Current
StringSplitEnumerator.chomp "Time:" &line
line.MoveNext () |> ignore
let times = ResizeArray ()
while line.MoveNext () do for i = 0 to tallies.Count - 1 do
if not line.Current.IsEmpty then if fst tallies.[i] = newNum then
times.Add (UInt64.Parse line.Current) tallies.[i] <- (fst tallies.[i], snd tallies.[i] + 1)
isAdded <- true
times if not isAdded then
tallies.Add (newNum, 1)
let distance = let inline parseHand
lines.MoveNext () |> ignore (tallyBuffer : ResizeArray<_>)
use mutable line = StringSplitEnumerator.make' ' ' lines.Current (adjustJoker : bool)
StringSplitEnumerator.chomp "Distance:" &line (s : ReadOnlySpan<char>)
line.MoveNext () |> ignore : Hand * HandContents
let distance = ResizeArray () =
let contents =
{
First = toByte adjustJoker s.[0]
Second = toByte adjustJoker s.[1]
Third = toByte adjustJoker s.[2]
Fourth = toByte adjustJoker s.[3]
Fifth = toByte adjustJoker s.[4]
}
while line.MoveNext () do tallyBuffer.Clear ()
if not line.Current.IsEmpty then tallyBuffer.Add (contents.First, 1)
distance.Add (UInt64.Parse line.Current) updateState tallyBuffer contents.Second
updateState tallyBuffer contents.Third
updateState tallyBuffer contents.Fourth
updateState tallyBuffer contents.Fifth
distance let jokerCount, jokerPos =
if not adjustJoker then
times, distance 0, -1
let furthest (distance : uint64) (toBeat : uint64) =
// Count i in [1 .. distance - 1] such that (distance - i) * i > toBeat
// i.e. such that distance * i - i * i > toBeat
// -i^2 + distance * i - toBeat = 0 when:
// i = (distance +- sqrt(distance^2 - 4 * toBeat)) / 2
let distFloat = float distance
let inside = sqrt (distFloat * distFloat - 4.0 * float toBeat)
let limit1 = (distFloat + inside) / 2.0
let limit2 = (distFloat - sqrt (distFloat * distFloat - 4.0 * float toBeat)) / 2.0
// round limit2 up and limit1 down
let limit1 = uint64 (floor limit1)
let limit2 = uint64 (ceil limit2)
// cope with edge case of an exact square
if (uint64 inside) * (uint64 inside) = uint64 distance * uint64 distance - 4uL * uint64 toBeat then
limit1 - limit2 - 1uL
else else
limit1 - limit2 + 1uL let mutable count = 0
let mutable jokerPos = -1
let part1 (s : string) = for i = 0 to tallyBuffer.Count - 1 do
let times, distance = parse s let card, tally = tallyBuffer.[i]
let mutable answer = 1uL
for i = 0 to times.Count - 1 do if card = 1uy then
let time = times.[i] count <- tally
let distance = distance.[i] jokerPos <- i
let winners = furthest time distance
answer <- answer * winners
answer count, jokerPos
let concat (digits : ResizeArray<uint64>) : uint64 = let hand =
let mutable answer = 0uL if jokerCount > 0 then
match tallyBuffer.Count with
| 1 ->
// Five jokers
Hand.Five
| 2 ->
// Jokers plus one other card type
Hand.Five
| 3 ->
// Jokers plus two other card types. Either full house, or four of a kind
if jokerCount >= 2 then
// JJABB or JJJAB
Hand.Four
else if
// JAABB or JAAAB
jokerPos <> 0
then
if snd tallyBuffer.[0] = 2 then
Hand.FullHouse
else
Hand.Four
else if snd tallyBuffer.[1] = 2 then
Hand.FullHouse
else
Hand.Four
| 4 ->
// Jokers plus three other card types, exactly one of which therefore is a two-of.
Hand.Three
| 5 ->
// Five different cards, one of which is a joker.
Hand.Pair
| _ -> failwith "bad tallyBuffer"
elif tallyBuffer.Count = 1 then
Hand.Five
elif tallyBuffer.Count = 2 then
// AAAAB or AAABB
if snd tallyBuffer.[0] = 3 || snd tallyBuffer.[0] = 2 then
Hand.FullHouse
else
Hand.Four
elif tallyBuffer.Count = 3 then
// AAABC or AABBC
if snd tallyBuffer.[0] = 3 then Hand.Three
elif snd tallyBuffer.[0] = 2 then Hand.TwoPairs
elif snd tallyBuffer.[1] = 3 then Hand.Three
elif snd tallyBuffer.[1] = 2 then Hand.TwoPairs
else Hand.Three
elif tallyBuffer.Count = 4 then
Hand.Pair
else
Hand.High
for digit in digits do hand, contents
let mutable power = 10uL
while digit >= power do let parse (adjustJoker : bool) (s : string) : ResizeArray<Hand * HandContents * int> =
power <- power * 10uL use mutable lines = StringSplitEnumerator.make '\n' s
let result = ResizeArray ()
let tallies = ResizeArray 5
answer <- answer * power + digit while lines.MoveNext () do
if not lines.Current.IsEmpty then
use mutable line = StringSplitEnumerator.make' ' ' lines.Current
line.MoveNext () |> ignore
let hand, contents = parseHand tallies adjustJoker line.Current
line.MoveNext () |> ignore
let bid = Int32.Parse line.Current
result.Add (hand, contents, bid)
result
let compArrBasic (a : HandContents) (b : HandContents) =
if a.First > b.First then 1
elif a.First < b.First then -1
elif a.Second > b.Second then 1
elif a.Second < b.Second then -1
elif a.Third > b.Third then 1
elif a.Third < b.Third then -1
elif a.Fourth > b.Fourth then 1
elif a.Fourth < b.Fourth then -1
elif a.Fifth > b.Fifth then 1
elif a.Fifth < b.Fifth then -1
else 0
let compBasic : IComparer<Hand * HandContents * int> =
{ new IComparer<_> with
member _.Compare ((aHand, aContents, _), (bHand, bContents, _)) =
match compare aHand bHand with
| 0 -> compArrBasic aContents bContents
| x -> x
}
let part1 (s : string) : int =
let parsed = parse false s
parsed.Sort compBasic
let mutable answer = 0
let mutable pos = 1
for _, _, bid in parsed do
answer <- answer + bid * pos
pos <- pos + 1
answer answer
let part2 (s : string) = let part2 (s : string) =
let times, distance = parse s let parsed = parse true s
let concatTime = concat times
let concatDist = concat distance parsed.Sort compBasic
furthest concatTime concatDist
let mutable answer = 0
let mutable pos = 1
for _, _, bid in parsed do
answer <- answer + bid * pos
pos <- pos + 1
answer

View File

@@ -13,34 +13,34 @@ module TestDay7 =
[<Test>] [<Test>]
let part1Sample () = let part1Sample () =
sample |> Day7.part1 |> shouldEqual 288uL sample |> Day7.part1 |> shouldEqual 6440
[<Test>] [<Test>]
let part2Sample () = let part2Sample () =
sample |> Day7.part2 |> shouldEqual 71503uL sample |> Day7.part2 |> shouldEqual 5905
[<Test>] [<Test>]
let part1Actual () = let part1Actual () =
let s = let s =
try try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day6.txt")) File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day7.txt"))
with with
| :? DirectoryNotFoundException | :? DirectoryNotFoundException
| :? FileNotFoundException -> | :? FileNotFoundException ->
Assert.Inconclusive () Assert.Inconclusive ()
failwith "unreachable" failwith "unreachable"
Day7.part1 s |> shouldEqual 32076uL Day7.part1 s |> shouldEqual 250058342
[<Test>] [<Test>]
let part2Actual () = let part2Actual () =
let s = let s =
try try
File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day6.txt")) File.ReadAllText (Path.Combine (__SOURCE_DIRECTORY__, "../../inputs/day7.txt"))
with with
| :? DirectoryNotFoundException | :? DirectoryNotFoundException
| :? FileNotFoundException -> | :? FileNotFoundException ->
Assert.Inconclusive () Assert.Inconclusive ()
failwith "unreachable" failwith "unreachable"
Day7.part2 s |> shouldEqual 34278221uL Day7.part2 s |> shouldEqual 250506580

View File

@@ -0,0 +1,5 @@
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483