通过从模式列表中添加整数来生成 Haskell 中的整数列表

Generate list of Ints in Haskell by adding Ints from a pattern list

我在玩 Haskell,主要是想学习一些解决问题的新技术。在没有任何实际应用的情况下,我想到了一件有趣的事情,但我找不到令人满意的解决方案。也许有人有更好的主意?

问题:

假设我们要使用起始值和整数列表生成一个整数列表,表示要按指定顺序添加的数字模式。所以第一个值给出,那么第二个值应该是起始值加上列表中的第一个值,第三个 that 值加上模式的第二个值,依此类推。当模式结束时,它应该重新开始。

例如:假设我们有一个起始值 v 和一个模式 [x,y],我们想要列表 [v,v+x,v+x+y,v+2x+y,v+2x+2y, ...]。换句话说,对于双值模式,下一个值是通过将 xy 交替添加到上次计算的数字来创建的。

如果模式足够短(2-3 个值?),可以生成单独的列表:

然后通过加法将它们压缩在一起。然而,一旦模式变长,这就会变得非常乏味。我最好的解决方案尝试是这样的:

generateLstByPattern :: Int -> [Int] -> [Int]
generateLstByPattern v pattern = v : (recGen v pattern)
  where
  recGen :: Int -> [Int] -> [Int]
  recGen lastN (x:[]) = (lastN + x) : (recGen (lastN + x) pattern)
  recGen lastN (x:xs) = (lastN + x) : (recGen (lastN + x) xs)

它按预期工作 - 但我感觉在某处有更优雅的 Haskell 解决方案(几乎总是如此!)。你怎么看?也许是一个很酷的列表理解?我忘记的高阶函数?

分离关注点。首先看一个只处理一次的列表。让它工作,测试它。提示:“用一些累加器遍历列表元素”通常很适合折叠。

然后剩下的就是重复输入列表并将其输入一次传递函数。方便的是,有 a standard function 用于此目的。只需确保您的一次处理器足够懒惰以处理无限列表输入。

单独实现列表可能更好,例如 x 的列表可以用:

实现
xseq :: (Enum a, Num a) => a -> [a]
xseq x = 0 : ([x, x+x ..] >>= replicate 2)

y 的序列可以实现为:

yseq :: (Enum a, Num a) => a -> [a]
yseq y = [0,y ..] >>= replicate 2

然后你可以用zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]把两个列表加在一起,加上v:

mylist :: (Enum a, Num a) => a -> a -> a -> [a]
mylist v x y = zipWith ((+) . (v +)) (xseq x) (yseq y)

因此对于 v = 1x = 2y = 3,我们得到:

Prelude> take 10 (mylist 1 2 3)
[1,3,6,8,11,13,16,18,21,23]

另一种方法是将我们每次先添加 x 然后添加 y 作为模式。因此,我们可以创建一个无限列表 [(x+), (y+)],并使用 scanl :: (b -> a -> b) -> b -> [a] -> [b] 每次应用其中一个函数并产生中间结果:

mylist :: Num a => a -> a -> a -> [a]
mylist v x y = scanl (<b>flip ($)</b>) v (cycle [(x+), (y+)])

这会产生相同的结果:

Prelude> take 10 $ mylist 1 2 3
[1,3,6,8,11,13,16,18,21,23]

现在唯一要做的就是将其概括列表。因此,例如,如果给出了添加列表,那么您可以将其实现为:

mylist :: Num a => [a] -> [a]
mylist v <b>xs</b> = scanl (<b>flip ($)</b>) v (cycle (<b>map (+) xs</b>))

或函数列表:

mylist :: Num a => [a -> a] -> [a]
mylist v <b>xs</b> = scanl (<b>flip ($)</b>) v (cycle (<b>xs</b>))

关于生成系列,我的第一个方法是 iterate or unfoldriterate 用于简单系列,unfoldr 用于那些携带某种状态但不使用任何 State monad 的人。

在这种特殊情况下,我认为 unfoldr 是理想的。

series :: Int -> [Int] -> [Int]
series s [x,y] = unfoldr (\(f,s) -> Just (f*x + s*y, (s+1,f))) (s,0)

λ> take 10 $ series 1 [1,1]
[1,2,3,4,5,6,7,8,9,10]

λ> take 10 $ series 3 [1,1]
[3,4,5,6,7,8,9,10,11,12]

λ> take 10 $ series 0 [1,2]
[0,1,3,4,6,7,9,10,12,13]

你描述的是

foo :: Num a => a -> [a] -> [a]
foo v pattern = scanl (+) v (cycle pattern)

通常会写成

foo :: Num a => a -> [a] -> [a]
foo v = scanl (+) v . cycle 

scanl (+) v xs 是计算 (v:xs) 部分和 的标准方法,而 cycle 是重复给定的标准方法循环列出 。这你所描述的。

这适用于任何正长度的模式列表,如您所愿。


您生成它的方法很有创意,但它几乎太聪明了(即看起来过于复杂)。它可以用一些列表理解来表达,如

foo v pat =
   let   -- the lists, as you describe them:
       lists = repeat v :
               [ replicate i 0 ++
                 [ y | x <- [p, p+p ..]
                     , y <- map (const x) pat ]
                 | (p,i) <- zip pat [1..] ]
   in
     -- OK, so what do we do with that? How do we zipWith
     --   over an arbitrary amount of lists?
     --   with a fold!
     foldr (zipWith (+)) (repeat 0) lists

map (const x) pat是"clever"的一种写法replicate (length pat) x。根据定义,它可以进一步缩短为 x <$ pat,因为 (<$) x xs == map (const x) xs。它可能看起来很模糊,直到您习惯了它,然后它才显得清晰明了。 :)

奇怪的是居然还没有人提到这种愚蠢的方式。

mylist x xs = x : zipWith (+) (mylist x xs) (cycle xs)

(如果你眯着眼睛,你可以看到与 scanl 答案的联系)。