Python 라이브러리 - collections

collections

source code

공식 문서

deque

deque는 앞과 뒤에서 데이터를 처리할 수 있는 양방향 자료형입니다. 스택(stack)처러 사용해도 되고, 큐(queue)처럼 사용해도 됩니다.

deque 사용법

deque는 list와 매우 비슷합니다. 스택과 큐로 활용할 수 있는 메서드도 대부분 일치합니다.

다만, deque에는 아래와 같은 메서드들이 더 있습니다.

  • appendleft(x) : 데크 왼쪽에 x 추가
  • popleft() : 데크 왼쪽에서 요소 제거
    물론 list에서도 pop(0)을 사용해서 첫 번째 요소를 제거할 수 있지만 deque를 사용하면 아래와 같은 장점이 있습니다.
  • deque는 list보다 속도가 빠릅니다. pop(0)을 수행하려면 O(N)연산을 수행하지만, popleft() 연산을 수행하면 O(1) 연산을 수행합니다.
  • 스레드 환경에서 안전합니다.
d = deque([1, 2, 3, 4, 5])
d.append(6)
d.appendleft(0)
print(d)        # deque([0, 1, 2, 3, 4, 5, 6])
d.pop()
d.popleft
print(d)        # deque([0, 1, 2, 3, 4, 5])

리스트를 n만큼 회전하는 문제는 collections.deque 모듈을 사용하면 간단하게 해결할 수 있습니다.

from collections import deque

list_a = [1, 2, 3, 4, 5]
q = deque(list_a)
q.rotate(2)     # 시계 방향 회전은 양수, 그 반대는 음수
result = list(q)
print(result)   # [4, 5, 1, 2, 3]

deque(list_a)로 deque 객체를 만든 후 rotate() 함수를 사용해 2만큼 오른쪽으로 회전하면 첫 값이 4를 가리키게 됩니다.

namedtuple

튜플(tuple)은 인덱스를 통해서만 데이터에 접근이 가능하지만, 네임드 튜플(namedtuple)은 인덱스뿐만 아니라 key로도 데이터에 접근할 수 있는 자료형입니다.

from collections import namedtuple

data = [
    ('kim', 30, '01012341234'),
    ('lee', 10, '01012331233'),
    ('park', 20, '01012221222'),
]

Employee = namedtuple(typename='Employee', field_names='name, age, cellphone')

# data의 각 튜플을 모두 Employee자료형(namedtuple자료형)으로 교체
# 리스트 컴프리헨션 이용한 경우
data = [Employee(name=emp[0], age=emp[1], cellphone=emp[2]) for emp in data]
# _make() 함수 사용한 경우
data = [Employee._make(emp) for emp in data]

emp = data[0]       # 첫 번째 직원
print(emp.name)     # kim
print(emp[0])       # kim   (인덱스로도 접근 가능)
print(emp.age)      # 30
print(emp.cellphone)# 01012341234

# 네임드 튜플은 값을 변경할 수 없는(immutable) 튜플의 특징을 그대로 가지고 있어서 속성 값을 변경할 수 없다.
# emp.name = 'cho'
# 다만, _replace() 함수를 사용하면 값을 변경할 수 있다.
new_emp = emp._replace(name="cho")
print(new_emp)      # Employee(name='cho', age=30, cellphone='01012341234')
# 단, _replace() 함수는 객체를 직접 변경하는 것이 아니라 값을 변경한 새로운 객체를 만들어 반환한다.

# _asdict() 함수를 사용해 딕셔너리로 변환
emp_dict = emp._asdict()
print(emp_dict)     # {'name': 'kim', 'age': 30, 'cellphone': '01012341234'}

Counter

collections.Counter는 리스트나 문자열과 같은 자료형의 요소 중 값이 같은 요소가 몇 개인지를 확인할 때 사용하는 클래스입니다.

아래의 코드는 시에서 가장 많이 사용한 단어와 그 개수를 구하려는 코드입니다.

from collections import Counter
import re 

data = """
나 보기가 역겨워
가실 때에는
말없이 고이 보내 드리우리다
영변에 약산
진달래 꽃
아름 따다 가실 길에 뿌리우리다
가시는 걸음걸음
놓인 그 꽃을
사뿐히 즈려밟고 가시옵소서
나 보기가 역겨워
가실 때에는
죽어도 아니 눈물 흘리우리다
"""

# 문장을 단어별로 나누고자 re 모듈 사용
words = re.findall(r'\w+', data)    # r: 정규표현식이 원시 문자열(raw string)임을 알려주는 문자
# \w+ : 단어를 의미
# re.findall() 함수를 이용하여 모든 단어를 리스트(words)로 반환
counter = Counter(words)
# Counter 클래스의 객체 counter 생성
print(counter)
# Counter({'가실': 3, '나': 2, '보기가': 2, '역겨워': 2, '때에는': 2, '말없이': 1, '고이': 1, '보내': 1, '드리우리다': 1, '영변에': 1, '약산': 1, '진달래': 1, '꽃': 1, '아름': 1, '따다': 1, '길에': 1, '뿌리우리다': 1, '가시는': 1, '걸음걸음': 1, '놓인': 1, '그': 1, '꽃을': 1, '사뿐히': 1, '즈려밟고': 1, '가시옵소서': 1, '죽어도': 1, '아니': 1, '눈물': 1, '흘리우리다': 1})
# 단어 빈도수가 큰 것부터 차례대로 출력
print(counter.most_common(1))
# [('가실', 3)]
# Counter 객체의 most_common() 함수를 이용해 빈도수가 가장 많은 1개의 단어 출력

# most_common() 함수는 빈도수가 많은 것부터 인수로 입력한 개수만큼 튜플로 반환
print(counter.most_common(2))
# [('가실', 3), ('나', 2)]

defaultdict

collections.defaultdict는 값(value)에 초깃값을 지정하여 딕셔너리를 생성하는 모듈

만약 문자열에서 사용한 문자(key)와 해당 문자의 사용 횟수(value)를 딕셔너리로 만든다면 일반적으로 아래 코드처럼 작성할 수 있습니다.

text = "Life is too short, You need Python."

d = dict()
for key in text:
    if key not in d:
        d[key] = 0
    d[key] += 1

print(d)
# {'L': 1, 'i': 2, 'f': 1, 'e': 3, ' ': 6, 's': 2, 't': 3, 'o': 5, 'h': 2, 'r': 1, ',': 1, 'Y': 1, 'u': 1, 'n': 2, 'd': 1, 'P': 1, 'y': 1, '.': 1}

위의 코드의 경우 딕셔너리 d의 키에 해당 문자가 없다면 그 문자를 키로 등록하고 값은 0으로 초기화하는 방어적인 코드가 존재해야 합니다.

# 방어적인 코드
if key not in d:
    d[key] = 0

만약 방어적인 코드가 없다면 딕셔너리에 해당 키 값이 없는 상태에서 += 연산을 수행하므로 에러가 발생할 것입니다.

for key in text:
    d[key] += 1

딕셔너리로 이와 같은 집계용 코드를 작성할 때는 항상 초깃값에 신경을 써야 합니다. 하지만 collections의 defaultdict를 사용하면 이러한 번거로움을 피할 수 있습니다.

from collections import defaultdict

text = "Life is too short, You need Python."

d = defaultdict(int)
for key in text:
    d[key] += 1

print(d)
# defaultdict(<class 'int'>, {'L': 1, 'i': 2, 'f': 1, 'e': 3, ' ': 6, 's': 2, 't': 3, 'o': 5, 'h': 2, 'r': 1, ',': 1, 'Y': 1, 'u': 1, 'n': 2, 'd': 1, 'P': 1, 'y': 1, '.': 1})
print(dict(d))
# {'L': 1, 'i': 2, 'f': 1, 'e': 3, ' ': 6, 's': 2, 't': 3, 'o': 5, 'h': 2, 'r': 1, ',': 1, 'Y': 1, 'u': 1, 'n': 2, 'd': 1, 'P': 1, 'y': 1, '.': 1}

defaultdict()의 인수로 int를 전달하여 딕셔너리 d를 생성합니다. int를 기준으로 생성한 딕셔너리 d의 값은 항상 0으로 자동 초기화됩니다.

 

 

 

 

'Python > Python' 카테고리의 다른 글

Python 라이브러리 - pprint  (0) 2023.05.16
Python 라이브러리 - heapq  (0) 2023.05.16
Python 라이브러리 - calendar  (0) 2023.05.11
Python 라이브러리 - datetime  (0) 2023.05.11
Python 라이브러리 - re  (0) 2023.05.11