파이썬에서 정렬되지 않은 두 개의 목록 (비 세트)을 효율적으로 비교하는 방법은 무엇입니까?


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

a와 b는 정확히 같은 요소를 가지기 때문에 다른 순서로만 고려되어야합니다.

내 실제 목록은 정수가 아닌 객체 (내 클래스 인스턴스)로 구성됩니다.


7
객체는 어떻게 비교됩니까?
Marcelo Cantos

2
실제 목록의 예상 크기는 얼마입니까? 비교되는 목록이 비슷한 크기이거나 매우 다른가요? 대부분의 목록이 일치하겠습니까?
Dmitry B.

len()먼저 s를 확인하십시오 .
greybeard

답변:


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. 면접관은 더 효율적인 방법이 있다고 주장했으며 분명히 공란을 그렸습니다. timeit모듈을 사용하여 Python 3에서 광범위한 테스트를 한 후에 는 정수 목록에서 일관되게 정렬됩니다. 1k 항목 목록에서 약 1.5 % 느리고 짧은 목록에서 10 항목, 7.5 % 느립니다. 생각?
arctelix

4
짧은 목록의 경우, 타이밍이 일정한 요인에 의해 좌우되므로 big-O 분석은 일반적으로 관련이 없습니다. 더 긴 목록의 경우 벤치마킹에 문제가있는 것 같습니다. 각각 5 회 반복되는 100 개의 정수의 경우 : 127 usec, 카운터는 42 (약 3 배 빠름). 반복 횟수가 5 번인 1,000 정수에서 카운터는 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

4
고맙지 만 사양 할게. 가짜 타이밍 스크립트를 디버깅하는 데 관심이 없습니다. 여기에는 많은 일이 있습니다 (순수한 파이썬 대 C 코드, 무작위 데이터 대 반 순서 데이터에 적용되는 팀 소트, 버전마다 다른 구현 세부 사항, 데이터에 중복 횟수 등)
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__하지만 컬렉션에는 불가능할 수 있습니다.
Jochen Ritzel

2
정렬은 복잡한 숫자와 같은 모든 것에서도 작동하지 않습니다.sorted([0, 1j])
John La Rooy

1
sorted ()는 하위 집합 / 슈퍼 셋 테스트를 위해 비교 연산자가 재정의 된 집합에서는 작동하지 않습니다.
Raymond Hettinger

12

항목이 항상 해시 가능하다는 것을 알고 있다면 A Counter()(O)를
사용할 수 있습니다. 항목이 항상 정렬 가능하다는 것을 알고 있다면 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= 객체와 객체를 비교할 수 있기 때문에 매개 변수 . 순서는 임의이지만 안정적이므로이 목적에 잘 작동합니다. 객체의 순서는 중요하지 않습니다. 파이썬 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 (첫 번째, 두 번째, msg = 없음)

순서에 관계없이 시퀀스에 두 번째와 동일한 요소가 포함되어 있는지 먼저 테스트하십시오. 그렇지 않은 경우 시퀀스 간의 차이를 나열하는 오류 메시지가 생성됩니다.

첫 번째와 두 번째를 비교할 때 중복 요소는 무시되지 않습니다. 두 시퀀스에서 각 요소의 개수가 같은지 확인합니다. 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의 답변에 추가하는 것은 무엇입니까 ?)
greybeard

3

목록에 해시 불가능한 항목 (예 : 객체 목록)이 포함 된 경우 카운터 클래스 와 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'

이 목록 모두의 모든 요소 보장됩니다 a및이 b에 상관없이 동일한 순서인지 아닌지의 동일합니다.

더 나은 이해를 위해이 질문에 대한 내 대답을 참조하십시오



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 's는 gnibbler가 시퀀스 만 가정하지 않는 것으로 가정합니다.
agf

0

은 Using unittest모듈 당신에게 깨끗하고 표준 접근 방식을 제공합니다.

import unittest

test_object = unittest.TestCase()
test_object.assertCountEqual(a, b)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.