2 people like it.

Find largest mass in 2D array

This is a modification of the flood fill algorithm to find the largest contiguous block of items in a 2D array. Also includes a simple flood fill finder given a canvas and the target point

  1: 
  2: 
  3: 
  4: 
  5: 
  6: 
  7: 
  8: 
  9: 
 10: 
 11: 
 12: 
 13: 
 14: 
 15: 
 16: 
 17: 
 18: 
 19: 
 20: 
 21: 
 22: 
 23: 
 24: 
 25: 
 26: 
 27: 
 28: 
 29: 
 30: 
 31: 
 32: 
 33: 
 34: 
 35: 
 36: 
 37: 
 38: 
 39: 
 40: 
 41: 
 42: 
 43: 
 44: 
 45: 
 46: 
 47: 
 48: 
 49: 
 50: 
 51: 
 52: 
 53: 
 54: 
 55: 
 56: 
 57: 
 58: 
 59: 
 60: 
 61: 
 62: 
 63: 
 64: 
 65: 
 66: 
 67: 
 68: 
 69: 
 70: 
 71: 
 72: 
 73: 
 74: 
 75: 
 76: 
 77: 
 78: 
 79: 
 80: 
 81: 
 82: 
 83: 
 84: 
 85: 
 86: 
 87: 
 88: 
 89: 
 90: 
 91: 
 92: 
 93: 
 94: 
 95: 
 96: 
 97: 
 98: 
 99: 
100: 
101: 
102: 
103: 
104: 
105: 
106: 
107: 
108: 
109: 
110: 
111: 
112: 
113: 
114: 
115: 
116: 
117: 
118: 
119: 
120: 
121: 
122: 
123: 
124: 
125: 
126: 
127: 
128: 
129: 
130: 
131: 
132: 
133: 
134: 
135: 
136: 
137: 
138: 
139: 
140: 
141: 
142: 
143: 
144: 
145: 
146: 
147: 
148: 
149: 
150: 
151: 
152: 
153: 
154: 
155: 
156: 
157: 
158: 
159: 
160: 
161: 
162: 
163: 
164: 
165: 
166: 
167: 
168: 
169: 
170: 
171: 
172: 
173: 
174: 
175: 
176: 
177: 
178: 
179: 
180: 
181: 
182: 
183: 
184: 
185: 
186: 
type Board<'T> = 'T[,]

type X = int

type Y = int

type Position = X * Y

type PositionList = Position list 

type ProcessedPositions = PositionList

type ContiguousPoints = PositionList

type MassFinder = ContiguousPoints * ProcessedPositions

type Earth = 
    | Land
    | Water

let board = array2D [[Land;  Land;  Land;  Water;];
                     [Water; Land;  Land;  Water;];
                     [Land;  Water; Water; Water;];
                     [Water; Land;  Land;  Water;]]

let boardInt = array2D [[0;  0;  0;  1;];
                        [1; 0;  0;  1;];
                        [0;  1; 1; 1;];
                        [1; 0;  0;  1;]]
(* 
    Helper methods to move the position around
*)
                                          
let moveRight position = 
    let (x,y) = position
    (x + 1, y)

let moveLeft position = 
    let (x,y) = position
    (x - 1, y)

let moveUp position = 
    let (x,y) = position
    (x, y + 1)

let moveDown position = 
    let (x,y) = position
    (x, y - 1)

(*
    Size helpers
*)

let xSize board = Array2D.length1 board

let ySize board = Array2D.length2 board

let offBoard position board = 
    let (x,y) = position
    x < 0 || y < 0 || x >= (xSize board) || y >= (ySize board)

(*
    Alias to push elements onto a list
*)

let markPosition position previousSpots = position::previousSpots

(*
    Determines if the position on the board equals the target
*)

let positionOnTarget position board target = 
    if offBoard position board then 
        false
    else
        let (x, y) = position
        (Array2D.get board x y) = target

(*
    Alias to find if we already processed a position
*)

let positionExists position list = 
    List.exists(fun pos -> pos = position) list

(* 
   Iterate over each element in a 2d array, passing the x and y
   coordinate and the board, to the supplied function
   which can return an item. The items are all cons together
   and the function returns a new list
*)

let forEachElement (applier:(X -> Y -> Board<'a> -> 'b)) (twoDimArray:Board<'a>) =
    let mutable items = [] 
    for x in 0..(xSize board) do
        for y in 0..(ySize board) do            
            items <- (applier x y twoDimArray)::items
    items


(*
    Looks for a specified contigoius block
    and keeps track of processed positions using a 
    reference cell of a list of positions (supplied by the caller)
*)

let findMassStartingAt (position:Position) (board:Board<'A>) (target:'A) (positionSeed:ProcessedPositions) : MassFinder = 
    let rec findMassStartingAt' position (currentMass:ContiguousPoints, processedList:ProcessedPositions) = 
            // if you move off the board return
        if offBoard position board then
            (currentMass, processedList)

        // if you already processed this position then don't do anything
        else if positionExists position processedList then
            (currentMass, processedList)
        else  
            
            // branch out left, up, right, and down and see what you can find
            let up = moveUp position
            let down = moveDown position
            let left = moveLeft position
            let right = moveRight position
            
            let found = positionOnTarget position board target

            match found with 
                | true ->
                    (position::currentMass, position::processedList)
                        |> findMassStartingAt' up 
                        |> findMassStartingAt' down 
                        |> findMassStartingAt' left 
                        |> findMassStartingAt' right 

                | false -> 
                    // if you didn't find anything return the masses that you 
                    // found prevoiusly
                    (currentMass, processedList)

    findMassStartingAt' position ([], positionSeed)

(*
    Finds all contiguous blocks of the specified type
    and returns a list of lists (each list is the points for a specific
    block)
*)

let getContiguousBlocks board target = 
    // keep track of all processed positions across the recursions
    let processed = ref []

    // go through each board element and find masses starting at the
    // the current position
    // filter out any positions that found no masses
    let massFinder x y board = findMassStartingAt (x, y) board target []

    forEachElement massFinder board
        |> List.map fst
        |> List.filter (fun i -> not (List.isEmpty i))
        

(*
    Returns a list of points representing a contigious block 
    of the type that the point was at. 
*)

let floodFillArea (point:Position) (canvas:Board<'T>) =
    let (x, y) = point
    let itemAtPoint = Array2D.get canvas x y
    
    findMassStartingAt point canvas itemAtPoint [] |> fst


(* 
    Test functions to run it
*)

let masses = getContiguousBlocks board Land

let largestList = List.maxBy(List.length) masses

let massAt = floodFillArea (2, 2) boardInt

let sizeOfMassAt22 = List.length massAt

System.Console.WriteLine("Largest mass is " + (List.length largestList).ToString());
System.Console.WriteLine("Mass size at (2,2) is " + sizeOfMassAt22.ToString());
type X = int

Full name: Script.X
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
type Y = int

Full name: Script.Y
type Position = X * Y

Full name: Script.Position
type PositionList = Position list

Full name: Script.PositionList
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
type ProcessedPositions = PositionList

Full name: Script.ProcessedPositions
type ContiguousPoints = PositionList

Full name: Script.ContiguousPoints
type MassFinder = ContiguousPoints * ProcessedPositions

Full name: Script.MassFinder
type Earth =
  | Land
  | Water

Full name: Script.Earth
union case Earth.Land: Earth
union case Earth.Water: Earth
val board : Earth [,]

Full name: Script.board
val array2D : rows:seq<#seq<'T>> -> 'T [,]

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.array2D
val boardInt : int [,]

Full name: Script.boardInt
val moveRight : int * 'a -> int * 'a

Full name: Script.moveRight
val position : int * 'a
val x : int
val y : 'a
val moveLeft : int * 'a -> int * 'a

Full name: Script.moveLeft
val moveUp : 'a * int -> 'a * int

Full name: Script.moveUp
val position : 'a * int
val x : 'a
val y : int
val moveDown : 'a * int -> 'a * int

Full name: Script.moveDown
val xSize : board:'a [,] -> int

Full name: Script.xSize
val board : 'a [,]
module Array2D

from Microsoft.FSharp.Collections
val length1 : array:'T [,] -> int

Full name: Microsoft.FSharp.Collections.Array2D.length1
val ySize : board:'a [,] -> int

Full name: Script.ySize
val length2 : array:'T [,] -> int

Full name: Microsoft.FSharp.Collections.Array2D.length2
val offBoard : int * int -> board:'a [,] -> bool

Full name: Script.offBoard
val position : int * int
val markPosition : position:'a -> previousSpots:'a list -> 'a list

Full name: Script.markPosition
val position : 'a
val previousSpots : 'a list
val positionOnTarget : int * int -> board:'a [,] -> target:'a -> bool (requires equality)

Full name: Script.positionOnTarget
val board : 'a [,] (requires equality)
val target : 'a (requires equality)
val get : array:'T [,] -> index1:int -> index2:int -> 'T

Full name: Microsoft.FSharp.Collections.Array2D.get
val positionExists : position:'a -> list:'a list -> bool (requires equality)

Full name: Script.positionExists
val position : 'a (requires equality)
Multiple items
val list : 'a list (requires equality)

--------------------
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val exists : predicate:('T -> bool) -> list:'T list -> bool

Full name: Microsoft.FSharp.Collections.List.exists
val pos : 'a (requires equality)
val forEachElement : applier:(X -> Y -> Board<'a> -> 'b) -> twoDimArray:Board<'a> -> 'b list

Full name: Script.forEachElement
val applier : (X -> Y -> Board<'a> -> 'b)
type Board<'T> = 'T [,]

Full name: Script.Board<_>
val twoDimArray : Board<'a>
val mutable items : 'b list
val x : int32
val y : int32
val findMassStartingAt : X * Y -> board:Board<'A> -> target:'A -> positionSeed:ProcessedPositions -> MassFinder (requires equality)

Full name: Script.findMassStartingAt
val position : Position
val board : Board<'A> (requires equality)
val target : 'A (requires equality)
val positionSeed : ProcessedPositions
val findMassStartingAt' : (int * int -> ContiguousPoints * ProcessedPositions -> ContiguousPoints * ProcessedPositions)
val currentMass : ContiguousPoints
val processedList : ProcessedPositions
val up : int * int
val down : int * int
val left : int * int
val right : int * int
val found : bool
val getContiguousBlocks : board:Board<'a> -> target:'a -> ContiguousPoints list (requires equality)

Full name: Script.getContiguousBlocks
val board : Board<'a> (requires equality)
val processed : obj list ref
Multiple items
val ref : value:'T -> 'T ref

Full name: Microsoft.FSharp.Core.Operators.ref

--------------------
type 'T ref = Ref<'T>

Full name: Microsoft.FSharp.Core.ref<_>
val massFinder : (X -> Y -> Board<'a> -> MassFinder) (requires equality)
val x : X
val y : Y
val map : mapping:('T -> 'U) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.map
val fst : tuple:('T1 * 'T2) -> 'T1

Full name: Microsoft.FSharp.Core.Operators.fst
val filter : predicate:('T -> bool) -> list:'T list -> 'T list

Full name: Microsoft.FSharp.Collections.List.filter
val i : ContiguousPoints
val not : value:bool -> bool

Full name: Microsoft.FSharp.Core.Operators.not
val isEmpty : list:'T list -> bool

Full name: Microsoft.FSharp.Collections.List.isEmpty
val floodFillArea : X * Y -> canvas:Board<'T> -> ContiguousPoints (requires equality)

Full name: Script.floodFillArea
val point : Position
val canvas : Board<'T> (requires equality)
val itemAtPoint : 'T (requires equality)
val masses : ContiguousPoints list

Full name: Script.masses
val largestList : Position list

Full name: Script.largestList
val maxBy : projection:('T -> 'U) -> list:'T list -> 'T (requires comparison)

Full name: Microsoft.FSharp.Collections.List.maxBy
val length : list:'T list -> int

Full name: Microsoft.FSharp.Collections.List.length
val massAt : ContiguousPoints

Full name: Script.massAt
val sizeOfMassAt22 : int

Full name: Script.sizeOfMassAt22
namespace System
type Console =
  static member BackgroundColor : ConsoleColor with get, set
  static member Beep : unit -> unit + 1 overload
  static member BufferHeight : int with get, set
  static member BufferWidth : int with get, set
  static member CapsLock : bool
  static member Clear : unit -> unit
  static member CursorLeft : int with get, set
  static member CursorSize : int with get, set
  static member CursorTop : int with get, set
  static member CursorVisible : bool with get, set
  ...

Full name: System.Console
System.Console.WriteLine() : unit
   (+0 other overloads)
System.Console.WriteLine(value: string) : unit
   (+0 other overloads)
System.Console.WriteLine(value: obj) : unit
   (+0 other overloads)
System.Console.WriteLine(value: uint64) : unit
   (+0 other overloads)
System.Console.WriteLine(value: int64) : unit
   (+0 other overloads)
System.Console.WriteLine(value: uint32) : unit
   (+0 other overloads)
System.Console.WriteLine(value: int) : unit
   (+0 other overloads)
System.Console.WriteLine(value: float32) : unit
   (+0 other overloads)
System.Console.WriteLine(value: float) : unit
   (+0 other overloads)
System.Console.WriteLine(value: decimal) : unit
   (+0 other overloads)
System.Int32.ToString() : string
System.Int32.ToString(provider: System.IFormatProvider) : string
System.Int32.ToString(format: string) : string
System.Int32.ToString(format: string, provider: System.IFormatProvider) : string

More information

Link:http://fssnip.net/ik
Posted:11 years ago
Author:devshorts
Tags: flood fill , algorithms