이 글의 순서 6개 단락
리스트에서 중복을 제거할 때 원래 순서까지 유지하려면, 각 값이 처음 등장한 위치를 기준으로 남겨야 합니다. 예를 들어 신청 목록에서 같은 이름을 한 번만 남기되 접수 순서는 유지하는 경우입니다. 값을 기준으로 순서를 바꾸는 정렬과는 목적이 다릅니다.
set으로 바꾸면 어떤 문제가 있을까?
items = ['banana', 'apple', 'banana', 'pear', 'apple']
unique_set = set(items)
print(unique_set)
set은 중복된 값을 하나로 모으지만 순서가 없는 자료형입니다. 따라서 list(set(items))로 다시 리스트를 만들더라도 입력에서 처음 등장한 순서가 유지된다고 기대할 수 없습니다. 출력 순서가 우연히 입력 순서와 같아 보여도 그 순서에 의존해서는 안 됩니다.
dict.fromkeys로 첫 등장 순서 유지하기
items = ['banana', 'apple', 'banana', 'pear', 'apple']
unique = list(dict.fromkeys(items))
print(unique)
예상 출력:
['banana', 'apple', 'pear']
dict.fromkeys(items)는 항목을 딕셔너리의 키로 사용합니다. 같은 키는 한 번만 들어가고, 딕셔너리는 키가 삽입된 순서를 유지합니다. 마지막으로 list()를 적용하면 키를 그 순서대로 담은 리스트가 됩니다. 이 예제에서는 banana, apple, pear의 첫 등장 순서가 유지됩니다. 딕셔너리의 삽입 순서 보장은 Python 3.7부터 언어 명세에 포함됩니다.
이 방법은 문자열이나 숫자처럼 해시할 수 있는 항목에 사용할 수 있습니다. 원본 리스트는 수정하지 않습니다.
반복문으로 같은 작업 살펴보기
items = ['banana', 'apple', 'banana', 'pear', 'apple']
seen = set()
unique = []
for item in items:
if item not in seen:
seen.add(item)
unique.append(item)
print(unique)
예상 출력:
['banana', 'apple', 'pear']
seen은 이미 나온 값을 확인하고, unique는 처음 나온 값을 순서대로 저장합니다. 위 문자열 목록에서는 dict.fromkeys 방식과 같은 항목을 같은 순서로 남깁니다. seen에 값을 넣기 때문에 이 방식도 항목을 해시할 수 있어야 합니다.
대소문자와 공백을 같은 값으로 취급하려면
기본 문자열 비교는 대소문자와 공백을 구분합니다. 따라서 'Python', 'python', ' Python '은 그대로 중복 제거하면 서로 다른 문자열입니다. 비교할 때만 양끝 공백과 대소문자를 무시하고, 처음 나온 원본 표기는 남기려면 별도의 비교 키를 만듭니다.
words = ['Python', 'python', ' Python ', 'Java']
seen = set()
unique = []
for word in words:
key = word.strip().casefold()
if key not in seen:
seen.add(key)
unique.append(word)
print(unique)
예상 출력:
['Python', 'Java']
strip()은 문자열 양끝의 공백을 제거하고, casefold()는 대소문자 구분 없는 비교를 위한 문자열을 만듭니다. 이 기준에서는 앞의 세 문자열이 같은 키로 비교되므로 처음 나온 'Python'을 남깁니다. 문자열 중간의 공백은 strip()으로 제거되지 않으므로, 어떤 차이를 무시할지는 데이터에 맞게 정해야 합니다.
리스트처럼 해시할 수 없는 항목은?
리스트를 항목으로 가진 리스트에 dict.fromkeys나 위의 seen 집합 방식을 그대로 사용하면 내부 리스트를 해시할 수 없어 TypeError가 발생합니다. 이때는 결과 리스트에서 이미 같은 항목이 있는지 직접 확인할 수 있습니다.
rows = [[1, 2], [1, 2], [2, 1]]
unique_rows = []
for row in rows:
if row not in unique_rows:
unique_rows.append(row)
print(unique_rows)
예상 출력:
[[1, 2], [2, 1]]
이 방식은 리스트의 값이 같은지 비교해 첫 번째 [1, 2]와 [2, 1]을 순서대로 남깁니다. 다만 새 항목마다 결과 리스트를 순회하며 비교할 수 있어 데이터가 많으면 느려질 수 있습니다. 또한 결과에 담긴 내부 리스트는 복사본이 아니라 원래 항목을 참조합니다.
항목이 해시 가능하고 기본 동등 비교가 목적이라면 dict.fromkeys가 간결합니다. 비교 기준을 직접 정해야 하거나 항목을 해시할 수 없다면 그 조건에 맞는 반복문을 선택하세요.
직접 연습하기
문자열 정규화 예제의 words를 [' Java ', 'java', 'Python', ' JAVA']로 바꾸면 어떤 원본 표기가 어떤 순서로 남을지 먼저 예상해 보세요. 그다음 코드를 실행해 예상과 비교하고, 각 문자열의 비교 키가 무엇인지 확인해 보세요.
CHECK THE SOURCE
참고한 공식 문서
- Python 문서: set과 frozenset ↗
- Python 문서: 딕셔너리와 fromkeys ↗
- Python 용어집: hashable ↗
- Python 문서: str.casefold ↗
기능 설명은 위 문서를 참고했으며, 예제의 입력·결과와 한계는 본문에 따로 기록했습니다.