Day 7: Bridge Repair

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

  • RagingHungryPanda@lemm.ee
    link
    fedilink
    English
    arrow-up
    1
    ·
    3 days ago

    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