Haskell 中的帕斯卡三角

问题描述 投票:0回答:6

我是 Haskell 新手,我真的需要一些帮助!

我必须编写一个程序,其中包含一个递归函数,以使用帕斯卡三角技术生成 n=12 次幂的二项式系数列表。

我脑子里有一些想法,但因为我才刚刚开始,所以我不知道如何将其实现到 haskell?!

有人可以帮我吗?

first row: (a+b)^0 = 1
second row: (a+b)^1 = 1a+1b
third row: (a+b)^2 = 1a^2+2ab+1b^2

等等...这是我的主要想法。但我什至无法尝试这个,因为我不知道如何将其放入 Haskell 中..总是出错

haskell recursion pascals-triangle
6个回答
8
投票

首先为三角形中的每个元素分配一个索引:

 | 0 1 2 3 4 5 6
--+--------------------------
0 | 1
1 | 1 1
2 | 1 2 1
3 | 1 3 3 1
4 | 1 4 6 4 1
5 | 1 5 10 10 5 1
6 | 1 6 15 20 15 6 1

在这里,我只是将三角形放在一边,以便我们可以对它们进行编号。 所以这里我想说

(6, 4)
处的元素是
15
,而
(4, 6)
不存在。 现在专注于编写函数

pascal :: Integer -> Integer -> Integer
pascal x y = ???

这样您就可以生成这个版本的三角形。 你可以从写开始

pascal x y
    | x == 0 = 1
    | x == y = 1
    | x <  y = error "Not a valid coordinate for Pascal's triangle."
    | otherwise = pascal ? ? + pascal ? ?

请注意,在这里,您可以通过直角坐标来完成,而不是弄清楚哪些元素应该通过对角线添加在一起。 在这里,您会注意到

y
是您所在的三角形中的哪一行,
x
是该行中元素的位置。 您需要做的就是弄清楚用什么来代替
?

一旦你开始工作,我就为这个三角形提供了一个更高效的单行线,并且可以在仍然使用递归的同时一次性生成整个三角形:

import Data.List (scanl1)

pascals :: [[Integer]]
pascals = repeat 1 : map (scanl1 (+)) pascals

不要尝试将这个解决方案交给你的教授,这不是他们想要的,如果你只做了一周的 Haskell,那么很明显有人给了你这个解决方案。 然而,它确实显示了 Haskell 对于此类问题的强大能力。 我将展示如何索引

pascals
以获得给定的
(n, k)
值,但这样做也会给你太多解决简单递归的提示。

由于存在一些混乱,我给出这个解决方案的原因是为了将它与经常显示的斐波那契数列的惰性实现进行比较:

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

相比

fib 0 = 1
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)

这个定义生成了所有斐波那契数的无限列表,并且效率很高(从 CPU 的角度来看,RAM 是另一回事)。 它在前 2 个元素中编码基本情况,然后编码一个可以计算其余部分的递归表达式。 对于斐波那契数列,您需要 2 个值才能开始,但对于帕斯卡三角形,您只需要一个值,该值恰好是一个无限列表。 我上面发布的网格中的列有一个很容易看到的模式,

scanl1 (+)
函数只是利用了这种模式并允许我们非常轻松地生成它,但这生成的是三角形的对角线而不是三角形的对角线。行。 要获取行,您可以为此列表建立索引,或者您可以使用
take
drop
和其他此类函数执行一些奇特的技巧,但这是另一天的练习。


8
投票

从三角形本身开始:

     1
    1 1
   1 2 1
  1 3 3 1
 1 4 6 4 1
    ...

您应该注意到,要写下下一行,您必须应用此规则:对前一行的相邻元素求和,对孤独的边缘元素使用

0
。视觉上:

    0   1   0
     \+/ \+/
  0   1   1   0
   \+/ \+/ \+/
0   1   2   1   0
 \+/ \+/ \+/ \+/
  1   3   3   1
       ...

操作上,看起来像这样:

For row 0:
[1]  (it's a given; i.e. base case)

For row 1:
[0, 1]   <- row 0 with a zero prepended ([0] ++ row 0)
 +  +
[1, 0]   <- row 0 with a zero appended  (row 0 ++ [0])
 =  =
[1, 1]   <- element-wise addition

For row 2:
[0, 1, 1]
 +  +  +
[1, 1, 0]
 =  =  =
[1, 2, 1]

Generally, for row N:

element-wise addition of:
  [0] ++ row(N-1)
  row(N-1) ++ [0]

请记住,Haskell 中列表的逐元素加法是

zipWith (+)

因此我们得出以下 Haskell 定义:

pascal 0 = [1]
pascal n = zipWith (+) ([0] ++ pascal (n-1)) (pascal (n-1) ++ [0])

或者以类似于著名的“懒惰谎言”的方式:

pascals = [1] : map (\xs -> zipWith (+) ([0] ++ xs) (xs ++ [0])) pascals

2
投票

另一种可能的解决方案(我认为更适合初学者):

pascal :: Integer -> [Integer]
pascal 0 = [1]
pascal 1 = [1, 1]
pascal n = let p = pascal (n - 1)
    in [1] ++ pascalStep p ++ [1]

pascalStep :: [Integer] -> [Integer]
pascalStep [] = []
pascalStep [_] = []
pascalStep (x:y:xs) = x + y : pascalStep (y : xs)

使用

let
避免更多空间使用
pascal
递归调用以查找所有先前的行,使用它们获取下一行,直到到达所需的行。

输出:

*Main> pascal 3
[1,3,3,1]
*Main> pascal 4
[1,4,6,4,1]
*Main> pascal 5
[1,5,10,10,5,1]

0
投票

从基本案例开始。

pascal 0 0 = 1

然后处理边缘情况

pascal n 0 = 1
pascal n r | n == r = 1

现在用递归步骤进行扩展

pascal n r = pascal (n - 1) (r - 1) + pascal (n - 1) r

如果您想要特定行的列表,请编写一个包装器

binom n = map (pascal n) [0..n]

弄清楚类型应该不难

pascal :: Integral a => a -> a -> a
binom :: Integral a => a -> [a]

0
投票

我在手机上,所以请原谅错误,但你可以在这里以一种非常酷的方式使用 Haskell 的惰性求值。

pascals :: [[Int]]
pascals = [1]:map (\r -> zipWith (+) (0:r) (r++[0])) pascals

你可以用叉子来实现这一点,但它相当深奥。

pascals :: [[Int]]
pascals = [1]:map ((zipWith (+) -<) (0:) (++[0])) pascals

但我个人真的很喜欢这段代码,并且认为它值得阅读 -

pascals :: [[Int]]
pascals = [1]:map next pascals
    where next = (zipWith (+) -<) (0:) (++[0])

但是,无论我多么喜欢无点编程,这样的组合器可能会有点令人困惑。


0
投票
ghci> :set +m
ghci> let pascal 1 = [1]
ghci|     pascal n = zipWith (+) (0:pascal (n-1)) (pascal (n-1) ++ [0])
ghci|

ghci> pascal 3
[1,2,1]
ghci> pascal 4
[1,3,3,1]
ghci> pascal 5
[1,4,6,4,1]
ghci> pascal 6
[1,5,10,10,5,1]
© www.soinside.com 2019 - 2024. All rights reserved.