tortus / elm-array-2d / Array2D

Implements 2D array using nested Arrays. Useful for implementing data grids, as it specifically provides row and column operations.

Cells are accessed by row, then column. Otherwise, it works very similarly to the Array class. The documentation examples usually omit converting Lists to Arrays for brevity.

Array2D's can be constructed from an Array or List of rows, where each row is an Array or List of cells.

Array2D.fromList
  [ ["Row 1-Col 1", "Row 1-Col 2"]
  , ["Row 2-Col 1", "Row 2-Col 2"]
  ]

If the nested arrays happen to be jagged, all rows will be truncated to the length of the smallest row! Be careful!

Array2D.fromList
  [ [0, 1]
  , [0, 1, 2]
  ]
-- Becomes:
  [ [0, 1]
  , [0, 1]
  ]

Drawbacks and caveats

Most examples of nested models in Elm use Lists of elements with a unique, constant ID, e.g.:

type alias Cell = { uid : Int, ... }

This allows messages to always be routed to the correct element, even if elements are re-ordered, removed, added, etc. If you use the index of an element instead to create a long Task that will change the element when it ends, be aware that the target element's index may have changed during the task!

For data grids you are probably not going to be re-positioning cells. Most data grids simply modify cells in place, which is what Array2D is mainly intended for. The danger comes from inserting and deleting rows and columns. During such operations, you may want to temporarily make your grid "read-only" somehow.

Base type


type alias Array2D a =
{ data : Array (Array a)
, columns : Basics.Int 
}

Base Array2D type

Initialization

empty : Array2D a

Create an empty Array2D

fromArray : Array (Array a) -> Array2D a

Create an Array2D from an Array of Arrays. All rows will be truncated to the length of the shortest row.

let
  row1 = Array.fromList [1, 2]
  row2 = Array.fromList [2, 3]
in
  fromArray (Array.fromList [row1, row2])

fromList : List (List a) -> Array2D a

Create an Array2D from a List of Lists.

fromList [[1, 2, 3], [4, 5, 6]]

initialize : Basics.Int -> Basics.Int -> (Basics.Int -> Basics.Int -> a) -> Array2D a

Initialize an Array2D. initialize rows cols f creates an array with the given dimensions with the element at index row col initialized to the result of (f row col). Similar to Array.initialize.

initialize 2 3 (\row col -> row + col)  == fromList [[0, 1, 2], [1, 2, 3]]

repeat : Basics.Int -> Basics.Int -> a -> Array2D a

Create a 2D of a given size, filled with a default element. Similar to Array.repeat

repeat 2 3 0 == [[0, 0, 0], [0, 0, 0]]

Getting info

rows : Array2D a -> Basics.Int

Get the number of rows in an Array2D

rows [[1, 2, 3], [4, 5, 6]] == 2

columns : Array2D a -> Basics.Int

Get the number of columns in an Array2D

columns [[1, 2, 3], [4, 5, 6]] == 3

isEmpty : Array2D a -> Basics.Bool

Check if an Array2D is empty.

isEmpty [] == True

Fetching/updating individual cells

get : Basics.Int -> Basics.Int -> Array2D a -> Maybe a

Get a cell.

get 1 1 [[1, 2], [3, 4]] == Just 4

set : Basics.Int -> Basics.Int -> a -> Array2D a -> Array2D a

Update a cell, returning the changed Array2D.

set 0 0 -100 [[1, 2], [3, 4]] == [[-100, 2], [3, 4]]

Adding/removing rows

getRow : Basics.Int -> Array2D a -> Maybe (Array a)

Get an individual row

getRow 1 [[1, 2], [3, 4]] == Just [3, 4]

appendRow : Array a -> a -> Array2D a -> Array2D a

Append a row. If the row is too long, it will be truncated, too short and it will be expanded with filler elements.

appendRow [3, 4] -1 [[1, 2]] == [[1, 2], [3, 4]]

-- Filler needed for short row:
appendRow [3] -1 [[1, 2]] == [[1, 2], [3, -1]]

deleteRow : Basics.Int -> Array2D a -> Array2D a

Delete a row. Does nothing if the index is out of bounds.

Adding/removing columns

getColumn : Basics.Int -> Array2D a -> Maybe (Array a)

get column-th cell of each row as an Array

getColumn 1 [[1, 2], [3, 4]] == Just [2, 4]

appendColumn : Array a -> a -> Array2D a -> Array2D a

Append a column. Filler will be used if the column length is less than the number of rows in the Array2D. If it is longer, it will be truncated.

appendColumn [2, 2] -1 [[1], [1]] == [[1, 2], [1,2]]

-- Filler needed for short column:
appendColumn [2] -1 [[1], [1]] == [[1, 2], [1,-1]]

deleteColumn : Basics.Int -> Array2D a -> Array2D a

Delete a column. If the index is invalid, nothing will happen.

Mapping cell data

map : (a -> b) -> Array2D a -> Array2D b

2D version of Array.map.

map
    (\cell -> toString cell)
    [[1, 2], [3, 4]]
    == [["1", "2"], ["3", "4"]]

indexedMap : (Basics.Int -> Basics.Int -> a -> b) -> Array2D a -> Array2D b

2D version of Array.indexedMap. First two arguments of map function are the row and column.

indexedMap
    (\row column cell -> toString row)
    [[1, 2], [3, 4]]
    == [["0", "0"], ["1", "1"]]