Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Traversing an F# 2D Array

Tags:

f#

I'm creating a board(type) game in F#, and am having a bit of trouble traversing the array in a "functional" way.

I have an array that looks, for example, like:

val myArray : int [,] = [[1; 1; 0; 0; 0]
                         [0; 0; 1; 0; 0]
                         [2; 0; 0; 0; 3]
                         [0; 0; 0; 0; 0]
                         [0; 1; 0; 0; 1]]

I want to create a new array based on the above, where:

  1. If the item is > 0, then in the new array should be the number 1
  2. If the item is = 0, then:
    1. If an item to the left or right or above or below is > 1, then in the new array the number should be 2
    2. Otherwise, if an item to the left of right or above or below is = 1, then in the new array the number should be 3
    3. Otherwise, the number should be 4

This should create a new array which looks like:

val myArray : int [,] = [[1; 1; 3; 4; 4]
                         [2; 3; 1; 3; 2]
                         [1; 2; 3; 2; 1]
                         [2; 3; 4; 4; 2]
                         [3; 1; 3; 3; 1]]

I can't see any simple way in F# of achieving this. In C# I'd just create a for loop to do this, but I thought there might be a sneaky way of doing it in F#, using things like the map functions - mapi looked promising but it only seems to give access to the current piece under consideration and its indices, not the entire array...

My issue seems to be that the rules of the game are dependent on the surrounding area, whereas the standard traversal methods don't seem to give access to the surrounding area - what's the best way to achieve what I'm doing?

like image 939
David_001 Avatar asked Apr 27 '11 08:04

David_001


1 Answers

I don't think I pulled any sneaky stunt in this solution. I used Array2D.init to build a new array. The function required by init determines the array value at each position.

Furthermore, I put gathering the neighbors in a separate function. In the neighbors function I used a list comprehension and yielding only valid neighbors, avoiding troubles around corners and edges.

This is what I came up with (warning: it will fail when the 2D-array is 1x1 or empty, I'll leave it to the reader to guard against this case):

let neighbors r c (A:'a[,]) =
    [if r > 0 then yield A.[r-1,c]
     if r < Array2D.length1 A - 1 then yield A.[r+1,c]
     if c > 0 then yield A.[r,c-1]
     if c < Array2D.length2 A - 1 then yield A.[r,c+1]]

let newArray A =
    Array2D.init (Array2D.length1 A) (Array2D.length2 A) 
        (fun r c ->
            if A.[r,c] > 0 then 1 
            else
                match neighbors r c A |> List.max with
                | 1 -> 3
                | 0 -> 4
                | _ -> 2
        )

Test in F# interactive:

let myArray = array2D [[1; 1; 0; 0; 0]
                       [0; 0; 1; 0; 0]
                       [2; 0; 0; 0; 3]
                       [0; 0; 0; 0; 0]
                       [0; 1; 0; 0; 1]]

let result = newArray myArray

result:

val result : int [,] = [[1; 1; 3; 4; 4]
                        [2; 3; 1; 3; 2]
                        [1; 2; 3; 2; 1]
                        [2; 3; 4; 4; 2]
                        [3; 1; 3; 3; 1]]
like image 107
cfern Avatar answered Oct 01 '22 09:10

cfern