假设我们有一个列表列表,我们希望将所有内部列表缩减到最短内部列表的长度 - 例如,从[[1,2,3,4],[2,3],[3,9]]
得到{{1} }}。
到目前为止,这是我的代码(它不起作用):
[[1,2],[2,3],[3,9]]
我试着这样解决: 必须有一个函数返回最短列表的长度,因此,我们需要一个进一步的函数,将所有列表从开头剪切到这个新长度。 但事实上,我并不知道。
答案 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解决方案的动机是试图避免在每个输入列表上调用length
和take
所涉及的额外工作,每次遍历几次。我不太确定我的解决方案在此目标中是否成功:据我所知sequenceA
遍历每个列表一次,transpose
再次转换它们,所以它可能就像昂贵。它也不是很容易阅读:使用length
和take
的解决方案非常清楚。
我确实认为它有点漂亮,并且认为这个问题在转换到不同的域时(即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)
这cut
与HTNW'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)]
这仅适用于三个列表,但上述功能适用于任何数字。