HOWTO · Python
Python에서 리스트를 비교하는 방법
순서와 중복 개수의 의미에 따라 `==`, `Counter`, 집합 연산 또는 순서를 유지하는 리스트 컴프리헨션으로 Python 리스트를 비교합니다.
이 페이지의 내용
두 Python 리스트를 비교할 때는 먼저 순서와 중복 횟수가 중요한지 정해야 합니다. 순서까지 같은 정확한 비교에는 ==, 순서는 무시하되 중복 횟수는 유지하려면 Counter, 고유 값의 교집합이나 차집합에는 set(), 결과가 입력 리스트의 순서를 따라야 하면 리스트 컴프리헨션을 사용합니다.
아래 예제는 모두 Python 표준 라이브러리만 사용하며 Python 3.14.7에서 검증했습니다. 프로젝트에서 다른 호환성 요구 사항을 정하지 않았다면 지원되는 이전 Python 3 버전에서도 같은 방법을 사용할 수 있습니다.
| 목적 | 권장 방법 | 순서가 중요한가 | 중복 횟수가 중요한가 | 요소 조건 |
|---|---|---|---|---|
| 정확히 같은 리스트인지 확인 | left == right |
예 | 예 | 요소가 동등성 비교를 지원해야 함 |
| 순서를 무시한 동등성 확인 | Counter(left) == Counter(right) |
아니요 | 예 | 요소가 해시 가능해야 함 |
| 고유 값 비교 | set() 연산 |
아니요 | 아니요 | 요소가 해시 가능해야 함 |
| 리스트 순서로 공통 항목 유지 | 멤버십 집합과 리스트 컴프리헨션 | 출력 순서만 | 순회하는 리스트의 반복은 남음 | 멤버십 값이 해시 가능해야 함 |
| 교집합이나 차집합에서 개수 유지 | Counter 연산 |
아니요 | 예 | 요소가 해시 가능해야 함 |
이 선택지는 하나의 보편적으로 가장 빠른 방법을 겨루는 것이 아니라 서로 다른 질문에 답합니다. 동등성 비교는 하나의 불리언을 반환하고, 집합 및 Counter 연산은 컬렉션 형태의 결과를 만들며, 컴프리헨션은 새 리스트의 모양과 순서를 제어합니다. 성능을 고려하기 전에 필요한 의미를 정하세요. 비교를 빠르게 하려고 표현을 바꾸면 답도 달라질 수 있습니다.
==로 정확히 정렬된 리스트 비교하기
Python 리스트 동등성은 대응하는 요소를 왼쪽에서 오른쪽으로 비교합니다. 길이가 같고 모든 위치의 값이 같아서 순서도 같을 때만 두 리스트는 같습니다. 처리 단계, 순위 결과, 순서가 있는 이벤트처럼 리스트가 시퀀스를 나타낼 때 가장 명확한 선택입니다.
"""Verify that list equality considers both values and order."""
first = [1, 2, 3]
same = [1, 2, 3]
reordered = [3, 2, 1]
print(first == same)
print(first == reordered)
True
False
두 리스트에 같은 고유 값이 들어 있어도 두 번째 비교 결과는 False입니다. 직접 동등성을 비교할 때는 해시 테이블을 만들지 않으므로 중첩 리스트처럼 해시할 수 없는 요소에도 사용할 수 있습니다.
요소 비교는 각 값의 동등성 규칙을 따릅니다. 예를 들어 사용자 정의 객체의 리스트를 비교하면 해당 객체의 __eq__ 구현이 호출될 수 있습니다. 이 방법은 중첩 구조에서 어디가 다른지 재귀적으로 알려 주지 않고 최종 불리언만 반환합니다.
Counter로 중복을 유지하며 순서 없는 리스트 비교하기
순서는 무시해도 각 값의 빈도가 중요하다면 collections.Counter를 사용합니다. Counter는 해시 가능한 항목마다 개수를 연결하므로 두 카운터는 두 리스트가 같은 값을 같은 중복 횟수로 포함할 때만 같습니다.
"""Compare unordered lists with and without duplicate multiplicity."""
from collections import Counter
left = [1, 2, 2, 3]
reordered = [3, 2, 1, 2]
fewer_duplicates = [3, 2, 1]
print(Counter(left) == Counter(reordered))
print(Counter(left) == Counter(fewer_duplicates))
print(set(left) == set(fewer_duplicates))
True
False
True
마지막 두 줄은 중요한 경계를 보여 줍니다. Counter는 두 번째 2가 빠진 것을 감지하지만 집합 비교는 중복을 버리므로 같다고 보고합니다. 반복 값에 의미가 있다면 set(left) == set(right)를 대체 수단으로 쓰지 마세요.
내장 함수 sorted()도 sorted(left) == sorted(right)를 통해 중복을 고려하는 다른 선택지가 되지만, 두 리스트의 모든 값을 서로 정렬할 수 있을 때만 가능합니다. 정렬된 리스트를 만들고 정렬 작업도 수행하므로 순서 없는 빈도 동등성에는 보통 Counter가 더 직접적입니다. 그래도 나중에 정렬된 시퀀스가 필요하다면 정렬은 유용합니다.
set()로 고유 값, 교집합, 차집합 비교하기
고유 값만 중요할 때는 리스트를 집합으로 변환합니다. 집합 교집합(&)은 두 입력에 모두 있는 값을 반환합니다. 차집합(-)은 방향성이 있고, 대칭 차집합(^)은 한쪽에만 있는 값을 반환합니다.
다음 검증된 예제에서는 표시할 집합 결과만 정렬하여 출력이 결정적이게 했습니다. 비교 자체가 집합에 순서를 부여하지는 않습니다.
집합 변환은 멤버십 확인이 많은 작업에서 검색 구조를 한 번 만들 수 있어 유용하지만, 출력이 위치나 중복 횟수를 유지해야 할 때는 적합하지 않습니다. 또한 집합이 출력하는 순서에 의존하지 마세요. 값들을 서로 정렬할 수 있고 안정적인 표시가 필요할 때만 명시적으로 정렬합니다.
"""Verify unique-value, order-preserving, and multiplicity-aware differences."""
from collections import Counter
left = [1, 2, 2, 3, 4]
right = [2, 3, 3, 5]
left_set = set(left)
right_set = set(right)
print(sorted(left_set & right_set))
print(sorted(left_set - right_set))
print(sorted(right_set - left_set))
right_members = set(right)
print([item for item in left if item in right_members])
left_counts = Counter(left)
right_counts = Counter(right)
print(sorted((left_counts & right_counts).elements()))
print(sorted((left_counts - right_counts).elements()))
print(sorted((right_counts - left_counts).elements()))
[2, 3]
[1, 4]
[5]
[2, 2, 3]
[2, 3]
[1, 2, 4]
[3, 5]
처음 세 출력 줄은 고유 값의 교집합과 방향 있는 차집합을 보여 줍니다. 정확히 한 집합에만 있는 값도 필요하면 left_set ^ right_set을 사용합니다. 이 입력에서는 1, 4, 5라는 고유 값이 나오지만 순서는 지정되지 않습니다.
리스트 컴프리헨션으로 결과 순서 유지하기
집합 결과는 첫 번째 리스트의 순서를 보존하지 않습니다. 출력 순서가 중요하면 멤버십 집합을 한 번 만들고 순서를 보존할 리스트를 순회합니다. 앞선 예제에서는 컴프리헨션마다 두 번째 리스트를 다시 만들거나 선형 검색하지 않고 right_members를 한 번만 만듭니다.
네 번째 출력 줄 [2, 2, 3]은 left의 순서와 반복 출현을 유지합니다. 이 결과는 의도적으로 비대칭입니다. 대신 right를 순회하면 right의 순서와 중복이 보존됩니다. 멤버십 값이 해시 가능하지 않다면 다른 리스트에 대해 직접 동등성 기반 멤버십을 사용하되, 큰 입력에서는 리스트를 반복해서 순회하는 비용이 커질 수 있음을 알아야 합니다.
Counter로 교집합과 차집합의 개수 유지하기
집합 연산은 고유 값에 관한 질문에 답합니다. Counter 연산은 개수를 보존하면서 같은 종류의 질문에 답합니다.
left_counts & right_counts는 공유하는 각 값의 양수 최소 개수를 남깁니다.left_counts - right_counts는 개수를 빼고 양수인 나머지만 남깁니다.right_counts - left_counts는 반대 방향의 차집합을 제공합니다..elements()는 결과 개수를 개별 값으로 다시 펼칩니다.
검증된 예제에서 다중 집합 교집합은 [2, 3]이며 [2, 2, 3]이 아닙니다. 2는 왼쪽에 두 번 있지만 오른쪽에는 한 번뿐이므로 최소 개수는 하나입니다. 방향 있는 왼쪽 나머지는 [1, 2, 4]이고 오른쪽 나머지는 [3, 5]입니다.
재고, 투표, 반복 태그처럼 수량이 의미 있는 데이터에는 이 방법을 선택하세요. 일반 집합은 그 정보를 조용히 지웁니다.
Counter 산술은 이 다중 집합 결과에서 개수가 0 또는 음수인 항목을 제거합니다. 이 동작은 남은 항목을 묻는 질문에는 유용하지만 가능한 모든 키에 대한 일반 숫자 뺄셈과는 다릅니다. 애플리케이션이 0 또는 음수 잔액도 보존해야 한다면 카운터를 직접 검사하세요.
해시할 수 없는 값을 처리하고 올바른 방법 고르기
set()과 Counter는 모두 해시 가능한 요소를 요구합니다. 리스트는 변경 가능하고 해시할 수 없으므로 중첩 리스트는 TypeError를 일으키지만, 직접 리스트 동등성은 계속 작동합니다.
"""Show the unhashable-item boundary of set and Counter comparisons."""
from collections import Counter
left = [[1], [2]]
right = [[1], [2]]
print(left == right)
for name, operation in (("set", set), ("Counter", Counter)):
try:
operation(left)
except TypeError as error:
print(f"{name}: {error}")
True
set: cannot use 'list' as a set element (unhashable type: 'list')
Counter: unhashable type: 'list'
정확한 set 진단 메시지는 Python 릴리스마다 달라질 수 있으므로 예외 유형과 요소가 해시할 수 없다는 원인을 안정적인 동작으로 보아야 합니다. 중첩 시퀀스를 불변 형태로 표현할 수 있다면 집합이나 Counter를 쓰기 전에 각 내부 리스트를 튜플로 바꾸세요. 그렇지 않으면 필요한 결과에 맞는 동등성 기반 비교를 유지합니다.
요약하면 데이터의 의미에 맞는 표현을 선택하세요. 순서 있는 시퀀스에는 ==, 순서를 무시하는 다중 집합에는 Counter, 고유 값에는 set(), 필터링한 출력에는 순서를 보존하는 컴프리헨션을 사용합니다. 순서와 중복 규칙을 명시하면 맞아 보이지만 다른 질문에 답하는 비교를 막을 수 있습니다.