I'm way behind, but I'm trying to learn F#.
I'm using the library Combinatorics in dotnet, which I've used in the past, generate in this case every duplicating possibility of the operations. I the only optimization that I did was to use a function to concatenate numbers without converting to strings, but that didn't actually help much.
I have parser helpers that use ReadOnlySpans over strings to prevent unnecessary allocations. However, here I'm adding to a C# mutable list and then converting to an FSharp (linked) list, which this language is more familiar with. Not optimal, but runtime was pretty good.
I'm not terribly good with F#, but I think I did ok for this challenge.
F#
// in another file:
let concatenateLong (a:Int64) (b:Int64) : Int64 =
let rec countDigits (n:int64) =
if n = 0 then 0
else 1 + countDigits (n / (int64 10))
let bDigits = if b = 0 then 1 else countDigits b
let multiplier = pown 10 bDigits |> int64
a * multiplier + b
// challenge file
type Operation = {Total:Int64; Inputs:Int64 list }
let parse (s:ReadOnlySpan<char>) : Operation =
let sep = s.IndexOf(':')
let total = Int64.Parse(s.Slice(0, sep))
let inputs = System.Collections.Generic.List<Int64>()
let right:ReadOnlySpan<char> = s.Slice(sep + 1).Trim()
// because the Split function on a span returns a SpanSplitEnumerator, which is a ref-struct and can only live on the stack,
// I can't use the F# list syntax here
for range in right.Split(" ") do
inputs.Add(Int64.Parse(sliceRange right range))
{Total = total; Inputs = List.ofSeq(inputs) }
let part1Ops = [(+); (*)]
let execute ops input =
input
|> PSeq.choose (fun op ->
let total = op.Total
let inputs = op.Inputs
let variations = Variations(ops, inputs.Length - 1, GenerateOption.WithRepetition)
variations
|> Seq.tryFind (fun v ->
let calcTotal = (inputs[0], inputs[1..], List.ofSeq(v)) |||> List.fold2 (fun acc n f -> f acc n)
calcTotal = total
)
|> Option.map(fun _ -> total)
)
|> PSeq.fold (fun acc n -> acc + n) 0L
let part1 input =
(read input parse)
|> execute part1Ops
let part2Ops = [(+); (*); concatenateLong]
let part2 input = (read input parse) |> execute part2Ops
The Gen0 garbage collection looks absurd, but Gen0 is generally considered "free".
Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
---|---|---|---|---|---|---|
Part1 | 19.20 ms | 0.372 ms | 0.545 ms | 17843.7500 | 156.2500 | 106.55 MB |
Part2 | 17.94 ms | 0.355 ms | 0.878 ms | 17843.7500 | 156.2500 | 106.55 MB |
V2 - concatenate numbers did little for the runtime, but did help with Gen1 garbage, but not the overall allocation.
Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
---|---|---|---|---|---|---|
Part1 | 17.34 ms | 0.342 ms | 0.336 ms | 17843.7500 | 125.0000 | 106.55 MB |
Part2 | 17.24 ms | 0.323 ms | 0.270 ms | 17843.7500 | 93.7500 | 106.55 MB |