463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122133

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?

Upvotes: 2

Views: 2138

Answers (2)

Nathaniel Ford
Nathaniel Ford

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

BrokenBenchmark
BrokenBenchmark

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

Related Questions