Skip to content

Commit 4a68650

Browse files
FR4NKESTI3Nrhettinger
authored andcommitted
bpo-35431: Implemented math.comb (GH-11414)
1 parent 5ac0b98 commit 4a68650

File tree

5 files changed

+241
-1
lines changed

5 files changed

+241
-1
lines changed

Doc/library/math.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,21 @@ Number-theoretic and representation functions
232232
:meth:`x.__trunc__() <object.__trunc__>`.
233233

234234

235+
.. function:: comb(n, k)
236+
237+
Return the number of ways to choose *k* items from *n* items without repetition
238+
and without order.
239+
240+
Also called the binomial coefficient. It is mathematically equal to the expression
241+
``n! / (k! (n - k)!)``. It is equivalent to the coefficient of k-th term in
242+
polynomial expansion of the expression ``(1 + x) ** n``.
243+
244+
Raises :exc:`TypeError` if the arguments not integers.
245+
Raises :exc:`ValueError` if the arguments are negative or if k > n.
246+
247+
.. versionadded:: 3.8
248+
249+
235250
Note that :func:`frexp` and :func:`modf` have a different call/return pattern
236251
than their C equivalents: they take a single argument and return a pair of
237252
values, rather than returning their second return value through an 'output

Lib/test/test_math.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,6 +1862,57 @@ def test_fractions(self):
18621862
self.assertAllClose(fraction_examples, rel_tol=1e-8)
18631863
self.assertAllNotClose(fraction_examples, rel_tol=1e-9)
18641864

1865+
def testComb(self):
1866+
comb = math.comb
1867+
factorial = math.factorial
1868+
# Test if factorial defintion is satisfied
1869+
for n in range(100):
1870+
for k in range(n + 1):
1871+
self.assertEqual(comb(n, k), factorial(n)
1872+
// (factorial(k) * factorial(n - k)))
1873+
1874+
# Test for Pascal's identity
1875+
for n in range(1, 100):
1876+
for k in range(1, n):
1877+
self.assertEqual(comb(n, k), comb(n - 1, k - 1) + comb(n - 1, k))
1878+
1879+
# Test corner cases
1880+
for n in range(100):
1881+
self.assertEqual(comb(n, 0), 1)
1882+
self.assertEqual(comb(n, n), 1)
1883+
1884+
for n in range(1, 100):
1885+
self.assertEqual(comb(n, 1), n)
1886+
self.assertEqual(comb(n, n - 1), n)
1887+
1888+
# Test Symmetry
1889+
for n in range(100):
1890+
for k in range(n // 2):
1891+
self.assertEqual(comb(n, k), comb(n, n - k))
1892+
1893+
# Raises TypeError if any argument is non-integer or argument count is
1894+
# not 2
1895+
self.assertRaises(TypeError, comb, 10, 1.0)
1896+
self.assertRaises(TypeError, comb, 10, "1")
1897+
self.assertRaises(TypeError, comb, "10", 1)
1898+
self.assertRaises(TypeError, comb, 10.0, 1)
1899+
1900+
self.assertRaises(TypeError, comb, 10)
1901+
self.assertRaises(TypeError, comb, 10, 1, 3)
1902+
self.assertRaises(TypeError, comb)
1903+
1904+
# Raises Value error if not k or n are negative numbers
1905+
self.assertRaises(ValueError, comb, -1, 1)
1906+
self.assertRaises(ValueError, comb, -10*10, 1)
1907+
self.assertRaises(ValueError, comb, 1, -1)
1908+
self.assertRaises(ValueError, comb, 1, -10*10)
1909+
1910+
# Raises value error if k is greater than n
1911+
self.assertRaises(ValueError, comb, 1, 10**10)
1912+
self.assertRaises(ValueError, comb, 0, 1)
1913+
1914+
1915+
18651916

18661917
def test_main():
18671918
from doctest import DocFileSuite
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Implement :func:`math.comb` that returns binomial coefficient, that computes
2+
the number of ways to choose k items from n items without repetition and
3+
without order.
4+
Patch by Yash Aggarwal and Keller Fuchs.

Modules/clinic/mathmodule.c.h

Lines changed: 50 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Modules/mathmodule.c

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2998,6 +2998,126 @@ math_prod_impl(PyObject *module, PyObject *iterable, PyObject *start)
29982998
}
29992999

30003000

3001+
/*[clinic input]
3002+
math.comb
3003+
3004+
n: object(subclass_of='&PyLong_Type')
3005+
k: object(subclass_of='&PyLong_Type')
3006+
3007+
Number of ways to choose *k* items from *n* items without repetition and without order.
3008+
3009+
Also called the binomial coefficient. It is mathematically equal to the expression
3010+
n! / (k! * (n - k)!). It is equivalent to the coefficient of k-th term in
3011+
polynomial expansion of the expression (1 + x)**n.
3012+
3013+
Raises TypeError if the arguments are not integers.
3014+
Raises ValueError if the arguments are negative or if k > n.
3015+
3016+
[clinic start generated code]*/
3017+
3018+
static PyObject *
3019+
math_comb_impl(PyObject *module, PyObject *n, PyObject *k)
3020+
/*[clinic end generated code: output=bd2cec8d854f3493 input=565f340f98efb5b5]*/
3021+
{
3022+
PyObject *val = NULL,
3023+
*temp_obj1 = NULL,
3024+
*temp_obj2 = NULL,
3025+
*dump_var = NULL;
3026+
int overflow, cmp;
3027+
long long i, terms;
3028+
3029+
cmp = PyObject_RichCompareBool(n, k, Py_LT);
3030+
if (cmp < 0) {
3031+
goto fail_comb;
3032+
}
3033+
else if (cmp > 0) {
3034+
PyErr_Format(PyExc_ValueError,
3035+
"n must be an integer greater than or equal to k");
3036+
goto fail_comb;
3037+
}
3038+
3039+
/* b = min(b, a - b) */
3040+
dump_var = PyNumber_Subtract(n, k);
3041+
if (dump_var == NULL) {
3042+
goto fail_comb;
3043+
}
3044+
cmp = PyObject_RichCompareBool(k, dump_var, Py_GT);
3045+
if (cmp < 0) {
3046+
goto fail_comb;
3047+
}
3048+
else if (cmp > 0) {
3049+
k = dump_var;
3050+
dump_var = NULL;
3051+
}
3052+
else {
3053+
Py_DECREF(dump_var);
3054+
dump_var = NULL;
3055+
}
3056+
3057+
terms = PyLong_AsLongLongAndOverflow(k, &overflow);
3058+
if (terms < 0 && PyErr_Occurred()) {
3059+
goto fail_comb;
3060+
}
3061+
else if (overflow > 0) {
3062+
PyErr_Format(PyExc_OverflowError,
3063+
"minimum(n - k, k) must not exceed %lld",
3064+
LLONG_MAX);
3065+
goto fail_comb;
3066+
}
3067+
else if (overflow < 0 || terms < 0) {
3068+
PyErr_Format(PyExc_ValueError,
3069+
"k must be a positive integer");
3070+
goto fail_comb;
3071+
}
3072+
3073+
if (terms == 0) {
3074+
return PyNumber_Long(_PyLong_One);
3075+
}
3076+
3077+
val = PyNumber_Long(n);
3078+
for (i = 1; i < terms; ++i) {
3079+
temp_obj1 = PyLong_FromSsize_t(i);
3080+
if (temp_obj1 == NULL) {
3081+
goto fail_comb;
3082+
}
3083+
temp_obj2 = PyNumber_Subtract(n, temp_obj1);
3084+
if (temp_obj2 == NULL) {
3085+
goto fail_comb;
3086+
}
3087+
dump_var = val;
3088+
val = PyNumber_Multiply(val, temp_obj2);
3089+
if (val == NULL) {
3090+
goto fail_comb;
3091+
}
3092+
Py_DECREF(dump_var);
3093+
dump_var = NULL;
3094+
Py_DECREF(temp_obj2);
3095+
temp_obj2 = PyLong_FromUnsignedLongLong((unsigned long long)(i + 1));
3096+
if (temp_obj2 == NULL) {
3097+
goto fail_comb;
3098+
}
3099+
dump_var = val;
3100+
val = PyNumber_FloorDivide(val, temp_obj2);
3101+
if (val == NULL) {
3102+
goto fail_comb;
3103+
}
3104+
Py_DECREF(dump_var);
3105+
Py_DECREF(temp_obj1);
3106+
Py_DECREF(temp_obj2);
3107+
}
3108+
3109+
return val;
3110+
3111+
fail_comb:
3112+
Py_XDECREF(val);
3113+
Py_XDECREF(dump_var);
3114+
Py_XDECREF(temp_obj1);
3115+
Py_XDECREF(temp_obj2);
3116+
3117+
return NULL;
3118+
}
3119+
3120+
30013121
static PyMethodDef math_methods[] = {
30023122
{"acos", math_acos, METH_O, math_acos_doc},
30033123
{"acosh", math_acosh, METH_O, math_acosh_doc},
@@ -3047,6 +3167,7 @@ static PyMethodDef math_methods[] = {
30473167
{"tanh", math_tanh, METH_O, math_tanh_doc},
30483168
MATH_TRUNC_METHODDEF
30493169
MATH_PROD_METHODDEF
3170+
MATH_COMB_METHODDEF
30503171
{NULL, NULL} /* sentinel */
30513172
};
30523173

0 commit comments

Comments
 (0)