2015-08-11 1 views
3

J'ai une liste de listes de ints [[1,2,3,4],[1,2,3,4]]Transposer à Elm sans peut-être

Je veux transposer cela à [[1,1],[2,2],[3,3]...]

Je:

transpose : List (List a) -> List (List a) 
transpose ll = case ll of 
    ((x::xs)::xss) -> (x :: (List.map List.head xss)) :: transpose (xs :: (List.map List.tail xss)) 
    otherwise -> [] 

mais le problème est que le compilateur doesn n'aime pas les opérations tête et queue et veut retourner un type Peut-être.

Comment est-ce que je transpose correctement une liste dans elm?

Répondre

4

Cela dépend ...
Voulez-vous le faire minutieusement, compte tenu de toutes les édgecases? Ou faire le chemin rapide et sale? Ce que j'appelle un edge-case est une liste de listes où les sous-listes ont une longueur différente.

Manière très sale

Dans bord des cas, vous obtenez un plantage du programme

unsafeHead l = 
    case l of 
    (h :: t) -> h 
    _ -> Debug.crash "unsafeHead called with empty list" 

unsafeTail l = 
    case l of 
    (h :: t) -> t 
    _ -> Debug.crash "unsafeTail called with empty list" 

transpose ll = 
    case ll of 
    ((x::xs)::xss) -> 
     let 
     heads = 
      List.map unsafeHead xss 

     tails = 
      List.map unsafeTail xss 
     in 
     (x :: heads) :: transpose (xs :: tails) 

    _ -> 
     [] 

Bonne chance avec les accidents de programme au hasard, ne dis pas que je ne vous ai pas prévenu!

Ne pas se soucier de bord des cas (ou: des lignes plus courtes sont ok)

Dans bord des cas, vous obtenez: transpose [[10,11],[20],[],[30,31,32]] == [[10,20,30],[11,31],[32]]

transpose ll = 
    case ll of 
    [] -> 
     [] 

    ([] :: xss) -> 
     transpose xss 

    ((x::xs) :: xss) -> 
     let 
     heads = 
      List.filterMap List.head xss 

     tails = 
      List.filterMap List.tail xss 
     in 
     (x :: heads) :: transpose (xs :: tails) 

Embrassez le Maybe

En cas bord vous obtenez un Nothing

Si vous seulement voulez transposer lorsque vous avez une liste de listes où tous les sous-listes sont de la même taille, il vous suffit de hisser les Maybe s que vous obtenez de la cartographie avec List.head/List.tail:

transpose : List (List a) -> Maybe (List (List a)) 
transpose ll = 
    case ll of 
    ((x::xs)::xss) -> 
     let 
     heads = 
      xss 
      |> List.map List.head 
      |> insideout 

     tails = 
      xss 
      |> List.map List.tail 
      |> insideout 
     in 
     (x #^ heads) ^#^ ((xs #^ tails) `Maybe.andThen` transpose) 

    _ -> 
     if ll == List.filter List.isEmpty ll then 
     Just [] 
     else 
     Nothing 

----- Some helper functions: ----- 


mCons : a -> Maybe (List a) -> Maybe (List a) 
mCons v ml = Maybe.map ((::) v) ml 

v #^ ml = mCons v ml 

-- this is really a Maybe.map2 (::) mv ml 
-- but the standard library doesn't provide map2 :(
m2Cons : Maybe a -> Maybe (List a) -> Maybe (List a) 
m2Cons mv ml = 
    case (mv,ml) of 
    (Just v, Just l) -> Just (v :: l) 
    _ -> Nothing 

mv ^#^ ml = m2Cons mv ml 

-- list of justs to just of list 
insideout : List (Maybe a) -> Maybe (List a) 
insideout l = 
    case l of 
    [] -> Just [] 
    ((Just v) :: tail) -> v #^ insideout tail 
    (Nothing :: _) -> Nothing 
2

Vous pouvez parfois utiliser List.take 1/List.drop 1 en place de List.head/List.tail dans les cas où il peut être plus logique d'obtenir un vide List au lieu de Nothing. Dans l'exemple de transpose, si vous voulez l'écrire de manière à supprimer des valeurs supplémentaires lorsque les listes n'ont pas la même longueur (c'est-à-dire transposer "autant que possible" selon la liste la plus courte), vous pouvez utiliser:

transpose : List (List a) -> List (List a) 
transpose ll = 
    let heads = List.map (List.take 1) ll |> List.concat 
     tails = List.map (List.drop 1) ll 
    in 
     if | List.length heads == List.length ll -> 
      heads::(transpose tails) 
     | otherwise -> 
      [] 

transpose [[1,2,3,4],[1,2,3,4]]  --> [[1,1],[2,2],[3,3],[4,4]] 
transpose [[10,11],[20],[],[30,31,32]] --> [] 

Si vous le voulez continuer à prendre des listes jusqu'à ce qu'ils soient tous partis (transposer « autant que possible » en fonction de la plus longue liste), vous pouvez utiliser:

transpose : List (List a) -> List (List a) 
transpose ll = 
    let heads = List.map (List.take 1) ll |> List.concat 
     tails = List.map (List.drop 1) ll 
    in 
     if | List.isEmpty heads -> 
      [] 
     | otherwise -> 
      heads::(transpose tails) 

transpose [[1,2,3,4],[1,2,3,4]]  --> [[1,1],[2,2],[3,3],[4,4]] 
transpose [[10,11],[20],[],[30,31,32]] --> [[10,20,30],[11,31],[32]] 

Les deux fonctionneront aussi bien dans le cas où la matrice est bien formé, donc si vous voulez vérifier les cas de bord et faire quelque chose d'autre, vous pouvez le faire en premier. Ils manipulent juste les cas de bord un peu différemment.