我想将[1,2,3,4]
转换为[[1 2] [2 3] [3 4]]
或[(1 2) (2 3) (3 4)]
。在clojure中我有(partition 2 1 [1,2,3,4])
。我怎么能在haskell中做到这一点?我怀疑标准api中有这样的功能,但我找不到它。
答案 0 :(得分:19)
这方面的标准技巧是zip
列表及其自己的tail
:
> let xs = [1,2,3,4] in zip xs (tail xs)
[(1,2),(2,3),(3,4)]
要了解其工作原理,请在视觉上排列列表及其尾部。
xs = 1 : 2 : 3 : 4 : []
tail xs = 2 : 3 : 4 : []
并注意zip
正在为每列创建一个元组。
为什么这总是做正确的事情有两个更微妙的原因:
zip
会停止。这是有道理的,因为我们最终不能有一个“不完整的对”,它也确保我们不会从单个元素列表中得到任何对。xs
为空时,人们可能希望tail xs
抛出异常。但是,因为zip
首先检查它的第一个参数,当它看到它是空列表时,第二个参数
永远不会被评估。上述所有内容也适用于zipWith
,因此您可以在需要将函数成对应用于相邻元素时使用相同的方法。
对于像Clojure partition
这样的通用解决方案,标准库中没有任何内容。但是,您可以尝试这样的事情:
partition' :: Int -> Int -> [a] -> [[a]]
partition' size offset
| size <= 0 = error "partition': size must be positive"
| offset <= 0 = error "partition': offset must be positive"
| otherwise = loop
where
loop :: [a] -> [[a]]
loop xs = case splitAt size xs of
-- If the second part is empty, we're at the end. But we might
-- have gotten less than we asked for, hence the check.
(ys, []) -> if length ys == size then [ys] else []
(ys, _ ) -> ys : loop (drop offset xs)
答案 1 :(得分:1)
只是使用不同的方法抛出另一个答案:
对于n = 2,您只需要zip
列表及其尾部。对于n = 3,您希望使用尾部和尾部尾部压缩列表。这种模式还在继续,所以我们所要做的就是概括它:
partition n = sequence . take n . iterate tail
但这仅适用于偏移量1.为了概括偏移量,我们只需要查看生成列表。它将始终具有以下形式:
[[1..something],[2..something+1],..]
所以剩下要做的就是选择每个offset
元素,我们应该没问题。我无耻地从this question的@ertes中偷走了这个版本:
everyNth :: Int -> [a] -> [a]
everyNth n = map head . takeWhile (not . null) . iterate (drop n)
整个功能现在变为:
partition size offset = everyNth offset . sequence . take size . iterate tail
答案 2 :(得分:0)
有时最好自己动手。递归函数赋予 LisP 强大的力量和吸引力。 Haskell 试图阻止他们,但通常最好使用递归函数来实现解决方案。它们通常很简单,就像生成对一样。 Haskell 模式匹配减少了代码。这可以通过仅将模式更改为 (x:y:yys) 来生成 (a,b), (c,d), (e,f) 来轻松更改。
> prs (x:yys@(y:_)) = (x,y):prs yys
> prs "abcdefg"
[('a','b'),('b','c'),('c','d'),('d','e'),('e','f'),('f','g')