This commit is contained in:
Patrick Stevens
2022-12-07 13:12:58 +00:00
committed by GitHub
parent ef903b33a9
commit ed0e191990
7 changed files with 1259 additions and 1 deletions

View File

@@ -13,6 +13,7 @@
<EmbeddedResource Include="..\AdventOfCode2022.Test\Inputs\Day4.txt" />
<EmbeddedResource Include="..\AdventOfCode2022.Test\Inputs\Day5.txt" />
<EmbeddedResource Include="..\AdventOfCode2022.Test\Inputs\Day6.txt" />
<EmbeddedResource Include="..\AdventOfCode2022.Test\Inputs\Day7.txt" />
</ItemGroup>
<ItemGroup>

View File

@@ -20,7 +20,7 @@ module Program =
[<EntryPoint>]
let main _ =
let days = Array.init 6 (fun day -> readResource $"Day%i{day + 1}.txt")
let days = Array.init 7 (fun day -> readResource $"Day%i{day + 1}.txt")
let inline day (i : int) = days.[i - 1]
@@ -58,6 +58,11 @@ module Program =
printfn "%i" (Day6.part1 day6)
printfn "%i" (Day6.part2 day6)
do
let day7 = day(7).Split '\n'
printfn "%i" (Day7.part1 day7)
printfn "%i" (Day7.part2 day7)
time.Stop ()
printfn $"Took %i{time.ElapsedMilliseconds}ms"
0

View File

@@ -14,12 +14,14 @@
<Compile Include="Day4.fs" />
<Compile Include="Day5.fs" />
<Compile Include="Day6.fs" />
<Compile Include="Day7.fs" />
<EmbeddedResource Include="Inputs\Day1.txt" />
<EmbeddedResource Include="Inputs\Day2.txt" />
<EmbeddedResource Include="Inputs\Day3.txt" />
<EmbeddedResource Include="Inputs\Day4.txt" />
<EmbeddedResource Include="Inputs\Day5.txt" />
<EmbeddedResource Include="Inputs\Day6.txt" />
<EmbeddedResource Include="Inputs\Day7.txt" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,54 @@
namespace AdventOfCode2022.Test
open System
open NUnit.Framework
open FsUnitTyped
open AdventOfCode2022
[<TestFixture>]
module TestDay7 =
let input =
"""$ cd /
$ ls
dir a
14848514 b.txt
8504156 c.dat
dir d
$ cd a
$ ls
dir e
29116 f
2557 g
62596 h.lst
$ cd e
$ ls
584 i
$ cd ..
$ cd ..
$ cd d
$ ls
4060174 j
8033020 d.log
5626152 d.ext
7214296 k
"""
[<Test>]
let ``Part 1, given`` () =
input.Split '\n' |> Day7.part1 |> shouldEqual 95437
[<Test>]
let ``Part 1`` () =
let input = Assembly.readResource "Day7.txt"
Day7.part1 (input.Split '\n') |> shouldEqual 1886043
[<Test>]
let ``Part 2, given`` () =
Day7.part2 (input.Split '\n') |> shouldEqual 24933642
[<Test>]
let ``Part 2`` () =
let input = Assembly.readResource "Day7.txt"
Day7.part2 (input.Split '\n') |> shouldEqual 3842121

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@
<Compile Include="Day4.fs" />
<Compile Include="Day5.fs" />
<Compile Include="Day6.fs" />
<Compile Include="Day7.fs" />
</ItemGroup>
</Project>

181
AdventOfCode2022/Day7.fs Normal file
View File

@@ -0,0 +1,181 @@
namespace AdventOfCode2022
open System.Collections.Generic
open System
type Day7Entry =
| Directory of string
| File of string * int
type Day7Command =
| Ls of Day7Entry IReadOnlyList
| Cd of destination : string
[<RequireQualifiedAccess>]
module Day7 =
let parse (lines : IEnumerator<string>) =
seq {
if not (lines.MoveNext ()) then
failwith "empty enumerator"
let mutable isDone = false
while not isDone do
let line = lines.Current.TrimEnd ()
if String.IsNullOrEmpty line then
isDone <- true
else
match line.[0] with
| '$' ->
if line.[1] <> ' ' then
failwith "expected space"
match line.[2..3] with
| "cd" ->
yield Day7Command.Cd line.[5..]
if not (lines.MoveNext ()) then
failwith "expected an ls after a cd"
| "ls" ->
let outputs = ResizeArray ()
while lines.MoveNext ()
&& not (String.IsNullOrEmpty lines.Current)
&& lines.Current.[0] <> '$' do
let current = lines.Current.TrimEnd ()
match current.[0] with
| 'd' ->
if current.[0..3] <> "dir " then
failwith "expected directory"
outputs.Add (Day7Entry.Directory current.[4..])
| _ ->
match current.Split ' ' with
| [| size ; file |] -> outputs.Add (Day7Entry.File (file, int size))
| _ -> failwith "unexpected spaces"
yield Day7Command.Ls outputs
| _ -> failwith "unrecognised command"
| _ -> failwith "should have been a command"
}
type Path = string list
type private Directory =
{
Path : Path
SubDirs : string list
Files : Map<string, int>
}
let private tree (commands : Day7Command seq) : Map<Path, Directory> =
let dirs, _ =
(([], []), commands)
||> Seq.fold (fun (dirs, parentPath) command ->
match command with
| Day7Command.Ls outputs ->
let subDirs, files =
(([], Map.empty), outputs)
||> Seq.fold (fun (subDirs, files) output ->
match output with
| Day7Entry.Directory d -> d :: subDirs, files
| Day7Entry.File (name, size) -> subDirs, (Map.add name size files)
)
let current =
{
Path = parentPath
SubDirs = subDirs
Files = files
}
let dirs = current :: dirs
dirs, parentPath
| Day7Command.Cd dir ->
let newPath =
match dir, parentPath with
| "..", _ :: rest -> rest
| "..", [] -> failwith "can't cd above root"
| _ -> dir :: parentPath
dirs, newPath
)
dirs |> Seq.map (fun dir -> dir.Path, dir) |> Map.ofSeq
let rec private cata<'ret>
(atFile : string -> int -> 'ret)
(atDir : Path -> 'ret list -> 'ret)
(startAt : Path)
(dirs : Map<Path, Directory>)
=
let dir =
match dirs.TryGetValue startAt with
| false, _ -> failwithf "could not find key: %+A" startAt
| true, v -> v
let fileResults =
dir.Files
|> Seq.map (fun (KeyValue (name, size)) -> atFile name size)
|> List.ofSeq
let dirResults =
dir.SubDirs
|> List.map (fun subDir -> cata atFile atDir (subDir :: startAt) dirs)
atDir startAt (fileResults @ dirResults)
let private totalSize =
cata (fun _ i -> i) (fun _ results -> List.sum results) [ "/" ]
let mergeTwo (m1 : Map<_, _>) (m2 : Map<_, _>) =
(m1, m2) ||> Map.fold (fun m k v -> Map.add k v m)
let mergeMaps (maps : Map<_, _> seq) =
(Map.empty, maps) ||> Seq.fold (fun result newMap -> mergeTwo result newMap)
let private cumulativeDirectorySizes =
let doFile (_name : string) (size : int) : int * Map<Path, int> = size, Map.empty
let doDir (path : Path) (subResults : (int * Map<Path, int>) list) : int * Map<Path, int> =
let sum = subResults |> Seq.map fst |> Seq.sum
let map = subResults |> Seq.map snd |> mergeMaps
sum, Map.add path sum map
cata doFile doDir [ "/" ]
let part1 (lines : string seq) : int =
let dirs =
seq {
use enum = lines.GetEnumerator ()
yield! parse enum
}
|> tree
let _totalSize, results = cumulativeDirectorySizes dirs
results
|> Seq.choose (fun (KeyValue (_, size)) -> if size <= 100000 then Some size else None)
|> Seq.sum
let part2 (lines : string seq) : int =
let dirs =
seq {
use enum = lines.GetEnumerator ()
yield! parse enum
}
|> tree
let totalSize, results = cumulativeDirectorySizes dirs
let unused = 70000000 - totalSize
let required = 30000000 - unused
results
|> Seq.choose (fun (KeyValue (path, size)) -> if size >= required then Some size else None)
|> Seq.min