Do diffence

[Python HOWTO] 파이썬 객체 정렬의 모든 것 본문

Python

[Python HOWTO] 파이썬 객체 정렬의 모든 것

고포릿 2021. 4. 4. 13:20

Python HOWTOs 를 정리한다

 

Sorting How To

파이썬 리스트 객체는 list.sort() 를 내장하고 있고, 표준 내장 함수 sorted() 는 반복을 통해 새 정렬된 리스트 객체를 반환해 준다.

기본 정렬

간단한 오름차순 정렬을 해보자.

import random as rnd

data = [ rnd.randint(1, x) for x in range(1,30)]

결과를 보면

data[:5]
[1, 1, 3, 2, 4]

list.sort()는 객체에서 정렬한 결과가 반영된다. 원래 데이터가 필요 없으면 간단하다.

data.sort()
data[:10]
[1, 1, 1, 2, 2, 3, 3, 4, 6, 6]

원래 데이터를 보존해야 하면 sorted()는 정렬한 결과를 새 리스트로 반환한다.

data = [ rnd.randint(1, x) for x in range(1,30)]
data = sorted(data)
data[:10]
[1, 1, 1, 1, 3, 3, 3, 3, 3, 4]

단 가장 큰 차이는 list.sort() 는 리스트만 대상으로 하지만 sorted() 는 이터러블를 모두 지원한다 - 리스트, 딕셔너리

sorted({1: 'D', 2: 'B', 3: 'B', 4: 'E', 5: 'A'})
[1, 2, 3, 4, 5]

key 기능

sorted(), .sort() 는 key 매개변수로 각 요소의 비교를 적용할 수 있는 함수를 명시한다. 예를 들어 아래는 소문자로 비교하도록 key를 지정한다.

sorted("This is a test string From Andrew".split(), key=str.lower)
['a', 'Andrew', 'From', 'is', 'string', 'test', 'This']

key 매개변수는 인자로 하나의 단일 매개변수 함수를 지정하고 정렬 목적에 맞는데 사용하는 키를 반환한다.

공통적인 패턴으로 복합 객체의 인덱스를 사용한 정렬레 key 를 사용하는 것이다. 다음은 복합 리스트 객체에서 숫자인 값을 키로 전달하는 키 함수이다.

student_tutples = [
    ('홍길동', 'B', 12),
    ('산울림', 'A', 15),
    ('고길동', 'B', 11)
]
sorted(student_tutples, key=lambda student: student[2])
[('고길동', 'B', 11), ('홍길동', 'B', 12), ('산울림', 'A', 15)]
sorted(student_tutples, key=lambda student: student[0])
[('고길동', 'B', 11), ('산울림', 'A', 15), ('홍길동', 'B', 12)]

같은 기법으로 이름 속성을 가진 개체에도 사용할 수 있다.

class Student:
    def __init__(self, name, grade, age):
        self.name = name
        self.grade = grade
        self.age = age
    def __repr__(self):
        return repr((self.name, self.grade, self.age))
student_ojects = [
    Student('홍길동', 'B', 12),
    Student('산울림', 'A', 15),
    Student('고길동', 'B', 11)
]
sorted(student_ojects, key=lambda x: x.age)
[('고길동', 'B', 11), ('홍길동', 'B', 12), ('산울림', 'A', 15)]
sorted(student_ojects, key=lambda x: x.name)
[('고길동', 'B', 11), ('산울림', 'A', 15), ('홍길동', 'B', 12)]

Operator module functions

key-function 패턴은 매우 흔해서 파이썬 접속자 기능을 쉽고 빠르게 할 수 있는 편의 기능을 제공한다. operator 모듈은 itemgetter(), attrgetter(), and a methodcaller() 함수를 제공한다. 이 함수를 사용하면 위 예가 더 쉽고 빠르게 해결된다.

from operator import itemgetter, attrgetter
sorted(student_tutples, key=itemgetter(2))
[('고길동', 'B', 11), ('홍길동', 'B', 12), ('산울림', 'A', 15)]
sorted(student_ojects, key=attrgetter('age'))
[('고길동', 'B', 11), ('홍길동', 'B', 12), ('산울림', 'A', 15)]

더 불어 operator 모듈 함수는 여러 단계 정렬을 허용해서 아래 같인 등급과 나이를 동시에 key로 제시할 수 있다.

sorted(student_tutples, key=itemgetter(1,2))
[('산울림', 'A', 15), ('고길동', 'B', 11), ('홍길동', 'B', 12)]
sorted(student_ojects, key=attrgetter('grade', 'age'))
[('산울림', 'A', 15), ('고길동', 'B', 11), ('홍길동', 'B', 12)]

오름차순 내림차순

list.sort(), sorted() 는 reverse 불리언 매개변수로 True 면 내림차순을 지원한다.

sorted(student_tutples, key=itemgetter(2), reverse=True)
[('산울림', 'A', 15), ('홍길동', 'B', 12), ('고길동', 'B', 11)]
sorted(student_ojects, key=attrgetter('age'), reverse=True)
[('산울림', 'A', 15), ('홍길동', 'B', 12), ('고길동', 'B', 11)]

정렬 안정성과 복합 정렬

졍렬은 안정성이 보장되야 한다. 이것은 다중 레코드에서 원래 속성을 따라 키 순서를 유지되야 한다는 것이다.

예를 들어 아래 같이 카드가 레드 2개, 블루 2개가 1, 2 순서를 가진 레코드가 있다면, 정렬을 한다면 숫자 순서만 고려하면 안되고 문자(속성)을 고려해 순서데로 정렬되야 하는 것이다. 레코드는 속성과 값 순서로 구성된다.

data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]
sorted(data)
[('blue', 1), ('blue', 2), ('red', 1), ('red', 2)]

키 함수를 사용해도 그렇다. blue 1 다음에 blue 2가 나오도록 유지되는 것을 볼 수 있다.

sorted(data, key=itemgetter(0))
[('blue', 1), ('blue', 2), ('red', 1), ('red', 2)]

이것은 일련의 정렬 단계로 복잡한 정렬를 만들수 있다. 예를 들어 학생 데이터를 등급으로 내림차순 정렬하고 나이로 오름 차순 정렬하는 것은, 처음에 나이로 오름차순 정렬하고 등급을 내림차순 정렬하는 것이다.

s = sorted(student_ojects, key=attrgetter('age'))
sorted(s, key=attrgetter('grade'), reverse=True)
[('고길동', 'B', 11), ('홍길동', 'B', 12), ('산울림', 'A', 15)]

파이썬에서 Timsort 알고리즘이 다중 정렬에 사용한다. 이것은 데이터 세트에 이미 있는 순서를 유지하는데 유리하다고 한다.

이전 Python2 버전

Decorate-Sort-Undecorate

The Old Way Using Decorate-Sort-Undecorate

cmp 매개변수

이전 버전에서는 sorted(), list.sort()에 키워드 매개변수가 없었다. 대신에 cmp 매개변수를 상요한다.

https://docs.python.org/3/howto/sorting.html#the-old-way-using-the-cmp-parameter

특이한 것

  1. 로케일 관련해 정렬시 locale.strxfrm(), locale.strcoll() 를 키의 비교 함수로 사용한다.
  2. reverse 파라미터는 여전히 정렬 안정성을 유지한다(동일한 키를 가진 레코드가 원래 순서를 유지함). 흥미롭게도, 그 영향은 내장된 reverse() 함수를 두 번 사용하여 파라미터 없이 시뮬레이션할 수 있다.
data = [('red', 1), ('blue', 1), ('red', 2), ('blue', 2)]
standard_way = sorted(data, key=itemgetter(0), reverse=True)
double_reversed = list(reversed(sorted(reversed(data), key=itemgetter(0))))
assert standard_way == double_reversed
standard_way, double_reversed
([('red', 1), ('red', 2), ('blue', 1), ('blue', 2)],
 [('red', 1), ('red', 2), ('blue', 1), ('blue', 2)])
  1. 정렬 루틴들은 두 객체를 비교할 때 __lt__() 를 사용하는 것을 보장한다. 그래서 클래스에 표준 정렬 순서를 추가하는 것이 간단하고 쉽다
Student.__lt__ = lambda self, other: self.age < other.age
sorted(student_ojects)
[('고길동', 'B', 11), ('홍길동', 'B', 12), ('산울림', 'A', 15)]
  1. key 함수는 정렬하는 객체에 직접 의존할 필요가 없다. 키 함수는 외부 자원에 접근할 수 있다. 예를 들어 학생 등급이 딕셔너리에서 정렬되면 학생 이름 리스트로 나누어 정렬에 사요할 수 있다.
students = ['산울림', '홍길동','고길동']
newgrades = { '고길동': 'F', '홍길동': 'A', '산울림': 'C'}
sorted(students)
['고길동', '산울림', '홍길동']

키 함수로 외부 자원과 매핑해서 정렬할 수 있다는 의미이다.

sorted(students, key=newgrades.__getitem__)
['홍길동', '산울림', '고길동']