Pythonic方法判断非空列表条目是否“连续”

45 投票
11 回答
4298 浏览
提问于 2025-04-17 14:55

我想找个简单的方法来判断一个列表中所有非空(不是None)的项目是否在一个连续的片段中。 我会用整数来举例说明这些非空的项目。

比如,列表 [None, None, 1, 2, 3, None, None] 符合我对连续整数的要求。相对而言,[1, 2, None, None, 3, None]不连续的,因为在整数之间有None的项目。

再举几个例子,让这个概念更清晰。

连续的:
[1, 2, 3, None, None]
[None, None, 1, 2, 3]
[None, 1, 2, 3, None]

不连续的:
[None, 1, None, 2, None, 3]
[None, None, 1, None, 2, 3]
[1, 2, None, 3, None, None]

我最开始的方法是用变量来跟踪我们是否遇到过None,以及是否遇到过整数——结果是嵌套了很多层的if/else语句,这让人很难理解,尤其是在for循环里。(而且我承认,这种方法在某些情况下并没有成功)。

有没有人知道更简单的方法来判断列表中的非None项目是否在一个连续的片段中?

11 个回答

12

你可以使用类似于 itertools.groupby 的东西:

from itertools import groupby

def are_continuous(items):
    saw_group = False

    for group, values in groupby(items, lambda i: i is not None):
        if group:
            if saw_group:
                return False
            else:
                saw_group = True

    return True

这个方法会一直循环,直到它看到同样的组出现两次为止。我不确定你是否会考虑 [None, None],所以你可以根据自己的需要进行调整。

25

老朋友 itertools.groupby 来帮忙了:

from itertools import groupby

def contiguous(seq):
    return sum(1 for k,g in groupby(seq, lambda x: x is not None) if k) == 1

结果是

>>> contiguous([1,2,3,None,None])
True
>>> contiguous([None, 1,2,3,None])
True
>>> contiguous([None, None, 1,2,3])
True
>>> contiguous([None, 1, None, 2,3])
False
>>> contiguous([None, None, 1, None, 2,3])
False
>>> contiguous([None, 1, None, 2, None, 3])
False
>>> contiguous([1, 2, None, 3, None, None])
False

[编辑]

因为评论里似乎有一些讨论,我来解释一下为什么我更喜欢这种方法,而不是其他一些方法。

我们想知道是否有一组连续的非空对象,

sum(1 for k,g in groupby(seq, lambda x: x is not None) if k)

这个方法会计算连续的非空对象的数量,使用的是标准库中专门用来收集连续组的函数。当我们看到 groupby 时,就会想到“连续组”,反之亦然。从这个角度来看,它的意思很清楚。这基本上就是我目标的 定义

在我看来,唯一的缺点是它没有短路功能,这个问题可以解决,但经过一番思考,我还是更喜欢这个方法,因为它使用了我喜欢的一个基本概念——“计算连续的非空组的数量”,而不是简单地“告诉我是否有超过一个连续的非空组,尽快给我答案”。

很多实现最后一种方法的思路依赖于对问题的巧妙观察,比如“如果只有一个连续的非空对象组,那么我们可以先扫描找到第一个非空对象,然后继续扫描直到找到第一个非空组,如果存在的话,最后看看剩下的是否是空的,这样就能得到答案。”(或者类似的说法,这也是我遇到的问题之一:我得思考一下。)对我来说,这感觉像是用“实现细节”来解决问题,关注的是我们可以用来解决问题的特性,而不是简单地把问题交给Python,让它来处理。

正如那句老话说的,我的脑袋不太灵光,我喜欢避免聪明的解决方案,因为根据我的经验,这条路上布满了失败。

当然,每个人的理解和经验可能不同,可能和他们的聪明才智成正比。

44
def contiguous(seq):
    seq = iter(seq)
    all(x is None for x in seq)        # Burn through any Nones at the beginning
    any(x is None for x in seq)        # and the first group
    return all(x is None for x in seq) # everthing else (if any) should be None.

这里有几个例子。你可以用 next(seq) 来获取迭代器中的下一个项目。我会在每个例子后面标记出下一个项目。

例子1:

seq = iter([None, 1, 2, 3, None])        #  [None, 1, 2, 3, None]
                                         # next^
all(x is None for x in seq)            
                                         #        next^
any(x is None for x in seq)            
                                         #                    next^ (off the end)
return all(x is None for x in seq)       # all returns True for the empty sequence

例子2:

seq = iter([1, 2, None, 3, None, None])  #    [1, 2, None, 3, None, None]
                                         # next^
all(x is None for x in seq)            
                                         #    next^
any(x is None for x in seq)            
                                         #             next^  
return all(x is None for x in seq)       # all returns False when 3 is encountered

撰写回答