2.10. เซต#
เซต คือคอลเลกชันของรายการที่ไม่ซ้ำกันและไม่มีลำดับ การเพิ่มค่าที่มีอยู่แล้วไม่มีผลใดๆ การวนซ้ำจะให้ค่าแต่ละค่าหนึ่งครั้ง เซตเป็นเครื่องมือที่เหมาะสมเมื่อการตรวจสอบสมาชิกและการกำจัดซ้ำมีความสำคัญ และลำดับไม่สำคัญ
2.10.1. การสร้างเซต#
ใช้วงเล็บปีกกาสำหรับเซตที่ไม่ว่าง หรือ set() สำหรับเซตว่าง:
colours = {"red", "green", "blue"}
empty = set()
วงเล็บปีกกาดูเหมือนลิเทอรัล dict และ {} เพียงอย่างเดียวคือ dict ว่างเปล่า ไม่ใช่เซตว่างเปล่า ซึ่งเป็นหนึ่งในความไม่สม่ำเสมอของ Python ใช้ set() สำหรับกรณีว่าง
set() ยังสร้างเซตจาก iterable ใดก็ได้ ซึ่งเป็นวิธีมาตรฐานในการลบซ้ำออกจากลำดับ:
nums = [1, 2, 2, 3, 1, 4]
unique = set(nums)
print(unique)
ผลลัพธ์:
{1, 2, 3, 4}
ลำดับการพิมพ์อาจแตกต่างกัน เซตไม่รับประกันการวนซ้ำตามลำดับใดเป็นพิเศษ
2.10.2. เซตกับ dict#
เซตและ dict ทั้งคู่เก็บรายการที่ไม่ซ้ำกันในตารางแฮช สิ่งที่แต่ละรายการพาติดตัวไปด้วยคือความแตกต่าง:
dictเก็บ คู่คีย์-ค่า การค้นหาคีย์คืนค่าของมันsetเก็บ เฉพาะรายการ การค้นหารายการบอกว่ามันอยู่ที่นั่นหรือไม่
การเลือกระหว่างสองแบบขึ้นอยู่กับว่า ค่าที่อยู่ข้างแต่ละรายการ มีความหมายหรือไม่:
ใช้ เซต เมื่อไม่มีค่าที่ต้องเก็บไว้ข้างแต่ละรายการ คุณสนใจเพียงว่ารายการนั้นมีอยู่หรือไม่ หรือคุณกำลังรวมกลุ่มของรายการที่ไม่ซ้ำกันด้วย union / intersection
ใช้ dict เมื่อแต่ละรายการจับคู่กับข้อมูลที่การค้นหาต้องการดึงออกมา เช่น แผนที่ config แคช ตัวนับที่คีย์ด้วยชื่อ
สองประเภทนี้ใช้ไวยากรณ์พื้นผิวร่วมกันมาก ซึ่งเป็นที่มาของความสับสนส่วนใหญ่ ความแตกต่างในบล็อกเดียว:
set | dict | |
|---|---|---|
เก็บ | รายการที่ไม่ซ้ำกัน | คีย์ที่ไม่ซ้ำกัน แต่ละคีย์มีค่า |
ลิเทอรัลที่มีข้อมูล |
|
|
ลิเทอรัลว่าง |
|
|
ทดสอบสมาชิก |
|
|
ดึงค่า | ไม่มี |
|
เพิ่มรายการ |
|
|
วนซ้ำ | ให้รายการ | ให้คีย์ (ใช้ |
ความไม่สมมาตรระหว่างลิเทอรัลที่มีข้อมูลและลิเทอรัลว่างเป็นประเด็นที่ควรระบุ:
วงเล็บปีกกาที่มี รายการอยู่ภายใน --
{1, 2, 3}-- คือลิเทอรัลเซต วงเล็บปีกกาที่มี คู่คีย์-ค่า --{"a": 1}-- คือลิเทอรัล dict ตัวแยกวิเคราะห์แยกแยะด้วยสิ่งที่อยู่ภายในวงเล็บปีกกาที่ ไม่มีอะไรอยู่ภายใน --
{}-- คือ dict ว่างเปล่า ไม่ใช่เซตว่างเปล่า dict มาก่อน ดังนั้นลิเทอรัลว่างจึงเป็นของ dict เซตว่างไม่มีลิเทอรัลวงเล็บปีกกาเลยและต้องเขียนเป็นset()
รูปแบบทั่วไปเมื่ออ่านเฉพาะคีย์ของ dict คือการเปลี่ยนไปใช้เซต ซึ่งทำให้เจตนาชัดเจนและตัดค่าที่ไม่ใช้ออกจากหน่วยความจำ
2.10.3. การเพิ่มและลบ#
set.add()-- แทรกรายการหนึ่งset.discard()-- ลบรายการหากมีอยู่ ไม่ทำอะไรหากไม่มีset.remove()-- ลบรายการ หากไม่มีจะเกิดKeyErrorset.clear()-- ทำให้เซตว่าง
s = {1, 2, 3}
s.add(4)
s.discard(99) # silent: 99 not in s
s.remove(2)
print(s)
ผลลัพธ์:
{1, 3, 4}
2.10.4. การตรวจสอบสมาชิก#
ตัวดำเนินการ in ทดสอบการเป็นสมาชิก บนเซตจะใช้เวลาคงที่โดยไม่ขึ้นกับขนาด ซึ่งเป็นเหตุผลหลักในการเลือกเซตแทน list เมื่อคุณต้องการถามเพียงว่า "ค่านี้อยู่ในนั้นหรือไม่":
if "red" in colours:
print("colour is allowed")
list ที่มีเนื้อหาเดียวกันจะสแกนจากต้นทุกครั้ง ซึ่งเหมาะสำหรับสิบรายการแต่ช้าสำหรับหมื่นรายการ
2.10.5. การดำเนินการเซต#
เซตสองชุดสามารถรวมกันด้วยการดำเนินการทางคณิตศาสตร์ปกติ แต่ละอย่างมีทั้งรูปแบบตัวดำเนินการและรูปแบบเมธอด:
a | bหรือa.union(b)-- ทุกอย่างในเซตใดเซตหนึ่งa & bหรือa.intersection(b)-- เฉพาะสิ่งที่ปรากฏในทั้งสองเซตa - bหรือa.difference(b)-- อยู่ในaแต่ไม่ในba ^ bหรือa.symmetric_difference(b)-- อยู่ในเซตหนึ่งแต่ไม่ใช่ทั้งคู่
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)
print(a & b)
print(a - b)
print(a ^ b)
ผลลัพธ์:
{1, 2, 3, 4, 5, 6}
{3, 4}
{1, 2}
{1, 2, 5, 6}
รูปแบบตัวดำเนินการเป็นแบบอ่านอย่างเดียว รูปแบบเมธอดรับ iterable ใดก็ได้ทางขวา ไม่ใช่แค่เซตอีกชุด (a.union([5, 6])) เลือกแบบที่อ่านได้ดีกว่าในบริบท
2.10.6. อะไรสามารถอยู่ในเซตได้#
องค์ประกอบเซตต้องสามารถ แฮช ได้ ซึ่งเป็นข้อจำกัดเดียวกับคีย์ dict ประเภท int, float, str, bool, bytes และ tuple (เมื่อเนื้อหาสามารถแฮชได้เอง) ล้วนใช้ได้ แต่ list และ dict ไม่ได้ การพยายามเพิ่มจะเกิด TypeError
2.10.7. frozenset#
set ปกติสามารถเปลี่ยนแปลงได้ ทุกการเรียก add / remove / discard จะเปลี่ยนออบเจกต์ในที่เดียวกัน ความสามารถในการเปลี่ยนแปลงนี้ทำให้มันไม่สามารถแฮชได้ ดังนั้นเซต ไม่สามารถ ใช้เป็นคีย์ dict หรือสมาชิกของเซตอื่น
frozenset คือคู่ที่ไม่เปลี่ยนแปลง มีการค้นหาและตัวดำเนินการ (in, |, &, -, ^) เหมือนกับ set แต่ไม่มี add / remove และไม่มีเมธอดที่แก้ไข เนื่องจากไม่มีอะไรเปลี่ยนเนื้อหาได้ แฮชของ frozenset จึงมีนิยามชัดเจน ดังนั้นมัน สามารถ แฮชได้:
primary = frozenset({"red", "green", "blue"})
secondary = frozenset({"yellow", "purple", "orange"})
palettes = {
primary: "RGB",
secondary: "mixed",
}
print(palettes[primary])
ผลลัพธ์:
RGB
สร้าง frozenset จาก iterable ใดก็ได้ ใช้ frozenset() สำหรับกรณีว่าง และ frozenset(some_set) เพื่อถ่ายสแนปช็อตที่ไม่เปลี่ยนแปลงของเซตที่มีอยู่:
snapshot = frozenset(s) # immutable copy of s
s.add("new") # snapshot does not change
เหตุผลทั่วไปสองประการในการเลือกใช้:
ใช้เป็นคีย์ dict หรือสมาชิกเซต ทุกที่ที่ค่าเดียวไม่สามารถรองรับสิ่งที่คุณต้องการได้
frozensetของค่าสามารถทำได้ เช่น "เซตของลักษณะเด่นที่ไดรเวอร์นี้รองรับ" หรือ "เซตของพินที่โปรไฟล์นี้ใช้"ล็อคค่าคงที่
frozensetระดับโมดูลของชื่อที่อนุญาตไม่สามารถถูกแก้ไขโดยบังเอิญโดยผู้เรียก แต่setปกติสามารถถูกแก้ไขได้ ควรใช้frozensetสำหรับสิ่งที่ตั้งใจให้อ่านอย่างเดียวหลังการสร้าง