在Haskell的列表中对相邻值进行分组

时间:2018-03-18 22:53:54

标签: list haskell

如果我有列表[1,2,3,4,5,6,7],并且我想将3个(或任何其他数字)相邻的值分组,那么我最终会得到列表:[[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7]]

我如何在Haskell中这样做?

5 个答案:

答案 0 :(得分:5)

plusZeroLeftNeutral

一些简短的解释:Data.List> f n xs = zipWith const (take n <$> tails xs) (drop (n-1) xs) Data.List> f 3 [1..7] [[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7]] 为我们提供了从原始位置开始的列表。我们只需要每个元素的前几个元素,因此我们在每个元素上运行tails。这让我们大部分都在那里,但最后留下了一些额外的悬空列表;在您的示例中,它们将从take n6开始,而不是7。我们可以通过计算输入列表的长度并仅获取那么多最终列表来实现这一点,但这对于无限输入列表或部分定义的输入列表不起作用。相反,由于输出应该总是比输入短[[6,7],[7],[]]个元素,我们使用标准的n技巧来切断额外的元素。

答案 1 :(得分:2)

来自稍高抽象级别的@ DanielWagner解决方案的另一种解释:

  

原始解决方案:

f n xs = zipWith const (take n <$> tails xs) (drop (n-1) xs)
           

take n <$> tails xs使用非确定性monad:

type NonDet a = [a]
-- instance Monad NonDet
tails :: [a] -> NonDet [a]
     

tails不确定&#34;选择&#34;子列表开始的位置,然后是纯函数

take n :: [a] -> [a]
     

fmap erminism图层下NonDet来切断尾部。这会在结果的末尾留下一些松懈,所以我们进入NonDet / []的管道,zipWith修复它。

这个新的解释也开辟了一个优化。 [] monad有一个失败的概念,即空列表。如果我们有一个take的版本,当它有一个太短的参数时会单独失败,我们就可以使用它而不用担心在结果的末尾删除短的子列表。所以:

import Control.Monad((<=<))
import Data.Maybe(maybeToList)

-- Maybe is the simplest failure monad.
-- Doesn't return [[a]] because this could conceivably be used in other
-- contexts and Maybe [a] is "smaller" and clearer than [[a]].
-- "safe" because, in the context of (do xs <- safeTake n ys),
-- length xs == n, definitely.
safeTake :: Int -> [a] -> Maybe [a]
safeTake 0 _ = return []
safeTake n [] = Nothing
safeTake n (x:xs) = (x:) <$> (safeTake $ n - 1) xs

-- maybeToList :: Maybe a -> [a]
-- maybeToList (return x) = return x / maybeToList (Just x) = [x]
-- maybeToList empty      = empty    / maybeToList Nothing  = [ ]
-- (.)   ::            (b ->   c) -> (a ->   b) -> (a ->   c)
-- (<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
f n = maybeToList . safeTake n <=< tails

f不再用monad之外的东西挖掘非确定性抽象。它也可以根据Kliesli的成分来编写,这肯定会在美容类别中给出它。 criterion基准测试也显示15-20%的加速(-O2下)。就个人而言,我认为看到更抽象的东西并使代码变得更漂亮&#34;也可以提供表现。

答案 2 :(得分:2)

let x:y:ls = [1,2,3,4,5,6,7] in zip3 (x:y:ls) (y:ls) ls

会给出

[(1,2,3),(2,3,4),(3,4,5),(4,5,6),(5,6,7)]

元组而不是列表。如果您需要列表,请应用\(a, b, c) -> [a, b, c]。或者做

let x:y:ls = [1,2,3,4,5,6,7] in [[a, b, c] | (a, b, c) <- zip3 (x:y:ls) (y:ls) ls]

答案 3 :(得分:0)

Daniel very clever answer的替代方案,您可以使用take和显式递归。

f n (x:xs) | length xs < (n-1) = []
           | otherwise         = (x : take (n-1) xs) : f n xs

然而,由于需要多次强制length xs,因此最终会大大减慢。

答案 4 :(得分:0)

Haskell似乎不会一次迭代列表3。列表理解也不会。没有迭代,似乎没有办法处理无限列表。 IDK,我对Haskell来说太新了。我能想到的只是一个递归函数。啊。该功能的一个特点是它可以参数化,它可以产生任意大小的子列表。使用Haskell模式匹配需要指定每个子列表中的元素数量,例如3,(x:y:z:xs),Haskell将在每次迭代时将xs减少3。一次四个(w:x:y:z:xs)。这引入了不良的硬编码功能。所以这个函数必须使用drop 3来减少xs,但是3也可以是参数3。一个辅助函数来获取每个子列表的大小并将常量[](空列表)作为第一个参数传递给主函数会有所帮助。

fl2 (ys) (xs) = if null xs then ys else (fl2 (ys ++ [take 3 xs]) (drop 3 xs))

fl2 [] [1..12] ......... [[1,2,3],[4,5,6],[7,8,9],[10,11 ,12]]

有趣的是函数对[取3 l,下降3 l]在函数中使用时对[1] [[1,2,3],[4,5,6,7,8,9, 10,11,12]。这基本上是fl2函数使用的内容,但子列表必须累积。

编辑3/19/18 我发现,令我惊讶的是,这很有用。我很可能会把它清理干净但是现在......

f7 ls = (take 3 ls, drop 3 ls)
f8 (a,b) = if null b then [a] else a : (f8 (f7 b))

这是如何运行的并不漂亮,但是......

f8 $ f7 [1..12]

产生[[1,2,3],[4,5,6],[7,8,9],[10,11,12]] 通过[]作为参数,这个IMO仍然更好。

这最后一个函数,可能是之前的句柄[]和[],[1]和[1]和奇数编号列表,相应地截断最后一个列表。这些都不是考虑写函数,而是结果。

编辑3/23/2018

非常感谢dfeuer,我尝试了splitAt而不是(\ xs - &gt;(需要3 xs,drop 3 xs)。我还改变了单行函数的语法,不使用if-then-else。调用函数仍然是相同的。第三个包装函数可能是有序的。

f7 = (splitAt 3)
f8 (a,b) | null b = [a] | True = a : (f8 $ f7 b)

我被单线函数中使用模式匹配保护所迷住了。如果 - 那么 - 其他是如此丑陋。我同意'if'应该是类似于类似lisp的语言的函数。

修改3/26/2018

Errrr。我不知道怎么说错了。结果列表是[1,2,3],[2,3,4],[3,4,5]而不是[1,2,3],[4,5,6],[7,8,9 ] 我觉得比正常人笨。 这里有两个修改过的函数来生成正确的结果列表。 第一个函数生成范围内所有可能的三元组,因为结果的子列表是三元组。

lt = [[a,b,c] | a <- [1..10], b <- [2..11], c <- [3..12]]

第二个函数通过计算的索引值选择'lt'的正确元素。对于12元素输入列表,索引值是1,111,222,... 999因此是111的倍数。所以这里的输入列表为[1..12]

[lt!!(x*111) | x <- [0..9]]

制作

[[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8],[7,8,9],[8,9,10],[9,10,11],[10,11,12]]

修改3/27/2018 好吧,生成的用于选择值的列表具有作为任何列表中下一个所需的集合的最后一个值。我最近被教导要仔细查看生成的列表。我从上面的lt生成函数生成了一些列表。每个列表的最后一个元素是任何大小列表的确切值。不再需要了。这一行完成了所有工作。

grp3 n = map (\x -> [(x-2),(x-1),x]) [3..n]

以下是最一般的。它将按任意数量进行分组,并且它将包括小于为完整性指定的组大小的组的子列表。

grp n ls = [take n.drop x $ ls)|x<-[0,n..]]

<强> 2018年5月7日 我经常这样做。我发现相对较快,我的一些函数可以通过代码中的微小更改来改变字符。我对版本不认真。最后一个版本生成错误的类型列表。

take 6 $ grp 3 [1..]

[[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18 ]

从枚举中取出n

grp n ls = [(take n.drop i$ls)| (i,x) <- zip [0..] ls]

take 6 $ grp 3 [1..]

[[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8 ]

我现在都添加了zip来限制输出。

grp 3 [1..10]

[[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8 ],[7,8,9],[8,9,10],[9,10],[10]]