Skip to content

Commit

Permalink
use a 1-dimensional array for Grid. Waaaaay faster!
Browse files Browse the repository at this point in the history
  • Loading branch information
BrianHicks committed Jan 12, 2020
1 parent 2a21e42 commit 50d0504
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 70 deletions.
152 changes: 82 additions & 70 deletions src/Grid.elm
Expand Up @@ -11,7 +11,7 @@ import Set
type Grid a
= Grid
{ -- rows x columns
items : Array (Array a)
items : Array a
, width : Int
, height : Int
}
Expand All @@ -24,7 +24,14 @@ type FromRowsAndColumnsProblem
initialize : { rows : Int, columns : Int } -> ({ row : Int, column : Int } -> a) -> Grid a
initialize { rows, columns } init =
Grid
{ items = Array.initialize rows (\row -> Array.initialize columns (\colNum -> init { row = row, column = colNum }))
{ items =
Array.initialize (rows * columns)
(\i ->
init
{ row = i // rows
, column = modBy rows i
}
)
, width = columns
, height = rows
}
Expand All @@ -46,7 +53,7 @@ fromRowsAndColumns rowsAndColumns =
fromRowsAndColumnsArray : Array (Array a) -> Grid a
fromRowsAndColumnsArray rowsAndColumns =
Grid
{ items = rowsAndColumns
{ items = Array.foldr Array.append Array.empty rowsAndColumns
, width =
Array.get 0 rowsAndColumns
|> Maybe.map Array.length
Expand All @@ -56,8 +63,14 @@ fromRowsAndColumnsArray rowsAndColumns =


toArrays : Grid a -> Array (Array a)
toArrays (Grid { items }) =
items
toArrays (Grid grid) =
Array.initialize grid.height
(\row ->
Array.slice
(row * grid.width)
(row * grid.width + grid.width)
grid.items
)


{-| Rotate a grid 90° clockwise.
Expand All @@ -73,7 +86,7 @@ rotate ((Grid { width, height }) as grid) =
|> Maybe.withDefault Array.empty
|> Array.foldr Array.push Array.empty
)
|> Array.fromList
|> List.foldr Array.append Array.empty
in
Grid
{ items = newItems
Expand All @@ -83,15 +96,13 @@ rotate ((Grid { width, height }) as grid) =


column : Int -> Grid a -> Maybe (Array a)
column colNum (Grid { items, height }) =
column colNum (Grid { items, height, width }) =
List.range 0 (height - 1)
|> List.foldl
(\row soFar ->
Maybe.andThen
(\arr ->
items
|> Array.get row
|> Maybe.andThen (Array.get colNum)
Array.get (row * width + colNum) items
|> Maybe.map (\val -> Array.push val arr)
)
soFar
Expand All @@ -103,48 +114,52 @@ column colNum (Grid { items, height }) =
edges of the input grid. We include tuples here, as they're useful as IDs.
-}
windows : { width : Int, height : Int } -> Grid a -> Grid (Grid a)
windows sizes (Grid { width, height, items }) =
let
-- when we reach the edge, we just need to wrap around.
-- Repeating once per axis should do it!
expanded =
Array.initialize (height * 2)
(\i ->
let
row =
items
|> Array.get (modBy height i)
|> Maybe.withDefault Array.empty
in
Array.append row row
)
in
windows sizes ((Grid { width, height, items }) as grid) =
Grid
{ items =
Array.initialize height
(\row ->
Array.initialize width
(\col ->
Grid
{ items =
expanded
|> Array.slice row (row + sizes.height)
|> Array.map (Array.slice col (col + sizes.width))
, width = sizes.width
, height = sizes.height
}
)
Array.initialize (height * width)
(\cell ->
cropWrapping
{ row = cell // width
, column = modBy width cell
, width = sizes.width
, height = sizes.height
}
grid
)
, width = width
, height = height
}


cropWrapping : { row : Int, column : Int, width : Int, height : Int } -> Grid a -> Grid a
cropWrapping bounds (Grid grid) =
let
expanded =
toArrays (Grid grid)
|> Array.map (\row -> Array.append row row)
|> (\all -> Array.append all all)

wrappedRow =
modBy grid.height bounds.row

wrappedColumn =
modBy grid.width bounds.column
in
Grid
{ items =
expanded
|> Array.slice wrappedRow (wrappedRow + bounds.height)
|> Array.map (Array.slice wrappedColumn (wrappedColumn + bounds.width))
|> Array.foldr Array.append Array.empty
, width = bounds.width
, height = bounds.height
}


get : { row : Int, column : Int } -> Grid a -> Maybe a
get coords (Grid { items }) =
items
|> Array.get coords.row
|> Maybe.andThen (Array.get coords.column)
get coords (Grid { items, width }) =
Array.get (coords.row * width + coords.column) items


{-| Still a maybe because the grid could be empty
Expand All @@ -165,19 +180,14 @@ topLeft =

set : { row : Int, column : Int } -> a -> Grid a -> Grid a
set coords newValue (Grid grid) =
case Array.get coords.row grid.items of
Nothing ->
Grid grid

Just row ->
Grid
{ grid
| items =
Array.set
coords.row
(Array.set coords.column newValue row)
grid.items
}
Grid
{ grid
| items =
Array.set
(coords.row * grid.width + coords.column)
newValue
grid.items
}


update : (a -> a) -> { row : Int, column : Int } -> Grid a -> Grid a
Expand All @@ -190,25 +200,17 @@ update fn coords grid =
grid


{-| TODO: could probably do this with CSS grids but I'm not sure how.
-}
view : (a -> Html msg) -> Grid a -> Html msg
view viewItem (Grid { items }) =
items
|> Array.map (Array.map viewItem >> Array.toList >> Html.tr [])
|> Array.toList
|> Html.table [ css [ Css.borderCollapse Css.collapse ] ]


indexedMap : ({ row : Int, column : Int } -> a -> b) -> Grid a -> Grid b
indexedMap fn (Grid grid) =
Grid
{ items =
Array.indexedMap
(\rowNum row ->
Array.indexedMap
(\colNum val -> fn { row = rowNum, column = colNum } val)
row
(\i val ->
fn
{ row = modBy grid.width i
, column = remainderBy grid.width i
}
val
)
grid.items
, width = grid.width
Expand All @@ -219,3 +221,13 @@ indexedMap fn (Grid grid) =
map : (a -> b) -> Grid a -> Grid b
map fn =
indexedMap (\_ a -> fn a)


{-| TODO: could probably do this with CSS grids but I'm not sure how.
-}
view : (a -> Html msg) -> Grid a -> Html msg
view viewItem grid =
toArrays grid
|> Array.map (Array.map viewItem >> Array.toList >> Html.tr [])
|> Array.toList
|> Html.table [ css [ Css.borderCollapse Css.collapse ] ]
51 changes: 51 additions & 0 deletions tests/GridTests.elm
@@ -0,0 +1,51 @@
module GridTests exposing (..)

import Expect
import Fuzz exposing (Fuzzer)
import Grid exposing (Grid)
import Test exposing (..)


getAndSetTest : Test
getAndSetTest =
fuzz coordsFuzzer "setting a value and then getting the same value should work" <|
\coords ->
blank10x10
|> Grid.set coords 1
|> Grid.get coords
|> Expect.equal (Just 1)


windowsTest : Test
windowsTest =
describe "windows"
[ test "creates a moving window over the grid data" <|
\_ ->
Grid.fromRowsAndColumns
[ [ 1, 2 ]
, [ 3, 4 ]
]
|> Grid.windows { width = 2, height = 2 }
|> Expect.equal
(Grid.fromRowsAndColumns
[ [ Grid.fromRowsAndColumns [ [ 1, 2 ], [ 3, 4 ] ]
, Grid.fromRowsAndColumns [ [ 2, 1 ], [ 4, 3 ] ]
]
, [ Grid.fromRowsAndColumns [ [ 3, 4 ], [ 1, 2 ] ]
, Grid.fromRowsAndColumns [ [ 4, 3 ], [ 2, 1 ] ]
]
]
)
]


blank10x10 : Grid Int
blank10x10 =
Grid.initialize { rows = 10, columns = 10 } (always 0)


coordsFuzzer : Fuzzer { row : Int, column : Int }
coordsFuzzer =
Fuzz.map2 (\row column -> { row = row, column = column })
(Fuzz.intRange 0 9)
(Fuzz.intRange 0 9)

0 comments on commit 50d0504

Please sign in to comment.