【问题标题】:Python Binomial CoefficientPython 二项式系数
【发布时间】:2021-12-31 21:35:08
【问题描述】:

import math
x = int(input("Enter a value for x: "))
y = int(input("Enter a value for y: "))

if y == 1 or y == x:
    print(1)

if y > x:
    print(0)        
else:
    a = math.factorial(x)
    b = math.factorial(y)
    div = a // (b*(x-y))
    print(div)  

这个二项式系数程序有效,但是当我输入两个相同的数字时,应该等于 1,或者当 y 大于 x 时,它应该等于 0。

【问题讨论】:

  • 您需要什么帮助?您用于二项式系数的公式看起来不太正确,是吗?
  • 你为什么使用while?你只能使用if !!
  • 当我输入一个大于 x 的数字时会出现错误或者如果 x 和 y 彼此相等
  • 为 x 输入一个值:1 为 y 输入一个值:1 1 回溯(最近一次调用最后一次):文件“D:\CE151 Computer Programming\ass1.py”,第 122 行,在 elif len(line)==1 and "1"
  • 你能说一下你实际上想做什么吗?

标签: python python-3.x


【解决方案1】:

这个问题很老了,但由于它在搜索结果中的排名很高,我会指出scipy 有两个函数用于计算二项式系数:

  1. scipy.special.binom()
  2. scipy.special.comb()

    import scipy.special
    
    # the two give the same results 
    scipy.special.binom(10, 5)
    # 252.0
    scipy.special.comb(10, 5)
    # 252.0
    
    scipy.special.binom(300, 150)
    # 9.375970277281882e+88
    scipy.special.comb(300, 150)
    # 9.375970277281882e+88
    
    # ...but with `exact == True`
    scipy.special.comb(10, 5, exact=True)
    # 252
    scipy.special.comb(300, 150, exact=True)
    # 393759702772827452793193754439064084879232655700081358920472352712975170021839591675861424
    

注意scipy.special.comb(exact=True) 使用 Python 整数,因此它可以处理任意大的结果!

在速度方面,三个版本给出的结果有些不同:

num = 300

%timeit [[scipy.special.binom(n, k) for k in range(n + 1)] for n in range(num)]
# 52.9 ms ± 107 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit [[scipy.special.comb(n, k) for k in range(n + 1)] for n in range(num)]
# 183 ms ± 814 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)each)

%timeit [[scipy.special.comb(n, k, exact=True) for k in range(n + 1)] for n in range(num)]
# 180 ms ± 649 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

(对于n = 300,二项式系数太大而无法使用float64 数字正确表示,如上所示)。

【讨论】:

  • 我只是提醒一下 scipy.special.binom 返回浮点近似值。这对于大多数应用程序来说已经足够了,但对于理论上的目的可能还不够。
  • Scipy 还提供了comb function,可用于计算精确值。
【解决方案2】:

注意,从Python 3.8 开始,标准库提供了math.comb 函数来计算二项式系数:

math.comb(n, k)

这是从n个项目中选择k个项目而不重复的方法数
n! / (k! (n - k)!):

import math
math.comb(10, 5)  # 252
math.comb(10, 10) # 1

【讨论】:

    【解决方案3】:

    这是一个实际使用 correct formula 的版本。 :)

    #! /usr/bin/env python
    
    ''' Calculate binomial coefficient xCy = x! / (y! (x-y)!)
    '''
    
    from math import factorial as fac
    
    
    def binomial(x, y):
        try:
            return fac(x) // fac(y) // fac(x - y)
        except ValueError:
            return 0
    
    
    #Print Pascal's triangle to test binomial()
    def pascal(m):
        for x in range(m + 1):
            print([binomial(x, y) for y in range(x + 1)])
    
    
    def main():
        #input = raw_input
        x = int(input("Enter a value for x: "))
        y = int(input("Enter a value for y: "))
        print(binomial(x, y))
    
    
    if __name__ == '__main__':
        #pascal(8)
        main()
    

    ...

    这是我几年前写的binomial() 的替代版本,它不使用math.factorial(),它在旧版本的Python 中不存在。但是,如果 r 不在 range(0, n+1) 内,则返回 1。

    def binomial(n, r):
        ''' Binomial coefficient, nCr, aka the "choose" function 
            n! / (r! * (n - r)!)
        '''
        p = 1    
        for i in range(1, min(r, n - r) + 1):
            p *= n
            p //= i
            n -= 1
        return p
    

    【讨论】:

    • 呸,谁在乎公式是否正确。只要我的代码不抛出任何异常就可以了,对吧?正确的? :)
    • @TimPietzcker 嘿,如果客户为他们希望你为他们编写的软件提供了错误的规格,那不是你的错。 :)
    • 您的(第二个)代码似乎也适用于整数除法,这是一个很棒的功能,因为对于大于(大约)60 的 n,由于精度错误,浮点数开始给出不正确的结果。
    • 这是一个关于效率较低的递归实现的问题:stackoverflow.com/questions/54932096/…
    • 这不是唯一的公式。 n*(n-1)...(n-r+1)/r! 的两个替代项是 n*(n-1)...(n-r+1)/r!和 n*(n-1)...(r+1)/(n-r)!。根据 n-r>r 与否,可以方便地选择第一种或第二种选择,以缩短分子处的产品序列。
    【解决方案4】:

    所以,如果您搜索“在 Python 中实现二项式系数”,首先会出现这个问题。只有this answer 在其第二部分中包含依赖于multiplicative formula 的有效实现。该公式执行最少的乘法运算。下面的函数不依赖于任何内置函数或导入:

    def fcomb0(n, k):
        '''
        Compute the number of ways to choose $k$ elements out of a pile of $n.$
    
        Use an iterative approach with the multiplicative formula:
        $$\frac{n!}{k!(n - k)!} =
        \frac{n(n - 1)\dots(n - k + 1)}{k(k-1)\dots(1)} =
        \prod_{i = 1}^{k}\frac{n + 1 - i}{i}$$
    
        Also rely on the symmetry: $C_n^k = C_n^{n - k},$ so the product can
        be calculated up to $\min(k, n - k).$
    
        :param n: the size of the pile of elements
        :param k: the number of elements to take from the pile
        :return: the number of ways to choose k elements out of a pile of n
        '''
    
        # When k out of sensible range, should probably throw an exception.
        # For compatibility with scipy.special.{comb, binom} returns 0 instead.
        if k < 0 or k > n:
            return 0
    
        if k == 0 or k == n:
            return 1
    
        total_ways = 1
        for i in range(min(k, n - k)):
            total_ways = total_ways * (n - i) // (i + 1)
    
        return total_ways
    

    最后,如果您需要更大的值并且不介意交易一些准确性,Stirling's approximation 可能是要走的路。

    【讨论】:

      【解决方案5】:

      这是一个使用条件表达式递归计算二项式系数的函数

      def binomial(n,k):
          return 1 if k==0 else (0 if n==0 else binomial(n-1, k) + binomial(n-1, k-1))
      

      【讨论】:

        【解决方案6】:

        y == x 的情况下,您的程序将继续执行第二个if 语句,从而导致ZeroDivisionError。您需要使陈述相互排斥;这样做的方法是使用elif ("else if") 而不是if:

        import math
        x = int(input("Enter a value for x: "))
        y = int(input("Enter a value for y: "))
        if y == x:
            print(1)
        elif y == 1:         # see georg's comment
            print(x)
        elif y > x:          # will be executed only if y != 1 and y != x
            print(0)
        else:                # will be executed only if y != 1 and y != x and x <= y
            a = math.factorial(x)
            b = math.factorial(y)
            c = math.factorial(x-y)  # that appears to be useful to get the correct result
            div = a // (b * c)
            print(div)  
        

        【讨论】:

          【解决方案7】:

          这个呢? :) 它使用正确的公式,避免math.factorial 并减少乘法运算:

          import math
          import operator
          product = lambda m,n: reduce(operator.mul, xrange(m, n+1), 1)
          x = max(0, int(input("Enter a value for x: ")))
          y = max(0, int(input("Enter a value for y: ")))
          print product(y+1, x) / product(1, x-y)
          

          另外,为了避免大整数运算,您可以使用浮点数,转换 把product(a[i])/product(b[i])改成product(a[i]/b[i]),把上面的程序改写为:

          import math
          import operator
          product = lambda iterable: reduce(operator.mul, iterable, 1)
          x = max(0, int(input("Enter a value for x: ")))
          y = max(0, int(input("Enter a value for y: ")))
          print product(map(operator.truediv, xrange(y+1, x+1), xrange(1, x-y+1)))
          

          【讨论】:

          • 为什么避免math.factorial 是一种优势?
          • @BartoszKP:可能不是,但@pm-2ring 在他的回答中指出 math.factorial 在旧 Python 中不存在,所以我决定避免它只是为了好玩。无论如何,我已经定义了product
          • @BartoszKP 和 firegurafiku : math.factorial() 以 C 速度运行,因此它可能比使用 Python 循环的解决方案快得多。 OTOH,阶乘()增长非常快:阶乘(13)太大而无法放入int,因此必须使用慢得多的long算术。 firegurafiku 的算法在这个分数上比简单的基于阶乘的算法要好,但它最终仍然可以处理大数。在下一条评论中继续...
          • @BartoszKP 和 firegurafiku :我的基于循环的版本是在没有大整数的语言中执行此操作的常用方法,因为每个循环中的除法使累积值尽可能小。此外,通过使用min(r, n - r),它执行的循环次数最少。
          【解决方案8】:

          对于 Python 3,scipy 具有函数 scipy.special.comb,它可以产生浮点以及精确整数结果

          import scipy.special
          
          res = scipy.special.comb(x, y, exact=True)
          

          请参阅scipy.special.comb 的文档。

          对于 Python 2,该函数位于 scipy.misc 中,其工作方式相同:

          import scipy.misc
          
          res = scipy.misc.comb(x, y, exact=True)
          

          【讨论】:

            【解决方案9】:

            我建议使用动态规划 (DP) 来计算二项式系数。与直接计算相比,它避免了大数的乘法和除法。除了递归解决方案之外,它还将先前解决的重叠子问题存储在一个表中以便快速查找。下面的代码显示了用于计算二项式系数的自下而上(表格)DP 和自上而下(记忆化)DP 实现。

            def binomial_coeffs1(n, k):
                #top down DP
                if (k == 0 or k == n):
                    return 1
                if (memo[n][k] != -1):
                    return memo[n][k]
            
                memo[n][k] = binomial_coeffs1(n-1, k-1) + binomial_coeffs1(n-1, k)
                return memo[n][k]
            
            def binomial_coeffs2(n, k):
                #bottom up DP
                for i in range(n+1):
                    for j in range(min(i,k)+1):
                        if (j == 0 or j == i):
                            memo[i][j] = 1
                        else:
                            memo[i][j] = memo[i-1][j-1] + memo[i-1][j]
                        #end if
                    #end for
                #end for
                return memo[n][k]
            
            def print_array(memo):
                for i in range(len(memo)):
                    print('\t'.join([str(x) for x in memo[i]]))
            
            #main
            n = 5
            k = 2
            
            print("top down DP")
            memo = [[-1 for i in range(6)] for j in range(6)]
            nCk = binomial_coeffs1(n, k)
            print_array(memo)
            print("C(n={}, k={}) = {}".format(n,k,nCk))
            
            print("bottom up DP")
            memo = [[-1 for i in range(6)] for j in range(6)]
            nCk = binomial_coeffs2(n, k)
            print_array(memo)
            print("C(n={}, k={}) = {}".format(n,k,nCk))
            

            注意:备忘录表的大小设置为较小的值 (6) 用于显示目的,如果您正在计算大 n 和 k 的二项式系数,则应增加它。

            【讨论】:

              【解决方案10】:

              最简单的方法是使用乘法公式。它按预期适用于 (n,n) 和 (n,0)。

              def coefficient(n,k):
                  c = 1.0
                  for i in range(1, k+1):
                      c *= float((n+1-i))/float(i)
                  return c
              

              Multiplicative formula

              【讨论】:

                【解决方案11】:

                应用递归定义是个好主意,如 Vadim Smolyakov 的回答,结合 DP(动态编程),但对于后者,您可以应用模块 functools 中的 lru_cache 装饰器:

                import functools
                
                @functools.lru_cache(maxsize = None)
                def binom(n,k):
                    if k == 0: return 1
                    if n == k: return 1
                    return binom(n-1,k-1)+binom(n-1,k)
                

                【讨论】:

                  【解决方案12】:

                  PM 2Ringalisianoi 给出的一个缩短的乘法变体。适用于 python 3,不需要任何包。

                  def comb(n, k):
                    # Remove the next two lines if out-of-range check is not needed
                    if k < 0 or k > n:
                      return None
                    x = 1
                    for i in range(min(k, n - k)):
                      x = x*(n - i)//(i + 1)
                    return x
                  

                  或者

                  from functools import reduce
                  def comb(n, k):
                    return (None if k < 0 or k > n else
                      reduce(lambda x, i: x*(n - i)//(i + 1), range(min(k, n - k)), 1))
                  

                  除法是在乘法之后立即完成的,而不是累积高数。

                  【讨论】:

                    【解决方案13】:

                    对于所有寻找二项式系数的 log(Theano 称之为 binomln)的人,this answer 有它:

                    from numpy import log
                    from scipy.special import betaln
                    
                    def binomln(n, k):
                        "Log of scipy.special.binom calculated entirely in the log domain"
                        return -betaln(1 + n - k, 1 + k) - log(n + 1)
                    

                    (如果您的语言/库缺少 betaln 但有 gammaln,就像 Go 一样,不要害怕,因为 betaln(a, b) 只是 gammaln(a) + gammaln(b) - gammaln(a + b),根据 MathWorld。)

                    【讨论】:

                      【解决方案14】:
                      import math
                      
                      def binomial_coefficients(n,k):
                            product = 1
                            for i in range(k):
                                 product = math.floor(((product * (n - i))/(i + 1)) 
                       return product 
                      

                      在计算二项式系数时,不应计算 (n, k) 和 k! 的有限乘积 n(n-1) ... (n - k +1)。这可能会导致溢出错误。因此,使用一点数论,我们可以假设输入总是整数形式(因为 (n, k) 的组合只接受整数))我们可以看到对于连续乘积中的整数“i”整数,乘积中的任何项 u 总是能被 i 整除。

                      注意:您可以在不导入数学模块的情况下执行此操作。 math.floor(a/b) 等价于 a // b

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2018-05-31
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2018-04-23
                        • 1970-01-01
                        • 2018-02-22
                        相关资源
                        最近更新 更多