在Haskell中将内部列表切割为相等的长度

时间:2018-03-23 15:08:45

标签: list haskell

假设我们有一个列表列表,我们希望将所有内部列表缩减到最短内部列表的长度 - 例如,从[[1,2,3,4],[2,3],[3,9]]得到{{1} }}。

到目前为止,这是我的代码(它不起作用):

[[1,2],[2,3],[3,9]]

我试着这样解决: 必须有一个函数返回最短列表的长度,因此,我们需要一个进一步的函数,将所有列表从开头剪切到这个新长度。 但事实上,我并不知道。

5 个答案:

答案 0 :(得分:4)

这是你想要的吗?

truncateList :: [[a]] -> [[a]]
truncateList list = map (\x -> take l x) list
  where
  l = minimum (map length list)

或更短,

truncateList :: [[a]] -> [[a]]
truncateList list = map (take l) list
  where
  l = minimum (map length list)

答案 1 :(得分:4)

由于你想要将任意数量的列表压缩在一起,一旦最短列表"用完就停止",这看起来像ZipList的工作。

让我们首先将每个输入列表包装在ZipList包装器类型中:

truncateList :: [[a]] -> [[a]]
truncateList xs = let lists = map ZipList xs
                      ...
                  in ...

现在,我们有lists :: [ZipList a],我们希望以某种方式折叠该列表,将所有ZipList a值组合到一个ZipList中,其中所有多余的元素都已被删除。为此,一个好的工具是

sequenceA :: [ZipList a] -> ZipList [a]

所以我们可以写

truncateList :: [[a]] -> [[a]]
truncateList xs = let lists = map ZipList xs
                      inverted = sequenceA lists
                  in ...

此时inverted接近您想要的结果,但不完全:我们[[1,2],[2,3],[3,9]]代替ZipList [[1,2,3],[2,3,9]]:列表已转置,并且它已被转置包裹在我们不再需要的新类型中。所以,我们打开它,并使用Data.List.transpose以我们想要的方式将其翻转:

truncateList :: [[a]] -> [[a]]
truncateList xs = let lists = map ZipList xs
                      inverted = sequenceA lists
                  in transpose (getZipList inverted)

最后我们可以注意到,所有这些只是将几个内置函数组合在一起:

import Data.List (transpose)
import Control.Applicative (ZipList(..))

truncateList :: [[a]] -> [[a]]
truncateList = transpose . getZipList . sequenceA . map ZipList

我找到ZipList解决方案的动机是试图避免在每个输入列表上调用lengthtake所涉及的额外工作,每次遍历几次。我不太确定我的解决方案在此目标中是否成功:据我所知sequenceA遍历每个列表一次,transpose再次转换它们,所以它可能就像昂贵。它也不是很容易阅读:使用lengthtake的解决方案非常清楚。

我确实认为它有点漂亮,并且认为这个问题在转换到不同的域时(即ZipList s的世界)看起来相对简单是一种有用的练习。

答案 2 :(得分:4)

我认为这个版本比其他版本更有效,因为它只遍历输入列表一次,产生了指向未来的指针。

cut xss = let (ret, len) = foldr (\xs (ret', len') ->
                                   (take len xs : ret', min len' (length xs))
                                 ) ([], maxBound) xss
           in ret

请注意len在自己的定义中的显示方式。在评估foldr时,它会跟踪最短的长度和剪切列表,并且还有一个指向len最终位置的指针。当它在剪切的顶部添加一个新列表时,它实际上并不创建列表,而只是编写一些代表表达式take len xs的代码(这是懒惰的结果),它指的是len通过上述指针。当循环结束时,len的最终值写在指针的另一端,并且在一点“远距离动作”中,最终“更新”所有take长度正确的thunk。

这个版本类似,但是,我不是使用有限类型来保持列表的长度,而是定义自然数加上无穷大的类型。这比ZipList解决方案表现得更好,但比仅限于有限的解决方案更糟糕,而且到目前为止,它是唯一能正确处理无限列表的解决方案。

data Nat = Z | S Nat
-- data [()] = [] | (:) () [()]
-- [()] is isomorphic to Nat, so a lot of these functions in the where
-- clause are just list ones in disguise. Nat appears to perform
-- better, though.
cut :: [[a]] -> [[a]]
cut xss = let (ret, len) = foldr (\xs (ret', len') ->
                                   (take' len xs : ret', min' len' (length' xs))
                                 ) ([], inf) xss
           in ret
  where inf = S inf -- repeat ()
        take' Z _ = [] -- flip (zipWith const)
        take' (S n) (x : xs) = x : take' n xs
        min' Z _ = Z -- zipWith const
        min' _ Z = Z
        min' (S n) (S m) = S $ min' n m
        length' [] = Z -- void
        length' (x:xs) = S $ length' xs

答案 3 :(得分:1)

cutHTNW's second solution非常相似(并且表现得与那个一样好);但是,我没有明确地使用懒惰技巧,而是依靠hylo from recursion-schemes来融合遍历。

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TemplateHaskell #-}

import Data.Functor.Foldable
import Data.Functor.Foldable.TH

-- At first, I used [()] instead; as HTNW describes, Nat does seem to 
-- improve performance
data Nat = Z | S Nat

-- A list, annotated at the end with the length to be used for cutting.
data CutList a = Cutter Nat | CutCons a (CutList a)
makeBaseFunctor ''CutList

cut :: [[a]] -> [[a]]
cut = snd . hylo algCut coalgSetup . (inf,) 
    where
    coalgSetup = \case
        (cutter, []) -> CutterF cutter
        (cutter, xs : xss) -> CutConsF xs (min' cutter (length' xs), xss)
    algCut = \case
        CutterF cutter -> (cutter, [])
        CutConsF xs (cutter, xss) -> (cutter, take' cutter xs : xss)
    inf = S inf
    take' (S n) (x : xs) = x : take' n xs
    take' _ _ = []
    min' (S n) (S m) = S (min' n m)
    min' _ _ = Z
    length' [] = Z
    length' (x : xs) = S (length' xs)

(这可能更适合表达某种zygo-hylomorphism,但是我必须以 recursion-schemes 的方式定义这样的东西以获得良好的性能,并且那会太分散注意力。)

答案 4 :(得分:0)

也许你可以从中得到一些想法。函数和赋值似乎都在函数中有对应关系。

let nl = [[1..4],[2,3],[3,9]]
let min = minimum $ map length nl
map (take min) nl 
[[1,2],[2,3],[3,9]]

如果有人反对使用长度,请尝试。

(\(a:b:c:xs) -> zip3 a b c ) [[1..5],[2..5],[3..5]]
[(1,2,3),(2,3,4),(3,4,5)]

这仅适用于三个列表,但上述功能适用于任何数字。