【问题标题】:Find the last element in a row that matches a criterion查找与条件匹配的行中的最后一个元素
【发布时间】:2020-02-23 10:23:46
【问题描述】:

除了这个问题“Get the first item from an iterable that matches a condition”之外,我还需要第一个匹配之前的元素。或完全等价:一行中与条件匹配的最后一个元素。例如,如果我需要此列表中第一个非素数之前的最后一个素数 [2, 3, 4, 5, 6, 7, 8, 9],则答案是 3。如果没有这样的元素,结果可能是 None、异常或默认值。

直接的解决方案是:

result = None
for x in numbers:
    if is_prime(x):
        result = x
    else:
        break

是否有一个简单的单线可以解决任务?

【问题讨论】:

    标签: python generator iterable


    【解决方案1】:

    不确定是否有更简单的方法。我的方法需要itertools.takewhile

    1. 使用itertools.takewhile在不再满足条件后停止迭代以创建素数列表直到第一个非素数出现,即[2, 3]
    2. 然后使用list[-1] 获取列表的最后一个元素。
    3. 如果列表为空,请使用(list or [None])[-1],以便返回None
    import itertools
    result = ([x for x in itertools.takewhile(lambda n: is_prime(n), numbers)] or [None])[-1]
    

    collections.deque()

    from collections import deque
    result = (deque((itertools.takewhile(lambda n: is_prime(n), numbers)), maxlen=1) or [None]).pop()
    

    【讨论】:

    • 此解决方案从生成器创建完整列表。失去可读性我们也失去了效率。理想的解决方案应该只保留以前和当前的元素。
    【解决方案2】:

    您可以使用zipislice 构造一个迭代器,它有自己的“未来”可用。

    next(
        filter(
            lambda pair: isprime(pair[1]),  # The predicate applied to a pair
            zip(xs, islice(xs, 1, None))  # The iterator and its 'future'
        ),
        (None, None)  # A default pair in case no matches are found
    )[0]  # Retrieve the 'current' entry from the matching pair
    

    在每个步骤中,您都有一对您可以认为是(present, future),谓词将应用于future。最后,我们解包present,它表示迭代器中最后一个不匹配的条目。

    请注意,如所示,此实现不会返回列表中的最后一个条目,因为本质上,如果您将[a, b, c] 压缩到其自己的移位版本,您最终会得到一个比另一个短的。你可以使用zip_longest(来自itertools)来克服这个问题,但你需要在你的谓词中处理fillvalue(通常是None)。


    正如评论中所述,这对生成器不起作用,因为它会消耗生成器。但是,使用折叠(Python 中的reduce)会变得更容易。首先,在更“Pythonic”的演示中,函数看起来像这样:

    def fold(hist, cur):
        stop, prev = hist
    
        if stop:
            return hist
    
        if isprime(cur):
            return (True, prev)
    
        return (False, cur)
    

    元组的第一个元素用作“停止”标记,第二个元素是我们的“针”。您可以将其与 reduce 一起使用:

    reduce(fold, xs, (False, None))[1]
    

    这并不完全是“一个衬里”,但我们可以将其压缩成lambda

    reduce(lambda z, x: (z if z[0] else ((1, z[1]) if isprime(x) else (0, x))), xs, (0, None))[1]
    

    【讨论】:

    • 有趣的解决方案。更接近我的预期。然而,这对生成器不起作用,但如果将表达式 zip(xs, islice(xs, 1, None)) 替换为仅接受一次 xs 的东西,那将起作用。
    • 你可以通过折叠来做到这一点。我会将其添加到答案中。
    猜你喜欢
    • 2022-01-16
    • 1970-01-01
    • 1970-01-01
    • 2015-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多