Last active
December 15, 2024 13:46
-
-
Save einarwh/6b6e5802f4f295840c872611d273057c to your computer and use it in GitHub Desktop.
Advent of Code 2024. Day 14: Restroom Redoubt. F# version.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Advent of Code 2024. Day 14: Restroom Redoubt. | |
// dotnet fsi aoc14.fsx | |
open System | |
open System.IO | |
open System.Diagnostics | |
type Pos = (int * int) | |
type Robot = { p : Pos; v : Pos } | |
let trim (input : string) = input.Trim() | |
let split (splitter : string) (input : string) = input.Split(splitter) | |
let substring (startIndex : int) (input : string) = input.Substring(startIndex) | |
let parsePos s = | |
match s |> split "=" |> Array.item 1 |> split "," |> Array.map int with | |
| [|x;y|] -> (x, y) | |
| _ -> failwith "pos ?" | |
let parseRobot s = | |
match s |> split " " with | |
| [|pStr;vStr|] -> | |
{ p = parsePos pStr; v = parsePos vStr } | |
| _ -> failwith "robot ?" | |
let move width height seconds robot = | |
let (x, y) = robot.p | |
let (vx, vy) = robot.v | |
let (x', y') = ((x + (vx + width) * seconds) % width, (y + (height + vy) * seconds) % height) | |
{ robot with p = (x', y') } | |
let simulate width height seconds (robots : Robot list) = | |
robots |> List.map (move width height seconds) | |
let countRobotsInRow yRow robots = | |
robots | |
|> List.map (fun r -> r.p) | |
|> List.choose (fun (x, y) -> if y = yRow then Some x else None) | |
|> List.countBy id | |
let countPositions (positions : Pos list) : int = | |
positions | |
|> List.countBy id | |
|> List.sumBy snd | |
let createRow robots width yRow = | |
let countedRobots = robots |> countRobotsInRow yRow | |
let robotMap : Map<int, int> = | |
countedRobots | |
|> Map.ofList | |
[ 0 .. (width - 1) ] | |
|> List.map (fun x -> Map.tryFind x robotMap |> Option.map string |> Option.defaultValue ".") | |
|> String.concat "" | |
let visualize width height robots = | |
[ 0 .. (height - 1) ] |> List.map (createRow robots width) |> String.concat "\n" |> printfn "%s" | |
let readLines = | |
File.ReadAllLines | |
>> Array.filter (fun line -> line <> String.Empty) | |
>> Array.toList | |
let toQuadrant predicate robots = | |
robots |> List.map (fun r -> r.p) |> List.filter predicate | |
let calculateSafetyFactor width height robots = | |
let midRow = height / 2 | |
let midCol = width / 2 | |
let nw = robots |> toQuadrant (fun (x, y) -> x < midCol && y < midRow) |> countPositions | |
let ne = robots |> toQuadrant (fun (x, y) -> x > midCol && y < midRow) |> countPositions | |
let sw = robots |> toQuadrant (fun (x, y) -> x < midCol && y > midRow) |> countPositions | |
let se = robots |> toQuadrant (fun (x, y) -> x > midCol && y > midRow) |> countPositions | |
nw * ne * sw * se | |
let neighbours (x, y) = | |
[ ((x - 1), (y - 1)); ((x, y - 1)); ((x + 1), (y - 1)) | |
((x - 1), y); ((x + 1), y) | |
((x - 1), (y + 1)); ((x, y + 1)); ((x + 1), (y + 1)) ] | |
let rec fill (possiblePositionsLeft : Set<Pos>) (positionsToAdd : Set<Pos>) (connected : Set<Pos>) = | |
if Set.count positionsToAdd = 0 then | |
(connected, possiblePositionsLeft) | |
else | |
let nextConnected = Set.union connected positionsToAdd | |
let nextPositionsToAdd = | |
positionsToAdd | |
|> Set.toList | |
|> List.collect neighbours | |
|> Set.ofList | |
|> Set.filter (fun p -> (Set.contains p possiblePositionsLeft)) | |
let possible = Set.difference possiblePositionsLeft nextPositionsToAdd | |
fill possible nextPositionsToAdd nextConnected | |
let fillFromPosition (positions : Set<Pos>) startPos = | |
let positionsToAdd = Set.empty |> Set.add startPos | |
fill positions positionsToAdd Set.empty | |
let tryFindTree (positions : Pos list) = | |
let rec loop posList = | |
match posList with | |
| [] -> None | |
| pos :: remaining -> | |
let possible = Set.ofList remaining | |
let (connected, updatedPossible) = fillFromPosition possible pos | |
if Set.count connected > 100 then | |
Some connected | |
else | |
let rem = updatedPossible |> Set.toList | |
loop rem | |
loop positions | |
let findTree width height robots = | |
let clock = Stopwatch.StartNew() | |
let rec loop seconds robots = | |
if seconds > 0 && seconds % 100 = 0 then printfn "Simulated %d 'seconds' in %d seconds" seconds ((int) clock.Elapsed.TotalSeconds) | |
let positions = robots |> List.map (fun r -> r.p) | |
match tryFindTree positions with | |
| Some connected -> | |
printfn "Is this a tree? After %d 'seconds'." seconds | |
visualize width height robots | |
| None -> | |
let moved = simulate width height 1 robots | |
loop (seconds + 1) moved | |
loop 0 robots | |
let run width height searchForTree fileName = | |
let lines = readLines fileName | |
let robots = lines |> List.map parseRobot | |
let moved = robots |> simulate width height 100 | |
moved |> calculateSafetyFactor width height |> printfn "%A" | |
if searchForTree then findTree width height robots | |
run 11 7 false "sample" | |
run 101 103 true "input" | |
//run 101 103 true "not-a-tree.txt" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment