| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- ASR
- RDBMS
- python기초
- 에이전트
- 데이터엔지니어
- Transformer
- 머신러닝
- python 기초
- 생성형 인공지능
- 자연어처리
- LangGraph
- 객체지향
- SQL
- 딥러닝
- CLIP
- Python
- 알고리즘
- 캐글
- 기초
- 소프트웨어 개발
- dementional reduction
- TTS
- UMAP
- CNN
- 데이터 시각화
- 힙정렬
- 트랜스포머
- 정보처리기사
- 랭그래프
- RNN
- Today
- Total
수달이네 기술 블로그
11.collections모듈(deque, OrderedDict, defaultdict, Counter, namedtuple) 본문
11.collections모듈(deque, OrderedDict, defaultdict, Counter, namedtuple)
슬픈 수달이 2025. 8. 29. 16:42collections모듈이란?
파이썬의 외장 자료구조 모듈로 앞에서 배운 자료구조들을 편리하고, 효율적으로 사용할 수 있도록 모아둔 라이브러리
from collections import *
# deque, OrderedDict, defaultdict, Counter, namedtuple
🔍deque 모듈
deque가 구현되어있는 모듈
deque: 스택과 큐를 모두 지원하는 모듈(double ended queue)
일반적인 큐와는 다르게 오른쪽 왼쪽 어디서나 append및 pop연산을 할 수 있다.
from collections import deque
deque_list = deque()
for i in range(5):
deque_list.append(i)
print(deque_list)
for i in range(5,10):
deque_list.appendleft(i)
print(deque_list)
print(deque_list.pop())
print(deque_list)
print(deque_list.popleft())
print(deque_list)
# deque([0, 1, 2, 3, 4])
# deque([9, 8, 7, 6, 5, 0, 1, 2, 3, 4])
# 4
# deque([9, 8, 7, 6, 5, 0, 1, 2, 3])
# 9
# deque([8, 7, 6, 5, 0, 1, 2, 3])
append(): 오른쪽으로 변수를 붙임
appendleft(): 왼쪽으로 변수를 붙임
pop(): 오른쪽의 변수를 반환하고 삭제
popleft(): 왼쪽의 변수를 반환하고 삭제
해당 colloections모듈에서의 deque은 환형 이중연결리스트의 특성을 가진 형태이다.
즉, 변수의 구조또한 다음 값의 주소, 이전값의 주소가 연결된 형태이며,
간단하게 주소값만 바꾸면 되는 형식으로 인덱스를 회전시키는 rotate및, 거꾸로 출력하는 reversed메서드를 지원한다.
from collections import deque
deque_list = deque()
for i in range(5):
deque_list.append(i)
print("rotate전 deque:",deque_list)
deque_list.rotate(2)
print("rotate후 deque:",deque_list)
# rotate전 deque: deque([0, 1, 2, 3, 4])
# rotate후 deque: deque([3, 4, 0, 1, 2])
또한 리스트에서 다른 리스트를 붙이는 함수로 extend()를 지원했는데 해당 deque에서도 마찬가지로 사용가능하다.
from collections import deque
deque_list = deque()
for i in range(5):
deque_list.append(i)
print(deque_list)
deque_list.extendleft([1, 2, 3])#왼쪽에 통째로 붙임
deque_list.extend([4, 5, 6])#오른쪽에 통째로 붙
print(deque_list)
# deque([0, 1, 2, 3, 4])
# deque([3, 2, 1, 0, 1, 2, 3, 4, 4, 5, 6])
extendleft(): 왼쪽에 리스트를 붙임
extend(): 오른쪽에 리스트를 붙임
또한 최대 길이를 지정할 수 있는 maxlen속성을 가지고 있다.
from collections import deque
deque_list = deque(maxlen=3)
for i in range(5):
deque_list.append(i)
print(deque_list)
# deque([2, 3, 4], maxlen=3)
위와 같이 초과된 입력의 경우 기존의 것을 삭제연산한다.
💡deque, list의 성능테스트
사실상 list또한 deque처럼 구현할 수 있긴하다. pop(0)을 이용하거나 insert(0)을 이용하여 구현할 수 있다.
그러면 왜 deque를 써야할까?
그건 시간에 있다.
from collections import deque
from time import time
deque_list = deque()
start = time()
for i in range(100000000):
deque_list.append(i)
end = time()
print("deque append 시간: ", end - start)
list = []
start = time()
for i in range(100000000):
list.append(i)
end = time()
print("list append 시간: ", end - start)
# deque append 시간: 5.854352712631226
# list append 시간: 10.346117496490479
append연산의 경우 2배정도의 시간이 차이나는 것을 확인했다.
이것은 만들어진 변수에 주소값만 연결해주는 연산을 하기 때문이다.
from collections import deque
from time import time
deque_list = deque()
start = time()
for i in range(100000):
deque_list.append(i)
end = time()
list = []
start = time()
for i in range(100000):
list.append(i)
end = time()
start = time()
for i in range(100000):
deque_list.popleft()
end = time()
print("deque pop 시간: ", end - start)
start = time()
for i in range(100000):
list.pop(0)
end = time()
print("list pop시간: ", end - start)
# deque pop 시간: 0.006000041961669922
# list pop시간: 9.850255012512207
pop연산의 경우 처음값을 pop해야하는 경우를 생각해보자,
위의 시간 결과로 보면 100배 이상 차이가 남을 알 수 있는데,
그 이유는 deque의 경우 연결 리스트 구조이기 때문에 앞의 주소를 끊어주기만 하면 되지만,
list의 경우 pop한 후 뒤의 요소들을 한칸씩 앞으로 당겨오는 연산을 추가로 해야하기 때문이다.
insert연산또한 마찬가지로 큰 시간차이가 날 것이다.
따라서 삽입 삭제가 많은 리스트가 필요한 경우, deque를 사용하는 것이 바람직하다.
🔍OrderedDict모듈
기존 딕셔너리는 순서를 보장하지 않는 객체였따. 하지만 OrderedDict모듈은 키의 순서를 보장하는 모듈이다.
from collections import OrderedDict
d = OrderedDict()
d['a'] = 1
d['x'] = 2
d['b'] = 3
d['z'] = 4
for k, v in d.items():
print(k, v)
# a 1
# x 2
# b 3
# z 4
위는 삽입 순서대로 출력되며,
# def sort_by_key(t):
# return t[0]
from collections import OrderedDict
d = dict()
d['a'] = 1
d['x'] = 2
d['b'] = 3
d['z'] = 4
for k, v in OrderedDict(sorted(d.items(), key = lambda t:t[0])).items():
print(k, v)
# a 1
# x 2
# b 3
# z 4
위는 정렬한 순서를 그대로 유지하며 출력된다.
그러나, 3.6버전 이후에선 딕셔너리 자체에서도 정렬된 값을 보장하도록 개선되었다.
하지만, OrderedDict를 사용하는 것이 호환성 측면에서 좋을 수 있다.
그렇다고 dict와 OrderedDict간의 차이가 없는 것은 아니다.
💡논리적 동등성
dic1 = {"a":1, "b":2, "c":3, "d":4}
dic2 = {"b":2, "c":3, "d":4, "a":1}
print(id(dic1), id(dic2))
print("dic1 == dic2?", dic1 == dic2)
# 2356952575424 2356952575616
# dic1 == dic2? True
위에서 볼 경우 주소와 정렬 순서 모두 둘이 다르나, 값이 같으므로 논리적으로 동등하다 하여 True를 출력한다. 그러나
OrderedDict를 보면
from collections import OrderedDict
dic1 = OrderedDict({"a":1, "b":2, "c":3, "d":4})
dic2 = OrderedDict({"b":2, "c":3, "d":4, "a":1})
print(id(dic1), id(dic2))
print("dic1 == dic2?", dic1 == dic2)
# 2508454175296 2508454175424
# dic1 == dic2? False
두개의 순서가 같지 않기 때문에 False를 출력함을 볼 수 있다.
한마디로 OrderedDict의 비교연산은 순서까지 보는 깐깐함을 알 수 있다.
🔍defaultdict 모듈
딕셔너리상 없는 값이라도 default값을 반환시켜주는 모듈
from collections import defaultdict
d = defaultdict(lambda: 0)
print(d["first"])
# 0
위처럼 dict의 key가 없는 값을 불러왔음에도 0이라는 기본 설정된 값을 출력한다.
즉, defualtdict의 역할은 기본값을 만들어서 보여주는 역할을 한다.
그렇기에 아래와 같이 선언되지 않은 키에도 연산이 가능하다.
from collections import defaultdict
d = defaultdict(int)
d['a'] += 10
print(d["a"])
# 10
위에서 보면 int가 들어가 있는데, 0으로 초기화 된다. list의 경우 빈 list[]를 반환해준다.
word = input('Enter a word: ')
counter = {}
for letter in word:
if letter not in counter:
counter[letter] = 0
counter[letter] += 1
print(counter)
# Enter a word: asdga
# {'a': 2, 's': 1, 'd': 1, 'g': 1}
원래는 반복문을 통해 딕셔너리에 새로운 값을 추가해야할 경우 위처럼 0으로 초기화를 해 준 후 값을 더해주어야 한다. 그러나
from collections import defaultdict
word = input('Enter a word: ')
counter = defaultdict(int)
for letter in word:
counter[letter] += 1
print(counter)
# Enter a word: asdfnlb
# defaultdict(<class 'int'>, {'a': 1, 's': 1, 'd': 1, 'f': 1, 'n': 1, 'l': 1, 'b': 1})
위처럼 초기화 따로 없이 구현할 수 있게 된다.
from collections import defaultdict
group_word = input("Enter a word: ").split()
groups = defaultdict(list)
for word in group_word:
length = len(word)
groups[length].append(word)
print(groups)
# Enter a word: a b c d sd eif si g dh
# defaultdict(<class 'list'>, {1: ['a', 'b', 'c', 'd', 'g'], 2: ['sd', 'si', 'dh'], 3: ['eif']})
또한 리스트를 기본값으로 주어 단어 길이 별로 묶어서 출력하는 것또한 가능하다.
defaultdict()가 아니더라도 기존 dict의 setdefault메서드를 사용하면
from collections import defaultdict
group_word = input("Enter a word: ").split()
groups = dict()
for word in group_word:
length = len(word)
groups.setdefault(length,[]).append(word)
print(groups)
# Enter a word: a b c d sd eif si g dh
# {1: ['a', 'b', 'c', 'd', 'g'], 2: ['sd', 'si', 'dh'], 3: ['eif']}
위와 같이 구현이 가능하지만,
계속해서 함수를 호출해야하는 점에서 defaultdict와 효율성이 다르다.
🔍Counter모듈
해당 모듈은 시퀀스 자료형의 데이터값의 개수를 딕셔너리 형태로 반환하는 자료구조이다.
from collections import Counter
text = input("Enter a sentence: ")
print(Counter(text))
# Enter a sentence: jasdfkjasfdjdk
# Counter({'j': 3, 'd': 3, 'a': 2, 's': 2, 'f': 2, 'k': 2})
이전에 했던 문자열의 개수를 세는 코드를 간단하게 한줄로 구현해낼 수 있다.
위와 같은 형태로도 구현이 가능하지만
from collections import Counter
c = Counter(a = 2, b = 3)
d = Counter({'b': 3, 'a': 2})
print(c)
print(d)
# Counter({'b': 3, 'a': 2})
# Counter({'b': 3, 'a': 2})
위와 같이 값=개수 형태로 만들어낼 수도 있으며,
딕셔너리 형태로도 만들어낼 수 있다.
from collections import Counter
c = Counter(a = 2, b = 3)
print(c)
print(sorted(c.elements()))
# Counter({'b': 3, 'a': 2})
# ['a', 'a', 'b', 'b', 'b']
elements()메서드는 Counter로 주어진 값의 요소에 해당하는 값을 풀어서 반환해주는 함수이다.
요소는 무작위로 반환하지만 1보다 작을 경우 출력하지 않는다.
from collections import Counter
a = Counter()
print(a)
a.update('hello')
print(a)
a.update({'l' : 3})
print(a)
# Counter()
# Counter({'l': 2, 'h': 1, 'e': 1, 'o': 1})
# Counter({'l': 5, 'h': 1, 'e': 1, 'o': 1})
위와 같이 update()메서드를 이용해 해당 요소의 개수를 더해줄 수 있다.
from collections import Counter
a = Counter("apple, orange, green, kick")
print(a)
print(a.most_common(3))
# Counter({'e': 4, ',': 3, ' ': 3, 'a': 2, 'p': 2, 'r': 2, 'n': 2, 'g': 2, 'k': 2, 'l': 1, 'o': 1, 'i': 1, 'c': 1})
# [('e', 4), (',', 3), (' ', 3)]
most_common()메서드를 이용해 가장 많이 나온 요소n개 만을 출력시킬 수 있다.
from collections import Counter
a = Counter("apple, orange")
b = Counter("green, kick")
a.subtract(b)
print(a)
#Counter({'a': 2, 'p': 2, 'l': 1, 'o': 1, 'e': 0, ',': 0, ' ': 0, 'r': 0, 'n': 0, 'g': 0, 'i': -1, 'c': -1, 'k': -2})
substract()메서드를 이용해 요소를 뺄 수도 있다.
+와 -를 통해서도 위를 구현할 수 있는데,
from collections import Counter
a = Counter("apple, orange")
b = Counter("green, kick")
a -= b
print(a)
#Counter({'a': 2, 'p': 2, 'l': 1, 'o': 1})
위처럼 구현 가능하다. 그러나 substract()와 다르게 음수를 출력하지 않는다.
from collections import Counter
a = Counter("apple, orange")
b = Counter("green, kick")
print(a & b)
# Counter({'e': 2, ',': 1, ' ': 1, 'r': 1, 'n': 1, 'g': 1})
위와 같이 교집합도 가능하다.
from collections import Counter
a = Counter("apple, orange")
b = Counter("green, kick")
print(a | b)
# Counter({'a': 2, 'p': 2, 'e': 2, 'k': 2, 'l': 1, ',': 1, ' ': 1, 'o': 1, 'r': 1, 'n': 1, 'g': 1, 'i': 1, 'c': 1})
합집합도 가능!
🔍 namedtuple 모듈
마치 c언어의 구조체와 같은 기능을 하는 모듈이다.
#일반적인 tuple
a = ('john', 28, '남')
b = ('sally', 24, '여')
for n in a,b:
print("%s는 %d세의 %s성입니다." %n)
# john는 28세의 남성입니다.
# sally는 24세의 여성입니다.
#namedtuple()을 사용하면
from collections import namedtuple
Person = namedtuple('Person', 'name age gender')
p1 = Person(name = "john", age = 28, gender = "남")
p2 = Person._make(['sally', 24, '여'])
for n in p1,p2:
print("%s는 %d세의 %s성입니다." %n)
# john는 28세의 남성입니다.
# sally는 24세의 여성입니다.
위처럼 구현하여 사용할 수 있으며,
구조체 Person을 만들어서 요소로 name, age, gender를 준것처럼 사용가능하다.
따라서
#namedtuple()을 사용하면
from collections import namedtuple
Person = namedtuple('Person', 'name age gender')
p1 = Person(name = "john", age = 28, gender = "남")
p2 = Person._make(['sally', 24, '여'])
print(p1.name)
# john
위같이 요소 하나를 출력할 수도 있다.
그러나 해당 값은 튜플이기 때문에, immutable객체이다 따라서
#namedtuple()을 사용하면
from collections import namedtuple
Person = namedtuple('Person', 'name age gender')
p1 = Person(name = "john", age = 28, gender = "남")
p2 = Person._make(['sally', 24, '여'])
p1.name = "hi"
print(p1.name)
# AttributeError: can't set attribute
그냥 변환하려 하면 오류가 나지만
replace()를 이용하여 (새로 할당하여)
#namedtuple()을 사용하면
from collections import namedtuple
Person = namedtuple('Person', 'name age gender')
p1 = Person(name = "john", age = 28, gender = "남")
p2 = Person._make(['sally', 24, '여'])
p1 = p1._replace(name = "hi")
print(p1.name)
# hi
위와 같이 변환 가능하다.
_fields를 이용하여 필드명을 tuple형식으로 리턴해줄수 있다.
#namedtuple()을 사용하면
from collections import namedtuple
Person = namedtuple('Person', 'name age gender')
p1 = Person(name = "john", age = 28, gender = "남")
p2 = Person._make(['sally', 24, '여'])
print(Person._fields)
for b in (p1, p2):
print(tuple([b.name, b.age, b.gender]))
# ('name', 'age', 'gender')
# ('john', 28, '남')
# ('sally', 24, '여')
아래와 같이 **연산자를 사용하여 dictionary를 namedtuple로 변환해줄수 있다.
#namedtuple()을 사용하면
from collections import namedtuple
Person = namedtuple('Person', 'name age gender')
p1 = Person(name = "john", age = 28, gender = "남")
dic = {'name': "tom",
'age': 22,
'gender': "남"}
p2 = Person(**dic)
print(p1)
print(p2)
# Person(name='john', age=28, gender='남')
# Person(name='tom', age=22, gender='남')'언어 > Python' 카테고리의 다른 글
| 13. 객체지향2(다중상속, 다형성, 추상클래스...) (2) | 2025.08.31 |
|---|---|
| 12. 객체지향 프로그래밍(클래스, 오버로딩, 오버라이딩, 상속 등..) (5) | 2025.08.30 |
| 10. 딕셔너리와 문자열 (4) | 2025.08.28 |
| 9. 자료구조 (9) | 2025.08.27 |
| 8. 리스트2(리스트 관련 함수들, 리스트 함축...) (6) | 2025.08.26 |