[英]Check if all elements in a list are identical
如果输入列表中的所有元素使用标准相等运算符评估为彼此相等,我需要一个函数,它接收一个list
并输出True
,否则输出False
。
我觉得最好遍历比较相邻元素的列表AND
然后遍历所有结果布尔值。 但我不确定最 Pythonic 的方式是什么。
使用itertools.groupby
(参见itertools
recipes ):
from itertools import groupby
def all_equal(iterable):
g = groupby(iterable)
return next(g, True) and not next(g, False)
或没有groupby
:
def all_equal(iterator):
iterator = iter(iterator)
try:
first = next(iterator)
except StopIteration:
return True
return all(first == x for x in iterator)
您可能会考虑使用许多替代的单线:
将输入转换为集合并检查它是否只有一个或零(如果输入为空)项
def all_equal2(iterator): return len(set(iterator)) <= 1
与没有第一项的输入列表进行比较
def all_equal3(lst): return lst[:-1] == lst[1:]
def all_equal_ivo(lst): return not lst or lst.count(lst[0]) == len(lst)
def all_equal_6502(lst): return not lst or [lst[0]]*len(lst) == lst
但它们也有一些缺点,即:
all_equal
和all_equal2
可以使用任何迭代器,但其他迭代器必须采用序列输入,通常是列表或元组等具体容器。all_equal
和all_equal3
一旦发现差异就停止(所谓的“短路”),而所有替代方案都需要遍历整个列表,即使您可以通过查看前两个元素来判断答案是否为False
.all_equal2
中,内容必须是hashable 。 例如,列表列表将引发TypeError
。all_equal2
(在最坏的情况下)和all_equal_6502
创建列表的副本,这意味着您需要使用双倍的内存。 在 Python 3.9 上,使用perfplot
,我们得到了这些时间(较低Runtime [s]
更好):
比使用 set() 处理序列(不是可迭代对象)更快的解决方案是简单地计算第一个元素。 这假设列表是非空的(但这很容易检查,并自己决定空列表上的结果应该是什么)
x.count(x[0]) == len(x)
一些简单的基准:
>>> timeit.timeit('len(set(s1))<=1', 's1=[1]*5000', number=10000)
1.4383411407470703
>>> timeit.timeit('len(set(s1))<=1', 's1=[1]*4999+[2]', number=10000)
1.4765670299530029
>>> timeit.timeit('s1.count(s1[0])==len(s1)', 's1=[1]*5000', number=10000)
0.26274609565734863
>>> timeit.timeit('s1.count(s1[0])==len(s1)', 's1=[1]*4999+[2]', number=10000)
0.25654196739196777
[编辑:此答案稍后解决当前投票最多的itertools.groupby
(这是一个很好的答案)答案。]
在不重写程序的情况下,最渐近性能和可读性最强的方式如下:
all(x==myList[0] for x in myList)
(是的,这甚至适用于空列表!这是因为这是 python 具有惰性语义的少数情况之一。)
这将在尽可能早的时间失败,因此它是渐近最优的(预期时间大约是 O(#uniques) 而不是 O(N),但最坏情况下的时间仍然是 O(N))。 这是假设您之前没有看过数据...
(如果您关心性能但不太关心性能,您可以先进行通常的标准优化,例如将myList[0]
常量提升出循环并为边缘情况添加笨拙的逻辑,尽管这是 python编译器最终可能会学会如何做,因此除非绝对必要,否则不应这样做,因为它破坏了可读性以获得最小的收益。)
如果您更关心性能,这将是上述速度的两倍,但更冗长:
def allEqual(iterable):
iterator = iter(iterable)
try:
firstItem = next(iterator)
except StopIteration:
return True
for x in iterator:
if x!=firstItem:
return False
return True
如果您更关心性能(但不足以重写您的程序),请使用当前投票最多的itertools.groupby
答案,它的速度是allEqual
的两倍,因为它可能是优化的 C 代码。 (根据文档,它应该(类似于这个答案)没有任何内存开销,因为惰性生成器永远不会被评估为一个列表......这可能会让人担心,但伪代码显示分组的“列表”是实际上是惰性生成器。)
如果您更关心性能,请继续阅读...
关于性能的旁注,因为其他答案出于某种未知原因正在谈论它:
...如果您以前看过数据并且可能使用某种集合数据结构,并且您真的很关心性能,那么您可以通过使用Counter
扩充您的结构来免费获得.isAllEqual()
O(1)每次插入/删除/等都会更新。 操作并检查它是否为{something:someCount}
即len(counter.keys())==1
形式; 或者,您可以将 Counter 放在单独的变量中。 可以证明,这比其他任何事情都要好,直到恒定因素。 也许您也可以将 python 的 FFI 与ctypes
与您选择的方法一起使用,也可以使用启发式方法(例如,如果它是带有getitem的序列,然后检查第一个元素,最后一个元素,然后按顺序检查元素)。
当然,为了可读性,有话要说。
您可以将列表转换为集合。 集合不能有重复项。 因此,如果原始列表中的所有元素都相同,则该集合将只有一个元素。
if len(set(input_list)) == 1:
# input_list has all identical elements.
值得一提的是,这最近出现在python-ideas 邮件列表中。 事实证明,已经有一个itertools 配方可以做到这一点: 1
def all_equal(iterable):
"Returns True if all the elements are equal to each other"
g = groupby(iterable)
return next(g, True) and not next(g, False)
据说它表现得非常好并且有一些不错的特性。
1换句话说,我不能把提出解决方案的功劳归功于我——我什至不能把找到它的功劳归功于我。
将列表转换为集合时,将删除重复的元素。 因此,如果转换后的集合的长度为1,则意味着所有元素都相同。
len(set(input_list))==1
这是一个例子
>>> a = ['not', 'the', 'same']
>>> b = ['same', 'same', 'same']
>>> len(set(a))==1 # == 3
False
>>> len(set(b))==1 # == 1
True
这会将输入列表的第一个元素与列表中的所有其他元素进行比较(等效)。 如果相等,则返回True,否则返回False。
all(element==input_list[0] for element in input_list)
这是一个例子
>>> a = [1, 2, 3, 4, 5]
>>> b = [1, 1, 1, 1, 1]
>>> all(number==a[0] for number in a)
False
>>> all(number==b[0] for number in b)
True
PS如果要检查整个列表是否等效于某个值,则可以为input_list [0]设置该值。
这是一种简单的方法:
result = mylist and all(mylist[0] == elem for elem in mylist)
这稍微复杂一些,它会产生函数调用开销,但语义更清楚地说明:
def all_identical(seq):
if not seq:
# empty list is False.
return False
first = seq[0]
return all(first == elem for elem in seq)
这是另一个选项,比len(set(x))==1
长列表更快(使用短路)
def constantList(x):
return x and [x[0]]*len(x) == x
检查所有元素是否等于第一个。
np.allclose(array, array[0])
关于使用reduce()
和lambda
。 这是一个我个人认为比其他一些答案更好的工作代码。
reduce(lambda x, y: (x[1]==y, y), [2, 2, 2], (True, 2))
如果所有项目都相同,则返回一个元组,其中第一个值是布尔值。
将列表转换为集合,然后找到集合中的元素数。 如果结果为1,则它具有相同的元素,否则,列表中的元素将不相同。
list1 = [1,1,1]
len(set(list1))
>1
list1 = [1,2,3]
len(set(list1)
>3
怀疑这是“最 Pythonic”,但类似于:
>>> falseList = [1,2,3,4]
>>> trueList = [1, 1, 1]
>>>
>>> def testList(list):
... for item in list[1:]:
... if item != list[0]:
... return False
... return True
...
>>> testList(falseList)
False
>>> testList(trueList)
True
会成功的。
def allTheSame(i):
j = itertools.groupby(i)
for k in j: break
for k in j: return False
return True
在 Python 2.4 中工作,它没有“全部”。
我会做:
not any((x[i] != x[i+1] for i in range(0, len(x)-1)))
因为一旦找到True
条件, any
停止搜索迭代。
你可以做:
reduce(and_, (x==yourList[0] for x in yourList), True)
python 让你导入像operator.and_
这样的运算符是相当烦人的。 从 python3 开始,您还需要导入functools.reduce
。
(您不应使用此方法,因为如果它发现不相等的值,它不会中断,但会继续检查整个列表。它只是包含在此处作为完整性的答案。)
如果您对更具可读性的东西感兴趣(但当然效率不高),您可以尝试:
def compare_lists(list1, list2):
if len(list1) != len(list2): # Weed out unequal length lists.
return False
for item in list1:
if item not in list2:
return False
return True
a_list_1 = ['apple', 'orange', 'grape', 'pear']
a_list_2 = ['pear', 'orange', 'grape', 'apple']
b_list_1 = ['apple', 'orange', 'grape', 'pear']
b_list_2 = ['apple', 'orange', 'banana', 'pear']
c_list_1 = ['apple', 'orange', 'grape']
c_list_2 = ['grape', 'orange']
print compare_lists(a_list_1, a_list_2) # Returns True
print compare_lists(b_list_1, b_list_2) # Returns False
print compare_lists(c_list_1, c_list_2) # Returns False
>>> a = [1, 2, 3, 4, 5, 6]
>>> z = [(a[x], a[x+1]) for x in range(0, len(a)-1)]
>>> z
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
# Replacing it with the test
>>> z = [(a[x] == a[x+1]) for x in range(0, len(a)-1)]
>>> z
[False, False, False, False, False]
>>> if False in z : Print "All elements are not equal"
或者使用 numpy 的diff
方法:
import numpy as np
def allthesame(l):
return np.all(np.diff(l)==0)
并致电:
print(allthesame([1,1,1]))
输出:
True
lambda lst: reduce(lambda a,b:(b,b==a[0] and a[1]), lst, (lst[0], True))[1]
下一个将短路:
all(itertools.imap(lambda i:yourlist[i]==yourlist[i+1], xrange(len(yourlist)-1)))
可以使用 map 和 lambda
lst = [1,1,1,1,1,1,1,1,1]
print all(map(lambda x: x == lst[0], lst[1:]))
将列表更改为一组。 然后,如果集合的大小仅为1,则它们必须相同。
if len(set(my_list)) == 1:
还有一个纯 Python 递归选项:
def checkEqual(lst):
if len(lst)==2 :
return lst[0]==lst[1]
else:
return lst[0]==lst[1] and checkEqual(lst[1:])
但是由于某种原因,在某些情况下它比其他选项慢两个数量级。 来自 C 语言的心态,我预计这会更快,但事实并非如此!
另一个缺点是 Python 中存在递归限制,在这种情况下需要调整。 例如使用这个.
或者使用 numpy 的 diff 方法:
import numpy as np
def allthesame(l):
return np.unique(l).shape[0]<=1
并致电:
print(allthesame([1,1,1]))
输出:
真的
lst = [1,1,1,1,1,1,1,1,1]
len_lst = len(list(set(lst)))
print(len_lst)
1
lst = [1,2,1,1,1,1,1,1,1]
len_lst = len(list(set(lst)))
print(len_lst)
2
关于实现 all_equal() 函数的各种方法,有一个不错的Twitter 线程。
给定一个列表输入,最好的提交是:
t.count(t[0]) == len(t)
这是线程的结果:
让 groupby() 比较相邻的条目。 这有一个不匹配的早期输出,不使用额外的内存,它以 C 速度运行。
g = itertools.groupby(s) next(g, True) and not next(g, False)
比较彼此偏移一个位置的两个切片。 这会使用额外的内存,但以 C 速度运行。
s[1:] == s[:-1]
切片比较的迭代器版本。 它以 C 速度运行,不使用额外的内存; 但是, eq调用很昂贵。
all(map(operator.eq, s, itertools.islice(s, 1, None)))
比较最低值和最高值。 这以 C 速度运行,不使用额外的内存,但每个数据需要进行两次不等式测试。
min(s) == max(s) # s must be non-empty
建立一套。 这以 C 速度运行并且使用很少的额外内存,但需要哈希性并且没有提前退出。
len(set(t))==1.
以巨大的代价,它处理了 NaN 和其他具有奇异相等关系的对象。
all(itertools.starmap(eq, itertools.product(s, repeat=2)))
拉出第一个元素并将所有其他元素与它进行比较,在第一个不匹配处停止。 唯一的缺点是它不能以 C 速度运行。
it = iter(s) a = next(it, None) return all(a == b for b in it)
只计算第一个元素。 这是快速、简单、优雅的。 它以 C 速度运行,不需要额外的内存,只使用相等测试,并且只对数据进行一次传递。
t.count(t[0]) == len(t)
您可以使用.nunique()
查找列表中唯一项目的数量。
def identical_elements(list):
series = pd.Series(list)
if series.nunique() == 1: identical = True
else: identical = False
return identical
identical_elements(['a', 'a'])
Out[427]: True
identical_elements(['a', 'b'])
Out[428]: False
您可以使用set
进行设置并删除重复的元素。 然后检查它有1个元素。
if set(your_list) == 1:
print('all ements are equal')
也许我低估了这个问题? 检查列表中唯一值的长度。
lzt = [1,1,1,1,1,2]
if (len(set(lzt)) > 1):
uniform = False
elif (len(set(lzt)) == 1):
uniform = True
elif (not lzt):
raise ValueError("List empty, get wrecked")
我最终得到了这个单线
from itertools import starmap, pairwise
all(starmap(eq, (pairwise(x)))
我发现使用itertools.groupby
的更多版本比原始版本更清晰(更多关于以下内容):
def all_equal(iterable)(iterable):
g = groupby(iterable)
return not any(g) or not any(g)
def all_equal(iterable):
g = groupby(iterable)
next(g, None)
return not next(g, False)
def all_equal(iterable)(iterable):
g = groupby(iterable)
return not next(g, False) or not next(g, False)
这是Itertools 食谱的原文:
def all_equal(iterable):
g = groupby(iterable)
return next(g, True) and not next(g, False)
请注意, next(g, True)
始终为真(它是非空tuple
或True
)。 这意味着它的价值无关紧要。 它纯粹是为了推进groupby
迭代器而执行的。 但是在return
表达式中包含它会导致读者认为它的值在那里被使用。 因为它没有,我发现这具有误导性和不必要的复杂性。 我上面的第二个版本将next(g, True)
视为它的实际用途,作为我们不关心其值的语句。
我的第三个版本采用了不同的方向,并且确实使用了第next(g, False)
的值。 如果根本没有第一组(即,如果给定的迭代是“空的”),那么该解决方案会立即返回结果并且甚至不检查是否存在第二组。
我的第一个解决方案与我的第三个解决方案基本相同,只是使用any
。 两种解决方案都读作“所有元素都相等,如果......没有第一组或没有第二组。”
基准测试结果(虽然速度真的不是我的重点,但清晰是,实际上如果有许多相等的值,大部分时间可能会花费在groupby
本身,从而减少这些差异的影响):
Python 3.10.4 on my Windows laptop:
iterable = ()
914 ns 914 ns 916 ns use_first_any
917 ns 925 ns 925 ns use_first_next
1074 ns 1075 ns 1075 ns next_as_statement
1081 ns 1083 ns 1084 ns original
iterable = (1,)
1290 ns 1290 ns 1291 ns next_as_statement
1303 ns 1307 ns 1307 ns use_first_next
1306 ns 1307 ns 1309 ns use_first_any
1318 ns 1319 ns 1320 ns original
iterable = (1, 2)
1463 ns 1464 ns 1467 ns use_first_any
1463 ns 1463 ns 1467 ns next_as_statement
1477 ns 1479 ns 1481 ns use_first_next
1487 ns 1489 ns 1492 ns original
Python 3.10.4 on a Debian Google Compute Engine instance:
iterable = ()
234 ns 234 ns 234 ns use_first_any
234 ns 235 ns 235 ns use_first_next
264 ns 264 ns 264 ns next_as_statement
265 ns 265 ns 265 ns original
iterable = (1,)
308 ns 308 ns 308 ns next_as_statement
315 ns 315 ns 315 ns original
316 ns 316 ns 317 ns use_first_any
317 ns 317 ns 317 ns use_first_next
iterable = (1, 2)
361 ns 361 ns 361 ns next_as_statement
367 ns 367 ns 367 ns original
384 ns 385 ns 385 ns use_first_next
386 ns 387 ns 387 ns use_first_any
基准代码:
from timeit import timeit
from random import shuffle
from bisect import insort
from itertools import groupby
def original(iterable):
g = groupby(iterable)
return next(g, True) and not next(g, False)
def use_first_any(iterable):
g = groupby(iterable)
return not any(g) or not any(g)
def next_as_statement(iterable):
g = groupby(iterable)
next(g, None)
return not next(g, False)
def use_first_next(iterable):
g = groupby(iterable)
return not next(g, False) or not next(g, False)
funcs = [original, use_first_any, next_as_statement, use_first_next]
for iterable in (), (1,), (1, 2):
print(f'{iterable = }')
times = {func: [] for func in funcs}
for _ in range(1000):
shuffle(funcs)
for func in funcs:
number = 1000
t = timeit(lambda: func(iterable), number=number) / number
insort(times[func], t)
for func in sorted(funcs, key=times.get):
print(*('%4d ns ' % round(t * 1e9) for t in times[func][:3]), func.__name__)
print()
这是一个有趣的阅读和思考。 感谢大家! 我不认为任何依赖纯计数的东西在所有情况下都是可靠的。 sum 也可以工作,但仅适用于数字或长度(再次导致计数场景)。
但我确实喜欢简单,所以这就是我想出的:
all(i==lst[c-1] for c, i in enumerate(lst))
或者,我确实认为@kennytm的这个聪明的方法也适用于所有情况(并且可能是最快的,有趣的是)。 所以我承认它可能比我的更好:
[lst[0]]*len(lst) == lst
我认为一个小小的奖励聪明也可以工作,因为 set 摆脱了重复(聪明很有趣,但通常不是维护代码的最佳实践)。 而且我认为@kennytm的那个仍然会更快,但实际上只与大型列表相关:
len(set(lst)) == 1
但是 Python 的简单和聪明是我最喜欢的语言之一。 再想一想,如果您无论如何都必须修改列表,就像我实际上所做的那样,因为我正在比较地址(并且将删除前导/尾随空格并转换为小写以消除可能的不一致,我的会更多适合这份工作)。 所以“更好”是主观的,因为我在使用这个词时使用引号来逃避! 但是您也可以事先清理列表。
祝你好运!
我认为,这是一个具有大量 Pythonicity 以及简单性和明显性平衡的代码,它应该也适用于相当旧的 Python 版本。
def all_eq(lst):
for idx, itm in enumerate(lst):
if not idx: # == 0
prev = itm
if itm != prev:
return False
prev = itm
return True
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.