@@ -2114,6 +2114,176 @@ the provided iterable is empty.\n\
21142114With 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]
21182288oct 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