# Minmax function alongside min and max

**URL:** <https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834>\
**Category:** Ideas\
**Created:** [December 11, 2019, 6:17pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834 "2019-12-11T18:17:42Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![avayert](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/avayert/32/1445_2.png) [@avayert](https://discuss.python.org/u/avayert)\
**Post date:** [December 11, 2019, 6:17pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/1 "2019-12-11T18:17:42Z")

</div>

There is a frequent pattern where you need to get both the `min` and `max` of some iterable. You cannot always call both `min` and `max` on the same iterable, as it might be exhausted upon iteration. While it is certainly simple to implement this with a raw loop, I think a `minmax` function would be a good addition to the language alongside the two existing built-ins.

As the name suggests, it would return a tuple of `min_elem, max_elem`, and would work as `min` and `max` do now (either pass in an iterable or `*args`, and have a `key` kwarg). If adding more built-in functions is a problem - for example due to reserving names in the global namespace - it could live in `itertools` or `math`.

---

<div class="post-metadata">

**Author:** ![pylang](https://avatars.discourse-cdn.com/v4/letter/p/7feea3/32.png) [@pylang](https://discuss.python.org/u/pylang)\
**Post date:** [December 11, 2019, 10:10pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/2 "2019-12-11T22:10:02Z")

</div>

I believe I’ve seen `minmax()` functions in libraries from other languages. I’m not too familiar with it. Can you please demonstrate some use cases in Python?

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [December 11, 2019, 10:50pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/3 "2019-12-11T22:50:46Z")

</div>

I believe it’s simply the following:

```python
def minmax(it):
    min = max = None
    for val in it:
        if min is None or val < min:
            min = val
        if max is None or val > max:
            max = val
    return min, max

```

The point being that it computes both min and max in _one pass_, which is important if the argument cannot be consumed repeatedly (for example, a generator) or the cost of iterating twice is a problem.

I’m not sure how useful this would be, given that the implementation is relatively straightforward. But of course, the same could be said of min or max, although they are much more commonly needed.

---

<div class="post-metadata">

**Author:** ![aeros](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/aeros/32/4140_2.png) [@aeros](https://discuss.python.org/u/aeros)\
**Post date:** [December 12, 2019, 8:16am UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/4 "2019-12-12T08:16:28Z")

</div>

> [@avayert](#):
>
> If adding more built-in functions is a problem - for example due to reserving names in the global namespace - it could live in `itertools` or `math` .

Yeah I think something like this would be specifically suited towards the `math` module.

> [@pf\_moore](#):
>
> I’m not sure how useful this would be, given that the implementation is relatively straightforward. But of course, the same could be said of min or max, although they are much more commonly needed.

Depending upon how common the use case would be, it could certainly benefit from being in the `math` module. Particularly if a C accelerator is implemented, as the performance improvements can be significant when iterating over a decent number of items.

---

<div class="post-metadata">

**Author:** ![pf\_moore](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pf_moore/32/35_2.png) [@pf\_moore](https://discuss.python.org/u/pf_moore)\
**Post date:** [December 12, 2019, 8:35am UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/5 "2019-12-12T08:35:58Z")

</div>

> [@aeros](#):
>
> Yeah I think something like this would be specifically suited towards the `math` module.

That suggests it’s intended only for use with numbers, which isn’t true of the builtin `min` and `max`.

---

<div class="post-metadata">

**Author:** ![steven.daprano](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steven.daprano/32/1083_2.png) [@steven.daprano](https://discuss.python.org/u/steven.daprano)\
**Post date:** [December 12, 2019, 1:05pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/7 "2019-12-12T13:05:42Z")

</div>

I think you have misunderstood what minmax does, it is nothing like the

clamp/clip functions you have linked to.

---

<div class="post-metadata">

**Author:** ![uranusjr](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/uranusjr/32/103_2.png) [@uranusjr](https://discuss.python.org/u/uranusjr)\
**Post date:** [December 12, 2019, 1:11pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/8 "2019-12-12T13:11:50Z")

</div>

Ah, you’re right, I don’t know how I somehow so terribly misunderstood the topic. Sorry for the disturbance.

---

<div class="post-metadata">

**Author:** ![avayert](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/avayert/32/1445_2.png) [@avayert](https://discuss.python.org/u/avayert)\
**Post date:** [December 12, 2019, 1:51pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/9 "2019-12-12T13:51:09Z")

</div>

> [@pylang](#):
>
> I believe I’ve seen `minmax()` functions in libraries from other languages. I’m not too familiar with it. Can you please demonstrate some use cases in Python?

The most frequent use case I personally face is having to find the upper and lower bounds of a desired value found in some collections. I’m sure there would be some other clever use cases that I haven’t thought of.

Concrete examples I’ve dealth with that I can remember include figuring out line lengths for wrapping, having a dictionary work as a simple “sparse array”, and dealing with time intervals (e.g. file creation dates, git commit dates, dates of news articles) in general.

A simple use case outlined in the original post would be finding both the min and max value of some iterator, without having to cast it to a collection such as list first. Sometimes this might not even be possible, as the result would take far too much memory. For this reason `minmax` is especially useful for dealing with files, as for example logfiles you might want to parse could be gigabytes in size. One might not be able to load them in memory, and reading through them twice is not ideal either.

> [@aeros](#):
>
> Depending upon how common the use case would be, it could certainly benefit from being in the `math` module. Particularly if a C accelerator is implemented, as the performance improvements can be significant when iterating over a decent number of items.

As far as I can tell the `itertools` module is also implemented in C, and as was said would more likely be a better fit as `math` is for number related functions.

---

<div class="post-metadata">

**Author:** ![encukou](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/encukou/32/2461_2.png) [@encukou](https://discuss.python.org/u/encukou)\
**Post date:** [December 12, 2019, 2:16pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/10 "2019-12-12T14:16:48Z")

</div>

Proposed additions to stdlib should generally go in a module on PyPI first. That way, they get some real-world validation and they’re easily available for older versions of Python.

A good place for minmax could be [more-itertools](https://pypi.org/project/more-itertools/), a fairly well-known project with “more routines for operating on iterables, beyond itertools”.  
(For me personally, the fact that `minmax` is not there yet makes me doubt its general usefulness.)

---

<div class="post-metadata">

**Author:** ![aeros](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/aeros/32/4140_2.png) [@aeros](https://discuss.python.org/u/aeros)\
**Post date:** [December 12, 2019, 2:39pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/11 "2019-12-12T14:39:13Z")

</div>

> [@pf\_moore](#):
>
> That suggests it’s intended only for use with numbers, which isn’t true of the builtin `min` and `max` .

Ah, good point. I primarily have used the existing `min()` and `max()` functions when working with numbers, so the math module was my first consideration.

But, I’m in agreement with @encukou that it should be implemented in another module before being potentially considered for itertools, which typically takes a highly conservative approach to adding new functions.

---

<div class="post-metadata">

**Author:** ![steven.daprano](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steven.daprano/32/1083_2.png) [@steven.daprano](https://discuss.python.org/u/steven.daprano)\
**Post date:** [December 12, 2019, 3:02pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/12 "2019-12-12T15:02:24Z")

</div>

I have long wanted an efficient version of minmax, because calculating  
it in Python is trickier than it seems. The biggest hassle is trying to  
match the existing min/max API.

The `minmax` function would be useful for the statistics module, where  
the statistical range function is a simple measure of dispersion and is  
defined as:

```
maximum data point - minimum data point

```

For small data sets, the range is sometimes considered a better measure  
of dispersion than the IQR or stdev. The range is taught in secondary  
school maths classes in Australia, from Years 7 to 10 or so.

`minmax` would also be useful for the midrange, although that isn’t  
commonly taught in Australian schools (as far as I know).

The main reason I haven’t already added minmax to statistics as a helper  
function for calculating the range is my anticipation of objections that  
“range” will shadow the built-in and so will be confusing.

---

<div class="post-metadata">

**Author:** ![steven.daprano](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steven.daprano/32/1083_2.png) [@steven.daprano](https://discuss.python.org/u/steven.daprano)\
**Post date:** [December 12, 2019, 3:21pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/13 "2019-12-12T15:21:24Z")

</div>

Petr Viktorin wrote:

“Proposed additions to stdlib should generally go in a module on PyPI first.”

This is Python, not Node.js

> **[The Node.js Ecosystem Is Chaotic and Insecure](https://medium.com/commitlog/the-internet-is-at-the-mercy-of-a-handful-of-people-73fac4bc5068)**
>
> It seems like only yesterday we had the “left-pad” fiasco where Azer Koçulu ended up pulling his packages after a name dispute.

We don’t typically install tiny packages from PyPI, nor should we have  
to. Demanding that each proposed new feature prove itself on PyPI first  
is just a stealth way of saying “No”, since the Python community  
typically doesn’t install single-function packages (as the Node.js  
community does); nor do we typically lift single functions from popular  
packages like more-itertools. Putting individual functions on PyPI will  
just doom them to obscurity: without a critical mass of interest, nobody  
will even know it exists, let alone download it.

PyPI is the right place for _big_ feature-sets to experiment first,  
where the API may be unstable for a while, or where the utility of the  
module is uncertain, or the release schedule doesn’t match Python’s. It  
is completely the wrong place for a single function like `minmax`.

The minmax function is trickier to get right than most people think, but  
for many purposes, calling min and max separately is “good enough”. But  
not all purposes.

---

<div class="post-metadata">

**Author:** ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)\
**Post date:** [December 12, 2019, 4:08pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/14 "2019-12-12T16:08:07Z")

</div>

> [@avayert](#):
>
> There is a frequent pattern where you need to get both the `min` and `max` of some iterable.

I would want to see several examples. I have not found any use of this pattern in the stdlib.

---

<div class="post-metadata">

**Author:** ![mjpieters](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/mjpieters/32/710_2.png) [@mjpieters](https://discuss.python.org/u/mjpieters)\
**Post date:** [December 12, 2019, 6:54pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/15 "2019-12-12T18:54:38Z")

</div>

> [@pf\_moore](#):
>
> I believe it’s simply the following:
> 
> ```auto
> def minmax(it):
> # ...
> 
> ```

For the standard library it’d have to match the existing [`min()`](https://docs.python.org/3/library/functions.html#min) and [`max()`](https://docs.python.org/3/library/functions.html#max) signatures, which accept either an iterable or multiple positional arguments, an optional `key` callable and an optional default value:

```python
_sentinel = object()

def minmax(*args, key=None, default=_sentinel):
    if len(args) > 1:
        if default is not _sentinel:
            raise ValueError(
                "Cannot specify a default for minmax() with "
                "multiple positional arguments"
           )
        v = args
    else:
        v = args[0]

    minval = minitem = maxval = maxitem = None
    for item in v:
        val = key(item) if key is not None else item
        if minval is None or val < minval:
            minval, minitem = val, item
        if maxval is None or val > maxval:
            maxval, maxitem = val, item
    
    if minval is None:
        assert minitem is maxval is maxitem is None
        if default is _sentinel:
            raise ValueError("minmax() arg is an empty sequence")
        minitem = maxitem = default
    
    return minitem, maxitem

```

Of course, in real-world code where someone found they need to find both the min and max of an iterator, they usually create a simpler version.

---

<div class="post-metadata">

**Author:** ![pylang](https://avatars.discourse-cdn.com/v4/letter/p/7feea3/32.png) [@pylang](https://discuss.python.org/u/pylang)\
**Post date:** [December 13, 2019, 12:50am UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/16 "2019-12-13T00:50:09Z")

</div>

Lovely discussion. Thanks everyone.

@avayert “A simple use case outlined in the original post would be finding both the min and max value of some iterator, without having to cast it to a collection such as list first.”

Based on your examples, in a pinch, I would use two iterators:

```python
def minmax(it, key=None):
    """Return a tuple of min and max."""
    if key is None:
        key = lambda x: x
    it1, it2 = itertools.tee(it)
    return min(it1, key=key), max(it2, key=key) 

```

This is a quick solution that should work on more than just numbers, although other posts have demonstrated some improvements such as one-pass (@pf_moore) and complete signatures (@mjpieters).

As @encukou suggests, I would definitely propose this to [`more_itertools`](https://github.com/erikrose/more-itertools) as it seems like a nice fit there.

---

<div class="post-metadata">

**Author:** ![steven.daprano](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/steven.daprano/32/1083_2.png) [@steven.daprano](https://discuss.python.org/u/steven.daprano)\
**Post date:** [December 13, 2019, 1:22am UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/17 "2019-12-13T01:22:31Z")

</div>

py wrote:

“Based on your examples, in a pinch, I would use two iterators:”

[snip sample code using iterator.tee]

Interally, itertools.tee has to save all the values already seen until  
the other iterators consume them. Essentially, under the hood tee saves  
the values in a FIFO queue.

See the docs, especially the note at the end:

“if one iterator uses most or all of the data before another iterator  
starts, it is faster to use list() instead of tee()”

[https://docs.python.org/2/library/itertools.html#itertools.tee](https://docs.python.org/2/library/itertools.html#itertools.tee)

Using tee in this case is an anti-pattern. But it’s a trap people can  
fall into if they don’t know the performance characteristics of tee.

---

<div class="post-metadata">

**Author:** ![Paddy3118](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/paddy3118/32/1396_2.png) [@Paddy3118](https://discuss.python.org/u/Paddy3118)\
**Post date:** [December 13, 2019, 4:07pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/18 "2019-12-13T16:07:20Z")

</div>

I would extend its usefulness to general collections and not just iterators. finding min and max with the same key function is, for me, a common way of getting an idea of the _extent_, or _range_, or _size_, of a collection. I use min and max because that is what is currently available but a minmax function that was twice as fast and worked on the same inputs, (as well as consuming only one iterator), seems good.

---

<div class="post-metadata">

**Author:** ![ruud](https://avatars.discourse-cdn.com/v4/letter/r/838e76/32.png) [@ruud](https://discuss.python.org/u/ruud)\
**Post date:** [December 13, 2019, 5:36pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/19 "2019-12-13T17:36:37Z")

</div>

Further to @mjpieters 's excellent Python version, I suggest a version which doesn’t need to check for None, neither for the key, neither for the value.

```auto
_sentinel = object()
def minmax(*args, key=None, default=_sentinel):
    if len(args) > 1:
        if default is not _sentinel:
            raise ValueError(
                "Cannot specify a default for minmax() with "
                "multiple positional arguments"
           )
        v = iter(args)
    else:
        v = iter(args[0])
    if key is None:
        key = lambda x:x
    try:
        minitem = maxitem = next(v)
        minval = maxval = key(minitem)

    except StopIteration:
        raise ValueError('minmax() arg is an empty sequence')        

    for item in v:
        val = key(item)
        if val < minval:
            minval, minitem = val, item
        if val > maxval:
            maxval, maxitem = val, item
        
    return minitem, maxitem

```

---

<div class="post-metadata">

**Author:** ![avayert](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/avayert/32/1445_2.png) [@avayert](https://discuss.python.org/u/avayert)\
**Post date:** [December 13, 2019, 7:36pm UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/20 "2019-12-13T19:36:09Z")

</div>

> [@storchaka](#):
>
> I would want to see several examples. I have not found any use of this pattern in the stdlib.

I’m not all too familiar with the CPython codebase but a cursory search with the GitHub search ignoring the test folders revealed at least _some_ uses:

- finding both min and max is used twice in [`colorsys`](https://github.com/python/cpython/blob/master/Lib/colorsys.py#L76-L77) within the functions
- it’s used in [`timeit`](https://github.com/python/cpython/blob/master/Lib/timeit.py#L362-L363) to deal with time intervals
- It seems to be used in multiple separate places to deal with common path handling [like here](https://github.com/python/cpython/blob/master/Lib/posixpath.py#L513-L514) and [here](https://github.com/python/cpython/blob/master/Lib/genericpath.py#L78-L79) (I think it was also in `ntpath`)
- It’s used in this seemingly out of place [test function](https://github.com/python/cpython/blob/master/Lib/random.py#L765-L766) inside `random`

Additionally, using global GitHub search I typed in “min max” into the github advanced search, limiting results to only show `.py` files, and sorted by most recently indexed [(link)](https://github.com/search?l=&o=desc&q=min+max+extension%3A.py&s=indexed&type=Code). Within only the last 15 or so minutes of me hitting search there were already plenty of uses, of which I’ve linked some here:  
[[1]](https://github.com/Pragmatismo/Pigrow/blob/db0f1bc7094f0f3c94f938eb0df72a0ee6e4ad35/scripts/gui/graph_modules/graph_icon.py#L75-L80) [[2]](https://github.com/chenkianwee/solar_travel_gui/blob/65977f5561638bdef610058b34f6882041b57abd/solar_travel_gui/stg_process_travel.py#L149-L152) [[3]](https://github.com/bestallouch/CyberSecurityProject/blob/ea348d9f081c7dd1b1394ca0b9d06b7e7805588f/pocker.py#L62)[[4]](https://github.com/BrainCOGS/U19-pipeline_python/blob/af600b16d0420f834f658f0227c0df14dcd5a374/u19_pipeline/imaging.py#L171) [[5]](https://github.com/JohnnyWobble/advent_of_code/blob/9aa783131e6b52c45f329c1132257e9d140585cc/day11/sol/solution.py#L120-L123) [[6]](https://github.com/cctbx/cctbx_project/blob/4c737ebb9aa62005cc14cae48fb696a3ef229062/crys3d/hklview/jsview_3d.py#L36-L37)

---

<div class="post-metadata">

**Author:** ![Marco\_Sulla](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/marco_sulla/32/1451_2.png) [@Marco\_Sulla](https://discuss.python.org/u/Marco_Sulla)\
**Post date:** [December 14, 2019, 12:07am UTC](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834/21 "2019-12-14T00:07:56Z")

</div>

If I can, I have a modification to your solution:

```python
_sentinel = object()

def minmax(*args, key=None):
    args_len = len(args)
    
    if args_len == 0:
        fname = minmax. __name__
        raise ValueError(f"{fname}() expected 1 argument, got 0")
    elif args_len == 1:
        seq = args[0]
    else:
        seq = args
    
    it = iter(seq)

    vmax = vmin = next(it, _sentinel)
    
    if vmax is _sentinel:
        fname = minmax. __name__
        raise ValueError(f"{fname}() arg is an empty sequence")
    
    if key is None:
        for val in it:
            if val > vmax:
                vmax = val
            elif val < vmin:
                vmin = val
    else:
        fmax = fmin = key(vmax)
        
        for val in it:
            fval = key(val)
            
            if fval > fmax:
                fmax = fval
                vmax = val
            elif fval < fmin:
                fmin = fval
                vmin = val
    
    return (vmin, vmax)

```

I know, it’s more verbose and less elegant, but I think it’s more fast for the majority of cases, ie `key=None`.

PS: no `default`. What must it returns, `(default, default)`? Or we have to define a `default_min` and a `default_max`? Overcomplicated IMHO.

[Next page](https://discuss.python.org/t/minmax-function-alongside-min-and-max/2834.md?page=2)
