在 Haskell 中生成无限列表 [0, 1, -1, 2, -2, ...]

7
假设我们想要在Haskell中生成列表[0, 1, -1, 2, -2, ...,那么最优雅的方法是什么?
我想出了以下解决方案:
solution = [0] ++ foldr (\(a,b) c->a:b:c) [] zip [1..] $ map negate [1..]

但我相信一定有更好的方法。
8个回答

20

这似乎是推导式最适用的场合:

solution = 0 : [y | x <- [1..], y <- [x, -x]]

12

使用 iterate

也许更优雅的方式是使用 iterate :: (a -> a) -> a -> [a] 函数,该函数通过生成每个后续项的函数来实现。例如:

solution = iterate nxt 0
    where nxt i | i > 0 = -i
                | otherwise = 1-i

或者我们可以使用if-then-else将其内联:

solution = iterate (\i -> if i > 0 then -i else 1-i) 0

或者,我们可以像 @melpomene 提到的那样,将布尔值转换成整数,使用 fromEnum 方法,并将其用于将 10 添加到答案中,如下:

solution = iterate (\i -> fromEnum (i < 1)-i) 0

哪个更加pointfree:

import Control.Monad(ap)

solution = iterate (ap subtract (fromEnum . (< 1))) 0

使用 (<**>)

我们也可以使用来自 applicate 的运算符 <**> 来产生一个数的正负变体,比如:

import Control.Applicative((<**>))

solution = 0 : ([1..] <**> [id, negate])

迭代(\i -> fromEnum(i <1)-i)? - melpomene
flip (-) 不就是 subtract 吗? - melpomene
2
优秀的应用程序使用! - Chris Martin
如果你使用 <*> 而不是翻转参数版本,那么你也不需要导入 Control.Applicative - 4castle
2
你需要翻转版本,以便 idnegate 交替出现;[id, negate] <*> [1..]id 应用于另一个列表的每个元素,然后将其应用于相同的 negate。由于第二个列表是无限的,因此您永远不会得到负数。(更简洁地说,<**> 不仅仅是 flip <*>。) - chepner

4
另一种原始解决方案。
alt = 0 : go 1
  where go n = n : -n : go (n+1)

4
如何呢?
concat (zipWith (\x y -> [x, y]) [0, -1 ..] [1 ..])

或者

concat (transpose [[0, -1 ..], [1 ..]])

?


4
如何呢:
tail $ [0..] >>= \x -> [x, -x]

经过一番思考,我认为使用nub而不是tail更加优雅。


5
nub会使程序的时间复杂度变为二次方并且会造成内存泄漏。因为它需要保存之前出现过的元素,以便检查当前元素是否已经出现过。 - Chris Martin
好的。我混淆了nub的作用;我想要删除仅连续重复的元素。我支持这样做的原因是语义上的。 - aplainzetakind

2

在这里你也可以使用 concatMap 替代 foldr,并将 map negate [1..] 替换为 [0, -1..]

solution = concatMap (\(a, b) -> [a, b]) $ zip [0, -1..] [1..]

如果你想使用 "negate",那么这是另一种选择:
solution = concatMap (\(a, b) -> [a, b]) $ (zip . map negate) [0, 1..] [1..]

1
只是因为没有人说出来:

0 : concatMap (\x -> [x,-x]) [1..]

0

虽然有点晚了,但这也可以解决问题

solution = [ (1 - 2 * (n `mod` 2)) * (n `div` 2) | n <- [1 .. ] ]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接