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) =
rest
|> List.filter (fun elem ->
Array.item 0 (elem.Split()) <> "place")
let completeSanitizedList = h :: sanitizeRestOfList t
return completeSanitizedList
}
sanitizeToFirstValidPlacement
function is never used 🤔 \$\endgroup\$let h::t... trimToFirstValidPlacement
. I'll edit the snippet right away. \$\endgroup\$