Транспонировать в Elm без Maybe

У меня есть список списков целых чисел [[1,2,3,4],[1,2,3,4]]

Я хочу перенести это на [[1,1],[2,2],[3,3]...]

У меня есть:

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 -> []

но проблема в том, что компилятору не нравятся операции с головкой и хвостом, и он хочет вернуть тип Maybe.

Как правильно перенести список в elm?


person leshow    schedule 11.08.2015    source источник


Ответы (2)


Это зависит от...
Вы хотите сделать это тщательно, учитывая все крайние случаи? Или сделать это быстро и грязно? Крайним случаем я называю список списков, в которых подсписки имеют разную длину.

Очень грязный способ

В крайних случаях вы получаете сбой программы

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)

    _ ->
      []

Удачи с этими случайными сбоями программы, не говорите, что я вас не предупреждал!

Не заботьтесь о крайних случаях (или: более короткие строки в порядке)

В крайних случаях вы получаете: 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)

Примите Maybe

В крайних случаях вы получаете Nothing

Если вы только хотите транспонировать, когда у вас есть список списков, в котором все подсписки имеют одинаковый размер, вы можете просто поднять Maybe, которые вы получаете от сопоставления с 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
person Apanatshka    schedule 11.08.2015

Иногда вы можете использовать List.take 1/List.drop 1 вместо List.head/List.tail в тех случаях, когда имеет смысл получить пустое List вместо Nothing.

В примере transpose, если вы хотите написать его таким образом, чтобы он удалял любые лишние значения, когда списки не имеют одинаковой длины (т.е. транспонировать только «насколько это возможно» в зависимости от кратчайшего списка), вы можете использовать:

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]] --> []

Если вы хотите, чтобы он продолжал брать из списков до тех пор, пока они все не исчезнут (т.е. транспонировать «насколько это возможно» в зависимости от самого длинного списка), вы можете использовать:

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]]

Оба будут работать одинаково хорошо в случае, если матрица правильно сформирована, поэтому, если вы хотите проверить крайние случаи и сделать что-то еще, вы можете сделать это в первую очередь. Они просто обрабатывают крайние случаи немного по-другому.

person robertjlooby    schedule 12.08.2015