用于确定空列表条目是否为“连续”的Pythonic方式

时间:2013-02-06 04:13:15

标签: python list null slice

我正在寻找一种方法来轻松确定列表中的所有非None项是否出现在单个连续切片中。我将使用整数作为非None项的示例。

例如,列表[None, None, 1, 2, 3, None, None]符合我对连续整数条目的要求。相比之下,[1, 2, None, None, 3, 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,以及我们是否遇到了int - 这最终会导致嵌入在for循环中的高度嵌套且非常难以遵循的if / else语句系列。 (除了丑陋之外,我承认在每种情况下都没有让它发挥作用)。

任何人都知道一种更简单的方法来确定列表中的非None项是否出现在一个连续切片中?

11 个答案:

答案 0 :(得分: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)从迭代器中获取下一个项目。我会在每个

之后添加一个指向下一个项目的标记

example1:

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

答案 1 :(得分: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

[edit]

由于评论中似乎有一些讨论,我会解释为什么我比其他人更喜欢这种方法。

我们试图找出是否有一组连续的非None对象,并且

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

使用stdlib中的函数计算连续的非None对象的数量,该函数用于收集连续的组。一旦我们看到groupby,我们就会认为“连续的群体”,反之亦然。从这个意义上讲,它是自我记录的。这基本上是我的目标的定义

恕我直言,唯一的缺点就是它不会短路,而且可以修复,但在考虑之后我仍然更喜欢这个,因为它使用我喜欢的原语 - “计算连续的非数量没有任何团体“ - 我更喜欢”只要你能告诉我是否有一个以上的连续非 - 无团体“。

实现最后一个方法的许多方法依赖于对该问题的巧妙观察,例如“如果只有一个连续的非-Onob对象组,那么如果我们扫描直到找到第一个not-None对象,然后扫描对象,直到找到第一个非None组(如果存在),然后剩下的是否为None给我们答案。“ (或类似的东西,这是我的问题的一部分:我必须考虑它。)对我来说,感觉就像使用“实现细节”解决问题,并关注我们可以用来解决的问题的属性它,而不是简单地向Python指定问题并让Python完成工作。

我是一个脑力很小的熊,正如俗话所说,我喜欢避免变得聪明,因为根据我的经验,这是一条充满失败的路线。

与往常一样,每个人的里程可能会有所不同,当然也可能与他们的聪明程度成比例。

答案 2 :(得分: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],因此请根据您的需要进行调整。

答案 3 :(得分:7)

这可能不是最好的方法,但您可以查找第一个非None条目和最后一个non-None条目,然后检查None的切片。 e.g:

def is_continuous(seq):
    try:
        first_none_pos = next(i for i,x in enumerate(seq) if x is not None)
        #need the or None on the next line to handle the case where the last index is `None`.
        last_none_pos = -next(i for i,x in enumerate(reversed(seq)) if x is not None) or None
    except StopIteration: #list entirely of `Nones`
        return False
    return None not in seq[first_none_pos:last_none_pos]

assert is_continuous([1,2,3,None,None]) == True
assert is_continuous([None, 1,2,3,None]) == True
assert is_continuous([None, None, 1,2,3]) == True
assert is_continuous([None, 1, None, 2,3]) == False
assert is_continuous([None, None, 1, None, 2,3]) == False
assert is_continuous([None, 1, None, 2, None, 3]) == False
assert is_continuous([1, 2, None, 3, None, None]) == False

这适用于任何序列类型。

答案 4 :(得分:7)

使用序列元素的自然方法是使用dropwhile

from itertools import dropwhile
def continuous(seq):
    return all(x is None for x in dropwhile(lambda x: x is not None,
                                            dropwhile(lambda x: x is None, seq)))

我们可以在没有嵌套函数调用的情况下表达这一点:

from itertools import dropwhile
def continuous(seq):
    core = dropwhile(lambda x: x is None, seq)
    remainder = dropwhile(lambda x: x is not None, core)
    return all(x is None for x in remainder)

答案 5 :(得分:5)

一个班轮:

contiguous = lambda l: ' ' not in ''.join('x '[x is None] for x in l).strip()

真正的工作是由strip功能完成的。如果剥离的字符串中有空格,则它们不是前导/尾随。函数的其余部分将列表转换为字符串,每个None都有一个空格。

答案 6 :(得分:3)

我做了一些分析来比较@ gnibbler的方法和groupby方法。 @ gnibber的方法总是更快,尤其是更长的名单。例如,我看到长度为3-100的随机输入的性能增益为50%,包含单个int序列(随机选择)的可能性为50%,否则为随机值。测试代码如下。我穿插了两种方法(随机选择哪一种方法)以确保任何缓存效果都被取消。基于此,我会说虽然groupby方法更直观,但如果分析表明这是整个代码优化的重要部分,@ gnibber的方法可能是合适的 - 在这种情况下,应该使用适当的注释通过使用all / any到消费者迭代器值来表明发生了什么。

from itertools import groupby
import random, time

def contiguous1(seq):
    # gnibber's approach
    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.

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

times = {'contiguous1':0,'contiguous2':0}

for i in range(400000):
    n = random.randint(3,100)
    items = [None] * n
    if random.randint(0,1):
        s = random.randint(0,n-1)
        e = random.randint(0,n-s)
        for i in range(s,e):
            items[i] = 3
    else:
        for i in range(n):
            if not random.randint(0,2):
                items[i] = 3
    if random.randint(0,1):
        funcs = [contiguous1, contiguous2]
    else:
        funcs = [contiguous2, contiguous1]
    for func in funcs:
        t0 = time.time()
        func(items)
        times[func.__name__] += (time.time()-t0)

print
for f,t in times.items():
    print '%10.7f %s' % (t, f)

答案 7 :(得分:2)

这是一个受numpy启发的解决方案。获取所有非null元素的数组索引。然后,将每个索引与其后的索引进行比较。如果差异大于1,则非空值之间存在空值。如果没有索引,其中下面的索引多于一个,则没有间隙。

def is_continuous(seq):
    non_null_indices = [i for i, obj in enumerate(seq) if obj is not None]
    for i, index in enumerate(non_null_indices[:-1]):
        if non_null_indices[i+1] - index > 1:
            return False
    return True

答案 8 :(得分:1)

这个算法的工作有一些缺点(它从列表中删除项目)。但这是一个解决方案。

基本上,如果您从开始和结束中删除所有连续None。如果你在列表中找到了一些None,则整数不是连续的形式。

def is_continuous(seq):
    while seq and seq[0] is None: del seq[0]
    while seq and seq[-1] is None: del seq[-1]

    return None not in seq

assert is_continuous([1,2,3,None,None]) == True
assert is_continuous([None, 1,2,3,None]) == True
assert is_continuous([None, None, 1,2,3]) == True
assert is_continuous([None, 1, None, 2,3]) == False
assert is_continuous([None, None, 1, None, 2,3]) == False
assert is_continuous([None, 1, None, 2, None, 3]) == False
assert is_continuous([1, 2, None, 3, None, None]) == False

然而,另一个小代码如何变得邪恶的例子。

我希望strip()可以使用list方法。

答案 9 :(得分:1)

  

我的第一个方法是使用变量来跟踪......

     

...这最终会在for循环中嵌入一系列高度嵌套且非常难以理解的if / else语句...

没有!实际上你只需要一个变量。用你的方法在有限状态机(FSM)的视图中思考这个问题将会得到一个非常好的解决方案。

我们称国家为p。起初,p为0.然后我们开始在各州之间行走。

FSM

当检查列表中的所有元素但仍未失败时,答案为True

在dict中编码转换表的一个版本

def contiguous(s, _D={(0,0):0, (0,1):1, (1,0):2, (1,1):1, (2,0):2, (2,1):3}):
    p = 0
    for x in s:
        p = _D[p, int(x is not None)]
        if p >= 3: return False
    return True

使用if语句的另一个版本:

def contiguous(s):
    p = 0
    for x in s:
        if x is None and p == 1 or x is not None and (p == 0 or p == 2):
            p += 1
        if p >= 3: return False
    return True

所以我的观点是使用iffor仍然是pythonic。

更新

我找到了另一种编码FSM的方法。我们可以将转换表打包成12位整数。

def contiguous(s):
    p = 0
    for x in s:
        p = (3684 >> (4*p + 2*(x!=None))) & 3
        if p >= 3: return False
    return True

这里3684,幻数,可以通过以下方式获得:

    _D[p,a]     3  2  1  2  1  0
         p      2  2  1  1  0  0
         a      1  0  1  0  1  0
bin(3684) = 0b 11 10 01 10 01 00 

可读性不如其他版本好,但它更快,因为它避免了字典查找。 第二个版本与此一样快,但这种编码思想可以推广以解决更多问题。

答案 10 :(得分:0)

这是仅使用numpy的一种方式:

a = np.array([1, 2, 3, np.nan, 4, 5, np.nan, 6, 7])

# This returns indices of nans
# eg. [[3], [6]]
# use .squeeze() to convert to [3, 6]
aa = np.argwhere(a != a).squeeze()

# use a diff on your array , if the nans
# are continuous, the diff will always be 1
# if not, diff will be > 1 , and using any() will return True
any(np.diff(aa) > 1)