이 글의 순서 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

참고한 공식 문서

기능 설명은 위 문서를 참고했으며, 예제의 입력·결과와 한계는 본문에 따로 기록했습니다.

한 줄씩 이해하고, 차곡차곡 기록합니다.이 글의 오류 제보하기 ↗