เรามาพูดถึงหัวข้อนี้ด้วยกรณีศึกษาและวิเคราะห์ว่าโปรแกรมเมอร์ในระดับต่างๆ จัดการกับปัญหานี้อย่างไร
รูปภาพที่คุณได้รับข้อมูลนักท่องเที่ยวสองรายการจากบริษัทท่องเที่ยวดังนี้:
tourists_visited_hk = [ {"first_name": "Sirena", "last_name": "Gross", "phone_number": "650-568-0388", "date_visited": "2018-03-14"}, {"first_name": "James", "last_name": "Ashcraft", "phone_number": "412-334-4380", "date_visited": "2014-09-16"}, {"first_name": "Emma", "last_name": "Johnson", "phone_number": "503-221-8792", "date_visited": "2020-06-02"}, {"first_name": "Oliver", "last_name": "Smith", "phone_number": "281-555-9012", "date_visited": "2019-11-30"}, {"first_name": "Sophia", "last_name": "Lee", "phone_number": "786-555-2468", "date_visited": "2017-08-25"}, {"first_name": "Benjamin", "last_name": "Brown", "phone_number": "408-555-7890", "date_visited": "2016-05-12"}, {"first_name": "Isabella", "last_name": "Davis", "phone_number": "917-555-1234", "date_visited": "2021-02-19"}, {"first_name": "Ethan", "last_name": "Wilson", "phone_number": "972-555-5678", "date_visited": "2015-07-08"}, {"first_name": "Ava", "last_name": "Thompson", "phone_number": "213-555-9876", "date_visited": "2013-12-23"}, {"first_name": "Noah", "last_name": "Martinez", "phone_number": "619-555-6543", "date_visited": "2018-09-10"} ] tourists_visited_nyc = [ {"first_name": "Justin", "last_name": "Malcom", "phone_number": "267-282-1964", "date_visited": "2011-03-13"}, {"first_name": "Albert", "last_name": "Potter", "phone_number": "702-249-3714", "date_visited": "2013-09-11"}, {"first_name": "Sirena", "last_name": "Gross", "phone_number": "650-568-0388", "date_visited": "2018-03-14"}, {"first_name": "Oliver", "last_name": "Smith", "phone_number": "281-555-9012", "date_visited": "2019-11-30"}, {"first_name": "Sophia", "last_name": "Lee", "phone_number": "786-555-2468", "date_visited": "2017-08-25"}, {"first_name": "Ethan", "last_name": "Wilson", "phone_number": "972-555-5678", "date_visited": "2015-07-08"}, {"first_name": "Mia", "last_name": "Anderson", "phone_number": "512-555-7890", "date_visited": "2017-04-05"}, {"first_name": "Liam", "last_name": "Taylor", "phone_number": "714-555-1234", "date_visited": "2019-11-18"}, {"first_name": "Charlotte", "last_name": "Wilson", "phone_number": "203-555-5678", "date_visited": "2016-03-22"} ]
ปัญหา:
เราต้องการค้นหานักท่องเที่ยวที่เคยมาเยือนฮ่องกงแต่ไม่ใช่นิวยอร์ก และทำโปรโมชั่นการเดินทางให้กับนักท่องเที่ยวเหล่านี้เมื่อมาเยือนนิวยอร์ก
โซลูชันที่ 1 (รุ่นน้องและรุ่นเดรัจฉาน):
def find_potential_customers_v1(): for hk_record in tourists_visited_hk: is_potential = True for nyc_record in tourists_visited_nyc: if hk_record['first_name'] == nyc_record['first_name'] and \ hk_record['last_name'] == nyc_record['last_name'] and \ hk_record['phone_number'] == nyc_record['phone_number']: is_potential = False break if is_potential: yield hk_record
ความซับซ้อนของเวลาของโซลูชันข้างต้นคือ O(m * n) ซึ่งมีค่าใช้จ่ายค่อนข้างสูง
โซลูชันที่ 2 (โปรแกรมเมอร์ระดับกลาง — ปรับให้เหมาะสมด้วยชุด)
def find_potential_customers_v2(): nyc_record_idx = { (rec['first_name'], rec['last_name'], rec['phone_number']) for rec in tourists_visited_nyc } for rec in tourists_visited_hk: key = (rec['first_name'], rec['last_name'], rec['phone_number']) if key not in nyc_record_idx: yield rec
ความซับซ้อนของเวลา: O(m + n) ตอนนี้ดีขึ้นแล้ว แต่ยังดีไม่พอ
โซลูชันที่ 3 (เวอร์ชันโปรแกรมเมอร์ที่มีทักษะ)
เราต้องคิดใหม่เกี่ยวกับปัญหาและสรุปด้วยปัญหาทางคณิตศาสตร์: A-B
ปัญหาเดียวกันกับปัญหาต่อไปนี้:
>>> a = {1, 3, 5, 7} >>> b = {3, 5, 8} >>> a - b {1, 7}
ดังนั้นวิธีแก้ปัญหาของเราอาจเป็น:
class VisitRecord: def __init__(self, first_name, last_name, phone_number, date_visited): self.first_name = first_name self.last_name = last_name self.phone_number = phone_number self.date_visited = date_visited def __hash__(self): return hash( (self.first_name, self.last_name, self.phone_number) ) def __eq__(self, other): if isinstance(other, VisitRecord) and hash(other) == hash(self): return True return False def __repr__(self): return f"VisitRecord(first_name={self.first_name}, last_name={self.last_name}, phone_number={self.phone_number}, date_visited={self.date_visited})\n" def find_potential_customers_v3(): return set(VisitRecord(**r) for r in tourists_visited_hk) - \ set(VisitRecord(**r) for r in tourists_visited_nyc) # Output {VisitRecord(first_name=James, last_name=Ashcraft, phone_number=412-334-4380, date_visited=2014-09-16) , VisitRecord(first_name=Ava, last_name=Thompson, phone_number=213-555-9876, date_visited=2013-12-23) , VisitRecord(first_name=Benjamin, last_name=Brown, phone_number=408-555-7890, date_visited=2016-05-12) , VisitRecord(first_name=Emma, last_name=Johnson, phone_number=503-221-8792, date_visited=2020-06-02) , VisitRecord(first_name=Noah, last_name=Martinez, phone_number=619-555-6543, date_visited=2018-09-10) , VisitRecord(first_name=Isabella, last_name=Davis, phone_number=917-555-1234, date_visited=2021-02-19) }
มาวิเคราะห์แต่ละส่วนของโค้ดกัน:
VisitRecord
คลาส:
- คลาสนี้มีเมธอด
__init__
ที่เริ่มต้นวัตถุVisitRecord
ด้วยแอตทริบิวต์first_name
,last_name
,phone_number
และdate_visited
ที่ให้ไว้ - เมธอด
__hash__
ถูกกำหนดไว้เพื่อคำนวณค่าแฮชของออบเจ็กต์ตามfirst_name
,last_name
และphone_number
ซึ่งช่วยให้สามารถใช้อินสแตนซ์ของVisitRecord
ในโครงสร้างข้อมูลแบบแฮช เช่น ชุดและพจนานุกรม - ใช้วิธี
__eq__
เพื่อตรวจสอบว่าวัตถุVisitRecord
สองวัตถุเท่ากันหรือไม่ จะตรวจสอบว่าวัตถุอื่นเป็นอินสแตนซ์ของVisitRecord
หรือไม่ และค่าแฮชของวัตถุนั้นเหมือนกันหรือไม่ - วิธีการ
__repr__
ส่งกลับการแสดงสตริงของวัตถุVisitRecord
ประกอบด้วยชื่อแอตทริบิวต์และค่าที่เกี่ยวข้อง
find_potential_customers_v3
ฟังก์ชั่น:
ฟังก์ชันนี้ส่งคืนชุดของวัตถุ VisitRecord
ที่แสดงถึงผู้มีโอกาสเป็นลูกค้า โดยจะใช้ชุดความเข้าใจเพื่อสร้างชุดอินสแตนซ์ VisitRecord
ตามข้อมูลที่ให้ไว้ใน tourists_visited_hk
และ tourists_visited_nyc
โดยรวมแล้ว รหัสจะกำหนดคลาส VisitRecord
ที่แสดงถึงบันทึกการเยี่ยมชมของนักท่องเที่ยว คลาสกำหนดวิธีการในการจัดการแฮช การเปรียบเทียบความเท่าเทียมกัน และการแทนค่าสตริง ฟังก์ชัน find_potential_customers_v3
ใช้อินสแตนซ์ของ VisitRecord
เพื่อระบุผู้มีโอกาสเป็นลูกค้าโดยพิจารณาจากบันทึกการเยี่ยมชมของพวกเขาในฮ่องกงและนิวยอร์กซิตี้
โซลูชันที่ 4 (เวอร์ชันโปรแกรมเมอร์ตัวยง)
from dataclasses import dataclass, field @dataclass(unsafe_hash=True) class VisitRecordDC: first_name: str last_name: str phone_number: str date_visited: str = field(hash=False, compare=False) def find_potential_customers_v4(): return set(VisitRecordDC(**r) for r in tourists_visited_hk) - \ set(VisitRecordDC(**r) for r in tourists_visited_nyc) print(find_potential_customers_v4())
โค้ดด้านบนกำหนดคลาสใหม่ที่เรียกว่า VisitRecordDC
โดยใช้ dataclass
มัณฑนากรของ Python มัณฑนากรนี้มอบวิธีที่สะดวกในการกำหนดคลาสที่จัดเก็บข้อมูล โดยใช้โค้ดสำเร็จรูปน้อยกว่าคำจำกัดความคลาสมาตรฐาน
คลาส VisitRecordDC
มีแอตทริบิวต์สี่รายการ: first_name
, last_name
, phone_number
และ date_visited
แอตทริบิวต์ date_visited
มีค่าเริ่มต้นเป็น None
และทำเครื่องหมายเป็น hash=False
และ compare=False
ซึ่งหมายความว่าจะไม่ถูกใช้เมื่อแฮชหรือเปรียบเทียบอินสแตนซ์ของคลาส
อาร์กิวเมนต์ unsafe_hash=True
ของมัณฑนากร dataclass
บอกให้ Python สร้างฟังก์ชันแฮชสำหรับคลาสตามคุณลักษณะทั้งหมด ฟังก์ชันแฮชนี้สามารถใช้เพื่อเปรียบเทียบอินสแตนซ์ของคลาสเพื่อความเท่าเทียมกันได้อย่างมีประสิทธิภาพ และจัดเก็บไว้ในชุดและพจนานุกรม
ฟังก์ชัน find_potential_customers_v4
จะสร้างอินสแตนซ์ VisitRecordDC
สองชุด ชุดหนึ่งจาก tourists_visited_hk
และอีกชุดจาก tourists_visited_nyc
จากนั้นคำนวณความแตกต่างที่กำหนดไว้ระหว่างอินสแตนซ์เหล่านั้นเพื่อค้นหาผู้มีโอกาสเป็นลูกค้าที่เคยมาเยือนฮ่องกงแต่ไม่ได้มาเยือนนิวยอร์กซิตี้
กฎอื่นๆ ที่โปรแกรมเมอร์ตัวยงใช้:
ใช้การจัดรูปแบบสตริงวัตถุ: __format__
class Student: def __init__(self, name, age): self.name = name self.age = age def get_simple_display(self): return f'{self.name}({self.age})' def get_long_display(self): return f'{self.name} is {self.age} years old.' david = Student('David', '33') print(david.get_simple_display()) # OUTPUT: David(33) print(david.get_long_display()) # OUTPUT: David is 33 years old.
สำหรับโค้ดข้างต้น คุณสามารถใช้ __format__
เพื่อเขียนด้วยกฎ Python ที่แท้จริง:
class Student: def __init__(self, name, age): self.name = name self.age = age def __format__(self, format_spec): if format_spec == 'long': return f'{self.name} is {self.age} years old.' elif format_spec == 'simple': return f'{self.name}({self.age})' raise ValueError('invalid format spec') david = Student('david', '33') print('{0:simple}'.format(david)) # OUTPUT: David(33) print('{0:long}'.format(david)) # OUTPUT: David is 33 years old.
ใช้ __getitem__
เพื่อกำหนดการดำเนินการของ Object Slice:
class Events: def __init__(self, events): self.events = events def is_empty(self): return not bool(self.events) def list_events_by_range(self, start, end): return self.events[start:end] events = Events([ 'Game of Thrones begins', 'Avengers Endgame released', 'Python 3.8 released', 'COVID-19 declared a pandemic', ]) if not events.is_empty(): print(events.list_events_by_range(1, 3))
วิธีแก้ปัญหาข้างต้นก็โอเค แต่ไม่ใช่ Pythonic เนื่องจาก Python ได้จัดเตรียมชุดกฎของวัตถุมาให้เรา
class Events: def __init__(self, events): self.events = events def __len__(self): return len(self.events) def __getitem__(self, index): return self.events[index] events = Events([ 'Game of Thrones begins', 'Avengers Endgame released', 'Python 3.8 released', 'COVID-19 declared a pandemic', ]) if events: print(events[1:3]) #Output ['Avengers Endgame released', 'Python 3.8 released']
เมื่อใช้เมธอด __len__
และ __getitem__
คุณจะเปิดใช้งานการใช้ฟังก์ชัน len()
และการดำเนินการจัดทำดัชนี/การแบ่งส่วนบนอ็อบเจ็กต์ Events
ซึ่งทำให้โค้ดใช้งานง่ายและเป็น Pythonic มากขึ้น
มีวิธีอื่นในการเขียนโค้ด Python ที่ชัดเจน เราสามารถพัฒนาทักษะการเขียนโปรแกรมของเราได้ง่ายๆ โดยปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดข้างต้น