Skip to content

Commit 805a332

Browse files
committed
implementation of the minmax function
1 parent 1834741 commit 805a332

File tree

2 files changed

+237
-0
lines changed

2 files changed

+237
-0
lines changed

Lib/test/test_builtin.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,72 @@ def __getitem__(self, index):
16301630
self.assertEqual(min(data, key=f),
16311631
sorted(data, key=f)[0])
16321632

1633+
1634+
def test_minmax(self):
1635+
self.assertEqual(minmax('123123'), ('1', '3'))
1636+
self.assertEqual(minmax(1, 2, 3), (1, 3))
1637+
self.assertEqual(minmax((1, 2, 3, 1, 2, 3)), (1, 3))
1638+
self.assertEqual(minmax([1, 2, 3, 1, 2, 3]), (1, 3))
1639+
1640+
self.assertEqual(minmax(1, 2, 3.0), (1, 3.0))
1641+
self.assertEqual(minmax(1, 2.0, 3), (1, 3))
1642+
self.assertEqual(minmax(1.0, 2, 3), (1.0, 3))
1643+
1644+
with self.assertRaisesRegex(
1645+
TypeError,
1646+
'minmax expected at least 1 argument, got 0'
1647+
):
1648+
minmax()
1649+
1650+
self.assertRaises(TypeError, minmax, 42)
1651+
with self.assertRaisesRegex(
1652+
ValueError,
1653+
r'minmax\(\) iterable argument is empty'
1654+
):
1655+
minmax(())
1656+
class BadSeq:
1657+
def __getitem__(self, index):
1658+
raise ValueError
1659+
self.assertRaises(ValueError, minmax, BadSeq())
1660+
1661+
for stmt in (
1662+
"minmax(key=int)", # no args
1663+
"minmax(default=None)",
1664+
"minmax(1, 2, default=None)", # require container for default
1665+
"minmax(default=None, key=int)",
1666+
"minmax(1, key=int)", # single arg not iterable
1667+
"minmax(1, 2, keystone=int)", # wrong keyword
1668+
"minmax(1, 2, key=int, abc=int)", # two many keywords
1669+
"minmax(1, 2, key=1)", # keyfunc is not callable
1670+
):
1671+
try:
1672+
exec(stmt, globals())
1673+
except TypeError:
1674+
pass
1675+
else:
1676+
self.fail(stmt)
1677+
1678+
self.assertEqual(minmax((1,), key=neg), (1, 1)) # one elem iterable
1679+
self.assertEqual(minmax((1,2), key=neg),(2, 1)) # two elem iterable
1680+
self.assertEqual(minmax(1, 2, key=neg), (2, 1)) # two elems
1681+
1682+
self.assertEqual(minmax((), default=None), (None, None)) # zero elem iterable
1683+
self.assertEqual(minmax((1,), default=None), (1, 1)) # one elem iterable
1684+
self.assertEqual(minmax((1,2), default=None), (1, 2)) # two elem iterable
1685+
1686+
self.assertEqual(minmax((), default=1, key=neg), (1, 1))
1687+
self.assertEqual(minmax((1, 2), default=1, key=neg), (2, 1))
1688+
1689+
self.assertEqual(minmax((1, 2), key=None), (1, 2))
1690+
1691+
data = [random.randrange(200) for i in range(100)]
1692+
keys = dict((elem, random.randrange(50)) for elem in data)
1693+
f = keys.__getitem__
1694+
1695+
sorted_vals = sorted(data, key=f)
1696+
self.assertEqual(minmax(data, key=f),
1697+
(sorted_vals[0], sorted_vals[-1]))
1698+
16331699
def test_next(self):
16341700
it = iter(range(2))
16351701
self.assertEqual(next(it), 0)

Python/bltinmodule.c

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2114,6 +2114,176 @@ the provided iterable is empty.\n\
21142114
With two or more positional arguments, return the largest argument.");
21152115

21162116

2117+
static PyObject *
2118+
builtin_minmax(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
2119+
{
2120+
PyObject *it = NULL, *item, *val, *maxitem, *maxval, *minitem, *minval, *minmax_obj, *keyfunc=NULL;
2121+
PyObject *defaultval = NULL;
2122+
static const char * const keywords[] = {"key", "default", NULL};
2123+
static _PyArg_Parser _parser = {"|$OO:minmax", keywords, 0};
2124+
2125+
if (nargs == 0) {
2126+
PyErr_Format(PyExc_TypeError, "minmax expected at least 1 argument, got 0");
2127+
return NULL;
2128+
}
2129+
2130+
if (kwnames != NULL && !_PyArg_ParseStackAndKeywords(args + nargs, 0, kwnames, &_parser,
2131+
&keyfunc, &defaultval)) {
2132+
return NULL;
2133+
}
2134+
2135+
const int positional = nargs > 1; // False iff nargs == 1
2136+
if (positional && defaultval != NULL) {
2137+
PyErr_Format(PyExc_TypeError,
2138+
"Cannot specify a default for minmax() with multiple "
2139+
"positional arguments");
2140+
return NULL;
2141+
}
2142+
2143+
if (!positional) {
2144+
it = PyObject_GetIter(args[0]);
2145+
if (it == NULL) {
2146+
return NULL;
2147+
}
2148+
}
2149+
2150+
if (keyfunc == Py_None) {
2151+
keyfunc = NULL;
2152+
}
2153+
2154+
maxitem = NULL; /* the max result */
2155+
maxval = NULL; /* the value associated with the max result */
2156+
2157+
minitem = NULL; /* the min result */
2158+
minval = NULL; /* the value associated with the min result */
2159+
2160+
while (1) {
2161+
if (it == NULL) {
2162+
if (nargs-- <= 0) {
2163+
break;
2164+
}
2165+
item = *args++;
2166+
Py_INCREF(item);
2167+
}
2168+
else {
2169+
item = PyIter_Next(it);
2170+
if (item == NULL) {
2171+
if (PyErr_Occurred()) {
2172+
goto Fail_it;
2173+
}
2174+
break;
2175+
}
2176+
}
2177+
2178+
/* get the value from the key function */
2179+
if (keyfunc != NULL) {
2180+
val = PyObject_CallOneArg(keyfunc, item);
2181+
if (val == NULL)
2182+
goto Fail_it_item;
2183+
}
2184+
/* no key function; the value is the item */
2185+
else {
2186+
val = Py_NewRef(item);
2187+
}
2188+
2189+
/* minimum/maximum value and item are unset; set them */
2190+
if (maxval == NULL || minval == NULL) {
2191+
maxitem = item;
2192+
maxval = val;
2193+
2194+
minitem = Py_NewRef(item);
2195+
minval = Py_NewRef(val);
2196+
}
2197+
/* minimum/maximum value and item are set; update them as necessary */
2198+
else {
2199+
/* check for new minimum value */
2200+
const int cmp_lt = PyObject_RichCompareBool(val, minval, Py_LT);
2201+
2202+
if (cmp_lt < 0) {
2203+
goto Fail_it_item_and_val;
2204+
}
2205+
2206+
if (cmp_lt > 0) {
2207+
Py_DECREF(minitem);
2208+
Py_DECREF(minval);
2209+
2210+
minval = val;
2211+
minitem = item;
2212+
} else {
2213+
/* Since we did not get a new minimum it could be a new maximum instead */
2214+
const int cmp_gt = PyObject_RichCompareBool(val, maxval, Py_GT);
2215+
2216+
if (cmp_gt < 0) {
2217+
goto Fail_it_item_and_val;
2218+
}
2219+
2220+
if(cmp_gt > 0) {
2221+
Py_DECREF(maxitem);
2222+
Py_DECREF(maxval);
2223+
2224+
maxval = val;
2225+
maxitem = item;
2226+
}
2227+
else {
2228+
Py_DECREF(item);
2229+
Py_DECREF(val);
2230+
}
2231+
}
2232+
}
2233+
}
2234+
if (maxval == NULL || minval == NULL) {
2235+
assert(maxitem == NULL);
2236+
assert(minitem == NULL);
2237+
if (defaultval != NULL) {
2238+
maxitem = Py_NewRef(defaultval);
2239+
minitem = Py_NewRef(defaultval);
2240+
} else {
2241+
PyErr_Format(PyExc_ValueError,
2242+
"minmax() iterable argument is empty");
2243+
2244+
goto Fail_it;
2245+
}
2246+
}else {
2247+
Py_DECREF(maxval);
2248+
Py_DECREF(minval);
2249+
}
2250+
2251+
Py_XDECREF(it);
2252+
2253+
if ((minmax_obj = PyTuple_New(2)) == NULL) {
2254+
goto Fail_it;
2255+
}
2256+
2257+
PyTuple_SET_ITEM(minmax_obj, 0, minitem);
2258+
PyTuple_SET_ITEM(minmax_obj, 1, maxitem);
2259+
2260+
return minmax_obj;
2261+
2262+
Fail_it_item_and_val:
2263+
Py_DECREF(val);
2264+
Fail_it_item:
2265+
Py_DECREF(item);
2266+
Fail_it:
2267+
Py_XDECREF(maxval);
2268+
Py_XDECREF(maxitem);
2269+
2270+
Py_XDECREF(minval);
2271+
Py_XDECREF(minitem);
2272+
2273+
Py_XDECREF(it);
2274+
return NULL;
2275+
}
2276+
2277+
PyDoc_STRVAR(minmax_doc,
2278+
"minmax(iterable, *[, default=obj, key=func]) -> (min_value, max_value)\n\
2279+
minmax(arg1, arg2, *args, *[, key=func]) -> (min_value, max_value)\n\
2280+
\n\
2281+
With a single iterable argument, return both its smallest and biggest item. The\n\
2282+
default keyword-only argument specifies an object to return if\n\
2283+
the provided iterable is empty.\n\
2284+
With two or more positional arguments, return the smallest and largest argument.");
2285+
2286+
21172287
/*[clinic input]
21182288
oct as builtin_oct
21192289
@@ -3392,6 +3562,7 @@ static PyMethodDef builtin_methods[] = {
33923562
BUILTIN_LOCALS_METHODDEF
33933563
{"max", _PyCFunction_CAST(builtin_max), METH_FASTCALL | METH_KEYWORDS, max_doc},
33943564
{"min", _PyCFunction_CAST(builtin_min), METH_FASTCALL | METH_KEYWORDS, min_doc},
3565+
{"minmax", _PyCFunction_CAST(builtin_minmax), METH_FASTCALL | METH_KEYWORDS, minmax_doc},
33953566
{"next", _PyCFunction_CAST(builtin_next), METH_FASTCALL, next_doc},
33963567
BUILTIN_ANEXT_METHODDEF
33973568
BUILTIN_OCT_METHODDEF

0 commit comments

Comments
 (0)