Created
October 30, 2019 23:13
-
-
Save xdaDaveShaw/faad35ccd89e72a221e2a1d428e6b321 to your computer and use it in GitHub Desktop.
Solution to the Robot Journey coding challenge
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
//A solution to Mike Hadlow's Journey coding challenge in F# | |
//Details here: https://github.com/mikehadlow/Journeys | |
//Inspiration from Mark Seemann's Haskell solution: | |
//https://blog.ploeh.dk/2019/10/28/a-basic-haskell-solution-to-the-robot-journeys-coding-exercise/ | |
[<Literal>] | |
let InputText = """1 1 E | |
RFRFRFRF | |
1 1 E | |
3 2 N | |
FRRFLLFFRRFLL | |
3 3 N | |
0 3 W | |
LLFFFLFLFL | |
2 4 S""" | |
//Types, based on the approach by Ploeh - before I stopped reading, and started solving :) | |
type Direction = | |
| North | |
| East | |
| South | |
| West | |
type Robot = { | |
Position: int * int | |
Facing: Direction } | |
type Command = | |
| TurnLeft | |
| TurnRight | |
| MoveForward | |
type Journey = { | |
Start: Robot | |
Commands: Command list | |
End: Robot } | |
module Parser = | |
open System | |
//Functional String.Split Helper | |
let split (by: string) (str: string) = | |
str.Split(by.ToCharArray(), StringSplitOptions.RemoveEmptyEntries) | |
let parseDirection = function | |
| "N" -> North | |
| "E" -> East | |
| "S" -> South | |
| "W" -> West | |
| _ -> failwith "Unexpected direction" | |
let parseRobot (robotPos: string) = | |
let parts = robotPos |> split " " | |
{ Position = int parts.[0], int parts.[1] | |
Facing = parseDirection parts.[2] } | |
let parseCommand = function | |
| 'L' -> TurnLeft | |
| 'R' -> TurnRight | |
| 'F' -> MoveForward | |
| _ -> failwith "Unexpected command" | |
let parseCommands (commands: string) = | |
commands | |
|> Seq.map parseCommand | |
|> Seq.toList | |
let parseJourney (parts: string seq) = | |
let parts = Seq.toList parts //Convert to list to allow indexing | |
{ Start = parseRobot parts.[0] | |
Commands = parseCommands parts.[1] | |
End = parseRobot parts.[2] } | |
let parseFile file = | |
file | |
|> split Environment.NewLine | |
|> Seq.toList | |
|> List.chunkBySize 3 | |
|> List.map parseJourney | |
#r "FParsec\\FParsecCS.dll" | |
#r "FParsec\\FParsec.dll" | |
module CParser = | |
open FParsec | |
let pDirection : Parser<Direction, unit> = | |
(pchar 'N' >>% North) | |
<|>(pchar 'E' >>% East) | |
<|>(pchar 'S' >>% South) | |
<|>(pchar 'W' >>% West) | |
let pRobot = | |
pint32 .>> spaces >>= fun x -> | |
pint32 .>> spaces >>= fun y -> | |
pDirection >>= fun direcetion -> | |
preturn { Position = x, y; | |
Facing = direcetion; } | |
let pCommand : Parser<Command, unit> = | |
(pchar 'F' >>% MoveForward) | |
<|>(pchar 'L' >>% TurnLeft) | |
<|>(pchar 'R' >>% TurnRight) | |
let pCommands = | |
many pCommand | |
let pJourney = | |
pRobot .>> newline >>= fun start -> | |
pCommands .>> newline >>= fun commands -> | |
pRobot >>= fun ``end`` -> | |
preturn { Start = start; | |
Commands = commands; | |
End = ``end``; } | |
let pJournies = | |
sepBy pJourney (newline >>. newline) | |
let parseFile txt = | |
match run pJournies txt with | |
| Success(res, _, _) -> res | |
| Failure(f, _, _) -> failwith f | |
module Execution = | |
let turnRight = function | |
| North -> East | |
| East -> South | |
| South -> West | |
| West -> North | |
let turnLeft = function | |
| North -> West | |
| East -> North | |
| South -> East | |
| West -> South | |
let moveForward (x, y) direction = | |
match direction with | |
| North -> x, y+1 //So Facing North increments "y" - ¯\_(ツ)_/¯ | |
| East -> x+1, y | |
| South -> x, y-1 | |
| West -> x-1, y | |
let executeCommand robot command = | |
match command with | |
| TurnRight -> { robot with Facing = turnRight robot.Facing } | |
| TurnLeft -> { robot with Facing = turnLeft robot.Facing } | |
| MoveForward -> { robot with Position = moveForward robot.Position robot.Facing } | |
let debugCommand robot command = | |
printfn "Robot %A" robot | |
printfn "Command %A" command | |
executeCommand robot command | |
let validateJourney journey = | |
let actual = | |
(journey.Start, journey.Commands) | |
//||> List.fold debugCommand //Swap with below to see the state *before* each execution | |
||> List.fold executeCommand | |
if (journey.End = actual) then | |
"Success" | |
else | |
sprintf "Expected %A\r\nActual %A" journey.End actual | |
//Swap between manual and parser combinator. | |
//let parseFile = Parser.parseFile | |
let parseFile = CParser.parseFile | |
let output = | |
InputText //File.ReadAllText | |
|> parseFile | |
|> Seq.map Execution.validateJourney | |
printfn "%A" output |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment