如何在Python中有效比较两个无序列表(不是集合)?


141
a = [1, 2, 3, 1, 2, 3]
b = [3, 2, 1, 3, 2, 1]

a和b应该视为相等,因为它们具有完全相同的元素,只是顺序不同。

问题是,我的实际列表将由对象(我的类实例)组成,而不是整数。


7
如何比较对象?
马塞洛·坎托斯

2
实际列表的预期大小是多少?所比较的列表大小是否可比或相差很大?您是否期望大多数列表匹配?
德米特里(Dmitry B)

可能会先检查len()
灰胡子

Answers:


245

O(n)Counter()方法最好(如果您的对象是可哈希的):

def compare(s, t):
    return Counter(s) == Counter(t)

O(n log n)sorted()方法是次佳的(如果对象是可排序的):

def compare(s, t):
    return sorted(s) == sorted(t)

O(n * n):如果对象既不可散列也不可排序,则可以使用相等性:

def compare(s, t):
    t = list(t)   # make a mutable copy
    try:
        for elem in s:
            t.remove(elem)
    except ValueError:
        return False
    return not t

1
谢谢。我将每个对象转换为字符串,然后使用Counter()方法。
johndir

嘿,@ Raymond,我最近在一次采访中遇到了这个问题,我习惯了sorted(),但我并不知道Counter。面试官坚持说有一种更有效的方法,显然我划了一个空白。在python 3中对该timeit模块进行了广泛的测试后,在整数列表上,一致排序的速度更快。在1000个项目的清单上,速度降低约1.5%,在短清单上,则减少10个项目,速度降低7.5%。有什么想法吗?
arctelix

4
对于短名单,大O分析通常是不相关的,因为时间是由恒定因素决定的。对于较长的列表,我怀疑您的基准测试有问题。对于100个整数(每个整数有5个重复),我得到:127个usec用于排序,42个用于Counter(大约快3倍)。重复5次,每次1,000个整数,Counter快4倍。 python3.6 -m timeit -s 'from collections import Counter' -s 'from random import shuffle' -s 't=list(range(100)) * 5' -s 'shuffle(t)' -s 'u=t[:]' -s 'shuffle(u)' 'Counter(t)==Counter(u)'
Raymond Hettinger

@Raymond确实,我们得到了不同的结果。我将设置发布到聊天室sorted vs counter..我对这里发生的事情很好奇。
arctelix '16

4
不用了,谢谢。我对调试虚假时序脚本没有太大兴趣。这里有很多事情(纯python vs C代码,timsort应用于随机数据vs半排序数据,不同版本的实现细节不同,数据中有多少重复等等)
Raymond Hettinger

16

您可以对两者进行排序:

sorted(a) == sorted(b)

一个计数排序也可能是更有效(但它需要的对象是哈希的)。

>>> from collections import Counter
>>> a = [1, 2, 3, 1, 2, 3]
>>> b = [3, 2, 1, 3, 2, 1]
>>> print (Counter(a) == Counter(b))
True

计数器确实使用哈希,但是对象本身并不是不可哈希的。您只需要实现一个明智的方法__hash__,但是对于集合来说可能是不可能的。
2011年

2
排序也不适合所有内容,例如复数sorted([0, 1j])
John La Rooy

1
sorted()也不适用于比较运算符已被子集/超集测试覆盖的集合。
Raymond Hettinger,

12

如果知道项目总是可哈希的,则可以使用Counter()O(n),
如果知道项目总是可排序的,则可以使用sorted()O(n log n)。

在一般情况下,您不能依靠能够排序或拥有元素,因此您需要像这样的后备,不幸的是,O(n ^ 2)

len(a)==len(b) and all(a.count(i)==b.count(i) for i in a)

5

最好的方法是对列表进行排序并进行比较。(Counter不能用于不可哈希的对象。)对于整数,这很简单:

sorted(a) == sorted(b)

使用任意对象会变得有些棘手。如果您关心对象的身份,即两个列表中是否存在相同的对象,则可以将该id()函数用作排序键。

sorted(a, key=id) == sorted(b, key==id)

(在Python 2.x中,您实际上不需要 key=参数,因为您可以将任何对象与任何对象进行比较。顺序是任意的,但很稳定,因此可以很好地实现此目的;对象的顺序无关紧要但是,在Python 3中,在很多情况下都不允许比较不同类型的对象-例如,您不能将字符串与整数进行比较-因此,如果有对象最好使用显式使用对象的ID。)

另一方面,如果要按比较列表中的对象,则首先需要定义“值”对对象的含义。然后,您将需要某种方式将其提供为键(对于Python 3,则为一致类型)。一种适用于许多任意对象的潜在方式是按其排序repr()。当然,这可能会浪费大量额外时间和内存来构建repr()大型列表等字符串。

sorted(a, key=repr) == sorted(b, key==repr)

如果对象都是您自己的类型,则可以__lt__()在它们上进行定义,以使对象知道如何将自身与其他对象进行比较。然后,您可以对它们进行排序,而不必担心key=参数。当然,您也可以定义__hash__()和使用Counter,这样会更快。


4

https://docs.python.org/3.5/library/unittest.html#unittest.TestCase.assertCountEqual

assertCountEqual(first,second,msg = None)

测试序列首先包含与第二序列相同的元素,而不管其顺序如何。否则,将生成一条错误消息,列出序列之间的差异。

比较第一个和第二个元素时,不会忽略重复的元素。它验证两个序列中每个元素的计数是否相同。等效于:assertEqual(Counter(list(first()),Counter(list(second)))),但也适用于不可哈希对象的序列。

3.2版中的新功能。

或2.7:https//docs.python.org/2.7/library/unittest.html#unittest.TestCase.assertItemsEqual


2
(这是什么的添加到jarekwg的答案?)

3

如果列表包含不可散列的项(例如对象列表),则可以使用Counter Class和id()函数,例如:

from collections import Counter
...
if Counter(map(id,a)) == Counter(map(id,b)):
    print("Lists a and b contain the same objects")

2

我希望以下代码可以在您的情况下工作:-

if ((len(a) == len(b)) and
   (all(i in a for i in b))):
    print 'True'
else:
    print 'False'

这将确保两个列表ab中的所有元素都是相同的,而不管它们的顺序是否相同。

为了更好地理解,请参考我在这个问题上的回答



1

让a,b列出

def ass_equal(a,b):
try:
    map(lambda x: a.pop(a.index(x)), b) # try to remove all the elements of b from a, on fail, throw exception
    if len(a) == 0: # if a is empty, means that b has removed them all
        return True 
except:
    return False # b failed to remove some items from a

无需将它们设为可散列或排序。


1
是的,但是正如其他几个发帖人所述,这是O(n ** 2),因此仅在其他方法不起作用时才应使用。它还假定a支持pop(可变)和index(是序列)。雷蒙德(Raymond)的假设都不是,而小狼人(Gnibbler)的假设只是一个序列。
2011年

0

使用该unittest模块可为您提供一种干净而标准的方法。

import unittest

test_object = unittest.TestCase()
test_object.assertCountEqual(a, b)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.