这两个实现有什么区别?

4
假设我有一个列表的列表。
S = [list1, list2, ...]

我希望编写一个函数find,针对输入x,该函数将查找x是否在S的某个子列表中,并输出该列表或返回None,如果未找到x

(注意:任何两个子列表的交集都为空,因此最多只能找到一个列表。)

我的代码非常简单:

def find(x):
    for L in S:
        if x in L:
            return L
    return None

但是我曾经看到有人这样写它:
def find(x):
    try:
        return next( L for L in S if x in L)
    except StopIteration:
        return None

我想知道这两个代码有什么区别?第二个代码比第一个更受欢迎吗?(例如,从软件项目的角度来看)


1
请查看此链接,看看是否能找到您要寻找的答案:https://dev59.com/HFDTa4cB1Zd3GeqPH0cd。好处是您不需要捕获异常,但如果没有找到合适的列表,您可以直接使用默认值,例如 return next((L for L in S if x in L), None) - AKS
1
@ozgur:第一个不会耗尽列表:一旦找到列表,它就会返回。 - zhaoL
谁曾试图在next中玩花样,却忘记了可选的第二个参数。它本可以写成一行代码,如return next((L for L in S if x in L), None)。不管怎样,这可能是一个相当慢的操作。如果经常使用,你可能想要维护一个将元素映射到包含它们的列表的字典,以加速这个例程。 - user2357112
2个回答

2
区别在于第二个版本构建了一个生成器,如果可以在列表S中找到x,则从该项中生成项目。
然后它尝试通过调用next返回从该生成器产生的第一个对象。
从概念上讲,这两个代码片段之间没有太大的区别,注意它们都使用for L in S -> if x in L,第一个是传统的for循环,其主体带有if语句,第二个是推导式的形式。两个版本都是惰性的,也就是说当找到匹配项时立即返回。
我认为你的代码完全没问题。第二个版本可以使用默认值来避免手动异常处理,即:
return next((L for L in S if x in L), None)

该函数尝试返回生成器产生的第一个项目,如果没有这样的项目,则返回None。在这里构造一个只应生成单个项目的生成器是否值得,而且它是否更易读?在我看来,“可能不值得”。


-1

你的代码没问题,但是可以使用列表推导式更简洁地编写。第二种解决方案使用生成器推导式创建了一个生成器。由于已知两个列表的交集为空集,因此生成器最多只包含一个元素。

然而,在这里使用生成器会引入一些开销,如果只比较几个列表,则列表推导式可能会更快。

def find_list(x, S):
    ret = [L for L in S if x in L]
    return ret[0] if len(ret) else None

def find_iter(x, S):
    ret = (L for L in S if x in L)
    try:
        return next(ret)
    except StopIteration:
        return None

在交互式iPython shell中的运行时测试:

In [1]: S = [["a"], ["b", "c",], ["d"]]

In [2]: %timeit find_list("b", S)
1000000 loops, best of 3: 475 ns per loop

In [3]: %timeit find_list("f", S)
1000000 loops, best of 3: 349 ns per loop

In [4]: %timeit find_iter("b", S)
1000000 loops, best of 3: 802 ns per loop

In [5]: %timeit find_iter("f", S)
100000 loops, best of 3: 1.58 µs per loop

编辑

使用由@timgeb优化的生成器版本,生成器理解能力得到了很大提升:

def find_iter_opt(x, S):
    ret = (L for L in S if x in L)
    return next(ret, None)

In [8]: %timeit find_iter_opt("b", S)
1000000 loops, best of 3: 751 ns per loop

In [9]: %timeit find_iter_opt("f", S)
1000000 loops, best of 3: 597 ns per loop

你的测试代码不等效 - find_list 收集所有数据(在一般情况下可能会匹配多个列表中的元素),然后将其返回。整个问题的重点是比较两种方法,它们都表现出了早期返回行为(至少有效地 - 因为从生成器中获取第一个值就有点类似于早期返回)。 - Łukasz Rogalski
我已经修正了代码,让它返回元素本身而不是元素列表。 - Jan Christoph Terasa
我现在意识到,如果搜索的元素早期被找到,那么这个解决方案会慢很多。好的。 - Jan Christoph Terasa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接