在没有 Maybe 的情况下在 Elm 中转置

Transpose in Elm without 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 -> []

但问题是编译器不喜欢 head 和 tail 操作,想要 return Maybe 类型。

如何在 elm 中正确转置列表?

这取决于...
考虑到所有的边缘情况,你想彻底地做到这一点吗?还是以快速而肮脏的方式进行?我所说的边缘案例是一个列表列表,其中的子列表具有不同的长度。

非常肮脏的方式

在极端情况下你会遇到程序崩溃

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

如果您想在您有一个所有子列表大小相同的列表列表时转置,您可以只提升您想要的 Maybes使用 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

您有时可以使用 List.take 1/List.drop 1 代替 List.head/List.tail,在这样的情况下,空 List 更有意义] 而不是 Nothing.

transpose 的例子中,如果你想这样写它,当列表长度不相等时它会删除任何额外的值(即只转置 "as much as it can" 取决于最短的列表),您可以使用:

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

如果你想让它不断地从列表中取出直到它们全部消失(即根据最长的列表转置 "as much as it can"),你可以使用:

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

在矩阵格式正确的情况下,两者都同样有效,因此如果您想检查边缘情况并做其他事情,您可以先做。他们只是处理边缘情况有点不同。