列表中的元素对

时间:2013-05-26 17:47:13

标签: haskell clojure

我想将[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中有这样的功能,但我找不到它。

3 个答案:

答案 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')