Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I split a list in to four lists in elm?

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
                ]
            ]
        ]
like image 864
Ananth Avatar asked Jul 05 '19 09:07

Ananth


2 Answers

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" ]))
            ]
        ]
like image 178
glennsl Avatar answered Oct 25 '22 20:10

glennsl


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.

like image 30
O.O.Balance Avatar answered Oct 25 '22 21:10

O.O.Balance