Skip to content

Commit 4511c7a

Browse files
bpo-35431: Add math.perm().
1 parent 56624a9 commit 4511c7a

File tree

5 files changed

+223
-61
lines changed

5 files changed

+223
-61
lines changed

Doc/library/math.rst

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@ Number-theoretic and representation functions
3636
:class:`~numbers.Integral` value.
3737

3838

39+
.. function:: comb(n, k)
40+
41+
Return the number of ways to choose *k* items from *n* items without repetition
42+
and without order.
43+
44+
Also called the binomial coefficient. It is mathematically equal to the expression
45+
``n! / (k! (n - k)!)``. It is equivalent to the coefficient of the *k*-th term in the
46+
polynomial expansion of the expression ``(1 + x) ** n``.
47+
48+
Raises :exc:`TypeError` if the arguments not integers.
49+
Raises :exc:`ValueError` if the arguments are negative or if *k* > *n*.
50+
51+
.. versionadded:: 3.8
52+
53+
3954
.. function:: copysign(x, y)
4055

4156
Return a float with the magnitude (absolute value) of *x* but the sign of
@@ -192,6 +207,18 @@ Number-theoretic and representation functions
192207
of *x* and are floats.
193208

194209

210+
.. function:: perm(n, k)
211+
212+
Return the number of ways to choose *k* items from *n* items without repetition.
213+
214+
It is mathematically equal to the expression ``n! / (n - k)!``.
215+
216+
Raises :exc:`TypeError` if the arguments not integers.
217+
Raises :exc:`ValueError` if the arguments are negative or if *k* > *n*.
218+
219+
.. versionadded:: 3.8
220+
221+
195222
.. function:: prod(iterable, *, start=1)
196223

197224
Calculate the product of all the elements in the input *iterable*.
@@ -232,21 +259,6 @@ Number-theoretic and representation functions
232259
:meth:`x.__trunc__() <object.__trunc__>`.
233260

234261

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 the *k*-th term in the
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-
250262
Note that :func:`frexp` and :func:`modf` have a different call/return pattern
251263
than their C equivalents: they take a single argument and return a pair of
252264
values, rather than returning their second return value through an 'output

Lib/test/test_math.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,6 +1862,61 @@ 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 testPerm(self):
1866+
perm = math.perm
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(perm(n, k),
1872+
factorial(n) // 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(perm(n, k), perm(n - 1, k - 1) * k + perm(n - 1, k))
1878+
1879+
# Test corner cases
1880+
for n in range(1, 100):
1881+
self.assertEqual(perm(n, 0), 1)
1882+
self.assertEqual(perm(n, 1), n)
1883+
self.assertEqual(perm(n, n), factorial(n))
1884+
1885+
# Raises TypeError if any argument is non-integer or argument count is
1886+
# not 2
1887+
self.assertRaises(TypeError, perm, 10, 1.0)
1888+
self.assertRaises(TypeError, perm, 10, decimal.Decimal(1.0))
1889+
self.assertRaises(TypeError, perm, 10, "1")
1890+
self.assertRaises(TypeError, perm, 10.0, 1)
1891+
self.assertRaises(TypeError, perm, decimal.Decimal(10.0), 1)
1892+
self.assertRaises(TypeError, perm, "10", 1)
1893+
1894+
self.assertRaises(TypeError, perm, 10)
1895+
self.assertRaises(TypeError, perm, 10, 1, 3)
1896+
self.assertRaises(TypeError, perm)
1897+
1898+
# Raises Value error if not k or n are negative numbers
1899+
self.assertRaises(ValueError, perm, -1, 1)
1900+
self.assertRaises(ValueError, perm, -2**1000, 1)
1901+
self.assertRaises(ValueError, perm, 1, -1)
1902+
self.assertRaises(ValueError, perm, 1, -2**1000)
1903+
1904+
# Raises value error if k is greater than n
1905+
self.assertRaises(ValueError, perm, 1, 2)
1906+
self.assertRaises(ValueError, perm, 1, 2**1000)
1907+
1908+
n = 2**1000
1909+
self.assertEqual(perm(n, 0), 1)
1910+
self.assertEqual(perm(n, 1), n)
1911+
self.assertEqual(perm(n, 2), n * (n-1))
1912+
self.assertRaises((OverflowError, MemoryError), perm, n, n)
1913+
1914+
for n, k in (True, True), (True, False), (False, False):
1915+
self.assertEqual(perm(n, k), 1)
1916+
self.assertIs(type(perm(n, k)), int)
1917+
self.assertEqual(perm(MyIndexable(5), MyIndexable(2)), 20)
1918+
self.assertIs(type(perm(MyIndexable(5), MyIndexable(2))), int)
1919+
18651920
def testComb(self):
18661921
comb = math.comb
18671922
factorial = math.factorial
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added :func:`math.perm`.

Modules/clinic/mathmodule.c.h

Lines changed: 36 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: 104 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2998,27 +2998,8 @@ math_prod_impl(PyObject *module, PyObject *iterable, PyObject *start)
29982998
}
29992999

30003000

3001-
/*[clinic input]
3002-
math.comb
3003-
3004-
n: object
3005-
k: object
3006-
/
3007-
3008-
Number of ways to choose k items from n items without repetition and without order.
3009-
3010-
Also called the binomial coefficient. It is mathematically equal to the expression
3011-
n! / (k! * (n - k)!). It is equivalent to the coefficient of k-th term in
3012-
polynomial expansion of the expression (1 + x)**n.
3013-
3014-
Raises TypeError if the arguments are not integers.
3015-
Raises ValueError if the arguments are negative or if k > n.
3016-
3017-
[clinic start generated code]*/
3018-
30193001
static PyObject *
3020-
math_comb_impl(PyObject *module, PyObject *n, PyObject *k)
3021-
/*[clinic end generated code: output=bd2cec8d854f3493 input=2f336ac9ec8242f9]*/
3002+
perm_comb(PyObject *n, PyObject *k, int comb)
30223003
{
30233004
PyObject *result = NULL, *factor = NULL, *temp;
30243005
int overflow, cmp;
@@ -3028,43 +3009,69 @@ math_comb_impl(PyObject *module, PyObject *n, PyObject *k)
30283009
if (n == NULL) {
30293010
return NULL;
30303011
}
3012+
if (!PyLong_CheckExact(n)) {
3013+
Py_SETREF(n, _PyLong_Copy((PyLongObject *)n));
3014+
if (n == NULL) {
3015+
return NULL;
3016+
}
3017+
}
30313018
k = PyNumber_Index(k);
30323019
if (k == NULL) {
30333020
Py_DECREF(n);
30343021
return NULL;
30353022
}
3023+
if (!PyLong_CheckExact(k)) {
3024+
Py_SETREF(k, _PyLong_Copy((PyLongObject *)k));
3025+
if (k == NULL) {
3026+
Py_DECREF(n);
3027+
return NULL;
3028+
}
3029+
}
30363030

30373031
if (Py_SIZE(n) < 0) {
30383032
PyErr_SetString(PyExc_ValueError,
30393033
"n must be a non-negative integer");
30403034
goto error;
30413035
}
3042-
/* k = min(k, n - k) */
3043-
temp = PyNumber_Subtract(n, k);
3044-
if (temp == NULL) {
3045-
goto error;
3046-
}
3047-
if (Py_SIZE(temp) < 0) {
3048-
Py_DECREF(temp);
3049-
PyErr_SetString(PyExc_ValueError,
3050-
"k must be an integer less than or equal to n");
3051-
goto error;
3052-
}
3053-
cmp = PyObject_RichCompareBool(k, temp, Py_GT);
3054-
if (cmp > 0) {
3055-
Py_SETREF(k, temp);
3036+
if (comb) {
3037+
/* k = min(k, n - k) */
3038+
temp = PyNumber_Subtract(n, k);
3039+
if (temp == NULL) {
3040+
goto error;
3041+
}
3042+
if (Py_SIZE(temp) < 0) {
3043+
Py_DECREF(temp);
3044+
PyErr_SetString(PyExc_ValueError,
3045+
"k must be an integer less than or equal to n");
3046+
goto error;
3047+
}
3048+
cmp = PyObject_RichCompareBool(temp, k, Py_LT);
3049+
if (cmp > 0) {
3050+
Py_SETREF(k, temp);
3051+
}
3052+
else {
3053+
Py_DECREF(temp);
3054+
if (cmp < 0) {
3055+
goto error;
3056+
}
3057+
}
30563058
}
30573059
else {
3058-
Py_DECREF(temp);
3059-
if (cmp < 0) {
3060+
cmp = PyObject_RichCompareBool(n, k, Py_LT);
3061+
if (cmp != 0) {
3062+
if (cmp > 0) {
3063+
PyErr_SetString(PyExc_ValueError,
3064+
"k must be an integer less than or equal to n");
3065+
}
30603066
goto error;
30613067
}
30623068
}
30633069

30643070
factors = PyLong_AsLongLongAndOverflow(k, &overflow);
30653071
if (overflow > 0) {
30663072
PyErr_Format(PyExc_OverflowError,
3067-
"min(n - k, k) must not exceed %lld",
3073+
"%s must not exceed %lld",
3074+
comb ? "min(n - k, k)" : "k",
30683075
LLONG_MAX);
30693076
goto error;
30703077
}
@@ -3099,14 +3106,16 @@ math_comb_impl(PyObject *module, PyObject *n, PyObject *k)
30993106
goto error;
31003107
}
31013108

3102-
temp = PyLong_FromUnsignedLongLong((unsigned long long)i + 1);
3103-
if (temp == NULL) {
3104-
goto error;
3105-
}
3106-
Py_SETREF(result, PyNumber_FloorDivide(result, temp));
3107-
Py_DECREF(temp);
3108-
if (result == NULL) {
3109-
goto error;
3109+
if (comb) {
3110+
temp = PyLong_FromUnsignedLongLong((unsigned long long)i + 1);
3111+
if (temp == NULL) {
3112+
goto error;
3113+
}
3114+
Py_SETREF(result, PyNumber_FloorDivide(result, temp));
3115+
Py_DECREF(temp);
3116+
if (result == NULL) {
3117+
goto error;
3118+
}
31103119
}
31113120
}
31123121
Py_DECREF(factor);
@@ -3125,6 +3134,55 @@ math_comb_impl(PyObject *module, PyObject *n, PyObject *k)
31253134
}
31263135

31273136

3137+
/*[clinic input]
3138+
math.perm
3139+
3140+
n: object
3141+
k: object
3142+
/
3143+
3144+
Number of ways to choose k items from n items without repetition.
3145+
3146+
It is mathematically equal to the expression n! / (n - k)!.
3147+
3148+
Raises TypeError if the arguments are not integers.
3149+
Raises ValueError if the arguments are negative or if k > n.
3150+
[clinic start generated code]*/
3151+
3152+
static PyObject *
3153+
math_perm_impl(PyObject *module, PyObject *n, PyObject *k)
3154+
/*[clinic end generated code: output=e021a25469653e23 input=bad86be85158ebfd]*/
3155+
{
3156+
return perm_comb(n, k, 0);
3157+
}
3158+
3159+
3160+
/*[clinic input]
3161+
math.comb
3162+
3163+
n: object
3164+
k: object
3165+
/
3166+
3167+
Number of ways to choose k items from n items without repetition and without order.
3168+
3169+
Also called the binomial coefficient. It is mathematically equal to the expression
3170+
n! / (k! * (n - k)!). It is equivalent to the coefficient of k-th term in
3171+
polynomial expansion of the expression (1 + x)**n.
3172+
3173+
Raises TypeError if the arguments are not integers.
3174+
Raises ValueError if the arguments are negative or if k > n.
3175+
3176+
[clinic start generated code]*/
3177+
3178+
static PyObject *
3179+
math_comb_impl(PyObject *module, PyObject *n, PyObject *k)
3180+
/*[clinic end generated code: output=bd2cec8d854f3493 input=2f336ac9ec8242f9]*/
3181+
{
3182+
return perm_comb(n, k, 1);
3183+
}
3184+
3185+
31283186
static PyMethodDef math_methods[] = {
31293187
{"acos", math_acos, METH_O, math_acos_doc},
31303188
{"acosh", math_acosh, METH_O, math_acosh_doc},
@@ -3174,6 +3232,7 @@ static PyMethodDef math_methods[] = {
31743232
{"tanh", math_tanh, METH_O, math_tanh_doc},
31753233
MATH_TRUNC_METHODDEF
31763234
MATH_PROD_METHODDEF
3235+
MATH_PERM_METHODDEF
31773236
MATH_COMB_METHODDEF
31783237
{NULL, NULL} /* sentinel */
31793238
};

0 commit comments

Comments
 (0)