diff --git a/src/Grid.elm b/src/Grid.elm index 0dd02ed..532b6f8 100644 --- a/src/Grid.elm +++ b/src/Grid.elm @@ -11,7 +11,7 @@ import Set type Grid a = Grid { -- rows x columns - items : Array (Array a) + items : Array a , width : Int , height : Int } @@ -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 } @@ -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 @@ -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. @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 ] ] diff --git a/tests/GridTests.elm b/tests/GridTests.elm new file mode 100644 index 0000000..f5fe9af --- /dev/null +++ b/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)