파이썬 - sort 정리

HR.lee·2022년 2월 9일
1

sort는 강력한 파이썬의 정렬함수이다

파이썬의 사기템은 3가지가 있는데 첫번째가 리스트, 두번째가 슬라이싱, 그리고 세번째가 바로 소트이다! 이 3신기는 코테 난이도를 어마무시하게 낮춰버린다!
그래서 파이썬을 밴하는 회사도 있다고 한다.

파이썬의 sort는 팀소트를 기반으로 하며 .sort() 하나만으로도 다른 정렬이 필요없을 만큼 효율적이고 빠른 스피드를 자랑한다.

이러한 sort를 잘 쓰기 위해서 알아두어야 할 점들이 있다!

참고!
https://infinitt.tistory.com/122
https://ooyoung.tistory.com/59
https://blockdmask.tistory.com/466
https://mgyo.tistory.com/161
https://docs.python.org/ko/3/howto/sorting.html

1. sort와 sorted의 차이!

sort와 sorted를 단순히 내부에서 정렬과 새 리스트 리턴의 차이라고 생각할 수 있지만 사실 조금 다른 개념이다. sort는 iterable한 객체들 중에서도 list만이 사용할 수 있는 리스트의 내장함수다!

그리고 sorted는 iterable한 객체(List, tuple, Dictionary, str)들을 정렬한 새 배열을 list 형태로 만들어주는 함수다...! 다시금 체감하는 리스트의 강력함!

그치만 리스트의 빌트인함수임에도 당당히 사기템 한자리를 차지하고 있다는 건 얘도 정말 강력하다는 뜻이다.

2. list.sort(key, reverse)

  • 아무 키도 넣지 않으면 오름차순으로 정렬된 리스트를 반환한다.
  • 매개가 리스트가 아니면 에러가 난다!
>>> num_list = [15, 22, 8, 79, 10]
>>> num_list.sort()
>>> print(num_list)
[8, 10, 15, 22, 79]

>>> str_list = ['1','좋은하루','good_morning','굿모닝','niceday']
>>> str_list.sort()
>>> print(str_list)
['1', 'good_morning', 'niceday', '굿모닝', '좋은하루']

sort에는 인자로 key와 reverse가 들어갈 수 있다.

  • reverse 부분엔 reverse = True! 없으면 비우거나 False
  • key 부분엔 key = func 정렬 목적으로 사용할 수 있는 단일 인자 키를 반환하는 모든 함수가 들어갈 수 있다.
  • 그렇다. 람다함수도, 빌트인함수도 그냥 내가 만든 함수도 정렬의 키값으로 사용가능하다면 다 된다. key=int이런거도 된다는 얘기다.

2. sorted(iterable, key, reverse)

  • sorted는 sort와 다르게 iterable한 모든 객체에 대한 정렬이 가능하다.
  • 그치만 sort처럼 키를 함수로 사용할 수도 있고, 리버스 기능도 있다!

예제 문제 : 대소 문자를 구분하지 않는 문자열 비교

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

리스트가 아닌 문자열을 .split()함수를 붙여 공백으로 구분해서 리턴하도록 했으며 키로는 str.lower함수를 써서 문자열을 - > 대소문자 구분 없이 정렬된 단어 리스트로 반환했다!

key 매개 변수의 값은 단일 인자를 취하고 정렬 목적으로 사용할 키를 반환하는 함수여야 한다. 키 함수는 각 입력 레코드에 대해 정확히 한 번 호출되어 군더더기 없는 효율성을 보인다.

예제 문제 : 튜플 정렬하기

람다함수를 가지고 객체의 인덱스를 키로 사용할 수 있다! 딕셔너리도 정렬 가능!
.items()나 .keys()도 물론 가능하다.

단, sorted 함수로 딕셔너리 정렬을 하게 되면 key, value가 각각 튜플로 묶이고 그것들의 리스트가 반환됨 주의...! 튜플이 되어버린다!

>>> student_tuples = [
...     ('john', 'A', 15),
...     ('jane', 'B', 12),
...     ('dave', 'B', 10),
... ]
>>> sorted(student_tuples, key=lambda student: student[2])   
# 인덱스의 0, 1, 2번째 값을 기준으로 정렬!
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

클래스 내부에 빌트인 된 커스텀 함수로도 정렬이 가능하다!

>>> 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_objects = [
...     Student('john', 'A', 15),
...     Student('jane', 'B', 12),
...     Student('dave', 'B', 10),
... ]
>>> sorted(student_objects, key=lambda student: student.age)   
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

응용 : 다중 조건 정렬!

array = [("A", 18, 300000) , ("F", 24, 10000), \\
("T", 24, 200000),("Q",24,5000000), ("B", 70, 5000)]
# 각각 코드네임, 레벨, 점수를 표현한 리스트

위 리스트처럼 정렬할 때 고려해야 할게 많은 경우에는
튜플형식으로 key = lambda x: (x[0] , x[2]) lambda식을 만들 수 있다.

첫번째 키 기준으로 먼저 정렬하고, 순위가 같다면 두번째 키를 적용시켜준다!
예제에서는 레벨을 기준으로 오름차순 정렬 -> 같은 레벨일 경우 점수를 기준으로 오름차순!

array.sort(key = lambda (x: x[1], x[2]))
print(array)

>>> [('A', 18, 300000), ('F', 24, 10000),\\
('T', 24, 200000), ('Q', 24, 5000000), ('B', 30, 5000000)]

일부 키는 내림차순으로 하고 싶을때는 마이너스 부호를 붙여주면 된다.
key= lambda x: (-x[0], x[2]) 간단!
레벨을 기준으로 오름차순 정렬하고 , 같은 레벨이라면 점수가 높은 순으로 내림차순 해준다!

array.sort(key = lambda (x: x[1], -x[2]))
print(array)

>>> [('A', 18, 300000), ('Q', 24, 5000000), \\
('T', 24, 200000), ('F', 24, 10000), ('B', 30, 5000000)]

나는 좀더 효율적으로 정렬하고 싶다!

operator 모듈 함수

이 모듈을 임포트하면 기능과 길이는 비슷하지만... 코드를 짜기 좀더 편하고, 많은 단위의 연산일 때 다른 함수보다 쪼금 더 빠른 두개의 함수를 사용할 수 있다.

  • itemgetter() : 인덱스 값 가져오기!

  • attrgetter() : 키 값으로 지정해둔 것 가져오기!

  • itemgetter()와 attrgetter()는 item을 꺼내는 callable object를 반환한다.

>>> from operator import itemgetter, attrgetter

>>> sorted(student_tuples, key=itemgetter(2))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

>>> sorted(student_objects, key=attrgetter('age'))
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

튜플+람다처럼 다중 정렬도 된다. grade로 정렬한 다음 age로 정렬하려면 이렇게!

>>> sorted(student_tuples, key=itemgetter(1,2))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

>>> sorted(student_objects, key=attrgetter('grade', 'age'))
[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]

예제 문제 : 딕셔너리를 여러 방식으로 정렬하기

import operator

d = {'b': 400, 'f': 300, 'a': 200, 'd': 100, 'c': 500}
 
 
print(d.items())

dict_items([('b', 400), ('f', 300), ('a', 200), ('d', 100), ('c', 500)])
 

f = sorted(d.items())
print(f)
 
[('a', 200), ('b', 400), ('c', 500), ('d', 100), ('f', 300)]
 

g = sorted(d.items(), key=operator.itemgetter(1)) # 1번째 인덱스로 정렬
print(g)

[('d', 100), ('a', 200), ('f', 300), ('b', 400), ('c', 500)]
 

h = sorted(d.items(), key=operator.itemgetter(1), reverse=True) # 리버스
print(h)

[('c', 500), ('b', 400), ('f', 300), ('a', 200), ('d', 100)]

추가 : 정렬 안정성과 복잡한 정렬

sorted함수는 여러 레코드가 같은 키를 가질 때, 원래의 순서를 유지한다!

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

blue라는 값을 가진 두 레코드가 원래 순서를 유지해서
('blue', 1)는 ('blue', 2)와 같은 블루지만 앞에 나온다!

이걸 이용하면 정렬을 여러번 시행해서 다중정렬을 만들 수도 있다!

예를 들어, age 정렬을 수행한 다음 grade를 사용하여 다시 정렬하면
나이는 오름차순으로 등급은 내림차순으로 정렬하는게 가능하다!

>>> s = sorted(student_objects, key=attrgetter('age')) 
# sort on secondary key
>>> sorted(s, key=attrgetter('grade'), reverse=True) 
# now sort on primary key, descending
[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]

이걸 함수화 할수도 있다.

>>> def multisort(xs, specs):
...     for key, reverse in reversed(specs):
...         xs.sort(key=attrgetter(key), reverse=reverse)
...     return xs

>>> multisort(list(student_objects), (('grade', True), ('age', False)))


[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
profile
It's an adventure time!

0개의 댓글