มอดูล collections ของไพธอนประกอบไปด้วยออบเจ็กต์จิปาถะที่อาจสะดวกที่จะใช้ในงานบางอย่าง เช่น collections.OrderedDict
ที่ได้เขียนไปใน
https://phyblas.hinaboshi.com/20190706
ในบทความนี้จะแนะนำออบเจ็กต์อีกตัวที่อาจมีโอกาสได้ใช้บ่อย คือ collections.Counter
Counter เป็นออบเจ็กต์ที่ใช้งานสะดวกในเวลาที่เราต้องการจะนับจำนวนของอะไรบางอย่างว่ามีอะไรอยู่เท่าไหร่
เช่นมีลิสต์ที่มีตัวเลขอยู่จำนวนหนึ่ง ต้องการจะนับดูว่ามีตัวเลขอะไรอยู่เท่าไหร่
ถ้าใช้ดิกชันนารีทั่วไปอาจเขียนได้แบบนี้
lislek = [12,10,11,12,14,11,11,13,11,10,13,12]
naplek = {}
for lek in lislek:
if(lek in naplek):
naplek[lek] += 1
else:
naplek[lek] = 1
print(naplek) # ได้ {12: 3, 10: 2, 11: 4, 14: 1, 13: 2}
แต่ถ้าใช้ Counter ก็จะทำได้โดยง่าย โดยแค่ใส่ลิสต์นั้นลงไปก็จะทำการนับให้เลย
from collections import Counter
lislek = [22,20,21,22,24,21,21,23,21,20,23,22]
naplek = Counter(lislek)
print(naplek) # ได้ Counter({21: 4, 22: 3, 20: 2, 23: 2, 24: 1})
ผลที่ได้จะได้เป็นออบเจ็กต์ Counter ที่เรียงลำดับตามจำนวนจากมากไปน้อยให้เลย
ออบเจ็กต์ Counter นี้โดยพื้นฐานแล้วมีส่วนคล้ายกับออบเจ็กต์ชนิดดิกชันนารี คือสามารถดูค่าได้โดยใส่คีย์
เพียงแต่ถ้าใส่คีย์ที่ไม่มีจะได้ค่า 0 และมีเมธอด .values() .keys() .items()
และสามารถแปลงเป็นดิกชันนารีได้โดยง่าย
print(naplek[22]) # ได้ 3
print(naplek[26]) # ได้ 0
print(naplek.keys()) # ได้ dict_keys([22, 20, 21, 24, 23])
print(naplek.values()) # ได้ dict_values([3, 2, 4, 1, 2])
print(naplek.items()) # ได้ dict_items([(22, 3), (20, 2), (21, 4), (24, 1), (23, 2)])
print(dict(naplek)) # ได้ {22: 3, 20: 2, 21: 4, 24: 1, 23: 2}
ตั้งแต่ไพธอน 3.6 เป็นต้นไปดิกชันนารีจะเรียงอย่างมีลำดับ ดังนั้น Counter
เมื่อแปลงเป็นดิกชันนารีแล้วลำดับในที่นี้จะเรียงตามลำดับก่อนหลังที่เริ่มเจอตัวแรก
เหมือนอย่างในตัวอย่างแรกที่วนไล่ด้วย for สร้างดิกชันนารีขึ้นมาเอง
Counter นอกจากจะใช้กับลิสต์แล้วก็ยังใช้กับสายอักขระได้ด้วย
โดยถ้าป้อนสายอักขระลงไปมันก็จะช่วยนับจำนวนให้ว่ามีอักษรตัวไหนอยู่กี่ตัว
napakson = Counter('จอดรถตรงตรอกยอมทนอดนอนอดกรนรอยลภมรดมดอกหอมบนขอนตรงคลองมอญ')
print(napakson) # ได้ Counter({'อ': 12, 'ร': 7, 'น': 6, 'ด': 5, 'ม': 5, 'ต': 3, 'ง': 3, 'ก': 3, 'ย': 2, 'ล': 2, 'จ': 1, 'ถ': 1, 'ท': 1, 'ภ': 1, 'ห': 1, 'บ': 1, 'ข': 1, 'ค': 1, 'ญ': 1})
Counter ที่สร้างมาแล้วสามารถเพิ่มข้อมูลลงไปได้ เช่นโดยใช้ +=
เช่นอาจลองสร้าง Counter ว่างเปล่าแล้วค่อยๆใส่สมาชิกเพิ่มเข้าไป โดยไล่นับเพิ่มไปเรื่อยๆเช่น
lislek = [2,1,4,5,2,6,2,6,3,2,4,1,2,2,3,2,1,4,2,4]
naplek = Counter()
for lek in lislek:
naplek[lek] += 1
print(naplek)
หรืออาจใช้เมธอด .update() เพื่อทำการเพิ่มข้อมูลใส่เข้าไปเรื่อยๆเช่น
napakson = Counter()
napakson.update('กนกพล')
print(napakson) # ได้ Counter({'ก': 2, 'น': 1, 'พ': 1, 'ล': 1})
napakson.update('คนตลก')
print(napakson) # ได้ Counter({'ก': 3, 'น': 2, 'ล': 2, 'พ': 1, 'ค': 1, 'ต': 1})
napakson.update('ตกคลอง')
print(napakson) # ได้ Counter({'ก': 4, 'ล': 3, 'น': 2, 'ค': 2, 'ต': 2, 'พ': 1, 'อ': 1, 'ง': 1})
นอกจากนี้ยังสามารถสร้างโดยแทนเลขจำนวนข้อมูลเข้าไป เช่นเดียวกับเวลาสร้างดิกชันนารีก็ได้
nap = Counter(a=7,b=2)
print(nap) # ได้ Counter({'a': 7, 'b': 2})
หรือจะสร้างโดยแปลงจากดิกชันนารีก็ได้
dic = {11: 2, 13: 4}
nap = Counter(dic)
print(nap) # ได้ Counter({13: 4, 11: 2})
กรณีที่สร้างด้วยวิธีนี้ ค่าอาจจะไม่ใช่ตัวเลขก็ได้ ไม่ทำให้เกิดข้อผิดพลาดอะไร แต่ถ้าตัวที่ไม่ใช่ตัวเลขถูกใส่ข้อมูลเพิ่มเช่นใช้เมธอด .update()
ในสภาพแบบนั้นก็จะเกิดข้อผิดพลาด
nap = Counter({"ก": "ข", "ค": "ง"})
print(nap) # ได้ Counter({'ค': 'ง', 'ก': 'ข'})
nap.update("ก") # ได้ TypeError: can only concatenate str (not "int") to str
ถ้าใช้เมธอด .most_common() จะได้ลิสต์ของแต่ละคู่โดยเรียงจากมากไปน้อยตามจำนวนลำดับที่กำหนด
แต่ถ้าไม่ได้ใส่เลขลำดับจะแสดงทั้งหมด
nap = Counter('สมพรสวดวนจนอรชรสองคนฉงนฉงวยงวยงงคอตกยอมนอนลงบนบก')
print(nap.most_common(3)) # ได้ [('น', 7), ('ง', 7), ('อ', 5)]
print(nap.most_common()) # ได้ [('น', 7), ('ง', 7), ('อ', 5), ('ว', 4), ('ส', 3), ('ร', 3), ('ย', 3), ('ม', 2), ('ค', 2), ('ฉ', 2), ('ก', 2), ('บ', 2), ('พ', 1), ('ด', 1), ('จ', 1), ('ช', 1), ('ต', 1), ('ล', 1)]
ถ้าใช้เมธอด .elements() จะได้อิเทอเรเตอร์ที่ไล่สมาชิกที่มีในนั้นทีละตัว
nap = Counter('ดวงกมลชงนมผงรอชมภมรบนดอน')
print(nap) # ได้ Counter({'ม': 4, 'ง': 3, 'น': 3, 'ด': 2, 'ช': 2, 'ร': 2, 'อ': 2, 'ว': 1, 'ก': 1, 'ล': 1, 'ผ': 1, 'ภ': 1, 'บ': 1})
el = nap.elements()
print(el) # ได้ <itertools.chain object at 0x10bbfae50>
print(list(el)) # ได้ ['ด', 'ด', 'ว', 'ง', 'ง', 'ง', 'ก', 'ม', 'ม', 'ม', 'ม', 'ล', 'ช', 'ช', 'น', 'น', 'น', 'ผ', 'ร', 'ร', 'อ', 'อ', 'ภ', 'บ']
กรณีที่สร้างโดยกำหนดจำนวนเข้าไปโดยตรงก็จะออกมาเรียงตามลำดับที่ใส่โดยตัวเดียวกันอยู่ติดกัน
nap = Counter(na=3,ka=4,ma=2)
print(list(nap.elements())) # ได้ ['na', 'na', 'na', 'ka', 'ka', 'ka', 'ka', 'ma', 'ma']
Counter สามารถนำมาบวกลบกันได้ โดยการบวกจะเป็นการเอาสมาชิกที่มีมารวมกัน
a = Counter('ปลวกจกหนอนลงคอ')
b = Counter('คงลอยคอลอยวน')
print(a) # ได้ Counter({'ล': 2, 'ก': 2, 'น': 2, 'อ': 2, 'ป': 1, 'ว': 1, 'จ': 1, 'ห': 1, 'ง': 1, 'ค': 1})
print(b) # ได้ Counter({'อ': 3, 'ค': 2, 'ล': 2, 'ย': 2, 'ง': 1, 'ว': 1, 'น': 1})
print(a+b) # ได้ Counter({'อ': 5, 'ล': 4, 'น': 3, 'ค': 3, 'ว': 2, 'ก': 2, 'ง': 2, 'ย': 2, 'ป': 1, 'จ': 1, 'ห': 1})
ส่วนการลบจะเอามาหักลบกัน เพียงแต่ถ้าติดลบหรือเป็น 0 จะหายไปเลย ไม่แสดง
a = Counter('พลพรรคครบสองคน')
b = Counter('ลงคอรอครอบครอง')
print(a) # ได้ Counter({'ร': 3, 'ค': 3, 'พ': 2, 'ล': 1, 'บ': 1, 'ส': 1, 'อ': 1, 'ง': 1, 'น': 1})
print(b) # ได้ Counter({'อ': 4, 'ค': 3, 'ร': 3, 'ง': 2, 'ล': 1, 'บ': 1})
print(a-b) # ได้ Counter({'พ': 2, 'ส': 1, 'น': 1})
print(b-a) # ได้ Counter({'อ': 3, 'ง': 1})
นอกจากนี้ยังมีคุณสมบัติแปลกๆเช่นถ้ามีจำนวนทั้งบวกและลบอยู่ในนี้ หากเจอเครื่องหมายบวกจะกำจัดตัวที่เป็นลบ
หากเจอเครื่องหมายลบจะเหลือแต่ตัวที่เป็นลบ และค่าจะกลายเป็นบวก
nap = Counter(ha=3,na=-2,ya=-4)
print(+nap) # ได้ Counter({'ha': 3})
print(-nap) # ได้ Counter({'ya': 4, 'na': 2})
สามารถใช้ตัวดำเนินการ & และ | ซึ่งจะให้ผลคล้ายกับการอินเตอร์เซ็กและยูเนียนกันของเซ็ต
a = Counter('สองสมรยอมลงคลองลอยคอ')
b = Counter('รอชวนภมรดมดอมดอกขจร')
print(a) # ได้ Counter({'อ': 5, 'ง': 3, 'ล': 3, 'ส': 2, 'ม': 2, 'ย': 2, 'ค': 2, 'ร': 1})
print(b) # ได้ Counter({'ร': 3, 'อ': 3, 'ม': 3, 'ด': 3, 'ช': 1, 'ว': 1, 'น': 1, 'ภ': 1, 'ก': 1, 'ข': 1, 'จ': 1})
print(a|b) # ได้ Counter({'อ': 5, 'ง': 3, 'ม': 3, 'ร': 3, 'ล': 3, 'ด': 3, 'ส': 2, 'ย': 2, 'ค': 2, 'ช': 1, 'ว': 1, 'น': 1, 'ภ': 1, 'ก': 1, 'ข': 1, 'จ': 1})
print(b&a) # ได้ Counter({'อ': 3, 'ม': 2, 'ร': 1})
เมธอด .subtract() จะให้ผลคล้ายกับการเอามาลบกัน แต่จะเป็นการเปลี่ยนแปลง Counter ตัวทางซ้าย (ตัวที่ใช้เมธอด)
แทนที่จะคืนค่า และจำนวนที่เป็น 0 และติดลบจะยังคงเหลืออยู่
a = Counter('ฝนตกตลอด')
b = Counter('ตกตรงตรอก')
print(a) # ได้ Counter({'ต': 2, 'ฝ': 1, 'น': 1, 'ก': 1, 'ล': 1, 'อ': 1, 'ด': 1})
print(b) # ได้ Counter({'ต': 3, 'ก': 2, 'ร': 2, 'ง': 1, 'อ': 1})
a.subtract(b)
print(a) # ได้ Counter({'ฝ': 1, 'น': 1, 'ล': 1, 'ด': 1, 'อ': 0, 'ต': -1, 'ก': -1, 'ง': -1, 'ร': -2})
a.subtract(b)
print(a) # ได้ Counter({'ฝ': 1, 'น': 1, 'ล': 1, 'ด': 1, 'อ': -1, 'ง': -2, 'ก': -3, 'ต': -4, 'ร': -4})
สุดท้ายขอยกตัวอย่างการนำมาประยุกต์ใช้งาน เช่นนับจำนวนเฉพาะที่เป็นองค์ประกอบในตัวเลข
def yaek(n):
nap = Counter()
i = 2
while(i*2<=n):
if(n%i==0):
nap[i] += 1
n = n//i
elif(i==2):
i += 1
else:
i += 2
if(n!=1):
nap[n] += 1
return nap
print(yaek(104)) # ได้ Counter({2: 3, 13: 1})
print(yaek(150)) # ได้ Counter({5: 2, 2: 1, 3: 1})
print(yaek(508)) # ได้ Counter({2: 2, 127: 1})
print(yaek(4500)) # ได้ Counter({5: 3, 2: 2, 3: 2})
print(yaek(16129)) # ได้ Counter({127: 2})
print(yaek(19600)) # ได้ Counter({2: 4, 5: 2, 7: 2})
print(yaek(51000)) # ได้ Counter({2: 3, 5: 3, 3: 1, 17: 1})