我正在寻找一种方法来轻松确定列表中的所有非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项是否出现在一个连续切片中?
答案 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.然后我们开始在各州之间行走。
当检查列表中的所有元素但仍未失败时,答案为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
所以我的观点是使用if
和for
仍然是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)