如何一次获得列表中成员的最小值和最大值?

How to get min and max value of member in list in one pass?

我可以使用 functools.reducemin / max 来获取列表中成员的最小值和最大值。但是要一次获得两者,我需要编写一个循环:

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)

是否可以在不编写普通循环的情况下一次获得 minmax

您可以使用元组作为聚合器。也许是这样的?

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]))

输出应该是一个元组,其中第一个是最小值,第二个是最大值。

REPL 中的示例,证明请求的对象已返回,并且值是正确的:

>>> 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

尽管如此,我认为如果使用辅助函数会更清楚一些。通过这种方式,更容易清楚地思考你的累加器是什么(你的元组)以及你如何进行比较和使用 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=}")

产量:

minimum=0 maximum=3

您可以定义 __lt__ 函数,然后像往常一样使用 min()max()

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)

这输出:

1 3

这是一个one-liner,但严格来说它不是one-pass。您可以为此使用 reduce()。其他回答者也描述了使用 reduce() 的方法,但在我看来,定义 __lt__ 会使语法更清晰:

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)

我喜欢其他答案,这并不是要回答问题,但我想我会分享一些使用 %%timeit 和 IPython 的不同 max, min 计算之间的比较。

我只针对两个不同的输入(1)与问题相同的输入(2)100 个随机整数的列表。这是结果。

这只是一个系统上的两个不同输入(因此很难得出硬性结论),但似乎 good-old one-pass 循环是至高无上的。

相同输入

与问题

中定义的相同x
# 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)

不同的输入

定义x

x = [foo(random.randint(0, 100)) for i in range(100)]

得到了

# 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)