เรามาพูดถึงหัวข้อนี้ด้วยกรณีศึกษาและวิเคราะห์ว่าโปรแกรมเมอร์ในระดับต่างๆ จัดการกับปัญหานี้อย่างไร

รูปภาพที่คุณได้รับข้อมูลนักท่องเที่ยวสองรายการจากบริษัทท่องเที่ยวดังนี้:

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 ที่ชัดเจน เราสามารถพัฒนาทักษะการเขียนโปรแกรมของเราได้ง่ายๆ โดยปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดข้างต้น

การเรียนรู้เพิ่มเติม:



https://www.youtube.com/watch?v=wf-BqAjZb8M