Python - collections (효율적인 컨테이너형 데이터)
collections (효율적인 컨테이너형 데이터)
알고리즘 문제 해설이나 모범 답안을 보면 itertools
와 같이 많이 보이는 것이 collections
이다.
collections
는 다른 객체를 등록하여 효율적으로 컬렉션을 관리할 수 있다.
ChainMap objects
여러 개의 dict 객체를 모아서 각가의 dict 요소를 하나의 dict으로 검색할 수 있다.
attr/method | desc |
---|---|
maps |
등록된 매핑 객체 리스트 |
new_child |
현재 인스턴스의 모든 맵을 포함된 새 객체를 반환 |
parents |
현재 인스턴스의 첫 번째 맵을 제외한 새 맵을 반환 |
from collections import ChainMap
baseline = {'music':'bach', 'art':'rembrandt'}
adjustments = {'art':'van gogh', 'opera':'carmen'}
c = ChainMap(adjustments, baseline)
d = c.new_child()
e = c.new_child()
e.maps[0]
# {}
e.maps[-1]
# {'music': 'bach', 'art': 'rembrandt'}
e.parents
# ChainMap({'art': 'van gogh', 'opera': 'carmen'}, {'music': 'bach', 'art': 'rembrandt'})
d['x'] = 1
d['x']
# 1
del d['x']
list(d)
# ['music', 'art', 'opera']
len(d)
# 3
d.items()
# ItemsView(ChainMap({}, {'art': 'van gogh', 'opera': 'carmen'}, {'music': 'bach', 'art': 'rembrandt'}))
dict(d)
# {'music': 'bach', 'art': 'van gogh', 'opera': 'carmen'}
Counter objects
입력 데이터에서 각 값의 counter를 셀때 사용.
cnt = Counter()
for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
cnt[word] += 1
cnt
# Counter({'red': 2, 'blue': 3, 'green': 1})
import re
words = re.findall(r'\w+', open('hamlet.txt').read().lower())
Counter(words).most_common(10)
# [('the', 1091), ('and', 969), ('to', 767), ('of', 675), ('i', 633), ('a', 571), ('you', 558), ('my', 520), ('in', 451), ('it', 421)]
elements()
지정한 개수만큼 반복되는 요소를 임의의 순서로 반환한다. 1보다 작으면 무시한다.
c = Counter(a=4, b=2, c=0, d=-2)
c
# Counter({'a': 4, 'b': 2, 'c': 0, 'd': -2})
sorted(c.elements())
# ['a', 'a', 'a', 'a', 'b', 'b']
most_common([n])
값이 큰 순서대로 키와 값으로 이루어진 tuple을 최대 n건의 리스트로 반환한다.
Counter('abracadabra').most_common(3)
# [('a', 5), ('b', 2), ('r', 2)]
subtract([반복 가능 또는 맵핑])
iterable 또는 매핑 객체의 값을 뺀다.
c = Counter(a=4, b=2, c=0, d=-2)
d = Counter(a=1, b=2, c=3, d=4)
c.subtract(d)
c
# Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6})
sum(c.values()) # total of all counts
c.clear() # reset all counts
list(c) # list unique elements
set(c) # convert to a set
dict(c) # convert to a regular dictionary
c.items() # convert to a list of (elem, cnt) pairs
Counter(dict(list_of_pairs)) # convert from a list of (elem, cnt) pairs
c.most_common()[:-n-1:-1] # n least common elements
+c # remove zero and negative counts
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
c + d
# Counter({'a': 4, 'b': 3})
c - d
# Counter({'a': 2})
c & d
# Counter({'a': 1, 'b': 1})
c | d
# Counter({'a': 3, 'b': 2})
# 단항에서의 연산은 빈 카운터 객체와 연산을 한다.
c = Counter(a=2, b=-4)
+c
# Counter({'a': 2})
-c
# Counter({'b': 4})
deque objects
큐의 맨 앞과 끝에 데이터를 추가, 삭제하여 데이터 수와 관계없이 일정한 속도로 수행한다.(Double ended Queue)
method | desc |
---|---|
append(x) |
x를 오른쪽에 추가 |
appendleft(x) |
x를 왼쪽에 추가 |
clear() |
모든 elem를 제거하여 길이를 0 상태로 만듬 |
copy() |
얕은 복사 |
count(x) |
x와 같은 elem의 수를 반환 |
extend(iterable) |
iterable을 오른쪽에 추가 |
extendleft(iterable) |
iterable을 왼쪽에 추가 |
index(x[, start[, stop]]) |
x의 인덱스를 반환 / 찾지 못하면 ValueError |
insert(i, x) |
i 인덱스에 x를 삽입 / maxlen 이상으로 커지만 IndexError |
pop() |
오른쪽에서 elem를 제거하고 반환 / elem이 없으면 IndexError |
popleft() |
왼쪽에서 elem를 제거하고 반환 / elem이 없으면 IndexError |
remove(value) |
먼저 발견된 value를 제거 / 없으면 ValueError |
reverse() |
순서를 뒤집음 |
rotate(n=1) |
n이 양수이면 오른쪽, 음수이면 왼쪽으로 돌림 |
maxlen |
deque의 최대 크기를 반환 / 제한이 없으면 None |
3.5 버전부터
__add__()
,__mul__()
,__imul__()
도 지원
from collections import deque
d = deque('ghi') # make a new deque with three items
for elem in d: # iterate over the deque's elements
print(elem.upper())
# G
# H
# I
d.append('j')
d.appendleft('f')
d
# deque(['f', 'g', 'h', 'i', 'j'])
d.pop()
# 'j'
d.popleft()
# 'f'
list(d)
# ['g', 'h', 'i']
d[0]
#'g'
d[-1]
# 'i'
list(reversed(d))
# ['i', 'h', 'g']
'h' in d
# True
d.extend('jkl')
d
# deque(['g', 'h', 'i', 'j', 'k', 'l'])
d.rotate(1)
d
# deque(['l', 'g', 'h', 'i', 'j', 'k'])
d.rotate(-1)
d
# deque(['g', 'h', 'i', 'j', 'k', 'l'])
deque(reversed(d))
# deque(['l', 'k', 'j', 'i', 'h', 'g'])
d.clear()
d.pop()
# Traceback (most recent call last):
# File "<pyshell#6>", line 1, in -toplevel-
# d.pop()
# IndexError: pop from an empty deque
d.extendleft('abc')
d
# deque(['c', 'b', 'a'])
deque recipes
def tail(filename, n=10):
'Return the last n lines of a file'
with open(filename) as f:
return deque(f, n)
# 오른쪽으로 데이터를 추가하고, 왼쪽으로 팝하여, 최근 추가된 요소를 유지하여 사용할 수 있다.
def moving_average(iterable, n=3):
# moving_average([40, 30, 50, 46, 39, 44]) --> 40.0 42.0 45.0 43.0
it = iter(iterable)
d = deque(itertools.islice(it, n-1))
d.appendleft(0)
s = sum(d)
for elem in it:
s += elem - d.popleft()
d.append(elem)
yield s / n
# deque를 사용하여 round robin 스케줄러 구현
# https://ko.wikipedia.org/wiki/라운드_로빈_스케줄링
def roundrobin(*iterables):
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
iterators = deque(map(iter, iterables))
while iterators:
try:
while True:
yield next(iterators[0])
iterators.rotate(-1)
except StopIteration:
# Remove an exhausted iterator.
iterators.popleft()
def delete_nth(d, n):
d.rotate(-n)
d.popleft()
d.rotate(n)
defaultdict objects
등록되어 있지 않은 키를 호출해도 KeyError가 발생하지 않고, 지정된 기본값을 반환한다.
-
__missing__(key)
요청 된 키를 찾을 수 없을때 클래스의__getitem__()
에 의해__missing__()
이 호출된다. 기본 dict과 마찬가지로 None을 반환하는 대신 기본값으로default_factory
(인스턴스 변수)를 반환한다. -
default_factory
__missing__()
에서 사용된다.defaultdict
의 첫번째 인수로 지정한다. 생략하면 None으로 초기화되어 일반 dict와 마찬가지로 KeyError가 발생한다.(지정안하면 의미가 없음)
# default_factory를 지정하지 않으면 None으로 지정된다.
c = defaultdict(i=1)
c
# defaultdict(None, {'i': 1})
# default_factory를 지정하지 않고, 없는 키를 호출하면 KeyError가 발생한다.
c['j']
# KeyError: 'j'
# 같은 키의 요소를 연결하기_1
# dict.setdefault()보다 간단하고 빠르다.
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = defaultdict(list)
d
# defaultdict(list, {})
for k, v in s:
d[k].append(v)
d
# defaultdict(list, {'yellow': [1, 3], 'blue': [2, 4], 'red': [1]})
# 같은 키의 요소를 연결하기_2
# dict.setdefault()으로 구현
d = {}
for k, v in s:
d.setdefault(k, []).append(v)
sorted(d.items())
# [('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
# 기본값으로 0을 반환 할때는 인수를 int형 객체를 지정한다.
s = 'mississippi'
d = defaultdict(int)
for k in s:
d[k] += 1
sorted(d.items())
# [('i', 4), ('m', 1), ('p', 2), ('s', 4)]
# 람다 함수를 사용하여 더 빠르고 유연하게 구현할 수 있다.
def constant_factory(value):
return lambda: value
d = defaultdict(constant_factory('<missing>'))
d.update(name='Jone', action='ran')
'%(name)s %(action)s to %(object)s' % d
'Jone ran to <missing>'
# 인수를 set으로 지정한 예
s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 4)]
d = defaultdict(set)
for k, v in s:
d[k].add(v)
sorted(d.items())
# [('blue', {2, 4}), ('red', {1, 3})]
namedtuple()
tuple은 데이터를 그룹으로 관리할때 자주 사용한다. namedtuple()
은 정수 인덱스 뿐아니라 ,속성 이름을 지정하여 요소를 취득할 수 있는 튜플의 파생형을 제공한다.
collections.namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)
- typename : 생성할 튜플현의 이름
- filed_names : 튜플 요소 이름을 지정
- rename : True일때 잘못된 요소의 이름을 수정
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(11, y=22)
p
# Point(x=11, y=22)
p[0] + p[1]
# 33
x, y = p
x, y
# (11, 22)
p.x + p.y
# 33
namedtuple()
은 csv나 sqlite3에서 반환된 결과 튜즐에 이름을 할당하는데 특히 효과적!
EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')
import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
print(emp.name, emp.title)
import sqlite3
conn = sqlite3.connect('/companydata')
cursor = conn.cursor()
cursor.execute('SELECT name, age, title, department, paygrade FROM employees')
for emp in map(EmployeeRecord._make, cursor.fetchall()):
print(emp.name, emp.title)
namedtuple은 튜플에서 상속받은 메소드 외에도 3가지 메소드와 2가지 속성을 추가로 지원한다!
somenamedtuple._make(iterable)
기존 시퀀스에서 새 인스턴스를 반환
t = [11, 22]
Point._make(t)
# Point(x=11, y=22)
somenamedtuple._asdict()
요소의 이름과 값을 매핑한 OrderedDict 인스턴스를 반환
p = Point(x=11, y=22)
p._asdict()
# OrderedDict([('x', 11), ('y', 22)])
somenamedtuple._replace(**kwargs)
지정된 필드를 새로운 값으로 교체한 튜플의 새 인스턴스를 반환
p = Point(x=11, y=22)
p._replace(x=33)
# Point(x=33, y=22)
somenamedtuple._fields
필드 이름을 나열하는 문자열 튜플을 반환.
p._fields
# ('x', 'y')
# 이렇게 활용하면 됨!
Color = namedtuple('Color', 'red green blue')
Pixel = namedtuple('Pixel', Point._fields + Color._fields)
Pixel(11, 22, 128, 255, 0)
# Pixel(x=11, y=22, red=128, green=255, blue=0)
somenamedtuple._field_defaults
dict에 필드 이름을 기본값으로 매핑
Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
Account._field_defaults
# {'balance': 0}
Account('premium')
# Account(type='premium', balance=0)
# 지정된 필드를 검색
getattr(p, 'x')
# 11
# dict를 tuple으로 변환(unpacking)
d = {'x': 11, 'y': 22}
Point(**d)
# Point(x=11, y=22)
# sub class를 사용하여 기능을 추가나 변경할 수 있다.
# 계산 된 필드와 고정 너비, print 형식을 추가하는 예
class Point(namedtuple('Point', ['x', 'y'])):
__slots__ = () # 인스턴스 dict 작성으로 방지하여 메모리 요구사항을 낮게 유지
@property
def hypot(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self):
return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot())
for p in Point(3, 4), Point(14, 5/7):
print(p)
# Point: x= 3.000 y= 4.000 hypot= 5.000
# Point: x=14.000 y= 0.714 hypot=14.018
# 기존에 선언한 namedtuple의 필드에 새로운 필드를 추가한 튜플 유형을 구현
Point3D = namedtuple('Point3D', Point._fields + ('z',))
Point3D('a', 'v', 'z')
# Point3D(x='a', y='v', z='z')
# __doc__ 필드에 직접 할당하여 문서 문자열을 사용자 정의 할 수 있습니다 .
Book.__doc__ += ': Hardcover book in active collection'
Book.id.__doc__ = '13-digit ISBN'
Book.title.__doc__ = 'Title of first printing'
Book.authors.__doc__ = 'List of authors sorted by last name'
# _replace()를 사용하여 기본값을 구현할 수 있다.
Account = namedtuple('Account', 'owner balance transaction_count')
default_account = Account('<owner name>', 0.0, 0)
johns_account = default_account._replace(owner='Jone')
johns_account
# Account(owner='Jone', balance=0.0, transaction_count=0)
OrderedDict objects
삽입 순서를 기억하는 dict 객체이다. 초기값을 지정할때 시퀀스는 순서를 기억하지만, dict, 키워드 인수 등은 순서를 기억하지 않는다.
-
popitem(last=True)
마지막이 True이면 LIFO 순으로 리턴, False이면 FIFO 순으로 리턴한다. -
move_to_end(key, last=True)
key를 정렬된 dict의 끝으로 이동하는데, True이면 오른쪽으로 이동하고, False이면 처음으로 이동한다. key가 없으면 KeyError가 발생한다.
from collections import OrderedDict
d = OrderedDict.fromkeys('abcde')
d.move_to_end('b')
''.join(d.keys())
# 'acdeb'
d.move_to_end('b', last=False)
''.join(d.keys())
# 'bacde'
OrderedDict recipes
# 마지막으로 삽입 된 순서를 기억하는 정렬된 dict
class LastUpdatedOrderedDict(OrderedDict):
def __setitem__(self, key, value):
super().__setitem__(key, value)
super().move_to_end(key)
# 최대 크기일때 가장 최근 키를 제거
class LRU(OrderedDict):
def __init__(self, maxsize=128, *args, **kwds):
self.maxsize = maxsize
super().__init__(*args, **kwds)
def __getitem__(self, key):
value = super().__getitem__(key)
self.move_to_end(key)
return value
def __setitem__(self, key, value):
super().__setitem__(key, value)
if len(self) > self.maxsize:
oldest = next(iter(self))
del self[oldest]
Let me know what you think of this article in the comment section below!