2016-05-03 12 views
2

Ich habe zwei Datenstrukturen, die ich in meinem Elm-Programm kombinieren möchte. Ihre Typen sind List (a, List b) und List c. Dies ist der einfachste Weg, die Daten im Modell zu speichern, aber ich möchte eine Funktion schreiben, um sie in ein Ansichtsmodell zu transformieren, bevor sie angezeigt wird.Wie können seltsam geformte Listen in Elm gezippt werden?

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 = 
    ??? 

Die toViewModel Funktion die Unterlisten in Modell 1 mit den Elementen des Modells zip sollte die Größe von model2 2. Angenommen, die gleiche ist wie die Summe der Größen der Unterlisten in Modell 1.

Wenn beispielsweise Modell1 die Länge 2 hat und die Unterlisten der beiden Elemente die Längen 3 und 4 haben, nehmen Sie an, Modell 2 hat die Länge 7. Die Unterliste des ersten Elements von Modell 1 sollte mit den ersten drei Elementen von Modell 2 und Modell gezippt werden Die Unterliste des zweiten Elements von 1 sollte mit den nächsten 4 Elementen des Modells 2 gezippt werden.

Hier ist ein Diagramm des vorherigen Beispiels:

Diagram of example drawn on paper

Wie kann die toViewModel Funktion konstruiert werden?

Antwort

3

Elm nicht über eine eingebaute in zip Funktion, aber es ist einfach, es in Bezug auf List.map2 zu definieren:

zip : List a -> List b -> List (a, b) 
zip = List.map2 (,) 

Sie können nicht von selbst auf der unzusammenhängenden Liste verwenden, aber Sie Sie können damit die Unterlisten in Model1 mit den ersten n Artikeln von Model2 kopieren. Um die Liste der Dinge zu erhalten, die gezippt werden müssen, müssen Sie nur List.take für den zipping-Teil und List.drop verwenden, um die restlichen Artikel von Model2 zu erhalten, die mit der nächsten Model1-Entität gezippt werden müssen. Dies kann mit etwas wie der folgenden Funktion geschehen.

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' 

Beachten Sie, dass zip Verwendung bei stoppen wird je nachdem, welche Liste endet erst, wenn Sie also Listen unterschiedlicher Länge haben, Einzelteile gehen zu gefallen lassen.

bearbeiten-Hier ist eine weitere Möglichkeit, dies zu tun, wo die explizite Rekursion in einer Art und Weise isoliert ist, dass die Hauptfunktionalität kann

Eine andere Möglichkeit, diese Model2 zu ersten Gruppe würde zu lösen durch Zuordnung erreicht werden in eine Liste von Listen, in denen jedes Element eine entsprechende Anzahl von Elementen für den entsprechenden Eintrag von Model1 enthält.

Dafür definiere ich eine Funktion takes, die eine Liste in eine Anzahl kleinerer Listen aufteilt, wobei die Größen dieser Listen als erster Parameter übergeben werden.

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) 

Jetzt können Sie die takes Funktion Gruppe die Liste Model2 verwenden, so dass Sie es gegen den Modell1 Eingang zur Karte, so dass Sie die internen Listen zip können.

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 
+0

ich die Typ-Aliasnamen festgelegt haben. Im eigentlichen Code, den ich schreibe, handelt es sich um konkrete Typen. Ich machte den Fehler, sie wegzulassen, als ich meinen Code zum Beispiel verallgemeinerte. – Eric

+0

Ich habe einen logischen Fehler in meiner Antwort behoben und die jetzt veraltete Typalias-Empfehlung entfernt. –

+0

Ist dies eine genaue, verallgemeinerte Zusammenfassung Ihrer Antwort? "Um merkwürdig geformte Listen zu kombinieren, verwenden Sie Pattern Matching und Rekursion, da' zip = List.map2 (,) 'wahrscheinlich nicht flexibel genug ist." – Eric

1

ist hier ein wenig von der Aufgabe simplier zu nehmen:

  1. Verwenden List.concatMap einem flachen List (a, b) von Model1
  2. Merge es mit Model2, erstellen List (a, b, c) mit Daten aus beiden Listen zu bekommen
  3. Gruppieren Sie die Liste nach a, um die List (a, List (b, c)) mit List.filterMap
  4. zu erhalten

Ich musste einige Daten verspotten, um die Tests hier zu starten.

Bitte beachten Sie das folgende Beispiel:

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