I have the following piece of code which is FSI tested and works.
However, I want to trim it as much as possible (not only the comments, which are there for my own future reference) and make it as idiomatic as possible.

The gist of the code is the first step of the good ole' "Robot on a grid" kata: receive a path to a file holding strings, only some of which are valid commands.

Verify the path and sanitize the file, where for my needs "sanitize" means:

  • Remove all invalid strings (i.e. not valid commands).
    • Valid commands are defined in the active pattern in the code.
  • Remove all invalid placement commands, regardless of their position in the file.
    • Invalid in the sense that they place the robot outside the grid's bounds.
  • Once a valid placement command was found, remove all subsequent valid placement commands while keeping the first.

The async is there because I'm learning how to use it. It isn't material to the sanitation algorithm.

P.S. By "trim" I don't mean my (some would say) verbose function names. In the days of IDEs with IntelliSense and tab-completion, that's a non-issue for me and I much prefer clarity over succinctness.

open System.IO
open System.Text.RegularExpressions

let validatePathInsecurely filePath =
    match File.Exists filePath with
    // Decided to go with an exception since the path to the file is supplied to the function as a parameter, not queried from the user at runtime
    // so, if the path is wrong, there is no way of fixing it in runtime, and since the application can't operate a non-existing file... BOOM!
    | false -> failwith "Path specified is non existent, you don't have permissions or another error occured"
    | true -> filePath

let onlyOneCorretPlaceAsync xmax ymax filePath = async {
    // Tried using this code first:
    // ```
    // let allowedCommands =
    //     [ "place"; "advance"; "turn right"; "turn left" ]
    // ```
    // In hindsight this is wrong since the `place` command MUST also take a facing direction and X/Y coordinates (that may be any natural number).
    // We'll limit it to max 999-by-999 though, for this implementation.
    let (|AllowedCommands|_|) candidate =
        let pattern = Regex @"(place (?:north|south|east|west) \d{3} \d{3})|(advance)|(turn right)|(turn left)"
        let matches = pattern.Match candidate
        if matches.Success then matches.Groups.[1].Value.Trim() |> Some else None

    let onlyValidStrings filePath =
        File.ReadAllLines filePath
        |> Array.toList
        |> List.map (fun term -> term.ToLower())
        |> List.filter (fun term ->
                match term with
                | AllowedCommands term -> true
                | _ -> false)

    let sanitizeToFirstValidPlacement (content: string list) =
        content |> List.skipWhile (fun elem ->
                                        // By now we KNOW all strings in the file are valid-looking strings, however placement command might be invalid due to bounds.
                                        // No command is valid before the robot has been placed in a valid position!
                                        let splitElement = elem.Split()
                                        splitElement.Length <> 4 ||
                                        (int)(Array.item 2 splitElement) > xmax ||
                                        (int)(Array.item 3 splitElement) > ymax)

    let h::t = (onlyValidStrings filePath |> sanitizeToFirstValidPlacement)
    let sanitizeRestOfList (rest: string list) =
        |> List.filter (fun elem ->
                            Array.item 0 (elem.Split()) <> "place")

    let completeSanitizedList = h :: sanitizeRestOfList t

    return completeSanitizedList
  • \$\begingroup\$ The sanitizeToFirstValidPlacement function is never used 🤔 \$\endgroup\$ Sep 26, 2019 at 19:52
  • \$\begingroup\$ @TheQuickBrownFox, yes, you're right, I did some refactoring prior to uploading, and didn't rename in all places. I forgot to replace the name in the let h::t... trimToFirstValidPlacement. I'll edit the snippet right away. \$\endgroup\$
    – user52901
    Sep 27, 2019 at 3:45
  • 1
    \$\begingroup\$ @Emma, sure: suppose the command file has a bunch of strings: "hi", "F# world!", "turn right", "place north 1000 1000". Now, if my grid size is 10X10, all those would be thrown out (the "place" command is out of bounds). Next, the file has "place south 1 4", "turn left", "advance". This is the gist of it. \$\endgroup\$
    – user52901
    Sep 27, 2019 at 3:48
  • \$\begingroup\$ @200_success, thanks for editing the post. Your edits make it more on the point, without a shadow of a doubt! \$\endgroup\$
    – user52901
    Sep 27, 2019 at 3:49
  • \$\begingroup\$ Could you provide a minimal use case with valid/invalid input and the expected output? \$\endgroup\$
    – user73941
    Sep 27, 2019 at 14:36


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.