-
Notifications
You must be signed in to change notification settings - Fork 10
/
Day20.fs
121 lines (102 loc) · 3.96 KB
/
Day20.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
module Year2019Day20
open AdventOfCode.FSharp.Common
open System.Collections.Generic
type Cell = Wall | Open | Outside of char
let parseCell =
function
| '#' -> Wall
| '.' -> Open
| c -> Outside c
type Maze =
{ Grid : Cell [] []
Entrance : int * int
Exit : int * int
Portals: Map<int * int, int * int>;
UnmatchedPortals : Map<string, int * int> }
static member create grid = { Grid = grid; Entrance = (-1, -1); Exit = (-1, -1); Portals = Map.empty; UnmatchedPortals = Map.empty }
let portalNameLocations (x, y) =
[|
(x, y - 2), (x, y - 1)
(x, y + 1), (x, y + 2)
(x - 2, y), (x - 1, y)
(x + 1, y), (x + 2, y)
|]
let parseMaze (grid : Cell [] []) =
let width = grid.[0].Length
let height = grid.Length
let cellAt (x, y) = grid.[y].[x]
let tryGetPortalName =
portalNameLocations
>> Array.choose (fun (p1, p2) ->
match cellAt p1, cellAt p2 with
| Outside a, Outside b -> charsToStr [a; b] |> Some
| _ -> None)
>> Array.tryHead
(Maze.create grid, seq { for y in 0 .. height - 1 do for x in 0 .. width - 1 -> (x, y) })
||> Seq.fold (fun m pos ->
match cellAt pos with
| Open ->
match tryGetPortalName pos with
| Some "AA" -> { m with Entrance = pos }
| Some "ZZ" -> { m with Exit = pos }
| Some s ->
match Map.tryFind s m.UnmatchedPortals with
| Some dest -> { m with Portals = m.Portals |> Map.add pos dest |> Map.add dest pos }
| None -> { m with UnmatchedPortals = Map.add s pos m.UnmatchedPortals }
| None -> m
| _ -> m)
let parse = parseEachLine (Seq.map parseCell >> Seq.toArray) >> Seq.toArray >> parseMaze
let astar start dest heuristic getEdges =
let seen = new HashSet<_>()
let rec astar' fringe =
if Set.isEmpty fringe then None
else
let (_, negDist : int, vertex) as minElem = Set.minElement fringe
let fringe' = Set.remove minElem fringe
let dist = -negDist
if seen.Contains(vertex) then astar' fringe'
elif vertex = dest then Some dist
else
seen.Add(vertex) |> ignore
getEdges vertex
|> Array.map (fun v ->
let dist = dist + 1
(heuristic v) + dist, -dist, v)
|> Set.ofArray
|> Set.union fringe'
|> astar'
astar' (Set.ofList [0, 0, start])
let bfs start dest getEdges = astar start dest (fun _ -> 0) getEdges
let neighbours (x, y) = [| (x + 1, y); (x - 1, y); (x, y + 1); (x, y - 1) |]
let solvePart1 maze =
let cellAt (x, y) = maze.Grid.[y].[x]
let getEdges pos =
neighbours pos
|> Array.choose (fun neighbour ->
match cellAt neighbour with
| Open -> Some neighbour
| Outside _ -> Map.tryFind pos maze.Portals
| Wall -> None)
bfs maze.Entrance maze.Exit getEdges
|> Option.get
let solvePart2 maze =
let width = maze.Grid.[0].Length
let height = maze.Grid.Length
let isOuterEdge (x, y) =
x <= 2 || x >= (width - 3) || y <= 2 || y >= (height - 3)
let cellAt (x, y) = maze.Grid.[y].[x]
let heuristic (level, _) = level
let getEdges (level, pos) =
neighbours pos
|> Array.choose (fun neighbour ->
match cellAt neighbour with
| Open -> Some (level, neighbour)
| Outside _ ->
match Map.tryFind pos maze.Portals with
| Some portalDest when isOuterEdge portalDest -> Some (level + 1, portalDest)
| Some portalDest when level > 0 -> Some (level - 1, portalDest)
| _ -> None
| Wall -> None)
astar (0, maze.Entrance) (0, maze.Exit) heuristic getEdges
|> Option.get
let solver = { parse = parse; part1 = solvePart1; part2 = solvePart2 }