I have two data structures I would like to combine in my Elm program. Their types are List (a, List b) and List c. This is the simplest way to store the data in the model, but I would like to write a function to transform it to a view model before displaying it.
type alias Model1 a b = List (a, List b)
type alias Model2 c = List c
type alias ViewModel a b c = List (a, List (b, c))
toViewModel : Model1 -> Model2 -> ViewModel
toViewModel model1 model2 =
???
The toViewModel function should zip the sub-lists in model 1 with the elements of model 2. Assume the size of model2 is the same as the sum of the sizes of the sub-lists in model 1.
For example, if model1 has length 2 and the two elements' sublists have lengths 3 and 4, assume model 2 will have length 7. Model 1's first element's sublist should be zipped with the first 3 elements of model 2, and model 1's second element's sublist should be zipped with the next 4 elements of model 2.
Here's a diagram of the previous example:

How can the toViewModel function be constructed?
Elm doesn't have a built-in zip function but it's easy to define it in terms of List.map2:
zip : List a -> List b -> List (a, b)
zip = List.map2 (,)
You can't use that on the disjointed list by itself, but you can use it to zip up the sub-lists in Model1 with the first n items from Model2. To get the list of things that need to be zipped up, you'll just need to use List.take for the zipping portion, and List.drop to get the remaining items from Model2 that need to be zipped up with the next Model1 entity. This can be done using something like the following functionn.
toViewModel : Model1 a b -> Model2 c -> ViewModel a b c
toViewModel m1 m2 =
case m1 of
[] ->
[]
((m1ID, m1List)::rest) ->
let
len = List.length m1List
init' = List.take len m2
tail' = List.drop len m2
in
(m1ID, zip m1List init') :: toViewModel rest tail'
Notice that using zip is going to stop at whichever list ends first, so if you have lists of uneven length, items are going to get dropped.
Edit - Here's an additional way of doing this where the explicit recursion is isolated in a way that the main functionality can be achieved through mapping
Another way to solve this would be to first group Model2 into a list of lists, where each item contains an appropriate number of elements for the equivalent Model1 entry.
For this I'll define a function takes which splits up a list into a number of smaller lists, the sizes of these lists being passed in as the first parameter.
takes : List Int -> List a -> List (List a)
takes counts list =
case counts of
[] -> []
(x::xs) -> List.take x list :: takes xs (List.drop x list)
Now you can use the takes function to group the Model2 list, allowing you to map it against the Model1 input so you can zip the internal lists.
toViewModel : Model1 a b -> Model2 c -> ViewModel a b c
toViewModel model1 model2 =
let
m2Grouped = takes (List.map (List.length << snd) model1) model2
mapper (m1ID, m1List) m2List = (m1ID, zip m1List m2List)
in
List.map2 mapper model1 m2Grouped
Here is a slightly simplier take on the task:
List (a, b) from Model1Model2, to get List (a, b, c) with data from both listsa to get final List (a, List (b, c)) with List.filterMapI've had to mock some data to run the tests here.
Please consider the following example:
import Graphics.Element exposing (show)
-- List (a, List b)
type alias Model1 = List (String, List Int)
-- List c
type alias Model2 = List Bool
-- List (a, List (b, c))
type alias ViewModel = List (String, List (Int, Bool))
toViewModel : Model1 -> Model2 -> ViewModel
toViewModel model1 model2 =
let
-- Map merge both lists and map values to the grouping key.
mappedList =
model1
|> List.concatMap (\(groupBy, list) -> List.map (\el -> (groupBy, el)) list)
|> List.map2 (\val2 (groupBy, val1) -> (groupBy, (val1, val2))) model2
-- Extract a list for for specified grouping key.
getListByKey search =
List.filterMap
(\(key, val) -> if key == search then Just val else Nothing)
mappedList
in
List.map (\(search,_) -> (search, getListByKey search)) model1
initModel1 : Model1
initModel1 =
[ ("foo", [ 1, 2, 3 ])
, ("bar", [ 4, 5, 6, 7])
]
initModel2 : Model2
initModel2 =
[ True
, False
, True
, False
, True
, False
, True
]
main =
toViewModel initModel1 initModel2
|> show
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