Reputation: 122133
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?
Upvotes: 2
Views: 2138
Reputation: 21220
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
Upvotes: 2
Reputation: 19223
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)
Upvotes: 0