检查列表中的所有元素是否相同

我需要一个函数,它接受list并输出True,如果输入列表中的所有元素使用标准相等操作符计算彼此相等,否则输出False

我觉得最好是遍历列表,比较相邻的元素,然后AND所有结果布尔值。但我不知道最python的方法是什么。

542324 次浏览

我怀疑这是“最python化的”,但类似于:

>>> 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

会成功的。

使用itertools.groupby(参见itertools食谱):

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)

您可以考虑使用许多其他的一行程序:

  1. 将输入转换为一个集合,并检查它只有一个或零(如果输入是空的)项

    def all_equal2(iterator):
    return len(set(iterator)) <= 1
    
  2. 与没有第一项的输入列表进行比较

    def all_equal3(lst):
    return lst[:-1] == lst[1:]
    
  3. < p > 计算第一项在列表中出现的次数

    def all_equal_ivo(lst):
    return not lst or lst.count(lst[0]) == len(lst)
    
  4. < p > 与重复的第一个元素的列表进行比较

    def all_equal_6502(lst):
    return not lst or [lst[0]]*len(lst) == lst
    

但它们也有一些缺点,即:

  1. all_equalall_equal2可以使用任何迭代器,但其他的必须接受序列输入,通常是具体的容器,如列表或元组。
  2. all_equalall_equal3一旦发现差异就会停止(所谓的“__abc3”),而所有的替代方法都需要遍历整个列表,即使你只看前两个元素就能知道答案是False
  3. all_equal2中,内容必须是hashable。例如,列表的列表将引发TypeError
  4. all_equal2(在最坏的情况下)和all_equal_6502创建一个列表的副本,这意味着你需要使用两倍的内存。

在Python 3.9中,使用perfplot,我们得到这些计时(较低的Runtime [s]更好):

对于前两个元素不同的列表,groupby是最快的对于一个没有差异的列表,count(l[0])是最快的

您可以将列表转换为集合。集合不能有重复项。所以如果原始列表中的所有元素都是相同的,那么集合就只有一个元素。

if len(set(input_list)) == 1:
# input_list has all identical elements.
>>> 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"

这是一种简单的方法:

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

比使用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
def allTheSame(i):
j = itertools.groupby(i)
for k in j: break
for k in j: return False
return True

适用于Python 2.4,它没有“all”。

我想做的事:

not any((x[i] != x[i+1] for i in range(0, len(x)-1)))

as any一旦发现True条件,就会停止搜索可迭代对象。

[编辑:这个答案解决了目前投票最多的itertools.groupby(这是一个很好的答案)答案。]

在不重写程序的情况下,最渐进性能最好的<强>和< / >强最可读的方法如下:

all(x==myList[0] for x in myList)

(是的,这甚至适用于空列表!这是因为这是python具有惰性语义的少数情况之一。)

这将在尽可能早的时间失败,因此它是渐近最优的(期望时间大约是O(#惟一)而不是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代码。(根据文档,它应该(类似于这个答案)没有任何内存开销,因为惰性生成器永远不会被计算到列表中…这可能会让人担心,但伪代码表明,分组的“列表”实际上是惰性生成器。)

如果你更关心性能,请继续阅读…


关于性能的旁注,因为其他答案都在谈论它,因为一些未知的原因:

... 如果你以前看过数据,并且可能使用某种类型的集合数据结构,并且你真的很关心性能,也许你也可以用python的FFI和你选择的方法一起使用ctypes,也许还有启发式(比如如果它是一个带有getitem的序列,那么按顺序检查第一个元素,最后一个元素,然后是元素)。

当然,可读性也有好处。

你可以:

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
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)))

将输入转换为set:

len(set(the_list)) <= 1

使用set删除所有重复的元素。<= 1是为了在输入为空时正确返回True

这要求输入中的所有元素都是hashable。例如,如果传入一个列表的列表,则会得到TypeError

关于使用reduce()lambda。这里有一个工作代码,我个人认为比其他一些答案更好。

reduce(lambda x, y: (x[1]==y, y), [2, 2, 2], (True, 2))

返回一个元组,其中第一个值是布尔值,如果所有项都相同或不相同。

不管怎样,这个最近出现在python想法邮件列表上。事实证明,已经有一个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. 短路:一旦发现第一个不相等的项,它就会停止从可迭代对象中消费项目。
  2. 不要求项是可哈希的。
  3. 它是懒惰的,只需要O(1)个额外的内存来进行检查。

换句话说,我不能把提出解决方案的功劳归到自己身上——甚至发现也不能归到自己身上

检查是否所有元素都等于第一个。

np.allclose(array, array[0])

可以使用map和lambda吗

lst = [1,1,1,1,1,1,1,1,1]


print all(map(lambda x: x == lst[0], lst[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.all(np.diff(l)==0)

并呼吁:

print(allthesame([1,1,1]))

输出:

True

或者使用numpy的diff方法:

import numpy as np
def allthesame(l):
return np.unique(l).shape[0]<=1

并呼吁:

print(allthesame([1,1,1]))

输出:

真正的

您可以使用.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 on list

如果所有元素都相同,len将为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

也许我低估了问题的严重性?检查列表中唯一值的长度。

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")

这是一段具有良好的Python性的代码,并且平衡了简单性和明显性,我认为,这应该也适用于相当老的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
这是一个有趣的阅读和思考。谢谢大家! 我不认为任何依赖于纯计数的方法对所有情况都是可靠的。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的简单和聪明是我最喜欢的语言之一。再想一下,如果你必须修改列表,就像我实际上做的那样,因为我正在比较地址(并将删除开头/结尾空格并转换为小写以消除可能的不一致,我的将更适合这项工作)。所以“better"是主观的,因为我在使用这个词时使用了引号!但是你也可以事先清理列表。

祝你好运!

最佳答案

在实现all_equal()函数的各种方法中,有一个很好的Twitter的线程

给定一个列表输入,最好的提交是:

 t.count(t[0]) == len(t)

其他方法

下面是线程的结果:

  1. 使用groupby()比较相邻条目。它有一个不匹配的early-out,不使用额外的内存,并且以C速度运行。

    g = itertools.groupby(s)
    next(g, True) and not next(g, False)
    
  2. 比较两个相互偏移一个位置的切片。这将使用额外的内存,但以C速度运行。

    s[1:] == s[:-1]
    
  3. 切片比较的迭代器版本。它以C速度运行,不使用额外的内存;然而,情商调用是昂贵的。

    all(map(operator.eq, s, itertools.islice(s, 1, None)))
    
  4. 比较最低值和最高值。它以C速度运行,不使用额外的内存,但每个数据需要进行两次不相等测试。

    min(s) == max(s)  # s must be non-empty
    
  5. 建立一个集合。它以C速度运行,使用很少的额外内存,但需要哈希性,并且没有early-out。

    len(set(t))==1.
    
  6. 这需要花费很大的代价来处理nan和其他具有奇异相等关系的对象。

    all(itertools.starmap(eq, itertools.product(s, repeat=2)))
    
  7. 取出第一个元素,并将所有其他元素与之比较,在第一个不匹配处停止。唯一的缺点是它不能以C速度运行。

     it = iter(s)
    a = next(it, None)
    return all(a == b for b in it)
    
  8. 只计算第一个元素。这是快速、简单、优雅的。它以C速度运行,不需要额外的内存,只使用相等性测试,并且只对数据进行一次传递。

      t.count(t[0]) == len(t)
    

我最后写了这样一句话

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)总是 真正的(它不是一个非空的tupleTrue)。这意味着它的值不重要。它执行纯粹的来推进groupby迭代器。但是在return表达式中包含它会导致读者认为它的值在那里被使用。因为它没有,我发现这是误导和不必要的复杂。我上面的第二个版本将next(g, True)视为它的实际用途,作为一个我们不关心其值的语句。

我的第三个版本走了一个不同的方向,使用第一个next(g, False)的值。如果根本没有第一个组(即,如果给定的可迭代对象是"empty"),那么该解决方案立即返回结果,甚至不检查是否有第二个组。

我的第一个解决方案基本上与我的第三个相同,只是使用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()