繁体   English   中英

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

[英]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)

您可能会考虑使用许多替代的单线:

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

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

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

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

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

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

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

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

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

比使用 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的序列,然后检查第一个元素,最后一个元素,然后按顺序检查元素)。

当然,为了可读性,有话要说。

将您的输入转换为一set

len(set(the_list)) <= 1

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

这要求输入中的所有元素都是可散列的。 例如,如果您传入列表列表,您将收到TypeError

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

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. 短路:一旦找到第一个不相等的项目,它将停止消耗可迭代的项目。
  2. 不需要项目是可散列的。
  3. 它是惰性的,只需要 O(1) 额外的内存来进行检查。

1换句话说,我不能把提出解决方案的功劳归功于我——我什至不能把找到它的功劳归功于我。

这是两种简单的方法

使用set()

将列表转换为集合时,将删除重复的元素。 因此,如果转换后的集合的长度为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

使用all()

这会将输入列表的第一个元素与列表中的所有其他元素进行比较(等效)。 如果相等,则返回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]))

输出:

真的

简单的解决方案是在列表上应用集合

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

最佳答案

关于实现 all_equal() 函数的各种方法,有一个不错的Twitter 线程

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

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

其他方法

这是线程的结果:

  1. 让 groupby() 比较相邻的条目。 这有一个不匹配的早期输出,不使用额外的内存,它以 C 速度运行。

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

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

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

     min(s) == max(s) # s must be non-empty
  5. 建立一套。 这以 C 速度运行并且使用很少的额外内存,但需要哈希性并且没有提前退出。

     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)

您可以使用.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)始终(它是非空tupleTrue )。 这意味着它的价值无关紧要。 纯粹是为了推进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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM