'How to get min and max value of member in list in one pass?
I can use functools.reduce or min / max to get min and max of members in a list. But to get both in one pass I need to write a loop:
from functools import reduce
class foo:
def __init__(self,value): self.value = value
x = []
x.append(foo(1))
x.append(foo(2))
x.append(foo(3))
min_value = reduce(lambda a,b: a if a.value < b.value else b,x).value
max_value = reduce(lambda a,b: a if a.value > b.value else b,x).value
print(min_value)
print(max_value)
min_value2 = min(x,key=lambda a: a.value).value
max_value2 = max(x,key=lambda a: a.value).value
print(min_value2)
print(max_value2)
min_value3 = x[0].value
max_value3 = x[0].value
for f in x:
if f.value < min_value3: min_value3 = f.value
if f.value > max_value3: max_value3 = f.value
print(min_value3)
print(max_value3)
Is it possible to get min and max in one pass without writing a plain loop?
Solution 1:[1]
You could use a tuple as your aggregator. Something like this maybe?
min_value, max_value = reduce(lambda a, b:
(a[0] if a[0].value < b.value else b, a[1] if a[1].value > b.value else b),
x,
(x[0], x[1]))
The output should be a tuple where the first is the minimum and the second the maximum.
Example in the REPL, demonstrating that the objects requested are returned, and that the values are correct:
>>> class Foo:
... def __init__(self,value): self.value = value
...
>>> ls = [Foo(1), Foo(2), Foo(3)]
>>> min_value, max_value = reduce(lambda a, b: (a[0] if a[0].value < b.value else b, a[1] if a[1].value > b.value else b), ls, (ls[0], ls[1]))
>>> min_value
<__main__.Foo object at 0x10bd20940>
>>> min_value.value
1
>>> max_value.value
3
For what it's worth, though, I think it's a little clearer if you use a helper function. In this way it's easier to think cleanly about what your accumulator is (your Tuple) and how you're doing the comparison and using reduce().
from typing import Tuple
from functools import reduce
class Foo:
def __init__(self, value): self.value = value
def __repr__(self):
return f"{self.value}"
def min_max(accumulator: Tuple[Foo, Foo], element: Foo) -> Tuple[Foo, Foo]:
minimum, maximum = accumulator
return (minimum if minimum.value < element.value else element,
maximum if maximum.value > element.value else element)
ls = [Foo(x) for x in range(0, 4)] # Or however you construct this list
minimum, maximum = reduce(min_max, ls, (ls[0], ls[0]))
print(f"{minimum=} {maximum=}")
Yielding:
minimum=0 maximum=3
Solution 2:[2]
You can define the __lt__ function, and then use min() and max() as you normally would:
from functools import reduce
class foo:
def __init__(self,value):
self.value = value
def __lt__(self, other):
return self.value < other.value
x = []
x.append(foo(1))
x.append(foo(2))
x.append(foo(3))
min_value, max_value = min(x), max(x)
print(min_value.value, max_value.value)
This outputs:
1 3
This is a one-liner, but strictly speaking it's not one-pass. You can use reduce() for that. Other answerers have described methods that use reduce() as well, but defining __lt__ makes the syntax much cleaner, in my opinion:
min_value, max_value = reduce(lambda a, b: (min(a[0], b), max(a[1], b)), x, (x[0], x[0]))
print(min_value.value, max_value.value)
Solution 3:[3]
I like the other answers and this is not meant to answer the question but I thought I would share a few comparisons between the different max, min calculations using %%timeit with IPython.
I only did it for two different inputs (1) the same input as the question (2) a list of 100 random integers. Here are the results.
This is only two different inputs on one system (so it is hard to draw hard conclusions), but it seems the good-old one-pass loop is supreme.
Same Input
Same x as defined in the question
# min_value, max_value
598 ns ± 9.98 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
# min_value2, max_value2
784 ns ± 6.29 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
# min_value3, max_value3
309 ns ± 1.47 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
# Nathan's
618 ns ± 1.47 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
# BrokenBenchmark
531 ns ± 5.78 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
Different Input
Defined x as
x = [foo(random.randint(0, 100)) for i in range(100)]
and got
# min_value, max_value
11.2 µs ± 21.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
# min_value2, max_value2
8.7 µs ± 78.1 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
# min_value3, max_value3
5.62 µs ± 104 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
# Nathan's
14 µs ± 46.6 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
# BrokenBenchMark
16 µs ± 54.8 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 |
