我试图在列表中找到最小值,超过某个阈值而不使用min()函数(我正在努力练习)。
我通过首先创建一个高于阈值的值列表然后循环遍历列表来管理,如果它小于先前看到的值,则将值保存到变量:
def minpass(mymarks, mypass):
passed= [x for x in mymarks if x >= mypass]
min_value = passed[0]
for x in passed:
if x < min_value:
min_value = x
return min_value
x = [2, 53, 90]
y = 50
minpass(x, y)
这正确地返回53.
是否可以在不创建第二个列表(已通过)的情况下执行此操作? 为什么添加第二个条件不起作用?例如
def minpass(mymarks, mypass):
min_value = mymarks[0]
for x in mymarks:
if x < min_value and x >= mypass:
min_value = x
return min_value
x = [2, 53, 90]
y = 50
minpass(x, y)
这错误地返回2而不是53.
答案 0 :(得分:4)
因为你这样做是为了学习经验:
为避免创建第二个列表,最有趣的替代方法是创建一个惰性迭代器。在幕后,这将根据需要计算下一个过滤值,而不是预先构建它们的列表。但是从你的大部分代码中,它实际上看起来好像你创建了一个列表。
有不同的方法来创建惰性迭代器 - 显式迭代器类,filter
内置函数,生成器函数 - 但在您的情况下,您可以使用生成器表达式而不是列表推导:
passed = (x for x in mymarks if x >= mypass)
我所要做的就是将方括号更改为括号,并且你神奇地得到了一个懒惰的迭代器。
但是,迭代器只能用于按顺序遍历值一次。你不能做索引(passed[0]
)之类的事情。所以你需要重新考虑一下你的代码。但事实证明这很容易:
def minpass(mymarks, mypass):
passed = (x for x in mymarks if x >= mypass)
min_value = next(passed) # this consumes the first value
for x in passed: # this loops over all the remaining values
if x < min_value:
min_value = x
return min_value
虽然我们正在使用它,但您可能需要考虑将代码重构为两个函数 - 编写自己的minvalue
函数,它接受任何可迭代的函数(可迭代的是一个惰性迭代器,或者像列表一样的序列,或者其他任何可以进入for
循环的东西)并返回最小值:
def minvalue(it):
it = iter(it) # this makes sure we have a lazy iterator
min_value = next(it) # this consumes the first value
for x in it: # this loops over all the remaining values
if x < min_value:
min_value = x
return min_value
def minpass(mymarks, mypass):
return minvalue(x for x in mymarks if x >= mypass)
或者可能进一步重构:
def passvalues(it, mypass):
return (x for x in it if x >= mypass)
def minpass(mymarks, mypass):
return minvalue(passvalues(mymarks, mypass))
请注意,此方法会自动解决您的第二个问题。您的问题是mymarks[0]
可能不是>= mypass
。要重写一些工作,你必须做这样的事情:
def minpass(mymarks, mypass):
for x in mymarks:
if x >= mypass:
min_value = x
break
for x in mymarks:
if x < min_value and x >= mypass:
min_value = x
return min_value
但是将事物写成迭代器变换链会强制你按顺序排列 - 首先进行过滤,然后进行最小查找,这意味着你将自动获得第一个过滤值而不是第一个值 - 而仍然按照你想要的方式交错工作(并避免创建一个完整不必要的列表的时间和空间成本)。
如果你想更深入地介绍这些想法,David Beazley的Generator Tricks for Systems Programmers是惊人的。
最后要考虑的一件事是:有没有办法摆脱对第一个值的特殊处理?
您可以从一个大于任何值的值开始,或者您可以使用一个标志来指定您到目前为止是否找到了最小值:
def minvalue(it):
found_min = False
for x in it:
if not found_min or x < min_value:
min_value = x
found_min = True
return min_value
这有一个优点(或者可能是缺点,取决于你想要的......)在传递空列表时仍然失败,但它简化了循环(无需拉出第一个值,这意味着无需调用{ {1}})。但手动标记管理可能会增加噪音,而不是帮助您删除。不过,值得比较两者并为自己做出决定。
您可能需要考虑自己尝试的其他事项:
reduce
重写iter
。minvalue
以使用“小于任何值”值。
minvalue
将起作用。__lt__
method定义float('inf')
类,并使用BiggestThing
。heapq
中的BiggestThing()
- 然后您可以将其展开以返回两个最低值,而不是仅返回最低值,或者使用{{1参数和并返回那么多。答案 1 :(得分:3)
通常,使用循环查找min
(或max
)要求您将返回变量初始化为巨大的(或巨大的负值)。
在您的情况下,您正在初始化列表中第一个元素的最小值。然后,对于后续元素,x < min_value
检查将评估为False
,因为此值已经是整个列表的最小值。
以下是修改代码的一种方法:
def minpass(mymarks, mypass):
min_value = 1e10 # something bigger than your maximum value
for x in mymarks:
if x < min_value and x >= mypass:
min_value = x
return min_value
x = [2, 53, 90]
y = 50
minpass(x, y)
#53