First hacky answer
This commit is contained in:
@@ -1,86 +1,201 @@
|
|||||||
namespace AdventOfCode2023
|
namespace AdventOfCode2023
|
||||||
|
|
||||||
open System
|
open System
|
||||||
|
open System.Collections.Generic
|
||||||
|
|
||||||
|
//type Hand =
|
||||||
|
// | Five of byte
|
||||||
|
// | Four of high: byte * low: byte
|
||||||
|
// | FullHouse of three: byte * two: byte
|
||||||
|
// | Three of three: byte * highOther: byte * lowOther: byte
|
||||||
|
// | TwoPairs of highPair: byte * lowPair: byte * other: byte
|
||||||
|
// | Pair of pair: byte * highOther: byte * midOther: byte * lowOther: byte
|
||||||
|
// | High of high: byte * mid: byte * low: byte * lowest: byte * lowestest: byte
|
||||||
|
|
||||||
|
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 =
|
||||||
|
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 inline private updateState (tallies : ResizeArray<_>) (newNum) =
|
||||||
|
let mutable isAdded = false
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if not isAdded then
|
||||||
|
tallies.Add (newNum, 1)
|
||||||
|
|
||||||
|
let inline parseHand (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]
|
||||||
|
}
|
||||||
|
|
||||||
|
let tallies = ResizeArray 5
|
||||||
|
tallies.Add (contents.First, 1)
|
||||||
|
updateState tallies contents.Second
|
||||||
|
updateState tallies contents.Third
|
||||||
|
updateState tallies contents.Fourth
|
||||||
|
updateState tallies contents.Fifth
|
||||||
|
|
||||||
|
let jokerCount, jokerPos =
|
||||||
|
let mutable count = 0
|
||||||
|
let mutable jokerPos = -1
|
||||||
|
|
||||||
|
for i = 0 to tallies.Count - 1 do
|
||||||
|
let card, tally = tallies.[i]
|
||||||
|
|
||||||
|
if card = 1uy then
|
||||||
|
count <- tally
|
||||||
|
jokerPos <- i
|
||||||
|
|
||||||
|
count, jokerPos
|
||||||
|
|
||||||
|
let hand =
|
||||||
|
if jokerCount > 0 then
|
||||||
|
match tallies.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
|
||||||
|
// JAABB or JAAAB
|
||||||
|
let tallies = tallies |> Seq.map snd |> Seq.toArray
|
||||||
|
|
||||||
|
if Array.contains 3 tallies then
|
||||||
|
Hand.Four
|
||||||
|
else
|
||||||
|
Hand.FullHouse
|
||||||
|
| 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
|
||||||
|
| _ -> failwithf "bad tallies: %+A" tallies
|
||||||
|
else
|
||||||
|
|
||||||
|
let tallies = tallies |> Seq.map snd |> Seq.toArray
|
||||||
|
|
||||||
|
if tallies |> Array.contains 5 then
|
||||||
|
Hand.Five
|
||||||
|
elif Array.contains 4 tallies then
|
||||||
|
Hand.Four
|
||||||
|
elif Array.contains 3 tallies then
|
||||||
|
if Array.contains 2 tallies then
|
||||||
|
Hand.FullHouse
|
||||||
|
else
|
||||||
|
Hand.Three
|
||||||
|
elif Array.contains 2 tallies then
|
||||||
|
if tallies |> Array.filter (fun x -> x = 2) |> Array.length = 2 then
|
||||||
|
Hand.TwoPairs
|
||||||
|
else
|
||||||
|
Hand.Pair
|
||||||
|
else
|
||||||
|
Hand.High
|
||||||
|
|
||||||
|
hand, contents
|
||||||
|
|
||||||
|
let parse (adjustJoker : bool) (s : string) : ResizeArray<Hand * HandContents * int> =
|
||||||
use mutable lines = StringSplitEnumerator.make '\n' s
|
use mutable lines = StringSplitEnumerator.make '\n' s
|
||||||
|
let result = ResizeArray ()
|
||||||
|
|
||||||
let times =
|
while lines.MoveNext () do
|
||||||
lines.MoveNext () |> ignore
|
if not lines.Current.IsEmpty then
|
||||||
use mutable line = StringSplitEnumerator.make' ' ' lines.Current
|
use mutable line = StringSplitEnumerator.make' ' ' lines.Current
|
||||||
StringSplitEnumerator.chomp "Time:" &line
|
line.MoveNext () |> ignore
|
||||||
line.MoveNext () |> ignore
|
let hand, contents = parseHand adjustJoker line.Current
|
||||||
let times = ResizeArray ()
|
line.MoveNext () |> ignore
|
||||||
|
let bid = Int32.Parse line.Current
|
||||||
|
|
||||||
while line.MoveNext () do
|
result.Add (hand, contents, bid)
|
||||||
if not line.Current.IsEmpty then
|
|
||||||
times.Add (UInt64.Parse line.Current)
|
|
||||||
|
|
||||||
times
|
result
|
||||||
|
|
||||||
let distance =
|
let compArrBasic (a : HandContents) (b : HandContents) =
|
||||||
lines.MoveNext () |> ignore
|
if a.First > b.First then 1
|
||||||
use mutable line = StringSplitEnumerator.make' ' ' lines.Current
|
elif a.First < b.First then -1
|
||||||
StringSplitEnumerator.chomp "Distance:" &line
|
elif a.Second > b.Second then 1
|
||||||
line.MoveNext () |> ignore
|
elif a.Second < b.Second then -1
|
||||||
let distance = ResizeArray ()
|
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
|
||||||
|
|
||||||
while line.MoveNext () do
|
let compBasic : IComparer<Hand * HandContents * int> =
|
||||||
if not line.Current.IsEmpty then
|
{ new IComparer<_> with
|
||||||
distance.Add (UInt64.Parse line.Current)
|
member _.Compare ((aHand, aContents, _), (bHand, bContents, _)) =
|
||||||
|
match compare aHand bHand with
|
||||||
|
| 0 -> compArrBasic aContents bContents
|
||||||
|
| x -> x
|
||||||
|
}
|
||||||
|
|
||||||
distance
|
let part1 (s : string) : int =
|
||||||
|
let parsed = parse false s
|
||||||
|
|
||||||
times, distance
|
parsed.Sort compBasic
|
||||||
|
|
||||||
let furthest (distance : uint64) (toBeat : uint64) =
|
let mutable answer = 0
|
||||||
// Count i in [1 .. distance - 1] such that (distance - i) * i > toBeat
|
let mutable pos = 1
|
||||||
// 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
|
|
||||||
limit1 - limit2 + 1uL
|
|
||||||
|
|
||||||
let part1 (s : string) =
|
for _, _, bid in parsed do
|
||||||
let times, distance = parse s
|
answer <- answer + bid * pos
|
||||||
let mutable answer = 1uL
|
pos <- pos + 1
|
||||||
|
|
||||||
for i = 0 to times.Count - 1 do
|
|
||||||
let time = times.[i]
|
|
||||||
let distance = distance.[i]
|
|
||||||
let winners = furthest time distance
|
|
||||||
answer <- answer * winners
|
|
||||||
|
|
||||||
answer
|
|
||||||
|
|
||||||
let concat (digits : ResizeArray<uint64>) : uint64 =
|
|
||||||
let mutable answer = 0uL
|
|
||||||
|
|
||||||
for digit in digits do
|
|
||||||
let mutable power = 10uL
|
|
||||||
|
|
||||||
while digit >= power do
|
|
||||||
power <- power * 10uL
|
|
||||||
|
|
||||||
answer <- answer * power + digit
|
|
||||||
|
|
||||||
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
|
||||||
|
@@ -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
|
||||||
|
@@ -0,0 +1,5 @@
|
|||||||
|
32T3K 765
|
||||||
|
T55J5 684
|
||||||
|
KK677 28
|
||||||
|
KTJJT 220
|
||||||
|
QQQJA 483
|
||||||
|
Reference in New Issue
Block a user