Notes & software courses · Free to learn
Aph's Blog
On this page

collections & itertools

👋 อ่านฟรีทั้งหมดบน Aph's Blog — เนื้อหาภาษาไทย ทำตามทีละหน้าใน sidebar ได้เลย หากมีข้อเสนอแนะหรืออยากให้เพิ่มหัวข้อไหน บอกได้เสมอ

เครื่องมือใน standard library ที่มืออาชีพใช้ทุกวัน — เขียนน้อยลง บั๊กน้อยลง

Python มีเครื่องมือสำเร็จรูปใน 2 โมดูลที่ช่วยให้งานซ้ำ ๆ สั้นลงและถูกต้องขึ้น: collections (โครงสร้างข้อมูลพิเศษ) และ itertools (เครื่องมือวนซ้ำ) การรู้จักมันคือสัญญาณของคนที่เขียน Python มาพอสมควร

Counter — นับความถี่

งานนับว่าอะไรเจอกี่ครั้ง ใช้ Counter แทนการเขียน dict เองทีละขั้น

python
from collections import Counter

words = ["a", "b", "a", "c", "a", "b"]
count = Counter(words)
print(count)              # Counter({'a': 3, 'b': 2, 'c': 1})
print(count["a"])         # 3
print(count.most_common(2))   # [('a', 3), ('b', 2)]  top-2

# นับตัวอักษรในข้อความก็ได้
print(Counter("banana"))  # Counter({'a': 3, 'n': 2, 'b': 1})

defaultdict — dict ที่มีค่า default

ปัญหาคลาสสิก: เพิ่มค่าใน dict ที่ key ยังไม่มี ต้องเช็คก่อนทุกครั้ง defaultdict ตั้งค่าเริ่มต้นให้อัตโนมัติ เหมาะกับการจัดกลุ่ม (grouping)

python
from collections import defaultdict

# จัดกลุ่มนักเรียนตามเกรด
students = [("Aph", "A"), ("Bee", "B"), ("Cha", "A")]
groups = defaultdict(list)        # ค่า default ของ key ใหม่ = list ว่าง
for name, grade in students:
    groups[grade].append(name)    # ไม่ต้องเช็คว่ามี key ไหม

print(dict(groups))   # {'A': ['Aph', 'Cha'], 'B': ['Bee']}

# นับด้วย defaultdict(int) ก็ได้
counts = defaultdict(int)
for ch in "banana":
    counts[ch] += 1               # key ใหม่เริ่มที่ 0 อัตโนมัติ

namedtuple & dataclass — ข้อมูลที่มีชื่อฟิลด์

แทนที่จะใช้ tuple ที่ต้องจำว่า index ไหนคืออะไร ใช้ namedtuple ให้แต่ละช่องมีชื่อ (สมัยใหม่นิยม dataclass ซึ่งจะเจอในบท type hints)

python
from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y)      # 3 4  (อ่านง่ายกว่า p[0], p[1])
print(p)             # Point(x=3, y=4)

deque — คิวสองหัวที่เร็ว

list ช้าเมื่อเพิ่ม/ลบหัวแถว (O(n)) deque ทำได้เร็ว (O(1)) ทั้งสองหัว เหมาะทำ queue (จะเจ​ออีกในบท Data Structures)

python
from collections import deque

q = deque([1, 2, 3])
q.append(4)          # ต่อท้าย -> [1, 2, 3, 4]
q.appendleft(0)      # ต่อหน้า -> [0, 1, 2, 3, 4]
print(q.popleft())   # 0  (ดึงหน้า เร็ว O(1))
print(q.pop())       # 4  (ดึงท้าย)

itertools — เครื่องมือวนซ้ำ

itertools มีฟังก์ชันสร้าง/รวม/จับคู่ลำดับที่ใช้บ่อย ทั้งหมดคืน iterator (ประหยัด memory ตามที่เรียนในหัวข้อ generator)

python
from itertools import chain, combinations, product, count

# chain: ต่อหลาย iterable เข้าด้วยกัน
print(list(chain([1, 2], [3, 4])))        # [1, 2, 3, 4]

# combinations: เลือก r ตัวจากชุด (ไม่สนลำดับ)
print(list(combinations(["a", "b", "c"], 2)))
# [('a','b'), ('a','c'), ('b','c')]

# product: ผลคูณคาร์ทีเซียน (ทุกการจับคู่)
print(list(product([1, 2], ["x", "y"])))
# [(1,'x'), (1,'y'), (2,'x'), (2,'y')]

# count: นับไม่รู้จบ (ใช้คู่ zip/break)
for i, ch in zip(count(1), "abc"):
    print(i, ch)    # 1 a / 2 b / 3 c
รู้จักไว้ ใช้เมื่อเจอโจทย์

ไม่ต้องท่องทุกฟังก์ชัน แค่จำว่า "นับความถี่ → Counter, จัดกลุ่ม → defaultdict, จับคู่/เลือกชุด → itertools" เมื่อเจอโจทย์แล้วจะนึกออกว่ามีของพร้อมใช้ ไม่ต้องเขียนเอง

สรุปหัวข้อนี้

  • Counter นับความถี่ + most_common(); defaultdict ตั้งค่า default ให้ key ใหม่
  • namedtuple ให้ tuple มีชื่อฟิลด์ (สมัยใหม่นิยม dataclass)
  • deque เพิ่ม/ลบสองหัวเร็ว O(1) เหมาะทำ queue
  • itertools: chain (ต่อ), combinations/product (จับคู่), count (นับไม่รู้จบ)
แบบฝึกหัด

1) ใช้ Counter หา 3 คำที่เจอบ่อยสุดในข้อความ 2) ใช้ defaultdict(list) จัดกลุ่มคำตามตัวอักษรแรก 3) สร้าง namedtuple ชื่อ Student มีฟิลด์ name, score 4) ใช้ combinations หาคู่ที่เป็นไปได้ทั้งหมดของ [1,2,3,4] เลือกทีละ 2