Haskell 列表生成器

Posted

技术标签:

【中文标题】Haskell 列表生成器【英文标题】:Haskell List Generator 【发布时间】:2013-08-12 00:41:16 【问题描述】:

我一直在处理涉及基于列表中先前元素生成列表的问题(例如pentagonal numbers)。我似乎找不到我想要的表单的内置函数。本质上,我正在寻找表单的功能:

([a] -> a) -> [a] -> [a]

([a] -> a) 到目前为止获取列表并产生应该在列表中的下一个元素,a[a] 是初始列表。我尝试使用 iterate 来实现这一点,但这会产生一个列表列表,每个连续列表都有一个更多元素(所以要获得第 3000 个元素,我必须使用 (list !! 3000) !! 3000) 而不是 list !! 3000

【问题讨论】:

我不认为你会找到任何内置的,但你可以通过unfoldr - \f -> unfoldr (\s -> let x = f s in Just (x, x:s)) 做到这一点。 您可以将选择器映射到迭代构造的列表上,例如map last,以从列表列表中获取所需的值列表。但最好建立由(:) 构建的反向临时列表,以便可以使用map head。然后,您可能会发现可以重组整个计算,如e.g. here,通过使用单独的引用,在您的“nextStep”函数中访问正在构建的结果列表(当然要注意正在定义的列表)保持对它到目前为止定义的部分的访问)。 Conduit 包,您将拥有源列表 【参考方案1】:

如果递归依赖于恒定数量的先前项,那么您可以使用标准核心递归来定义序列,就像使用斐波那契数列一样:

-- fibs(0) = 1
-- fibs(1) = 1
-- fibs(n+2) = fibs(n) + fibs(n+1)
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

-- foos(0) = -1
-- foos(1) = 0
-- foos(2) = 1
-- foos(n+3) = foos(n) - 2*foos(n+1) + foos(n+2)
foos = -1 : 0 : 1 : zipWith (+) foos 
                        (zipWith (+) 
                            (map (negate 2 *) (tail foos)) 
                            (tail $ tail foos))

虽然你可以引入一些自定义函数来让语法更好一点

(#) = flip drop
infixl 7 #
zipMinus = zipWith (-)
zipPlus  = zipWith (+)

-- foos(1) = 0
-- foos(2) = 1
-- foos(n+3) = foos(n) - 2*foos(n+1) + foos(n+2)
foos = -1 : 0 : 1 : ( ( foos # 0  `zipMinus` ((2*) <$> foos # 1) )
                                  `zipPlus`  foos # 2 )

但是,如果术语的数量不同,那么您将需要一种不同的方法。

例如,考虑 p(n),即给定正整数可以表示为正整数之和的方式数。

p(n) = p(n-1) + p(n-2) - p(n-5) - p(n-7) + p(n-12) + p(n-15) - ...

我们可以更简单地定义为

p(n) = ∑ k ∈ [1,n) q(k) p(n-k)

在哪里

-- q( i ) | i == (3k^2+5k)/2 = (-1) ^ k
--        | i == (3k^2+7k+2)/2 = (-1) ^ k
--        | otherwise         = 0
q = go id 1
  where go zs c = zs . zs . (c:) . zs . (c:) $ go ((0:) . zs) (negate c)

 ghci> take 15 $ zip [1..] q
 [(1,1),(2,1),(3,0),(4,0),(5,-1),(6,0),(7,-1),(8,0),(9,0),(10,0),(11,0),(12,1),
  (13,0),(14,0),(15,1)]

那么我们可以用iterate来定义p

 p = map head $ iterate next [1]
   where next xs = sum (zipWith (*) q xs) : xs

注意iterate next 如何创建一系列p反转 前缀,以便于使用q 计算p 的下一个元素。然后我们取每个反转前缀的头元素来找到p

ghci> next [1]
[1,1]
ghci> next it
[2,1,1]
ghci> next it
[3,2,1,1]
ghci> next it
[5,3,2,1,1]
ghci> next it
[7,5,3,2,1,1]
ghci> next it
[11,7,5,3,2,1,1]
ghci> next it
[15,11,7,5,3,2,1,1]
ghci> next it
[22,15,11,7,5,3,2,1,1]

将其抽象为一个模式,我们可以得到您正在寻找的功能:

construct :: ([a] -> a) -> [a] -> [a]
construct f = map head . iterate (\as -> f as : as)

p = construct (sum . zipWith (*) q) [1]

或者,如果我们定义一个辅助函数来生成列表的反向前缀,我们可以在标准的核心递归样式中执行此操作:

rInits :: [a] -> [[a]]
rInits = scanl (flip (:)) []

p = 1 : map (sum . zipWith (*) q) (tail $ rInits p)

【讨论】:

上面第一个模式中的fibs是什么类型的? @jwg 它是一些未指定数字类型的列表,所以它可能是[Int],[Integer],[Float],甚至是Num a =&gt; [a] 感谢您帮助我。 Corecursion 依赖于恒定数量的先前术语。这对于熟练的眼睛来说可能很明显,但对新手来说有很大帮助。出色的答案。 带有construct辅助函数的例子仍然是corecursive,即使corecursion被iterate抽象出来了,对吧?

以上是关于Haskell 列表生成器的主要内容,如果未能解决你的问题,请参考以下文章

是否有可能在haskell中有一套理解?

在 Haskell 中为逻辑表达式生成真值表

在 Haskell 中整理列表理解

在Haskell中,如何创建具有多种类型的列表?

无限列表的 Haskell 笛卡尔积

列表生成函数的延迟评估?