Skip to content

Commit

Permalink
bpo-35431: Add math.perm().
Browse files Browse the repository at this point in the history
  • Loading branch information
serhiy-storchaka committed Jun 1, 2019
1 parent 56624a9 commit 4511c7a
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 61 deletions.
42 changes: 27 additions & 15 deletions Doc/library/math.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ Number-theoretic and representation functions
:class:`~numbers.Integral` value.


.. 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 the *k*-th term in the
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


.. function:: copysign(x, y)

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


.. function:: perm(n, k)

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

It is mathematically equal to the expression ``n! / (n - k)!``.

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

.. versionadded:: 3.8


.. function:: prod(iterable, *, start=1)

Calculate the product of all the elements in the input *iterable*.
Expand Down Expand Up @@ -232,21 +259,6 @@ 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 the *k*-th term in the
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
55 changes: 55 additions & 0 deletions Lib/test/test_math.py
Original file line number Diff line number Diff line change
Expand Up @@ -1862,6 +1862,61 @@ def test_fractions(self):
self.assertAllClose(fraction_examples, rel_tol=1e-8)
self.assertAllNotClose(fraction_examples, rel_tol=1e-9)

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

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

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

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

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

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

# Raises value error if k is greater than n
self.assertRaises(ValueError, perm, 1, 2)
self.assertRaises(ValueError, perm, 1, 2**1000)

n = 2**1000
self.assertEqual(perm(n, 0), 1)
self.assertEqual(perm(n, 1), n)
self.assertEqual(perm(n, 2), n * (n-1))
self.assertRaises((OverflowError, MemoryError), perm, n, n)

for n, k in (True, True), (True, False), (False, False):
self.assertEqual(perm(n, k), 1)
self.assertIs(type(perm(n, k)), int)
self.assertEqual(perm(MyIndexable(5), MyIndexable(2)), 20)
self.assertIs(type(perm(MyIndexable(5), MyIndexable(2))), int)

def testComb(self):
comb = math.comb
factorial = math.factorial
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added :func:`math.perm`.
37 changes: 36 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.

149 changes: 104 additions & 45 deletions Modules/mathmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2998,27 +2998,8 @@ math_prod_impl(PyObject *module, PyObject *iterable, PyObject *start)
}


/*[clinic input]
math.comb
n: object
k: object
/
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=2f336ac9ec8242f9]*/
perm_comb(PyObject *n, PyObject *k, int comb)
{
PyObject *result = NULL, *factor = NULL, *temp;
int overflow, cmp;
Expand All @@ -3028,43 +3009,69 @@ math_comb_impl(PyObject *module, PyObject *n, PyObject *k)
if (n == NULL) {
return NULL;
}
if (!PyLong_CheckExact(n)) {
Py_SETREF(n, _PyLong_Copy((PyLongObject *)n));
if (n == NULL) {
return NULL;
}
}
k = PyNumber_Index(k);
if (k == NULL) {
Py_DECREF(n);
return NULL;
}
if (!PyLong_CheckExact(k)) {
Py_SETREF(k, _PyLong_Copy((PyLongObject *)k));
if (k == NULL) {
Py_DECREF(n);
return NULL;
}
}

if (Py_SIZE(n) < 0) {
PyErr_SetString(PyExc_ValueError,
"n must be a non-negative integer");
goto error;
}
/* k = min(k, n - k) */
temp = PyNumber_Subtract(n, k);
if (temp == NULL) {
goto error;
}
if (Py_SIZE(temp) < 0) {
Py_DECREF(temp);
PyErr_SetString(PyExc_ValueError,
"k must be an integer less than or equal to n");
goto error;
}
cmp = PyObject_RichCompareBool(k, temp, Py_GT);
if (cmp > 0) {
Py_SETREF(k, temp);
if (comb) {
/* k = min(k, n - k) */
temp = PyNumber_Subtract(n, k);
if (temp == NULL) {
goto error;
}
if (Py_SIZE(temp) < 0) {
Py_DECREF(temp);
PyErr_SetString(PyExc_ValueError,
"k must be an integer less than or equal to n");
goto error;
}
cmp = PyObject_RichCompareBool(temp, k, Py_LT);
if (cmp > 0) {
Py_SETREF(k, temp);
}
else {
Py_DECREF(temp);
if (cmp < 0) {
goto error;
}
}
}
else {
Py_DECREF(temp);
if (cmp < 0) {
cmp = PyObject_RichCompareBool(n, k, Py_LT);
if (cmp != 0) {
if (cmp > 0) {
PyErr_SetString(PyExc_ValueError,
"k must be an integer less than or equal to n");
}
goto error;
}
}

factors = PyLong_AsLongLongAndOverflow(k, &overflow);
if (overflow > 0) {
PyErr_Format(PyExc_OverflowError,
"min(n - k, k) must not exceed %lld",
"%s must not exceed %lld",
comb ? "min(n - k, k)" : "k",
LLONG_MAX);
goto error;
}
Expand Down Expand Up @@ -3099,14 +3106,16 @@ math_comb_impl(PyObject *module, PyObject *n, PyObject *k)
goto error;
}

temp = PyLong_FromUnsignedLongLong((unsigned long long)i + 1);
if (temp == NULL) {
goto error;
}
Py_SETREF(result, PyNumber_FloorDivide(result, temp));
Py_DECREF(temp);
if (result == NULL) {
goto error;
if (comb) {
temp = PyLong_FromUnsignedLongLong((unsigned long long)i + 1);
if (temp == NULL) {
goto error;
}
Py_SETREF(result, PyNumber_FloorDivide(result, temp));
Py_DECREF(temp);
if (result == NULL) {
goto error;
}
}
}
Py_DECREF(factor);
Expand All @@ -3125,6 +3134,55 @@ math_comb_impl(PyObject *module, PyObject *n, PyObject *k)
}


/*[clinic input]
math.perm
n: object
k: object
/
Number of ways to choose k items from n items without repetition.
It is mathematically equal to the expression n! / (n - k)!.
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_perm_impl(PyObject *module, PyObject *n, PyObject *k)
/*[clinic end generated code: output=e021a25469653e23 input=bad86be85158ebfd]*/
{
return perm_comb(n, k, 0);
}


/*[clinic input]
math.comb
n: object
k: object
/
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=2f336ac9ec8242f9]*/
{
return perm_comb(n, k, 1);
}


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

0 comments on commit 4511c7a

Please sign in to comment.