Day 18
authorUmmon <greg.burri@gmail.com>
Mon, 18 Dec 2017 15:00:39 +0000 (16:00 +0100)
committerUmmon <greg.burri@gmail.com>
Mon, 18 Dec 2017 15:00:39 +0000 (16:00 +0100)
AdventOfCode2017/AdventOfCode2017.fsproj
AdventOfCode2017/Day18Part1.fs [new file with mode: 0644]
AdventOfCode2017/Day18Part2.fs [new file with mode: 0644]
AdventOfCode2017/Program.fs
Tests/Day18 tests.fs [new file with mode: 0644]
Tests/Tests.fsproj

index 16d2ffd..2d2f009 100644 (file)
@@ -25,7 +25,7 @@
     <PlatformTarget>AnyCPU</PlatformTarget>
     <DocumentationFile>bin\$(Configuration)\$(AssemblyName).XML</DocumentationFile>
     <Prefer32Bit>true</Prefer32Bit>
-    <StartArguments>16</StartArguments>
+    <StartArguments>18</StartArguments>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
     <DebugType>pdbonly</DebugType>
@@ -75,6 +75,8 @@
     <Compile Include="Day15.fs" />
     <Compile Include="Day16.fs" />
     <Compile Include="Day17.fs" />
+    <Compile Include="Day18Part1.fs" />
+    <Compile Include="Day18Part2.fs" />
     <Compile Include="Program.fs" />
     <None Include="App.config" />
     <Content Include="Data\day01.input">
     <Content Include="Data\day17.input">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="Data\day18.input">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="packages.config" />
   </ItemGroup>
   <ItemGroup>
diff --git a/AdventOfCode2017/Day18Part1.fs b/AdventOfCode2017/Day18Part1.fs
new file mode 100644 (file)
index 0000000..7bdf670
--- /dev/null
@@ -0,0 +1,54 @@
+module AdventOfCode2017.Day18Part1
+
+open System
+
+type From =
+    | FromReg of char
+    | FromValue of int64
+
+type Instruction =
+    | Sound of From
+    | Set of char * From
+    | Add of char * From
+    | Mul of char * From
+    | Mod of char * From
+    | Recover of char
+    | Jump of From * From
+
+let parseInput (lines : string[]) : Instruction[] =
+    let readFrom (str : string) = if Char.IsLetter str.[0] then FromReg str.[0] else FromValue (int64 str)
+    lines
+    |> Array.map (
+        fun line ->
+            match line.Split ' ' with
+            | [| "snd"; v |]    -> Sound (readFrom v)
+            | [| "set"; reg; v |] -> Set (reg.[0], readFrom v)
+            | [| "add"; reg; v |] -> Add (reg.[0], readFrom v)
+            | [| "mul"; reg; v |] -> Mul (reg.[0], readFrom v)
+            | [| "mod"; reg; v |] -> Mod (reg.[0], readFrom v)
+            | [| "rcv"; reg |]    -> Recover reg.[0]
+            | [| "jgz"; v1; v2 |] -> Jump (readFrom v1, readFrom v2)
+            | _ -> failwithf "Can't parse line: %s" line
+    )
+
+type Register = Map<char, int64>
+
+let run (instructions : Instruction[]) =
+    let rec exec (register : Register) (cursor : int) (lastSoundPlayed : int64) : int64 =
+        let get = function FromReg reg -> register |> Map.tryFind reg |> Option.defaultValue 0L | FromValue v -> v
+        let set (reg : char) (v : int64) = register |> Map.add reg v
+
+        match instructions.[cursor] with
+        | Sound from -> exec register (cursor + 1) (get from)
+        | Set (reg, from) -> exec (set reg (get from)) (cursor + 1) lastSoundPlayed
+        | Add (reg, from) -> exec (set reg (get (FromReg reg) + get from)) (cursor + 1) lastSoundPlayed
+        | Mul (reg, from) -> exec (set reg (get (FromReg reg) * get from)) (cursor + 1) lastSoundPlayed
+        | Mod (reg, from) -> exec (set reg (get (FromReg reg) % get from)) (cursor + 1) lastSoundPlayed
+        | Recover reg ->
+            if lastSoundPlayed <> 0L && get (FromReg reg) <> 0L then
+                lastSoundPlayed
+            else
+                exec register (cursor + 1) lastSoundPlayed
+        | Jump (from1, from2) -> exec register (cursor + if get from1 > 0L then get from2 |> int else 1) lastSoundPlayed
+
+    exec Map.empty 0 0L
diff --git a/AdventOfCode2017/Day18Part2.fs b/AdventOfCode2017/Day18Part2.fs
new file mode 100644 (file)
index 0000000..58cbf84
--- /dev/null
@@ -0,0 +1,84 @@
+module AdventOfCode2017.Day18Part2
+
+open System
+open System.Threading
+
+type From =
+    | FromReg of char
+    | FromValue of int64
+
+type Instruction =
+    | Receive of char
+    | Send of From
+    | Set of char * From
+    | Add of char * From
+    | Mul of char * From
+    | Mod of char * From
+    | Jump of From * From
+
+let parseInput (lines : string[]) : Instruction[] =
+    let readFrom (str : string) = if Char.IsLetter str.[0] then FromReg str.[0] else FromValue (int64 str)
+    lines
+    |> Array.map (
+        fun line ->
+            match line.Split ' ' with
+            | [| "snd"; v |]      -> Send (readFrom v)
+            | [| "set"; reg; v |] -> Set (reg.[0], readFrom v)
+            | [| "add"; reg; v |] -> Add (reg.[0], readFrom v)
+            | [| "mul"; reg; v |] -> Mul (reg.[0], readFrom v)
+            | [| "mod"; reg; v |] -> Mod (reg.[0], readFrom v)
+            | [| "rcv"; reg |]    -> Receive reg.[0]
+            | [| "jgz"; v1; v2 |] -> Jump (readFrom v1, readFrom v2)
+            | _ -> failwithf "Can't parse line: %s" line
+    )
+
+type Register = Map<char, int64>
+
+type Agent (instructions : Instruction[], id : int) as this =
+    let mutable nbSent = 0
+    let finishedEvent = new AutoResetEvent false
+    let mailbox =
+        new MailboxProcessor<int64> (
+            fun inbox ->
+                let rec exec (register : Register) (cursor : int) : Async<unit> =
+                    let get = function FromReg reg -> register |> Map.tryFind reg |> Option.defaultValue 0L | FromValue v -> v
+                    let set (reg : char) (v : int64) = register |> Map.add reg v
+                    async {
+                        match instructions.[cursor] with
+                        | Send from ->
+                            nbSent <- nbSent + 1
+                            this.Other.Value.Post (get from)
+                            return! exec register (cursor + 1)
+                        | Set (reg, from) -> return! exec (set reg (get from)) (cursor + 1)
+                        | Add (reg, from) -> return! exec (set reg (get (FromReg reg) + get from)) (cursor + 1)
+                        | Mul (reg, from) -> return! exec (set reg (get (FromReg reg) * get from)) (cursor + 1)
+                        | Mod (reg, from) -> return! exec (set reg (get (FromReg reg) % get from)) (cursor + 1)
+                        | Receive reg ->
+                            let! value = inbox.TryReceive 100
+                            match value with
+                            | Some value -> return! exec (set reg value) (cursor + 1)
+                            | None -> finishedEvent.Set () |> ignore
+                        | Jump (from1, from2) ->
+                            return! exec register (cursor + if get from1 > 0L then get from2 |> int else 1)
+                    }
+                exec (Map.ofList [ 'p', int64 id ]) 0
+        )
+
+    member val Other : MailboxProcessor<int64> option = None with get, set
+    member this.Start () = mailbox.Start ()
+    member this.Mailbox = mailbox
+    member this.NbSent =
+        finishedEvent.WaitOne () |> ignore
+        nbSent
+
+let run (instructions : Instruction[]) =
+    let agent0 = Agent (instructions, 0)
+    let agent1 = Agent (instructions, 1)
+
+    agent0.Other <- Some agent1.Mailbox
+    agent1.Other <- Some agent0.Mailbox
+
+    agent0.Start ()
+    agent1.Start ()
+
+    agent1.NbSent
\ No newline at end of file
index 299f276..f845472 100644 (file)
@@ -79,6 +79,10 @@ let day17 () =
     let input = File.ReadAllText "Data/day17.input" |> int
     sprintf "part1 = %A, part2 = %A" (Day17.spinLock1 input) (Day17.spinLock2 input)
 
+let day18 () =
+    let input = File.ReadAllLines "Data/day18.input"
+    sprintf "part1 = %A, part2 = %A" (Day18Part1.run (Day18Part1.parseInput input)) (Day18Part2.run (Day18Part2.parseInput input))
+
 let doDay (n : int) =
     let sw = Diagnostics.Stopwatch ()
     sw.Start ()
@@ -101,6 +105,7 @@ let doDay (n : int) =
         | 15 -> day15 ()
         | 16 -> day16 ()
         | 17 -> day17 ()
+        | 18 -> day18 ()
         | _ -> raise <| NotImplementedException ()
     printfn "Result of day %i: %s (time : %i ms)" n result sw.ElapsedMilliseconds
 
diff --git a/Tests/Day18 tests.fs b/Tests/Day18 tests.fs
new file mode 100644 (file)
index 0000000..4a5576e
--- /dev/null
@@ -0,0 +1,41 @@
+namespace AdventOfCode2017.Tests
+
+open System
+open Xunit
+open Xunit.Abstractions
+open Swensen.Unquote
+
+open AdventOfCode2017
+
+type ``Day18 tests`` (output : ITestOutputHelper) =
+
+    [<Fact>]
+    let ``(Part1) From web page`` () =
+        let input =
+            [|
+                "set a 1"
+                "add a 2"
+                "mul a a"
+                "mod a 5"
+                "snd a"
+                "set a 0"
+                "rcv a"
+                "jgz a -1"
+                "set a 1"
+                "jgz a -2"
+            |]
+        Day18Part1.run (Day18Part1.parseInput input) =! 4L
+
+    [<Fact>]
+    let ``(Part2) From web page`` () =
+        let input =
+            [|
+                "snd 1"
+                "snd 2"
+                "snd p"
+                "rcv a"
+                "rcv b"
+                "rcv c"
+                "rcv d"
+            |]
+        Day18Part2.run (Day18Part2.parseInput input) =! 3
\ No newline at end of file
index c9a9bd6..32cfbf5 100644 (file)
@@ -72,6 +72,7 @@
     <Compile Include="Day15 tests.fs" />
     <Compile Include="Day16 tests.fs" />
     <Compile Include="Day17 tests.fs" />
+    <Compile Include="Day18 tests.fs" />
     <Content Include="packages.config" />
     <Content Include="App.config" />
   </ItemGroup>