I've got a list of items that have to be rendered. I have a function called viewItem
that can render one item. I do a simple List.map viewItem items
and now I have a list of items that can be displayed.
My view has four columns. How can I split this list into four lists that contain all of the elements from my original list?
This is how I'm doing it now, but there has to be something I'm missing. I want to be able to split it into five columns or even six without having to write col4 = ...
and col5 = ...
every time.
splitColumns : Int -> Array a -> Array (List a)
splitColumns cnum xs =
let
ixdList =
Array.toIndexedList xs
in
List.filterMap
(\a ->
if modBy 4 (Tuple.first a) == cnum then
Just (Tuple.second a)
else
Nothing
)
ixdList
viewItems : Array Item -> Html msg
viewItems items =
let
itemsHtml =
Array.map viewItem items
col0 =
splitColumns 0 itemsHtml
col1 =
splitColumns 1 itemsHtml
col2 =
splitColumns 2 itemsHtml
col3 =
splitColumns 3 itemsHtml
in
main_
[ class "section" ]
[ Html.div
[ class "container" ]
[ Html.div
[ class "columns" ]
[ Html.div
[ class "column" ]
col0
, Html.div
[ class "column" ]
col1
, Html.div
[ class "column" ]
col2
, Html.div
[ class "column" ]
col3
]
]
]
You could rewrite your current approach as a fold that only does one pass like this:
cols : List a -> { col0 : List a, col1 : List a, col2 : List a, col3 : List a }
cols list =
list
|> List.foldl
(\x ( i, cols ) ->
case modBy 4 i of
0 ->
( i + 1, { cols | col0 = x :: cols.col0 } )
1 ->
( i + 1, { cols | col1 = x :: cols.col1 } )
2 ->
( i + 1, { cols | col2 = x :: cols.col2 } )
3 ->
( i + 1, { cols | col3 = x :: cols.col3 } )
_ ->
( i + 1, cols )
)
( 0, { col0 = [], col1 = [], col2 = [], col3 = [] } )
|> Tuple.second
This also keeps track of the index internally, so it doesn't require that you give it an indexed list, but it's still hard-coded for four columns. If we want to be able to use it with an arbitrary number of columns, we have to use a data structure that can hold an arbitrary number of items in sequence. An array is perfect for this, allowing us to update it with an index computed using modBy
:
cols : Int -> List a -> List (List a)
cols n list =
list
|> List.foldl
(\x ( i, cols ) ->
let
index =
modBy n i
tail =
cols |> Array.get index |> Maybe.withDefault []
in
( i + 1, Array.set index (x :: tail) cols )
)
( 0, Array.repeat n [] )
|> Tuple.second
|> Array.toList
We can then use List.map
in the view function to render them:
viewItems : Array Item -> Html msg
viewItems items =
let
itemsHtml =
Array.map viewItem items
|> Array.toList
in
main_
[ class "section" ]
[ Html.div
[ class "container" ]
[ Html.div
[ class "columns" ]
(cols 4 itemsHtml |> List.map (Html.div [ class "column" ]))
]
]
You can use List.map
with List.range
. List.range a b
generates a list of integers from a
to b
(inclusive).
Your viewItems
function is greatly simplified by this:
viewItems : Array Item -> Html msg
viewItems items =
main_
[ class "section" ]
[ Html.div
[ class "container" ]
[ Html.div
[ class "columns" ]
List.map (\n -> Html.div [ class "column" ] (splitColumns n (Array.map viewItem items)) (List.range 0 3)
]
]
If you want to support a different number of columns, you can of course replace the hard-coded 4
in splitColumns
with a parameter.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With