딕셔너리 기초 → Counter/lambda → 딕셔너리 정렬 → 연습 문제 6문제
| 작업 | Java (HashMap) | Python (dict) |
|---|---|---|
| 생성 | new HashMap<>() | {} 또는 dict() |
| 값 넣기 | m.put("a", 1) | d["a"] = 1 |
| 값 가져오기 | m.get("a") | d["a"] 또는 d.get("a") |
| 키 존재 확인 | m.containsKey("a") | "a" in d |
| 삭제 | m.remove("a") | del d["a"] |
| 전체 순회 | for (var e : m.entrySet()) | for k, v in d.items(): |
d["key"] vs d.get("key")d = {"apple": 3}
d["cherry"] # KeyError! 에러 발생
d.get("cherry") # None 반환
d.get("cherry", 0) # 0 반환 (기본값 지정)
get(key, 0)은 빈도 카운팅의 핵심 패턴!
freq = {}
for ch in "banana":
freq[ch] = freq.get(ch, 0) + 1
# {'b': 1, 'a': 3, 'n': 2}
from collections import Counter
counter = Counter("banana")
# Counter({'a': 3, 'n': 2, 'b': 1})
Counter는 자동으로 개수를 세어주는 특수 딕셔너리다. 일반 dict와 달리 없는 키에 접근해도 에러가 나지 않고 0을 반환한다.
most_common()counter = Counter(["apple", "banana", "apple", "cherry", "banana", "apple"])
counter.most_common(1) # [('apple', 3)] — 가장 많은 1개
counter.most_common() # 전부 빈도순 정렬
최빈값을 꺼낼 때는 튜플 언패킹을 활용한다:
most_item, most_count = counter.most_common(1)[0]
# most_item = 'apple', most_count = 3
→ counter.most_common(1)[0]의 결과는 'apple'이 아니라 ('apple', 3) 튜플 전체다. [0]은 리스트의 첫 번째 원소를 꺼낸 것이지, 튜플 안의 첫 번째 값을 꺼낸 게 아니다. 그리고 most_item, most_count = ('apple', 3) 이렇게 왼쪽에 변수를 쉼표로 나열하면 Python이 튜플을 자동으로 풀어서 순서대로 할당한다. 이것이 튜플 언패킹이라는 Python 고유 문법이다.
# 튜플 언패킹 — Java에는 없는 문법
x, y = (10, 20) # x=10, y=20
a, b, c = [1, 2, 3] # a=1, b=2, c=3
# for문에서도 자주 사용
for key, value in d.items():
print(key, value)
주의: 왼쪽 변수 개수와 오른쪽 값 개수가 다르면 ValueError!
→ 맞다. Counter는 입력 순서와 무관하게 항상 빈도 높은 순으로 저장한다. 그리고 sorted()의 결과가 튜플 리스트가 되는 건 맞는데, dict()로 감싸면 딕셔너리로 복원할 수 있다.
# sorted → 튜플 리스트
sorted(counter.items()) # [('a', 3), ('b', 1), ('n', 2)]
# dict()로 감싸면 → 딕셔너리 복원!
dict(sorted(counter.items())) # {'a': 3, 'b': 1, 'n': 2}
Python 3.7+에서는 일반 dict도 삽입 순서를 보장하므로, dict(sorted(...))하면 정렬된 순서가 유지된다. 단, Counter 타입이 아닌 일반 dict가 되므로 most_common() 같은 Counter 전용 메서드는 못 쓴다.
lambda는 이름 없는 한 줄짜리 함수. Java의 화살표 함수와 거의 같다.
// Java
(x) -> x[1]
# Python
lambda x: x[1]
lambda 매개변수: 반환값 — 이게 전부. return을 안 쓴다.
sorted()의 key 파라미터에 "뭘 기준으로 정렬할지"를 함수로 전달할 때 주로 사용:
people = [("철수", 25), ("영희", 20)]
sorted(people, key=lambda x: x[1])
# [('영희', 20), ('철수', 25)] — 나이순 정렬
| 목적 | 코드 |
|---|---|
| 키 오름차순 | sorted(d.items()) |
| 키 내림차순 | sorted(d.items(), reverse=True) |
| 값 오름차순 | sorted(d.items(), key=lambda x: x[1]) |
| 값 내림차순 | sorted(d.items(), key=lambda x: -x[1]) |
| 결과를 dict로 | dict(sorted(...)) |
딕셔너리를 순회하면서 항목을 삭제하면 RuntimeError가 발생한다.
# 에러 발생!
for key in scores:
if scores[key] < 60:
del scores[key] # RuntimeError
# 해결: 키 리스트 복사본을 순회
for key in list(scores): # list()로 키를 복사
if scores[key] < 60:
del scores[key] # 안전!
→ 맞다. 딕셔너리를 list(), for문, in 등에 넣으면 기본 동작이 키 기준이다. Java의 HashMap도 keySet()이 기본인 것처럼, Python은 아무것도 안 붙이면 키가 나온다.
d = {"a": 1, "b": 2}
list(d) # ['a', 'b'] — 키만 나옴
for x in d: # x는 키
"a" in d # 키에서 검색
# 값이나 쌍이 필요하면 명시적으로
list(d.values()) # [1, 2]
list(d.items()) # [('a', 1), ('b', 2)]
→ Counter끼리 +, - 연산, elements(), update() 등이 있긴 하다. 하지만 COS Pro에서 나오는 건 거의 most_common() 뿐이라 핵심만 다뤘다. 나머지는 Phase 2에서 실전 문제 풀다가 필요하면 그때 다시 정리할 예정.