Skip to content

Commit

Permalink
queue version
Browse files Browse the repository at this point in the history
  • Loading branch information
giuliohome committed Jan 1, 2020
1 parent a4c0d96 commit 98736fb
Show file tree
Hide file tree
Showing 2 changed files with 297 additions and 0 deletions.
120 changes: 120 additions & 0 deletions Day15-IntCode-QueueVersion.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
namespace AoC2019

module IntCode =
open System
open System.IO
open MyQueue

let mem_size = 10000

let readInts (path:string) : int64 [] =
use sw = new StreamReader (path)
sw.ReadToEnd().Split(',')
|> Array.map Int64.Parse

type Status =
{
position: int; finished: bool; mutable suspended: bool;
mutable output: queue<int64>; mutable phase: queue<int64>; base_addr: int}
type Destinations = |Immediate of int64| MemoryDest of int | Skip

let getParamVal (mode:int64) (memory: byref<int64[]>) (pos:int) (base_addr:int) =
match mode with
| 0L -> memory.[ (int)memory.[pos] ]
| 1L -> memory.[pos]
| 2L -> memory.[base_addr + (int)memory.[pos] ]
| _ -> failwith "wrong par mode"

let runCmd (before: Status) (memory:byref<int64[]>) : Status =
let opcodeStr = memory.[before.position]
let opcode = opcodeStr % 100L
let mode_op3 = opcodeStr / 10000L
if mode_op3 = 1L then failwith "dest must be pos or rel mode"
let mode_op2 =((opcodeStr / 100L) % 100L) / 10L
let mode_op1 =((opcodeStr / 100L) % 100L) % 10L
let dest =
match opcode with
| 1L
| 2L
| 7L
| 8L -> MemoryDest ((int)memory.[before.position + 3] + (if mode_op3 = 2L then before.base_addr else 0))
| 3L -> MemoryDest ((int)memory.[before.position + 1] + (if mode_op1 = 2L then before.base_addr else 0))
| 4L
| 5L
| 6L
| 9L -> Skip
| _ -> failwith "wrong position"

match dest with
| Immediate _ -> failwith "dest can't be immediate"
| MemoryDest dest ->
if dest >= mem_size then failwith "please extend memory"
memory.[dest] <-
match opcode with
| 1L ->
(+)
(getParamVal mode_op1 &memory (before.position + 1) before.base_addr)
(getParamVal mode_op2 &memory (before.position + 2) before.base_addr)
| 2L ->
(*)
(getParamVal mode_op1 &memory (before.position + 1) before.base_addr)
(getParamVal mode_op2 &memory (before.position + 2) before.base_addr)
| 3L ->
let (a, q) = MyQueue.dequeue before.phase
// https://stackoverflow.com/questions/59552476/copyofstruct-not-defined?noredirect=1
// https://github.com/dotnet/fsharp/issues/8069

This comment has been minimized.

Copy link
@giuliohome

giuliohome Jan 2, 2020

Author Owner

This is another example for dotnet/fsharp#8069 based on https://adventofcode.com/2019/day/15
Sic stantibus rebus, it's better to switch from F# to C# Queue<T>.Dequeue
Indeed OCaml - by which F# is inspired - has such sort of queue: see https://github.com/bcc32/advent-of-code-2019/blob/master/15/main.ml#L63


Edit
See also
OCaml - how to create queue
https://stackoverflow.com/a/20647601
The standard Queue module is not a functor. Moreover, it provides imperative-style queues. In other words, the standard enqueue/dequeue operations mutate the state of the queue


From the functional style viewpoint, It's worth considering the
Breadth-first Search in F# (BFS) with queue head and tail (but anyway they are two operations, not a single one, which is better to guarantee code correctness)
Seehttps://stackoverflow.com/a/6068257


A smart idea from haskell pop pattern of @glguy
https://github.com/glguy/advent2019/blob/master/common/Advent/Search.hs#L77
could be rendered by F# active pattern
Anyway, the functional approach doesn't use mutable but immutable queue so a simple let returns the new queue and the head element in one instruction: in that case however, as per haskell above, one needs a recursive bfs. Therefore you need to save everything in the state, in AoC day 15 example also the intcode program and memory: I was not a fan of it in the first place: premature optimization or actually not efficient? Afaik it is not tail recursive, so it might be problematic.

before.phase <- q
a
| 7L -> if ((getParamVal mode_op1 &memory (before.position + 1) before.base_addr)
< (getParamVal mode_op2 &memory (before.position + 2) before.base_addr)
) then 1L else 0L
| 8L -> if ((getParamVal mode_op1 &memory (before.position + 1) before.base_addr)
= (getParamVal mode_op2 &memory (before.position + 2) before.base_addr)
) then 1L else 0L
| _ -> failwith "wrong position"
| Skip -> ()
if opcode = 4L then
before.output <- MyQueue.enqueue before.output (getParamVal mode_op1 &memory (before.position + 1) before.base_addr)
let position =
match opcode with
| 1L
| 2L
| 7L
| 8L -> before.position + 4
| 3L
| 4L
| 9L -> before.position + 2
| 5L -> if ((getParamVal mode_op1 &memory (before.position + 1) before.base_addr) <> (int64) 0)
then (int)(getParamVal mode_op2 &memory (before.position + 2) before.base_addr)
else before.position + 3
| 6L -> if ((getParamVal mode_op1 &memory (before.position + 1) before.base_addr) = (int64) 0)
then (int)(getParamVal mode_op2 &memory (before.position + 2) before.base_addr)
else before.position + 3
| _ -> failwith "wrong position"

let base_addr =
if opcode = 9L
then before.base_addr + (int)(getParamVal mode_op1 &memory (before.position + 1) before.base_addr)
else before.base_addr

let opcodeStrNext = memory.[position]
let opcodeNext = opcodeStrNext % 100L

{
position = position; finished = opcodeNext = 99L;
suspended = (opcodeNext = 3L) && (MyQueue.length before.phase = 0); output = before.output; phase = before.phase; base_addr = base_addr}

let extendMemory (memory:Int64[]) (initial:queue<int64>) : int64[] * Status =
let extended_memory =
[|0 .. mem_size - 1|]
|> Array.mapi(fun i t ->
if i < memory.Length
then memory.[i]
else (int64)0
)
extended_memory, {position = 0; finished = false; suspended = false; output = MyQueue.empty; phase = initial; base_addr= 0}

let bootstrap (input_path:string) (initial:queue<int64>) : int64[] * Status =
let memory = readInts input_path
extendMemory memory initial

177 changes: 177 additions & 0 deletions Day15-Part1-QueueVersion.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
open System
open System.IO
open Checked
open System.Diagnostics
open AoC2019.IntCode
open System.Collections
open MyQueue
//https://adventofcode.com/2019/day/15

type Position = {X:int;Y:int}

[<EntryPoint>]
let main _ =

let sw = Stopwatch()
sw.Start()

let mutable (memory, status) = bootstrap "C:\dev\FSharp\AoC2019\Day15\input.txt" MyQueue.empty
let debug = false
let mutable found = false
let mutable distance = 0
let mutable possible_ways : queue<int64[]> = MyQueue.empty
let mutable visited : Map<Position,char> = Map.empty
let mutable position = {X=0;Y=0}

let track = function
//because of the reply of 2, you know you've found the oxygen system
| 2L -> 'O'
//a reply of 1 means the movement was successful
| 1L -> '.'
//If it replies with 0, you know that location is a wall
| 0L -> '#'
| _ -> failwith "can't track it"


let show (visited:Map<Position,char>) =
let visited_keys = visited |> Map.toArray |> Array.map fst
let y = visited_keys |> Array.map(fun p -> p.Y)
let yMin = y |> Array.min
let yMax = y |> Array.max
let x = visited_keys |> Array.map(fun p -> p.X)
let xMin = x |> Array.min
let xMax = x |> Array.max
Console.Clear()
for y in [|yMin..yMax|] |> Array.rev do
for x in xMin..xMax do
visited
|> Map.tryFind {X=x;Y=y}
|> Option.fold (fun _ c -> c) ' '
|> printf "%c"
printfn ""


//Only four movement commands are understood: north (1), south (2), west (3), and east (4).
let back = function
| 1L -> 2L
| 2L -> 1L
| 3L -> 4L
| 4L -> 3L
| _ -> failwith "wrong direction"
let moveTo = function
| 1L -> fun position -> {position with Y = position.Y + 1}
| 2L -> fun position -> {position with Y = position.Y - 1}
| 3L -> fun position -> {position with X = position.X - 1}
| 4L -> fun position -> {position with X = position.X + 1}
| _ -> failwith "wrong direction"
for move in [1L..4L] do
if found then () else
status.phase <- MyQueue.enqueue status.phase move
status.suspended <- false
while not (status.suspended || status.finished) do
status <- runCmd status &memory
// https://stackoverflow.com/questions/59552476/copyofstruct-not-defined?noredirect=1
// https://github.com/dotnet/fsharp/issues/8069
let output, q = MyQueue.dequeue status.output
status.output <- q
visited <- visited.Add( (position |> moveTo move) , track output )
match output with
//because of the reply of 2, you know you've found the oxygen system
| 2L ->
found <- true
//a reply of 1 means the movement was successful
| 1L ->
position <- position |> moveTo move
distance <- distance + 1
possible_ways <- enqueue possible_ways [|move|]
status.phase <- MyQueue.enqueue status.phase <| back move
status.suspended <- false
while not (status.suspended || status.finished) do
status <- runCmd status &memory
let output, q = MyQueue.dequeue status.output
status.output <- q
if output <> 1L then failwith "path changed"
position <- position |> moveTo (back move)
distance <- distance - 1
//If it replies with 0, you know that location is a wall and that the droid didn't move
| 0L ->
()
| _ -> failwith "wrong output"

let mutable way = [||]
while not found && MyQueue.length possible_ways > 0 do
if debug then show visited
let (way_, possible_ways_) = dequeue possible_ways
// https://github.com/dotnet/fsharp/issues/8069
// https://stackoverflow.com/questions/59552476/copyofstruct-not-defined?noredirect=1&lq=1
way <- way_
possible_ways <- possible_ways_
if position.X <> 0 || position.Y <> 0 || distance <> 0 then failwith "position is not initial"

for move in way do
status.phase <- MyQueue.enqueue status.phase move
status.suspended <- false
while not <| status.suspended || status.finished do
status <- runCmd status &memory
let output, q = MyQueue.dequeue status.output
status.output <- q
if output <> 1L then failwith "path changed"
distance <- distance + 1
position <- position |> moveTo move

for move in [1L..4L] do
if found then () else
if visited.ContainsKey(position |> moveTo move) then () else
status.phase <- MyQueue.enqueue status.phase move
status.suspended <- false
while not (status.suspended || status.finished) do
status <- runCmd status &memory
let output, q = MyQueue.dequeue status.output
status.output <- q
visited <- visited.Add( (position |> moveTo move) , track output )
match output with
//because of the reply of 2, you know you've found the oxygen system
| 2L ->
distance <- distance + 1
found <- true
//a reply of 1 means the movement was successful
| 1L ->
position <- position |> moveTo move
distance <- distance + 1
possible_ways <- enqueue possible_ways <| Array.append way [|move|]
status.phase <- MyQueue.enqueue status.phase <| back move
status.suspended <- false
while not (status.suspended || status.finished) do
status <- runCmd status &memory
let output, q = MyQueue.dequeue status.output
status.output <- q
if output <> 1L then failwith "path changed"
distance <- distance - 1
position <- position |> moveTo (back move)
//If it replies with 0, you know that location is a wall and that the droid didn't move
| 0L ->
()
| _ -> failwith "wrong output"

if not found then
for move in way |> Array.rev do
status.phase <- MyQueue.enqueue status.phase <| back move
status.suspended <- false
while not <| status.suspended || status.finished do
status <- runCmd status &memory
let output, q = MyQueue.dequeue status.output
status.output <- q
if output <> 1L then failwith "path changed"
distance <- distance - 1
position <- position |> moveTo (back move)
if debug then show visited
sw.Stop()

if found then
show visited
printfn "Answer Part 1 is %d" distance
else printfn "Answer Part 1 not found!"

printfn "executed in %d ms" sw.ElapsedMilliseconds
Console.ReadKey() |> ignore
0

0 comments on commit 98736fb

Please sign in to comment.