【问题标题】:Check if all elements in a list are identical检查列表中的所有元素是否相同
【发布时间】:2011-04-20 04:14:53
【问题描述】:

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

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

【问题讨论】:

  • a == b 相同或与a is b 相同?
  • 解决方案是否应该处理空列表?如果是,应该返回什么?
  • 等于 a == b。应该处理空列表,并返回 True。
  • 虽然我知道它比其他一些建议要慢,但我很惊讶functools.reduce(operator.eq, a) 没有被建议。
  • @user2846495 functools.reduce(operator.eq, a) 不起作用。例如列表[True, False, False],它将返回((True == False) == False),即True。而函数应该返回 False (因为元素不都是相等的)

标签: python algorithm comparison


【解决方案1】:

使用itertools.groupby(见the 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. Counting how many times the first item appears in the list

    def all_equal_ivo(lst):
        return not lst or lst.count(lst[0]) == len(lst)
    
  4. Comparing against a list of the first element repeated

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

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

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

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

【讨论】:

  • 不要忘记对非常大的数组进行内存使用分析,这是一种本机解决方案,可优化在 lhs is rhs 时对 obj.__eq__ 的调用,以及乱序优化以允许短路排序列表更多很快。
  • 如果 checkEqual3 的直觉对其他人来说不是很明显:如果first == second and second == third and ...,所有项目都相同
  • 这非常快。方法也很巧妙。对于那些使用pytest 实现的有用建议,您应该在断言 True/False 之前返回结果。 (将此函数包装在另一个函数中。)
  • @Boris:这些图表的代码是什么?
  • @ChaimG 如果您点击“编辑”,代码将隐藏在答案文本的注释中。
【解决方案2】:

比使用 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

【讨论】:

  • 天啊,这比设置的解决方案快 6 倍! (2.8 亿元素/秒与我笔记本电脑上的 4500 万元素/秒)。为什么???有什么办法可以修改它使其短路(我猜不是......)
  • 我猜 list.count 有一个高度优化的 C 实现,并且列表的长度是在内部存储的,所以 len() 也很便宜。没有办法使 count() 短路,因为您需要真正检查所有元素以获得正确的计数。
  • 我可以将其更改为:x.count(next(x)) == len(x) 以便它适用于任何容器 x 吗?啊.. nm,刚刚看到 .count 仅适用于序列.. 为什么其他内置容器没有实现它?在字典中计数本身就没有列表中的意义吗?
  • 迭代器可能没有长度。例如。它可以是无限的或只是动态生成的。你只能通过将它转换为一个列表来找到它的长度,这会带走大部分迭代器的优势
  • 对不起,我的意思是为什么 count 没有为可迭代对象实现,而不是为什么 len 不能用于迭代器。答案可能是这只是一个疏忽。但这对我们来说无关紧要,因为序列的默认.count() 非常慢(纯python)。您的解决方案如此之快的原因是它依赖于 list 提供的 C 实现的 count。因此,我认为在 C 中实现 count 方法的任何可迭代对象都将从您的方法中受益。
【解决方案3】:

[编辑:此答案稍后解决当前投票最多的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 代码。 (根据文档,它应该(类似于这个答案)没有任何内存开销,因为惰性生成器永远不会被评估为一个列表......这可能会让人担心,但伪代码显示分组的“列表”是实际上是惰性生成器。)

如果您更关心性能,请继续阅读...


关于性能的旁注,因为其他答案出于某种未知原因正在谈论它:

...如果您以前看过数据并且可能使用某种集合数据结构,并且您真的很关心性能,您可以通过增加您的 O(1) 免费获得 .isAllEqual()带有Counter 的结构,每次插入/删除/等都会更新。操作并检查它是否为{something:someCount}len(counter.keys())==1 的形式;或者,您可以将 Counter 放在单独的变量中。可以证明,这比其他任何东西都要好。也许你也可以使用 python 的 FFI 和 ctypes 和你选择的方法,也许还有启发式(比如它是一个带有 getitem 的序列strong>,然后依次检查第一个元素、最后一个元素,然后是元素)。

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

【讨论】:

  • 这可行,但它比@KennyTM checkEqual1 慢一点(1.5 倍)。我不知道为什么。
  • max:可能是因为我懒得执行优化first=myList[0]all(x==first for x in myList),也许
  • 我认为每次迭代都会评估 myList[0]。 >>> timeit.timeit('all([y == x[0] for y in x])', 'x=[1] * 4000', number=10000) 2.707076672740641 >>> timeit.timeit('x0 = x[0]; all([y == x0 for y in x])', 'x=[1] * 4000', number=10000) 2.0908854261426484
  • 我当然应该澄清优化first=myList[0] 将在一个空列表中抛出一个IndexError,因此讨论我提到的优化的评论者将不得不处理边缘情况一个空列表。但是原始版本很好(x==myList[0]all 中很好,因为如果列表为空,则永远不会对其进行评估)。
  • 这显然是正确的方法。如果您在每种情况下都想要速度,请使用 numpy 之类的东西。
【解决方案4】:

将您的输入转换为set

len(set(the_list)) <= 1

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

这要求输入中的所有元素都是hashable。例如,如果您传入列表列表,您将获得 TypeError

【讨论】:

    【解决方案5】:

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

    if len(set(input_list)) == 1:
        # input_list has all identical elements.
    

    【讨论】:

    • 这很好,但它不会短路,您必须计算结果列表的长度。
    • @AaronMcSmooth:仍然是 py 中的菜鸟。甚至不知道 py 中的短路是什么意思:)
    • @codaddict。这意味着即使前两个元素不同,它仍然会完成整个搜索。它还使用 O(k) 额外空间,其中 k 是列表中不同元素的数量。
    • @max.因为构建集合发生在 C 中,并且您的实现很糟糕。您至少应该在生成器表达式中执行此操作。请参阅 KennyTM 的答案,了解如何在不使用集合的情况下正确完成。
    • 如果您的列表为空,则返回 False,何时返回 True。它还要求您的所有元素都是可散列的。
    【解决方案6】:

    对于它的价值,这是最近出现在python-ideas mailing list 上的。事实证明,已经有一个 itertools recipe 可以这样做: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换句话说,我不能把提出解决方案的功劳归功于我——我也不能把自己的功劳归于找到它。

    【讨论】:

    • return next(g, f := next(g, g)) == f(当然来自 py3.8)
    【解决方案7】:

    这是一个简单的方法:

    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)
    

    【讨论】:

    • 您可以使用for elem in mylist[1:] 避免此处的冗余比较。怀疑它会大大提高速度,因为我猜elem[0] is elem[0] 所以解释器可能可以很快地进行比较。
    【解决方案8】:

    这是另一种选择,比len(set(x))==1 长列表更快(使用短路)

    def constantList(x):
        return x and [x[0]]*len(x) == x
    

    【讨论】:

    • 它比我电脑上设置的解决方案慢3倍,忽略短路。因此,如果在列表的前三分之一中平均找到不相等的元素,则平均速度会更快。
    【解决方案9】:

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

    np.allclose(array, array[0])

    【讨论】:

    • 需要第三方模块。
    【解决方案10】:

    怀疑这是“最 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
    

    会成功的。

    【讨论】:

    • 您的for 循环可以更Pythonic 到if any(item != list[0] for item in list[1:]): return False,语义完全相同。
    【解决方案11】:
    def allTheSame(i):
        j = itertools.groupby(i)
        for k in j: break
        for k in j: return False
        return True
    

    在没有“全部”的 Python 2.4 中工作。

    【讨论】:

    • for k in j: break 等价于next(j)。如果您不关心效率,您也可以使用def allTheSame(x): return len(list(itertools.groupby(x))&lt;2)
    【解决方案12】:

    我愿意:

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

    因为any 在找到True 条件后立即停止搜索迭代。

    【讨论】:

    • 如果生成器表达式是唯一的参数,则不需要额外的括号。
    • all() 也是如此,为什么不使用 all(x == seq[0] for x in seq) 呢?看起来更像 Python 并且应该执行相同的操作
    【解决方案13】:

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

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

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

    【讨论】:

    • 编写的代码中有一个小错误(尝试[1, 2, 2]):它没有考虑以前的布尔值。这可以通过将x[1] == y 替换为x[0] and x[1] == y 来解决。
    【解决方案14】:
    >>> 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"
    

    【讨论】:

      【解决方案15】:

      如果您对更具可读性的东西感兴趣(但当然效率不高),您可以尝试:

      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
      

      【讨论】:

      • 我实际上是在尝试查看一个列表中的所有元素是否相同;如果两个单独的列表相同,则不是。
      • 这也是非常低效的;对于长度为 N 的输入,它需要 N^2 步。**至少**,如果值是可散列的,则使用一个集合进行包含测试。
      【解决方案16】:

      或者使用numpy的diff方法:

      import numpy as np
      def allthesame(l):
          return np.all(np.diff(l)==0)
      

      然后调用:

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

      输出:

      True
      

      【讨论】:

      • 我认为not np.any(np.diff(l)) 可能会快一点。
      【解决方案17】:

      你可以这样做:

      reduce(and_, (x==yourList[0] for x in yourList), True)
      

      python 让你导入像operator.and_ 这样的运算符是相当烦人的。从 python3 开始,您还需要导入 functools.reduce

      (您不应该使用此方法,因为如果它找到不相等的值,它不会中断,但会继续检查整个列表。它只是包含在此处作为完整性的答案。)

      【讨论】:

        【解决方案18】:
        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)))
        

        【讨论】:

        • 你的第一个代码显然是错误的:reduce(lambda a,b:a==b, [2,2,2]) 产生False...我编辑了它,但这样它不再漂亮了
        【解决方案19】:

        可以使用map和lambda

        lst = [1,1,1,1,1,1,1,1,1]
        
        print all(map(lambda x: x == lst[0], lst[1:]))
        

        【讨论】:

          【解决方案20】:

          或者使用numpy的diff方法:

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

          然后调用:

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

          输出:

          是的

          【讨论】:

          • 这个答案与去年 U9-Forward 的答案相同。
          • 好眼光!我使用相同的结构/API,但我的方法使用 np.unique 和 shape。 U9 的函数使用 np.all() 和 np.diff() -- 我不使用这两个函数。
          【解决方案21】:

          还有一个纯 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 中存在递归限制,在这种情况下需要调整。例如使用this

          【讨论】:

            【解决方案22】:

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

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

            【讨论】:

            【解决方案23】:

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

            【讨论】:

              【解决方案24】:

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

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

              【讨论】:

              • 我不会害怕空列表......你能说没有(零计数)(非)值彼此不同吗?
              • 你有例子吗?
              【解决方案25】:

              我认为,这里的代码具有大量的 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
              

              【讨论】:

              • (几乎有用,但缺少文档字符串:虽然名称是助记符,但我喜欢在我的 IDE 中检查悬停,例如:all_eq([])?)
              • @greybeard 抱歉,这不是官方包
              • (你写了“未记录”的代码?对我没用。)
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-09-01
              • 1970-01-01
              • 1970-01-01
              • 2013-09-19
              • 1970-01-01
              • 2012-08-22
              • 2019-12-22
              相关资源
              最近更新 更多