This is definitely great for functional programming and F #. There are many possible approaches. I think the pad decision is probably the most straightforward, and that is a really good starting point. If you need something more general then Huusom's solution is pretty nice.
There is an even more general approach, which is to create a domain (DSL) for defining patterns in an array. This is a more advanced functional technique, but it works great for your example. If you did, you could express rather complex patterns in a very concise way. Here is an example:
// Create a detector that tests if a location // contains 1 and returns 'false' when out of range let one = border false (equals 1) // A shape detector for your pattern let pattern = around (0, 0) one <&> around (1, 0) one <&> around (-1, 1) one // Test pattern with any rotation: Combine // 4 possible rotations with logical or. let any = pattern <|> rotate pattern <|> rotate (rotate pattern) <|> rotate (rotate (rotate pattern))
This example uses various primitives to create a declarative specification for the template. The value any is a function that you can run to check if a pattern is occurring in a specific place. It handles all the rotation of the template, and also checks the boundaries. You will also need to add mirror templates, but this will be a fairly simple extension.
An explanation of the implementation will probably require a full blog post, but here is the code with comments, which should be readable:
/// A type that represents a function that tests /// whether an array contains some pattern at a /// specified location. It gets the location to /// test & the array as arguments and returns bool. type ShapeDetector = SD of (int -> int -> int[,] -> bool) /// A primitive that tests whether the value at the /// current location contains a value 'v' let equals v = SD (fun xy arr -> arr.[x,y] = v) /// A combinator that takes 'ShapeDetector' and /// creates a new one that returns 'def' when /// accessing outside of the array bounds let border def (SD f) = SD (fun xy arr -> if x < 0 || y < 0 || x >= arr.GetLength(0) || y >= arr.GetLength(1) then def else fxy arr) /// A combinator that calls a given ShapeDetector /// at a location specified by offset dx, dy let around (dx, dy) (SD f) = SD (fun xy arr -> f (x + dx) (y + dy) arr) /// A combinator that takes a ShapeDetector and /// builds a new one, which is rotated by 90 degrees let rotate (SD f) = SD (fun xy arr -> f -yx arr) /// Creates a shape detector that succeeds only /// when both of the arguments succeed. let (<&>) (SD f1) (SD f2) = SD (fun xy arr -> f1 xy arr && f2 xy arr) /// Creates a shape detector that succeeds /// when either of the arguments succeed. let (<|>) (SD f1) (SD f2) = SD (fun xy arr -> f1 xy arr || f2 xy arr)
Finally, here is an example that runs a pattern detector on a sample 2D array:
// Create a 2D array as a sample input let inp = array2D [ [ 0; 0; 1 ] [ 0; 1; 0 ] [ 0; 1; 0 ] ] // Get the underlying function and run it // for all possible indices in the array let (SD f) = any for x in 0 .. 2 do for y in 0 .. 2 do printfn "%A %A" (x, y) (fxy inp)
Tomas petricek
source share