我想转换[1,2,3,4]
为[[1 2] [2 3] [3 4]]
or [(1 2) (2 3) (3 4)]
。在clojure中我有(partition 2 1 [1,2,3,4])
. 我怎样才能在haskell中做到这一点?我怀疑标准api中有这样的功能,但我找不到。
问问题
2438 次
3 回答
21
这样做的标准技巧是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)
于 2013-05-26T17:50:25.317 回答
1
只是用不同的方法抛出另一个答案:
对于 n=2,您只想简单地zip
列出带有尾部的列表。对于 n=3,您希望使用它的尾部和尾部的尾部压缩列表。这种模式继续下去,所以我们要做的就是概括它:
partition n = sequence . take n . iterate tail
但这仅适用于偏移量 1。要概括偏移量,我们只需查看生成的列表。它将始终具有以下形式:
[[1..something],[2..something+1],..]
所以剩下要做的就是选择每一个offset
元素,我们应该没问题。我无耻地从这个问题中从@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
于 2016-08-02T07:27:15.240 回答
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')
于 2021-05-19T21:55:23.000 回答