DEV Community

Discussion on: AoC Day 4: Repose Record

Collapse
 
arnaud_vdp profile image
Arnaud VdP

I have been trying to learn F#, coming from C#, and I'm quite "happy" with my previous solutions. This one challenged me more, having to think 'functional'! I am not happy with how it looks, but here it is anyway!

My other attempts are on github. Critique is highly welcome!

I made a datastructure that mapped guards to days to minutes with type: Map<string,Map<string,bool array> which made it 'easy' (or so I though) to extract the data.

let rec handle guard (datastructure:Map<string,Map<string,bool []>>) (arr:string list) =
    match arr with
    | head :: tail ->
        match head with
        | Regex @"\[(\d+)-(\d\d)-(\d\d) (\d+):(\d+)\] (.+)" [year; month; day; hour; min; action] ->
            let date = new DateTime(year|>int,month|>int,day|>int,hour|>int,min|>int,0)
            let origday = date.ToString "MM-dd"
            let newdate =
                if date.Hour = 0 then
                    date
                else
                    new DateTime (date.AddDays(1.0).Year,date.AddDays(1.0).Month,date.AddDays(1.0).Day)
            let newday = newdate.ToString "MM-dd"
            match action with
            | Regex @"Guard #(\d+)" [newguard] ->
                if datastructure.ContainsKey newguard then
                    if not (datastructure.[newguard].ContainsKey newday) then
                        let newDs =
                            let minutes : bool array = Array.zeroCreate 60
                            let newDay =
                                if hour = "00" then
                                    minutes.[min|>int] <- true
                                else
                                    minutes.[0] <- true
                                datastructure.[newguard]
                                |> Map.add newday minutes
                            datastructure |> Map.add newguard newDay
                        handle newguard newDs tail
                    else
                        handle newguard datastructure tail
                else
                    // create new level in datastructure
                    // new array of waking minutes, default to sleep
                    let minutes : bool array = Array.zeroCreate 60
                    // guard starts out as awake
                    let newMap =
                        Map.empty
                        |> Map.add newday minutes
                    let newDs =
                        datastructure
                        |> Map.add newguard newMap
                    handle newguard newDs tail
            | Regex @"wakes up" [] ->
                // set time to awake
                datastructure.[guard].[newday].[min|>int] <- true
                handle guard datastructure tail
            | Regex @"falls asleep" []->
                // set all times before this to awake
                let maxMin = min|> int
                let minMin =
                    try
                        Array.findIndexBack (fun a -> a) datastructure.[guard].[newday].[0..maxMin]
                    with
                        | :? KeyNotFoundException as ex -> 0

                for i in [minMin..maxMin-1] do
                    datastructure.[guard].[newday].[i] <- true
                handle guard datastructure tail
            | _ -> handle guard datastructure tail
        | _ -> handle guard datastructure tail
    | [] ->
        // guard ends his day awake
        Map.iter (fun g dm ->
            Map.iter (fun d ms ->
                let minMin =
                    try
                        Array.findIndexBack (fun a -> a) ms
                    with
                        | :? KeyNotFoundException as ex -> 0
                for i in [minMin..59] do
                    ms.[i] <- true
            ) dm ) datastructure
        // guard starts his day awake
        Map.iter (fun g dm ->
            Map.iter (fun d ms ->
                let minMin =
                    try
                        Array.findIndex (fun a -> a) ms
                    with
                        | :? KeyNotFoundException as ex -> 0
                for i in [0..minMin] do
                    ms.[i] <- true
            ) dm ) datastructure
        datastructure

Then Part 1

let testinput ="[1518-11-01 00:00] Guard #10 begins shift
[1518-11-01 00:05] falls asleep
[1518-11-01 00:25] wakes up
[1518-11-01 00:30] falls asleep
[1518-11-01 00:55] wakes up
[1518-11-01 23:58] Guard #99 begins shift
[1518-11-02 00:40] falls asleep
[1518-11-02 00:50] wakes up
[1518-11-03 00:05] Guard #10 begins shift
[1518-11-03 00:24] falls asleep
[1518-11-03 00:29] wakes up
[1518-11-04 00:02] Guard #99 begins shift
[1518-11-04 00:36] falls asleep
[1518-11-04 00:46] wakes up
[1518-11-05 00:03] Guard #99 begins shift
[1518-11-05 00:45] falls asleep
[1518-11-05 00:55] wakes up"

    let stopwatch = System.Diagnostics.Stopwatch.StartNew()

    let split =
        split testinput
        |> Array.sort

    // (guard,day,minute) -> awake
    let datastruct =
        split
        |> Array.toList
        |> handle "" Map.empty

    stopwatch.Stop()
    printfn "creating datastructure took %i milliseconds" stopwatch.ElapsedMilliseconds
    stopwatch.Restart()

    // find guard that is asleep most
    let sleepyGuard =
        datastruct
        |> Map.map (fun guard day ->
            Map.map (fun day mins ->
                Array.sumBy (fun awake -> if awake then 0 else 1) mins
            ) day
        )
        |> Map.map (fun guard day ->
            day
            |> Map.fold (fun acc day mins -> acc+mins ) 0
        )
        |> Map.toList
        |> List.maxBy (fun (g,min) -> min)
        |> fst
    stopwatch.Stop()
    printfn "sleepiest guard is %s" sleepyGuard
    printfn "\t calculation too %i milliseconds" stopwatch.ElapsedMilliseconds
    stopwatch.Restart()
    // find that guards most likely minute of being asleep

    let sleepyMinute =
        datastruct.[sleepyGuard]
        |> Map.map (fun day mins -> Array.indexed mins)
        |> Map.toList
        |> List.map (fun i -> snd i)
        |> List.collect (fun arr -> Array.toList arr)
        |> List.groupBy (fun (index, awake) -> index)
        |> List.map (fun arr -> snd arr)
        |> List.map (fun a -> List.map (fun b -> snd b) a)
        |> List.map (fun a -> a |> List.filter (fun awake -> not awake) |> List.length )
        |> List.indexed
        |> List.maxBy (fun (index,m) -> m)
        |> fst

    stopwatch.Stop()
    printfn "sleepiest minute of guard %s is %i" sleepyGuard sleepyMinute
    printfn "result of day 4 part 1 is: %i" ((sleepyGuard|>int) * sleepyMinute)
    printfn "\t calculation took %i milliseconds" stopwatch.ElapsedMilliseconds

and Part 2

let popular =
        datastruct
        |> Map.map (fun guard day ->
            Map.toList day
            |> List.map snd
            |> List.map (fun i -> Array.toList i |> List.indexed)
            |> List.concat
            //|> List.filter (fun (_,awake) -> not awake)
            |> List.groupBy (fun (i,_) -> i)
            |> List.map (fun (i,l) -> (i,(List.fold (fun acc (_,awake) -> if awake then acc else acc+1) 0 l)))
            |> List.maxBy (fun (_,l) -> l)
        )
        |> Map.toList
        |> List.maxBy (fun (_,(_,t)) -> t)

    stopwatch.Stop()
    printfn "Guard %s spent minute %i asleep more than anyone: %i times" (fst popular) (fst (snd popular)) (snd (snd popular))
    printfn "result of day 4 part 2 is: %i" (((fst popular)|>int) * (fst (snd popular)))
    printfn "\t calculation took %i milliseconds" stopwatch.ElapsedMilliseconds