เจาะลึกการใช้งาน List, Tuple, Dictionary และ Set ในภาษา Python

Nuttavut Thongjor

ข้อมูลถือเป็นสิ่งสำคัญในทุกภาษาไม่ว่านั่นจะเป็นภาษามนุษย์หรือภาษาโปรแกรมก็ตาม

วัตถุต่างๆบนโลกเราสามารถปาดนิ้วไปชี้พร้อมบอกว่า นี่คือหมู นั่นคือหมา และโน่นคือนายกได้ ลักษณะแบบนี้คือการบ่งชี้ข้อมูลตัวเดียว

นายกไม่ได้มีแค่คนเดียวฉันใด ข้อมูลก็ไม่จำเป็นต้องอยู่โดดเดี่ยวฉันนั้น เราสามารถรวมกลุ่มสิ่งของที่สัมพันธ์กันเพื่อจัดเป็นก้อนเดียวกันได้ เช่น ขีปนาวุธ 5 ลูก เครื่องบินรบอีก 9 ลำ พร้อมกับหมูคุโรบูตะอีกพันล้านตัว แบบนี้เราเรียกว่าคอลเล็กชัน (Collection)

คอลเล็กชันที่สำคัญในภาษา Python ที่เราจะหยิบยกมาพูดถึงกัน ได้แก่ List, Tuple, Dictionary และ Set และเนื่องจากบทความนี้เป็นบทความต่อเนื่องจาก เจาะลึก classes และ objects ใน Python 3 ผู้อ่านจึงควรอ่านบทความดังกล่าวก่อนครับ เพื่อป้องกันสภาวะตบะแตกที่จะพึงเจอได้ในบทความนี้

ชนิดข้อมูลแบบ Mutable และ Immutablea

ชนิดข้อมูลในภาษา Python นั้น หลักๆเราแบ่งได้เป็นสองประเภท คือ ชนิดข้อมูลแบบเปลี่ยนแปลงได้ (Mutable Types) และชนิดข้อมูลแบบเปลี่ยนแปลงไม่ได้ (Immutable Types)

str หรือข้อมูลประเภทข้อความเป็นตัวอย่างของชนิดข้อมูลแบบ Immutable เราเข้าถึงค่าข้อมูลของมันได้ แต่ไม่สามารถแก้ไขได้

Python
1s = 'Hello World'
2s[0] # 'H'
3s[0] = 'Y' # TypeError: 'str' object does not support item assignment

จากตัวอย่างข้างต้นเมื่อเราเข้าถึงค่า S[0] เราสามารถทำได้เพราะเป็นการดึงค่าอักขระตัวแรกออกมาจากข้อความ แต่เราไม่สามารถเขียนค่าทับไปที่ S[0] ได้ นั่นเพราะ str เป็น ชนิดข้อมูลแบบ Immutable นั่นเอง

ไม่ใช่สำหรับข้อมูลประเภท List ที่เป็นชนิดข้อมูลแบบ Mutable เราจึงสามารถเปลี่ยนแปลงค่าของมันได้

Python
1lst = ['A', 'B', 'Z']
2lst[2] = 'C' # lst เปลี่ยนเป็น ['A', 'B', 'C']

บทความนี้เราจะได้เรียนรู้คอลเล็กชันต่างๆ โดยมี list, dict และ set เป็นชนิดข้อมูลแบบ Mutable ส่วนของ tuple นั้นจะเป็นตัวอย่างของคอลเล็กชันที่มีประเภทเป็นชนิดข้อมูลแบบ Immutable นั่นเอง

ข้อมูลประเภท List

ลิสต์นั้นเป็นข้อมูลแบบ Mutable เราจึงสามารถแก้ไข เพิ่ม หรือลบ ข้อมูลออกจากลิสต์ได้

การสร้างลิสต์นั้นก็ไม่แตกต่างจากการสร้างอาร์เรย์ในภาษาอื่น เช่น

Python
1l1 = [1, 2, 3]
2l2 = ['Java', 'Python', 123] # สามารถสร้างลิสต์ที่เก็บข้อมูลต่างชนิดกันได้

นอกจากนี้เรายังสามารถสร้างลิสต์โดยอาศัยคอนสตรัคเตอร์ของคลาส list ได้ เช่น

Python
1l1 = list('abc') # ['a', 'b', 'c']

กลไกการเข้าถึงและเปลี่ยนแปลงค่าลิสต์ก็เป็นธรรมชาติแบบเดียวกับที่ปรากฎในภาษาอื่น

Python
1l = ['Java', 'Python', 'C++']
2print(l[0]) # Java
3
4l[1] = 'Ruby'
5print(l) # ['Java', 'Ruby', 'C++']

ทว่ารูปแบบการใช้งานเช่นนี้ในภาษาโปรแกรมอื่นเราเรียกว่าอาร์เรย์ แต่ทำไม Python จึงเรียกสิ่งนี้ว่าลิสต์? แท้จริงแล้วลิสต์เป็น Random Access แบบอาร์เรย์ หรือว่าเป็น Sequential Access แบบ Linked List กันแน่?

List เป็น Random Access และไม่ใช่ Linked List

วิธีการสร้างลิสต์แบบที่แสดงในตัวอย่างข้างต้นนั้นแท้จริงแล้วเราสามารถสร้างได้สองแบบ วิธีแรกคือการใช้ Linked List ดังนี้

Python
1lst = [1, 2, 3]

List as Linked List

จากรูปข้างต้นจะพบว่า lst เป็นเพียงชื่อที่อ้างอิงถึงส่วนหัวของลิสต์เท่านั้น เมื่อเราต้องการเข้าถึงข้อมูลตัวใดเราต้องไล่จากส่วนหัวไปเรื่อยๆจนกว่าจะพบข้อมูลตัวที่ต้องการ เช่น หากต้องการเลข 3 เราต้องวนไล่จากหัวของลิสต์ ผ่านเลข 1 และ 2 ก่อนจะค้นพบข้อมูลคือเลข 3 ในลำดับถัดไป วิธีการแบบนี้เราเรียกว่าเป็น Sequential Access

วิธีการสร้างลิสต์แบบ Sequential Access นั้นจะมีผลต่อประสิทธิภาพของการใช้งาน เราต้องไล่ค้นหาจากต้นลิสต์เสมอ และต้องไล่จากต้นลิสต์ไปจนถึงท้ายลิสต์ก่อนจะทำการเพิ่มของชิ้นถัดไปเพื่อต่อท้ายลิสต์เช่นกัน Python จึงเลือกที่จะสร้างลิสต์ด้วยวิธีการแบบที่สองคือ Random Access

CPython (Official Python ที่เราโหลดใช้งานกันจากเว็บหลัก) เลือกสร้างลิสต์ให้เหมือนอาร์เรย์ในภาษา C

List as Array

ในแต่ละช่องของลิสต์จะมีขนาดเท่าๆกัน โดย lst จะทำการชี้อยู่ที่หัวลิสต์ซึ่งมีที่อยู่ที่แน่นอนเป็น Memory Address ตามตัวอย่างคือ 0x123

ในการเข้าถึงอีลีเมนต์ตัวไหนของลิสต์เราจะใช้เวลาในการเข้าถึงเท่ากันทั้งสิ้น นั่นเพราะเราทราบจุดเริ่มต้นของลิสต์ (0x123) และทราบว่าแต่ละช่องของลิสต์มีขนาดเท่ากัน จึงสามารถเข้าสมการ 0x123 + (ความกว้างของช่อง x เลขช่องที่ต้องการเข้าถึง) เพื่อกระโดดไปยังช่องนั้นได้อย่างรวดเร็ว

จากบทความก่อนหน้า เราทราบกันไปแล้วว่าทุกสรรพสิ่งใน Python เป็นออบเจ็กต์ ดังนั้นสิ่งที่เก็บอยู่ในแต่ละช่องของลิสต์นั้นแท้จริงแล้วจึงไม่ใช่ตัวเลขดังที่ปรากฎ แต่มันคือตัวชี้ (pointer) ที่ชี้ไปหาออบเจ็กต์ของตัวเลขอีกทีนั่นเอง

List Item Reference

List กับ Dunder Methods

การเข้าถึงค่าต่างๆในลิสต์นั้น เราสามารถใช้ subscription ได้ด้วยการใส่ [] ดังนี้

Python
1lst = ['Python', 'Java', 'C#']
2
3lst[0] # Python

Python ไม่ได้สร้างไวยากรณ์ใหม่คือ [] ขึ้นมาเพื่อจำเพาะกับลิสต์เท่านั้น แต่ [] ยังสามารถนำไปใช้กับออบเจ็กต์อื่นๆได้หากออบเจ็กต์นั้นมีการสร้างเมธอด __getitem__ เช่น

Python
1class Double:
2 def __getitem__(self, index):
3 return index * 2
4
5double = Double()
6
7print(double[3]) # 6

คลาส Double มีการสร้างเมธอด __getitem__ เราจึงอนุมานได้ว่าเมื่อมีการเรียก [] เมธอดดังกล่าวจะถูกเรียกตาม ผลลัพธ์จากสิ่งที่คนกลับของเมธอดนี้ ก็จะเป็นผลลัพธ์ของการใช้งาน [] เช่นกัน

เราสามารถใช้ตัวดำเนินการ + และ * กับลิสต์ได้เช่นกัน นั่นเพราะลิสต์มีการอิมพลีเมนต์ __add__ และ __mul__ เอาไว้

Python
1lst = [1, 2, 3]
2l2 = lst * 2

เราพบว่าผลลัพธ์จากการดำเนินการดังกล่าวจะเป็นการสร้างลิสต์ใหม่ให้เกิดขึ้น มิใช่การแก้ไขบนลิสต์เดิม

List Multiplication

นอกจากการดำเนินการที่เป็นผลให้เกิดลิสต์ใหม่แล้ว Python ยังมีการดำเนินการประเภทอื่นที่เปลี่ยนแปลงโดยตรงบนลิสต์เดิม เช่น +=

Code
1lst = [1, 2, 3]
2lst += [4, 5, 6]

เช่นเดียวกับการบวกและการคูณ การบวกสะสม (+=) ก็เป็นการเรียก dunder method ที่ชื่อว่า __iadd__ เช่นกัน ผลลัพธ์จากการทำงานจะบวกสะสมไปที่ลิสต์ตัวเดิม ดังนี้

Augmented List Assignment

List กับ Slicing

การเข้าถึงค่าข้อมูลภายใต้ลิสต์เราไม่จำเป็นต้องเข้าถึงครั้งละหนึ่งตัว Python มีกลไกของการทำ slice เพื่อเข้าถึงข้อมูลภายใต้ลิสต์ครั้งละหลายๆตัวได้

Slicing นั้นคือการระบุว่าต้องการอีลีเมนต์ตัวที่เท่าไหร่ของลิสต์ โดยสามารถระบุเงื่อนไขได้ถึงสามตัวด้วยกัน คือ จุดเริ่มต้น จุดสิ้นสุด และ step

Python
1lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2
3lst[0:3] # [1, 2, 3] เอาข้อมูลตั้งแต่ index ช่องที่ 0 จนถึง 3 โดยไม่รวมช่องที่ 3
4lst[3:5] # [4, 5] เอาข้อมูลตั้งแต่ index ช่องที่ 3 จนถึง 5 โดยไม่รวมช่องที่ 5

นอกจากนี้เรายังสามารถระบุจำนวนลบเข้าไปใน slice ได้เช่นเดียวกัน โดย -1 จะหมายถึงอินเด็กซ์ของอีลีเมนต์ตัวสุดท้ายในลิสต์

List Index

เมธอดที่สำคัญของ List

ลิสต์นั้นมีเมธอดให้เรียกใช้งานอยู่มากมาย แต่สำหรับบทความนี้เราจะมาทำความรู้จักเฉพาะกับเมธอดและฟังก์ชันที่เรียกใช้งานกันบ่อยๆ

ทั้ง list, tuple, dict และ set เราสามารถหาจำนวนอีลีเมนต์ได้ด้วยการเรียกฟังก์ชัน len โดยฟังก์ชันดังกล่าว Python จะทำการเรียกเมธอด __len__ ภายใต้ออบเจ็กต์ของชนิดข้อมูลนั้นให้อีกที

Python
1len([1, 2, 3, 4]) # 4

เราสามารถที่จะทำการเพิ่มอีลีเมนต์ใหม่เข้าไปในลิสต์ได้ด้วยการต่อท้ายลิสต์เดิมผ่านการใช้ append

Python
1lst = [1, 2, 3]
2lst.append(4)
3
4print(lst) # [1, 2, 3, 4]

เราสามารถใช้ del เพื่อทำการลบสมาชิกในตำแหน่งต่างๆของลิสต์ หรือใช้เมธอด remove เพื่อลบค่าอีลีเมนต์ออกจากลิสต์นั้นก็ได้เช่นกัน

Python
1languages = ['Python', 'Java', 'C++', 'Ruby']
2
3del languages[1]
4print(languages) # ['Python', 'C++', 'Ruby']
5
6languages.remove('C++')
7print(languages) # ['Python', 'Ruby']

เมื่อมีการลบสมาชิกจากตำแหน่ง ก็ต้องมีการเพิ่มสมาชิกในตำแหน่งที่ระบุ เราสามารถใช้เมธอด insert เพื่อเพิ่มสมาชิกใหม่เข้าไปยังตำแหน่งที่ระบุได้ สมาชิกเดิมที่เคยอยู่ในตำแหน่งดังกล่าวก็จะเคลื่อนถัดไปอีกหนึ่งตำแหน่ง

Python
1languages = ['Python', 'Java', 'C++', 'Ruby']
2
3languages.insert(0, 'Elixir')
4print(languages) # ['Elixir', 'Python', 'Java', 'C++', 'Ruby']

reverse เป็นเมธอดสำหรับการเรียงลำดับแบบย้อนกลับของลิสต์

Python
1lst = [1, 2, 3]
2lst = lst.reverse()
3
4print(lst) # [3, 2, 1]

แต่หากเราต้องการเรียงลำดับลิสต์โดยไม่ได้เรียงย้อนกลับแบบ reverse เราสามารถใช้ sort หรือ sorted ได้ โดย sorted จะทำการสร้างผลลัพธ์จากการเรียงลำดับเป็นลิสต์ใหม่ ในขณะที่ sort จะทำการแก้ไขด้วยการเรียงลำดับบนลิสต์เดิม

Python
1lst = [4, 1, 3]
2
3print(sorted(lst)) # [1, 3, 4]
4print(lst) # [4, 1, 3]
5
6lst.sort()
7print(lst) # [1, 3, 4]

List Comprehension

ลิสต์นั้นเป็นข้อมูลแบบสายลำดับ (Sequential Types) เราจึงสามารถเข้าถึงข้อมูลแต่ละตัวของลิสต์แบบเป็นลำดับได้

เมื่อเราต้องการสร้างลิสต์ใหม่ด้วยการดำเนินการซักอย่างกับลิสต์ตัวเดิม เราสามารถวนลูปเพื่อเข้าถึงอีลีเมนต์แต่ละตัวของลิสต์เพื่อดำเนินการและสร้างผลลัพธ์ตามต้องการได้

Python
1lst = [1, 2, 3, 4, 5]
2new_lst = []
3
4for item in lst:
5 new_lst.append(item * 2)

จากตัวอย่างข้างต้น เราต้องการสร้างลิสต์ใหม่จากลิสต์เดิมด้วยการนำ 2 เข้าคูณทุกๆอีลีเมนต์ของลิสต์เดิม วิธีการนี้เราสามารถลดรูปเพื่อลดความซับซ้อนลงได้ด้วยการใช้ List Cpmprehension

List Comprehension เป็นวิธีการสร้างลิสต์แบบหนึ่งที่ช่วยให้เราสร้างลิสต์ใหม่ขึ้นมาได้ง่ายขึ้น จากตัวอย่างเดิมเราสามารถแก้ไขให้เป็นรูปแบบของ List Comprehension ได้ดังนี้

Python
1lst = [1, 2, 3, 4, 5]
2new_lst = [item * 2 for item in lst] # ทุกๆ item ใน lst ให้นำไปคูณสอง

Generator Expression

List Comprehension นั้นเป็นวิธีการสร้างลิสต์ขึ้นมาใหม่ทั้งก้อนทำให้เกิดผลเสียอย่างหนึ่งเมื่อเราไม่ต้องการใช้ข้อมูลทั้งหมดของลิสต์ในเวลานั้น

Python
1for item in [item * 2 for item in lst]:
2 print(item)

จากโค้ดข้างต้นพบว่าเราต้องการเข้าถึงค่าของ item ทีละตัว แต่การใช้ list comprehension นั้นจะเป็นการสร้างลิสต์ใหม่ทั้งหมดขึ้นมาก่อน หาก lst มีข้อมูลหนึ่งแสนตัว ลิสต์ใหม่จากการสร้างด้วย list comprehension ก็จะปรากฎแสนตัวก่อนการวนลูปเช่นกัน นับเป็นสิ่งที่สิ้นเปลืองเพราะเราต้องการข้อมูลแค่ครั้งละ item ไม่มีความจำเป็นใดๆที่ต้องสร้างลิสต์ทั้งหมดขึ้นมาก่อน

Generator Expression (ต่อไปนี้ขอเรียกว่า genexp) เกิดมาเพื่อแก้ไขปัญหานี้ การใช้งาน genexp เพียงแค่เปลี่ยนจาก [] เป็น () ดังนี้

Python
1for item in (item * 2 for item in lst):
2 print(item)

genexp จะทำการสร้างข้อมูลขึ้นทีละตัวเฉพาะส่วนที่ใช้ขณะนั้น เหตุนี้ในการวนลูปรอบแรกจึงเกิดการนำ item ตัวแรกของลิสต์เดิมมาคูณสองแล้วส่งผลลัพธ์ออกมาพิมพ์ โดยไม่มีการสร้างส่วนที่เหลือของลิสต์ไว้ในหน่วยความจำตั้งแต่แรก วิธีนี้จึงดีกว่าในแง่ของการใช้งานหน่วยความจำอย่างมีประสิทธิภาพสูงสุด

รู้จัก Tuple

เราทราบกันไปแล้วครับว่า List เป็นชนิดข้อมูลแบบ Sequential Types ที่มีความสามารถในการเปลี่ยนแปลงค่าได้

Tuple นั้นก็เป็น Sequential Types เช่นเดียวกับลิสต์ครับ เพียงแต่ Tuple เป็นชนิดข้อมูลแบบ Immutable นั่นหมายความว่าคุณจะไม่สามารถเปลี่ยนแปลงค่าของมันได้เลย

Python
1t = (1, 2, 3) # สามารถใช้ () เพื่อแทนการประกาศ tuple ได้
2t[0] = 4 # TypeError: 'tuple' object does not support item assignment

โดยธรรมชาติของ tuple นั้น ค่าต่างๆที่อยู่ภายใต้ตัวมันเราเปลี่ยนแปลงแก้ไขไม่ได้ แต่ทำไมตัวอย่างข้างล่างนี้จึงทำการเพิ่มค่าเข้าไปในลิสต์ที่เป็นอีลีเมนต์ตัวนึงของ tuple ได้ ?

Python
1t = (1, 2, [3, 4])
2t[2].append(5)
3
4print(t) # (1, 2, [3, 4, 5])

เรากล่าวว่า tuple แก้ไขค่าไม่ได้ แต่ตอนนี้เรากลับเพิ่มค่า 5 เข้าไปในลิสต์ที่อยู่ใต้ tuple ได้ซะงั้น?

List in Tuple

จากรูปข้างต้นเราพบว่าในอินเด็กซ์ช่องที่สองนั้นเราไม่ได้เก็บลิสต์ครับ แต่เราเก็บตัวชี้ (pointer) ไปหาลิสต์ต่างหาก เมื่อเราทำการเพิ่มเลข 5 เข้าไปในลิสต์ เราไม่ได้เปลี่ยนค่าของตัวชี้เลย จึงไม่ผิดคอนเซปต์ของ tuple ที่ห้ามเปลี่ยนค่า ตรงจุดนี้โค้ดของเราจึงทำงานได้อย่างราบลื่น

หากเราแก้ไขโค้ดของเราใหม่ ดังนี้

Python
1t = (1, 2, [3, 4])
2t[2] = [3, 4, 5] # TypeError: 'tuple' object does not support item assignment

แบบนี้จะพบบข้อผิดพลาดเกิดขึ้น นั่นเพราะเราทำการเปลี่ยนตัวชี้จากเดิมที่ชี้ไปยัง [3, 4] ให้ชี้ไปยังลิสต์ใหม่คือ [3, 4, 5] เมื่อมีการเปลี่ยนแปลงเกิดขึ้นจึงผิดคอนเซปต์ของ tuple ที่ห้ามแก้ไขค่าใดๆทั้งสิ้นนั่นเอง

Tuple ไม่ได้เป็นแค่ Immutable List

หลายคนมักเข้าใจว่า tuple แตกต่างจาก list เพียงแค่เป็น immutable แต่ความจริงนั้นยังมีอีกสิ่งที่ทำให้ tuple แตกต่างจาก list

เราทราบแล้วว่า tuple ไม่สามารภเปลี่ยนแปลงค่าได้ เมื่อตัวมันเองเปลี่ยนแปลงค่าไม่ได้และมีลำดับของข้อมูลที่ชัดเจน เราจึงกล่าวได้ว่าข้อมูลแต่ละตัวใน tuple สามารถใช้เพื่อสื่อความหมายได้

Python
1t = (404, 'Not Found')

เมื่อเราประกาศตัวแปร t ขึ้นมาเป็น tuple เรามั่นใจได้ว่าข้อมูลที่ t ชี้อยู่จะไม่ถูกเปลี่ยนแปลงแน่ๆ เราจึงกล่าวได้อย่างสนิทใจจากข้อเท็จจริงในข้อมูลได้ว่า t[0] คือ HTTP Status Code ในขณะที่ t[1] คือ HTTP Status Message

รู้จัก namedtuple

เพื่อให้ tuple ของเราสื่อความเป็น record ที่ใช้ในการเก็บข้อมูลเชิงความหมายมากขึ้น จึงสมควรที่เราจะใช้ namedtuple เพื่อประกาศชนิดข้อมูลใหม่ ดังนี้

Python
1from collections import namedtuple
2
3HTTPStatus = namedtuple('HTTPStatus', 'code message')
4
5res = HTTPStatus(code=404, message='Not Found')
6
7print(res.code) # 404
8print(res.message) # Not Found

จากตัวอย่างข้างต้น HTTPStatus จะเป็นชนิดข้อมูลใหม่ที่เก็บข้อมูลเชิง record ไว้สื่อความหมายถึง code และ message ของ HTTP Status นั่นเอง

Tuple Unpacking

เมื่อเราเขียน tuple ขึ้นมาในเชิง record เพื่อสื่อความหมายแต่ละค่าในตัวมัน จึงไม่ใช่เรื่องแปลกที่เราจะสามารถแกะค่าต่างๆภายใน tuple ออกมาใส่ตัวแปรได้

Python
1code, message = (404, 'Not Found')
2
3print(code) # 404
4print(message) # Not Found

ในความเป็นจริงแล้ว ไม่ใช่แค่ tuple ที่สามารถแงะข้อมูลภายในออกมาได้ list ก็สามารถดึงข้อมูลภายในออกมาในทำนองเดียวกันได้เช่นกัน

Python
1code, message = [404, 'Not Found']
2
3print(code) # 404
4print(message) # Not Found

สร้างข้อมูลแบบ key-value ด้วย Dictionary

list และ tuple นั้นใช้อินเด็กซ์เพื่อบ่งบอกการเข้าถึงค่าข้อมูลภายใน โดยอินเด็กซ์จะเป็นตัวเลขจำนวนเต็มเท่านั้น สำหรับภาษาอื่นเรามี Associative Array ที่ทำให้เราสามารถเข้าถึงค่าข้อมูลผ่าน key ที่ไม่จำเป็นต้องเป็นเลขจำนวนเต็ม

ภาษา Python สามารถสร้าง associative array ได้เช่นกันผ่าน dict โดยมีเงื่อนไขว่า key จะเป็นค่าอะไรก็ได้ขอแค่ให้เป็น hashable object ก็พอ

Python
1d = { 'key1': 'Hello', 'key2': 'World' } # สร้าง dict ผ่าน {}
2d['key1'] # Hello

รู้จัก Hashable Object

จากหัวข้อก่อนหน้า สิ่งที่จะเป็นคีย์สำหรับดิกชันนารีได้นั้นต้องเป็น hashable object เท่านั้น

hashable object คือออบเจ็กต์ที่มีค่าแฮชและเป็นค่าเดิมเสมอตลอดช่วงอายุของมัน

ชนิดข้อมูลแบบ Immutable ทั้งหลาย เช่น str หรือ tuple จะเป็น hashable ยกเว้น tuple นั้นมีข้อมูลแบบ mutable อื่นปนอยู่ด้วย

Python
1t1 = (1, 2, 3)
2hash(t1) # หาค่าได้
3
4t2 = (1, 2, [3, 4])
5hash(t2) # TypeError: unhashable type: 'list'

สำหรับ User-defined Types เช่นการสร้างคลาสและออบเจ็กต์ขึ้นมาเอง โดยปกติจะถือว่าเป็น hashable object นั่นเพราะค่าปกติของ hash เหล่านี้ก็คือค่า id ของออบเจ็กต์ซึ่งจะเป็นค่าเดิมเสมอตลอดช่วงอายุของมันนั่นเอง

โดยสรุปจึงกล่าวได้ว่า Numeric Types, str, boolean, tuple และ User-defined Types สามารถนำมาสร้างเป็นคีย์ของดิกชันนารีได้ นั่นเพราะชนิดข้อมูลเหล่านี้ล้วนเป็น hashable objects

Dictionary Comprehension

เมื่อลิสต์มี list comprehension จึงไม่แปลกที่ดิกชันนารีก็จะมี dictionary comprehension บ้าง

Python
1http_statuses = [
2 (200, 'OK'),
3 (201, 'Created'),
4 (404, 'Not Found')
5]
6
7d = {}
8
9for code, message in http_statuses:
10 d[message] = code
11
12# {'OK': 200, 'Created': 201, 'Not Found': 404}

ตัวอย่างข้างต้นเรามีลิสต์ของ tuple ที่เป็นคู่ของ code และ message ของ HTTP Status เราใช้ for เพื่อวนรอบในการเพิ่ม key และ value เข้าไปในดิกชันนารี d จากตัวอย่างนี้เราสามารถใช้ dictionary comprehension เพื่อสร้าง d ขึ้นมาแบบง่ายๆได้ดังนี้

Python
1http_statuses = [
2 (200, 'OK'),
3 (201, 'Created'),
4 (404, 'Not Found')
5]
6
7d = {message: code for code, message in http_statuses}

ง่ายใช่ไหมละ!

ชนิดข้อมูลประเภท Set

เซตทางคณิตศาสตร์หมายถึงกลุ่มของสิ่งใดๆที่มีเงื่อนไขแน่นอนว่าสิ่งใดอยู่ในกลุ่มนั้นและสิ่งใดไม่อยู่ในกลุ่มนั้น เช่น เซตของนางสาวไทย แบบนี้เรียกว่าเซต เพราะเราบอกได้ว่าใครเป็นนางสาวไทยบ้าง แต่กลุ่มของคนหล่อนั้นไม่เป็นเซต เพราะเราบอกไม่ได้ว่าคนหล่อคือใคร มันขึ้นอยู่กับมาตรฐานของแต่ละคน โดยข้อกำหนดหนึ่งของเซตคือสมาชิกภายในเซตจะไม่ซ้ำกัน

เราสามารถสร้างข้อมูลประเภทเซตได้ผ่านทางคอนสตรัคเตอร์ของคลาส set ดังนี้

Python
1s = set([1, 2, 3, 4, 2, 1, 1, 2]) # หรือสร้างจาก {1, 2, 3, 4}
2
3print(s) # {1, 2, 3, 4}

จากตัวอย่างข้างต้นเป็นการยืนยันว่าเซตจะประกอบด้วยสมาชิกที่ไม่ซ้ำค่ากัน คำถามคือเราจะรู้ได้อย่างไรว่าค่าสมาชิกที่เราจะเพิ่มเข้าไปใหม่ในเซตนั้นซ้ำกันหรือไม่?

สมาชิกภายใต้เซตต้องเป็น hashable object เพื่อให้เราบอกได้ว่าสมาชิกตัวที่ใส่ใหม่เป็นของที่มีอยู่แล้วหรือไม่นั่นเอง

ตัวดำเนินการระหว่างเซต

ตัวดำเนินการพื้นฐานของเซตที่เราจะพูดถึงกัน ได้แก่ union, intersection และ difference

ตัวดำเนินการ Union

การนำเซตสองเซตมา union กันนั้นคือการรวมสมาชิกของทั้งสองเซตเข้าด้วยกัน เราสามารถสร้างเซตใหม่จากการรวมสมาชิกในเซตทั้งสองได้ผ่านการเรียกเมธอด union หรือการใช้ |

Python
1s1 = {1, 2, 3, 4}
2s2 = {4, 5, 6}
3
4print(s1.union(s2)) # {1, 2, 3, 4, 5, 6}
5print(s1 | s2) # {1, 2, 3, 4, 5, 6}

ตัวดำเนินการ intersection

Intersection คือการหาจุดร่วมของสมาชิกระหว่างเซต ผลลัพธ์จากการดำเนินการจะได้เซตใหม่ที่ประกอบด้วยสมาชิกที่ปรากฎในเซตทั้งสอง เราสามารถใช้เมธอด intersection หรือ & ได้เพื่อทำ intersection

Python
1s1 = {1, 2, 3, 4}
2s2 = {4, 5, 6}
3
4print(s1.intersection(s2)) # {4}
5print(s1 & s2) # {4}

ตัวดำเนินการ difference

ผลต่างของเซตคือ difference เป็นการสร้างเซตใหม่โดยลบสมาชิกที่ปรากฎในเซตที่สองออกจากเซตตั้งต้น การหาผลต่างนี้สามารถทำผ่านเมธอด difference หรือ - ก็ย่อมได้

Python
1s1 = {1, 2, 3, 4}
2s2 = {4, 5, 6}
3
4print(s1.difference(s2)) # {1, 2, 3}
5print(s1 - s2) # {1, 2, 3}

สรุป

บทความนี้ได้พาเพื่อนๆไปรู้จักกับชนิดข้อมูลแบบกลุ่มต่างๆของ Python ในความเป็นจริงแล้ว Python ยังมีข้อมูลกลุ่มต่างๆอีกมากครับ เช่น defaultdict, frozenset เป็นต้น เพื่อนๆที่สนใจสามารถอ่านรายละเอียดเพิ่มเติมจากเอกสารหลักของ Python3 ได้ครับ

สารบัญ

สารบัญ

  • ชนิดข้อมูลแบบ Mutable และ Immutablea
  • ข้อมูลประเภท List
  • List เป็น Random Access และไม่ใช่ Linked List
  • List กับ Dunder Methods
  • List กับ Slicing
  • เมธอดที่สำคัญของ List
  • List Comprehension
  • Generator Expression
  • รู้จัก Tuple
  • Tuple ไม่ได้เป็นแค่ Immutable List
  • รู้จัก namedtuple
  • Tuple Unpacking
  • สร้างข้อมูลแบบ key-value ด้วย Dictionary
  • รู้จัก Hashable Object
  • Dictionary Comprehension
  • ชนิดข้อมูลประเภท Set
  • ตัวดำเนินการระหว่างเซต
  • สรุป