在 Haskell 中将不寻常的序列实现为无限列表

Implementing an unusual sequence as an infinite list in Haskell

我有两个元素作为列表的开头 [1, 2]

这个不寻常的序列是它复制了三个元素后面的某种类型的位数中的数字。例如,在 1 和 2 之后,我们会有另一个 2,然后是两个 1。 所需列表的前几个元素将产生

[1, 2, 2, 1, 1, 2, 1, 2, 2, 1, 2, 2, 1, 1, 2, 1, 1, 2, 2] 因为

1  2   2  1 1   2  1  2   2  1   2   2

其中前面的数字代表相同数字的迷你序列的长度。

到目前为止,我已尝试使用 replicate 函数根据列表中较早的元素重复相同的数字。

selfrle :: [Int]
selfrle = 1 : 2 : [x | a <- [0..], let x = replicate (selfrle !! a) (selfrle !! (a + 1))) ]

问题是我不明白为什么它不起作用。

您可以做的第一件事就是正确设置类型。 replicate 构建一个列表,因此列表理解中的 x 具有类型 [Int],并且整个列表理解是所有 x 值及其类型的列表是 [[Int]]。当前两个元素是 12 时,您不能使用 lists of Int 作为列表的尾部。类型不匹配:您需要确定这是 Int 的列表,还是 lists of Int.[=22= 的列表]

根据你的描述,我怀疑你想通过使用 concat 展平列表理解来解决这个问题,就像这样:

selfrie = 1 : 2 : concat [x | a <- [0..],
                              let x = replicate (selfrie !! a) (selfrie !! (a + 1))]

如果您尝试这样做,您将不会获得您描述的列表。我不知道如何为下一部分提供帮助,那是因为我不理解你对所需列表的描述。这与其说是一个规范问题,不如说是一个编程问题,所以也许你可以回到原始来源,看看那里是如何解释的?

并不罕见,因为它出现在 https://oeis.org/A000002 的 OEIS 中,并在那里被命名为 Kolakoski 序列。他们甚至在 2011 年举办了 John Tromp 的 Haskell 节目:

a = 1:2: drop 2 (concat . zipWith replicate a . cycle $ [1, 2])

我们可以通过使用一个额外的变量 b 来编写比 稍微优雅的代码,我们 "tie a knot":

a :: [Int]
a = 1 : 2 : b
    where b = 2 : concat (zipWith replicate b (cycle [1, 2]))

因此,我们在这里首先产生 1 : 2 : b,其中 b2 开头,然后将 b 的副本与无限列表 [1, 2, 1, 2, …].

接受的答案似乎简洁且非常有效,但很难推理,特别是如果您是初学者并且忽略了 Haskell 的懒惰。

我们可以改写成

a = 1:2:2: (concat . zipWith replicate (drop 2 a) . cycle) [1, 2]

我们必须记住 a 只是一个像 1:2:2:?: ... ?:[] 一样的半构造列表,我们通过丢弃前两项 (drop 2 a) 开始使用它,剩下 [=16] =] 应用于 zipWith replicate 这足以让我们无限期地 运行 我们的逻辑。请记住 2:?: ... ?:[] 是一个像 cycle [1,2] 一样计算的列表。初始项目的可用性 2 可以计算下一个项目,然后计算下一个项目,然后再计算下一个项目,依此类推。

这是 Haskell 中的一个漂亮模式,如果您知道初始项,它可以生成无限列表。以斐波那契数列为例

fib = 1:1: (zipWith (+) fib (tail fib))

您需要做的就是调整您的思维方式,认为 fib 已经存在但尚未计算。

State Monad 的另一种方法:

但是,我相信给定的 Kolakoski 系列也可能是学习 State monad 的一个很好的案例研究。让我们看看是否能获得相同的性能..?

import Control.Monad.Trans.State

invert :: Int -> Int
invert = (+1) . (`mod` 2)

rle :: Int -> State Int [Int]
rle n = state $ \s -> (replicate n s, invert s)

kolakoski :: [Int] -> Int -> [Int]
kolakoski ps s = ps ++ kolakoski xs i
                 where (xs,i) = runState (mapM rle ps >>= pure . concat) s
  • 我们有一个 invert 函数,当输入 1 时给出 2,当输入 2 时给出 1.
  • rle 采用 Int 类型,returns 采用 State 类型,该函数采用状态并通过 n 和 returns 复制列表以及一个新状态,invert s。所以我们的状态一路交替 1,2,1,2...

然而,当使用 ghci (:set +s) 进行测试时,虽然两者都显示出线性时间复杂度,但这需要 10 倍的时间才能完成。有人愿意发表评论吗?