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
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>]
module Day7 =
let parse (s : string) =
use mutable lines = StringSplitEnumerator.make '\n' s
let inline toByte (adjustJoker : bool) (c : char) : byte =
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 =
lines.MoveNext () |> ignore
use mutable line = StringSplitEnumerator.make' ' ' lines.Current
StringSplitEnumerator.chomp "Time:" &line
line.MoveNext () |> ignore
let times = ResizeArray ()
let inline private updateState (tallies : ResizeArray<_>) newNum =
let mutable isAdded = false
while line.MoveNext () do
if not line.Current.IsEmpty then
times.Add (UInt64.Parse line.Current)
for i = 0 to tallies.Count - 1 do
if fst tallies.[i] = newNum then
tallies.[i] <- (fst tallies.[i], snd tallies.[i] + 1)
isAdded <- true
times
if not isAdded then
tallies.Add (newNum, 1)
let distance =
lines.MoveNext () |> ignore
use mutable line = StringSplitEnumerator.make' ' ' lines.Current
StringSplitEnumerator.chomp "Distance:" &line
line.MoveNext () |> ignore
let distance = ResizeArray ()
let inline parseHand
(tallyBuffer : ResizeArray<_>)
(adjustJoker : bool)
(s : ReadOnlySpan<char>)
: Hand * HandContents
=
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
if not line.Current.IsEmpty then
distance.Add (UInt64.Parse line.Current)
tallyBuffer.Clear ()
tallyBuffer.Add (contents.First, 1)
updateState tallyBuffer contents.Second
updateState tallyBuffer contents.Third
updateState tallyBuffer contents.Fourth
updateState tallyBuffer contents.Fifth
distance
times, distance
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
let jokerCount, jokerPos =
if not adjustJoker then
0, -1
else
limit1 - limit2 + 1uL
let mutable count = 0
let mutable jokerPos = -1
let part1 (s : string) =
let times, distance = parse s
let mutable answer = 1uL
for i = 0 to tallyBuffer.Count - 1 do
let card, tally = tallyBuffer.[i]
for i = 0 to times.Count - 1 do
let time = times.[i]
let distance = distance.[i]
let winners = furthest time distance
answer <- answer * winners
if card = 1uy then
count <- tally
jokerPos <- i
answer
count, jokerPos
let concat (digits : ResizeArray<uint64>) : uint64 =
let mutable answer = 0uL
let hand =
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
let mutable power = 10uL
hand, contents
while digit >= power do
power <- power * 10uL
let parse (adjustJoker : bool) (s : string) : ResizeArray<Hand * HandContents * int> =
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
let part2 (s : string) =
let times, distance = parse s
let concatTime = concat times
let concatDist = concat distance
furthest concatTime concatDist
let parsed = parse true 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

View File

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