Skip to content

Commit

Permalink
bpo-35431: Implemented math.comb (GH-11414)
Browse files Browse the repository at this point in the history
  • Loading branch information
FR4NKESTI3N authored and rhettinger committed Jun 1, 2019
1 parent 5ac0b98 commit 4a68650
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 1 deletion.
15 changes: 15 additions & 0 deletions Doc/library/math.rst
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,21 @@ Number-theoretic and representation functions
:meth:`x.__trunc__() <object.__trunc__>`.


.. function:: comb(n, k)

Return the number of ways to choose *k* items from *n* items without repetition
and without order.

Also called the binomial coefficient. It is mathematically equal to the expression
``n! / (k! (n - k)!)``. It is equivalent to the coefficient of k-th term in
polynomial expansion of the expression ``(1 + x) ** n``.

Raises :exc:`TypeError` if the arguments not integers.
Raises :exc:`ValueError` if the arguments are negative or if k > n.

.. versionadded:: 3.8


Note that :func:`frexp` and :func:`modf` have a different call/return pattern
than their C equivalents: they take a single argument and return a pair of
values, rather than returning their second return value through an 'output
Expand Down
51 changes: 51 additions & 0 deletions Lib/test/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -1862,6 +1862,57 @@ def test_fractions(self):
self.assertAllClose(fraction_examples, rel_tol=1e-8)
self.assertAllNotClose(fraction_examples, rel_tol=1e-9)

def testComb(self):
comb = math.comb
factorial = math.factorial
# Test if factorial defintion is satisfied
for n in range(100):
for k in range(n + 1):
self.assertEqual(comb(n, k), factorial(n)
// (factorial(k) * factorial(n - k)))

# Test for Pascal's identity
for n in range(1, 100):
for k in range(1, n):
self.assertEqual(comb(n, k), comb(n - 1, k - 1) + comb(n - 1, k))

# Test corner cases
for n in range(100):
self.assertEqual(comb(n, 0), 1)
self.assertEqual(comb(n, n), 1)

for n in range(1, 100):
self.assertEqual(comb(n, 1), n)
self.assertEqual(comb(n, n - 1), n)

# Test Symmetry
for n in range(100):
for k in range(n // 2):
self.assertEqual(comb(n, k), comb(n, n - k))

# Raises TypeError if any argument is non-integer or argument count is
# not 2
self.assertRaises(TypeError, comb, 10, 1.0)
self.assertRaises(TypeError, comb, 10, "1")
self.assertRaises(TypeError, comb, "10", 1)
self.assertRaises(TypeError, comb, 10.0, 1)

self.assertRaises(TypeError, comb, 10)
self.assertRaises(TypeError, comb, 10, 1, 3)
self.assertRaises(TypeError, comb)

# Raises Value error if not k or n are negative numbers
self.assertRaises(ValueError, comb, -1, 1)
self.assertRaises(ValueError, comb, -10*10, 1)
self.assertRaises(ValueError, comb, 1, -1)
self.assertRaises(ValueError, comb, 1, -10*10)

# Raises value error if k is greater than n
self.assertRaises(ValueError, comb, 1, 10**10)
self.assertRaises(ValueError, comb, 0, 1)




def test_main():
from doctest import DocFileSuite
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Implement :func:`math.comb` that returns binomial coefficient, that computes
the number of ways to choose k items from n items without repetition and
without order.
Patch by Yash Aggarwal and Keller Fuchs.
51 changes: 50 additions & 1 deletion Modules/clinic/mathmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

121 changes: 121 additions & 0 deletions Modules/mathmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2998,6 +2998,126 @@ math_prod_impl(PyObject *module, PyObject *iterable, PyObject *start)
}


/*[clinic input]
math.comb
n: object(subclass_of='&PyLong_Type')
k: object(subclass_of='&PyLong_Type')
Number of ways to choose *k* items from *n* items without repetition and without order.
Also called the binomial coefficient. It is mathematically equal to the expression
n! / (k! * (n - k)!). It is equivalent to the coefficient of k-th term in
polynomial expansion of the expression (1 + x)**n.
Raises TypeError if the arguments are not integers.
Raises ValueError if the arguments are negative or if k > n.
[clinic start generated code]*/

static PyObject *
math_comb_impl(PyObject *module, PyObject *n, PyObject *k)
/*[clinic end generated code: output=bd2cec8d854f3493 input=565f340f98efb5b5]*/
{
PyObject *val = NULL,
*temp_obj1 = NULL,
*temp_obj2 = NULL,
*dump_var = NULL;
int overflow, cmp;
long long i, terms;

cmp = PyObject_RichCompareBool(n, k, Py_LT);
if (cmp < 0) {
goto fail_comb;
}
else if (cmp > 0) {
PyErr_Format(PyExc_ValueError,
"n must be an integer greater than or equal to k");
goto fail_comb;
}

/* b = min(b, a - b) */
dump_var = PyNumber_Subtract(n, k);
if (dump_var == NULL) {
goto fail_comb;
}
cmp = PyObject_RichCompareBool(k, dump_var, Py_GT);
if (cmp < 0) {
goto fail_comb;
}
else if (cmp > 0) {
k = dump_var;
dump_var = NULL;
}
else {
Py_DECREF(dump_var);
dump_var = NULL;
}

terms = PyLong_AsLongLongAndOverflow(k, &overflow);
if (terms < 0 && PyErr_Occurred()) {
goto fail_comb;
}
else if (overflow > 0) {
PyErr_Format(PyExc_OverflowError,
"minimum(n - k, k) must not exceed %lld",
LLONG_MAX);
goto fail_comb;
}
else if (overflow < 0 || terms < 0) {
PyErr_Format(PyExc_ValueError,
"k must be a positive integer");
goto fail_comb;
}

if (terms == 0) {
return PyNumber_Long(_PyLong_One);
}

val = PyNumber_Long(n);
for (i = 1; i < terms; ++i) {
temp_obj1 = PyLong_FromSsize_t(i);
if (temp_obj1 == NULL) {
goto fail_comb;
}
temp_obj2 = PyNumber_Subtract(n, temp_obj1);
if (temp_obj2 == NULL) {
goto fail_comb;
}
dump_var = val;
val = PyNumber_Multiply(val, temp_obj2);
if (val == NULL) {
goto fail_comb;
}
Py_DECREF(dump_var);
dump_var = NULL;
Py_DECREF(temp_obj2);
temp_obj2 = PyLong_FromUnsignedLongLong((unsigned long long)(i + 1));
if (temp_obj2 == NULL) {
goto fail_comb;
}
dump_var = val;
val = PyNumber_FloorDivide(val, temp_obj2);
if (val == NULL) {
goto fail_comb;
}
Py_DECREF(dump_var);
Py_DECREF(temp_obj1);
Py_DECREF(temp_obj2);
}

return val;

fail_comb:
Py_XDECREF(val);
Py_XDECREF(dump_var);
Py_XDECREF(temp_obj1);
Py_XDECREF(temp_obj2);

return NULL;
}


static PyMethodDef math_methods[] = {
{"acos", math_acos, METH_O, math_acos_doc},
{"acosh", math_acosh, METH_O, math_acosh_doc},
Expand Down Expand Up @@ -3047,6 +3167,7 @@ static PyMethodDef math_methods[] = {
{"tanh", math_tanh, METH_O, math_tanh_doc},
MATH_TRUNC_METHODDEF
MATH_PROD_METHODDEF
MATH_COMB_METHODDEF
{NULL, NULL} /* sentinel */
};

Expand Down

0 comments on commit 4a68650

Please sign in to comment.