منابع ترجمه · Think Python · فصل ۷

تکرار و جست‌وجو (فصل ۷ Think Python)

۱۸ دقیقه مطالعه · به‌روزرسانی ۲ آبان ۱۴۰۵

منبع اصلی: Think Python, Chapter 7: Iteration and Search — Allen B. Downey

💡 این صفحه مثل یک نوت‌بوک Jupyter است: متغیر و تابعی که در یک سلول تعریف شود، در سلول‌های بعدی همین صفحه در دسترس است. سلول‌ها را به ترتیب اجرا کنید، یا با «⏮ تا اینجا» همهٔ سلول‌های قبلیِ اجرانشده را یک‌جا اجرا کنید.

توضیح: خروجی‌های نمایش‌داده‌شده‌ی زیر کدها را با اجرای همین کدها (روی فایل words.txt کتاب) به دست آورده‌ام. برای اجرای کدهای این فصل به فایل words.txt نیاز دارید؛ در نوت‌بوک اصلی با تابع download دانلود می‌شود.

در سال ۱۹۳۹ Ernest Vincent Wright رمانی ۵۰٬۰۰۰ کلمه‌ای به نام Gadsby منتشر کرد که هیچ حرف «e» در آن به کار نرفته است. چون «e» رایج‌ترین حرف در زبان انگلیسی است، نوشتن حتی چند کلمه بدون آن دشوار است. برای اینکه حس کنیم چقدر دشوار است، در این فصل کسری از کلمه‌های انگلیسی را که دست‌کم یک «e» دارند حساب می‌کنیم.

برای این کار از دستورهای for استفاده می‌کنیم تا حرف‌های یک رشته و کلمه‌های یک فایل را پیمایش کنیم، و متغیرها را داخل حلقه به‌روز می‌کنیم تا تعداد کلمه‌هایی را که «e» دارند بشماریم. از عملگر in برای بررسی اینکه یک حرف در کلمه هست یا نه استفاده می‌کنیم و با الگوی برنامه‌نویسی‌ای به نام «جست‌وجوی خطی» (linear search) آشنا می‌شوید.

در تمرین‌ها، از این ابزارها برای حل یک معمای کلمه‌ای به نام «Spelling Bee» استفاده می‌کنید.

۷.۱. حلقه‌ها و رشته‌ها

در فصل ۳ یک حلقه‌ی for دیدیم که با تابع range دنباله‌ای از عددها را نمایش می‌داد.

for i in range(3):
    print(i, end=' ')
0 1 2 

این نسخه از آرگومان کلیدواژه‌ای end استفاده می‌کند تا تابع print به‌جای رفتن به خط بعد، بعد از هر عدد یک فاصله بگذارد.

از حلقه‌ی for می‌توانیم برای نمایش حرف‌های یک رشته هم استفاده کنیم.

for letter in 'Gadsby':
    print(letter, end=' ')
G a d s b y 

دقت کنید که نام متغیر را از i به letter تغییر دادم، که اطلاعات بیشتری درباره‌ی مقداری که به آن اشاره می‌کند می‌دهد. متغیری که در حلقه‌ی for تعریف می‌شود متغیر حلقه (loop variable) نامیده می‌شود.

حالا که می‌توانیم حرف‌های یک کلمه را پیمایش کنیم، می‌توانیم بررسی کنیم که آیا حرف «e» را دارد.

for letter in "Gadsby":
    if letter == 'E' or letter == 'e':
        print('This word has an "e"')

قبل از ادامه، این حلقه را در یک تابع کپسوله می‌کنیم.

def has_e():
    for letter in "Gadsby":
        if letter == 'E' or letter == 'e':
            print('This word has an "e"')

و آن را به یک تابع خالص تبدیل می‌کنیم که اگر کلمه «e» داشته باشد True و در غیر این صورت False برگرداند.

def has_e():
    for letter in "Gadsby":
        if letter == 'E' or letter == 'e':
            return True
    return False

می‌توانیم آن را تعمیم دهیم تا کلمه را به‌عنوان پارامتر بگیرد.

def has_e(word):
    for letter in word:
        if letter == 'E' or letter == 'e':
            return True
    return False

حالا می‌توانیم آن را این‌طور آزمایش کنیم:

has_e('Gadsby')
False
has_e('Emma')
True

۷.۲. خواندن فهرست کلمه‌ها

برای اینکه ببینیم چند کلمه «e» دارند، به یک فهرست کلمه نیاز داریم. فهرستی که استفاده می‌کنیم شامل حدود ۱۱۴٬۰۰۰ کلمه‌ی مجاز در جدول‌های کلمات متقاطع (crossword) است؛ یعنی کلمه‌هایی که در جدول‌های کلمات متقاطع و بازی‌های کلمه‌ای معتبر شناخته می‌شوند.

سلول زیر فهرست کلمه‌ها را دانلود می‌کند، که نسخه‌ی تغییریافته‌ی فهرستی است که Grady Ward به‌عنوان بخشی از پروژه‌ی واژگان Moby جمع‌آوری کرد و به مالکیت عمومی (public domain) اهدا کرد (نگاه کنید به http://wikipedia.org/wiki/Moby_Project).

download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/words.txt');

فهرست کلمه‌ها در فایلی به نام words.txt است که در نوت‌بوک این فصل دانلود می‌شود. برای خواندن آن از تابع داخلی open استفاده می‌کنیم که نام فایل را به‌عنوان پارامتر می‌گیرد و یک شیء فایل (file object) برمی‌گرداند که با آن می‌توانیم فایل را بخوانیم.

file_object = open('words.txt')

شیء فایل تابعی به نام readline دارد که نویسه‌ها را از فایل می‌خواند تا به یک نویسه‌ی خط جدید برسد و نتیجه را به‌صورت یک رشته برمی‌گرداند:

file_object.readline()
'aa\n'

دقت کنید که شیوه‌ی فراخوانی readline با توابعی که تا الان دیده‌ایم فرق دارد. دلیلش این است که یک متد (method) است، یعنی تابعی که به یک شیء وابسته است. در این مورد readline به شیء فایل وابسته است، پس آن را با نام شیء، عملگر نقطه و نام متد فراخوانی می‌کنیم.

اولین کلمه‌ی فهرست «aa» است، که نوعی گدازه است. دنباله‌ی \n نویسه‌ی خط جدید را نشان می‌دهد که این کلمه را از کلمه‌ی بعدی جدا می‌کند.

شیء فایل مکان خود در فایل را به خاطر می‌سپارد، پس اگر دوباره readline را فراخوانی کنید، کلمه‌ی بعدی را می‌گیرید:

line = file_object.readline()
line
'aah\n'

برای حذف خط جدید از انتهای کلمه می‌توانیم از strip استفاده کنیم، که متدی وابسته به رشته‌هاست و می‌توانیم این‌طور فراخوانی‌اش کنیم.

word = line.strip()
word
'aah'

strip نویسه‌های فاصله‌ای – شامل فاصله، تب و خط جدید – را از ابتدا و انتهای رشته حذف می‌کند.

می‌توانید از یک شیء فایل در یک حلقه‌ی for هم استفاده کنید. این برنامه words.txt را می‌خواند و هر کلمه را در یک خط نمایش می‌دهد:

for line in open('words.txt'):
    word = line.strip()
    print(word)

حالا که می‌توانیم فهرست کلمه‌ها را بخوانیم، قدم بعدی شمردن آن‌هاست. برای این کار به توانایی به‌روزرسانی متغیرها نیاز داریم.

۷.۳. به‌روزرسانی متغیرها

شاید خودتان متوجه شده باشید که مجاز است بیش از یک بار به یک متغیر مقدار نسبت دهید. نسبت‌دهی جدید باعث می‌شود متغیر موجود به مقدار جدیدی اشاره کند (و دیگر به مقدار قبلی اشاره نکند).

برای مثال، این یک نسبت‌دهی اولیه است که متغیر را می‌سازد.

x = 5
x
5

و این نسبت‌دهی‌ای است که مقدار متغیر را عوض می‌کند.

x = 7
x
7

شکل زیر نشان می‌دهد این نسبت‌دهی‌ها در یک نمودار وضعیت (state diagram) چه شکلی هستند.

(در کتاب اصلی اینجا نمودار رسم شده است: متغیر x با یک پیکان نقطه‌چین به 5 و یک پیکان پیوسته به 7.)

پیکان نقطه‌چین نشان می‌دهد x دیگر به 5 اشاره نمی‌کند. پیکان پیوسته نشان می‌دهد اکنون به 7 اشاره می‌کند.

یک نوع رایج نسبت‌دهی به‌روزرسانی (update) است، که در آن مقدار جدید متغیر به مقدار قدیمی‌اش بستگی دارد.

x = 7
x = x + 1
x
8

این دستور یعنی «مقدار فعلی x را بگیر، یکی اضافه کن و نتیجه را دوباره به x نسبت بده.»

اگر سعی کنید متغیری را که وجود ندارد به‌روزرسانی کنید، خطا می‌گیرید، چون پایتون عبارت سمت راست را قبل از نسبت دادن مقدار به متغیر سمت چپ ارزیابی می‌کند.

z = z + 1
NameError: name 'z' is not defined

قبل از اینکه بتوانید متغیری را به‌روزرسانی کنید، باید آن را مقداردهی اولیه (initialize) کنید، معمولاً با یک نسبت‌دهی ساده:

z = 0
z = z + 1
z
1

افزایش مقدار یک متغیر افزایش (increment) و کاهش مقدار آن کاهش (decrement) نامیده می‌شود. چون این عملیات بسیار رایج‌اند، پایتون عملگرهای نسبت‌دهی افزوده (augmented assignment operators) دارد که متغیر را مختصرتر به‌روز می‌کنند. برای مثال، عملگر += متغیر را به‌اندازه‌ی مقدار داده‌شده افزایش می‌دهد.

z += 2
z
3

برای بقیه‌ی عملگرهای حسابی هم عملگر نسبت‌دهی افزوده وجود دارد، از جمله -= و *=.

۷.۴. حلقه و شمارش

برنامه‌ی زیر تعداد کلمه‌های فهرست را می‌شمارد.

total = 0

for line in open('words.txt'):
    word = line.strip()
    total += 1

این برنامه با مقداردهی اولیه‌ی total به 0 شروع می‌کند. هر بار که حلقه تکرار می‌شود، total را یک واحد افزایش می‌دهد. پس وقتی حلقه تمام می‌شود، total به تعداد کل کلمه‌ها اشاره می‌کند.

total
113783

متغیری مثل این که برای شمردن تعداد دفعاتی که چیزی رخ می‌دهد به کار می‌رود شمارنده (counter) نامیده می‌شود.

می‌توانیم یک شمارنده‌ی دوم به برنامه اضافه کنیم تا تعداد کلمه‌هایی را که «e» دارند نگه دارد.

total = 0
count = 0

for line in open('words.txt'):
    word = line.strip()
    total = total + 1
    if has_e(word):
        count += 1

ببینیم چند کلمه «e» دارند.

count
76162

به‌صورت درصدی از total، حدود دو سوم کلمه‌ها حرف «e» دارند.

count / total * 100
66.93618554617122

پس می‌توانید بفهمید چرا نوشتن یک کتاب بدون استفاده از چنین کلمه‌هایی دشوار است.

۷.۵. عملگر in

نسخه‌ای از has_e که در این فصل نوشتیم پیچیده‌تر از لازم است. پایتون عملگری به نام in دارد که بررسی می‌کند آیا یک نویسه در یک رشته وجود دارد یا نه.

word = 'Gadsby'
'e' in word
False

پس می‌توانیم has_e را این‌طور بازنویسی کنیم.

def has_e(word):
    if 'E' in word or 'e' in word:
        return True
    else:
        return False

و چون شرط دستور if یک مقدار بولی دارد، می‌توانیم دستور if را حذف کنیم و مقدار بولی را مستقیماً برگردانیم.

def has_e(word):
    return 'E' in word or 'e' in word

می‌توانیم این تابع را باز هم ساده‌تر کنیم با استفاده از متد lower که حرف‌های یک رشته را به حروف کوچک تبدیل می‌کند. این یک مثال است.

word.lower()
'gadsby'

lower یک رشته‌ی جدید می‌سازد – رشته‌ی موجود را تغییر نمی‌دهد – پس مقدار word بدون تغییر می‌ماند.

word
'Gadsby'

این‌طور می‌توانیم از lower در has_e استفاده کنیم.

def has_e(word):
    return 'e' in word.lower()
has_e('Gadsby')
False
has_e('Emma')
True

۷.۶. جست‌وجو

بر اساس این نسخه‌ی ساده‌تر has_e، تابع کلی‌تری به نام uses_any می‌نویسیم که پارامتر دومی دارد که رشته‌ای از حرف‌هاست. این تابع اگر کلمه از هر یک از حرف‌ها استفاده کند True و در غیر این صورت False برمی‌گرداند.

def uses_any(word, letters):
    for letter in word.lower():
        if letter in letters.lower():
            return True
    return False

این مثالی است که نتیجه‌اش True است.

uses_any('banana', 'aeiou')
True

و این یکی که نتیجه‌اش False است.

uses_any('apple', 'xyz')
False

uses_any هر دوی word و letters را به حروف کوچک تبدیل می‌کند، پس با هر ترکیبی از حروف بزرگ و کوچک کار می‌کند.

uses_any('Banana', 'AEIOU')
True

ساختار uses_any شبیه has_e است. حرف‌های word را پیمایش می‌کند و یکی‌یکی بررسی‌شان می‌کند. اگر یکی را پیدا کند که در letters باشد، بلافاصله True برمی‌گرداند. اگر بدون پیدا کردن هیچ‌کدام تا انتهای حلقه برسد، False برمی‌گرداند.

این الگو جست‌وجوی خطی (linear search) نامیده می‌شود. در تمرین‌های انتهای این فصل، توابع بیشتری با این الگو می‌نویسید.

۷.۷. Doctest

در فصل ۴ از یک docstring برای مستندسازی یک تابع استفاده کردیم – یعنی برای توضیح اینکه چه می‌کند. می‌توان از docstring برای آزمودن تابع هم استفاده کرد. این نسخه‌ای از uses_any است که docstring آن شامل آزمون‌هاست.

def uses_any(word, letters):
    """Checks if a word uses any of a list of letters.
    
    >>> uses_any('banana', 'aeiou')
    True
    >>> uses_any('apple', 'xyz')
    False
    """
    for letter in word.lower():
        if letter in letters.lower():
            return True
    return False

هر آزمون با >>> شروع می‌شود، که در بعضی محیط‌های پایتون به‌عنوان اعلان (prompt) استفاده می‌شود تا نشان دهد کاربر کجا می‌تواند کد را تایپ کند. در یک doctest، بعد از اعلان یک عبارت می‌آید، معمولاً یک فراخوانی تابع. خط بعدی مقداری را که عبارت در صورت درست کار کردن تابع باید داشته باشد نشان می‌دهد.

در مثال اول، 'banana' از 'a' استفاده می‌کند، پس نتیجه باید True باشد. در مثال دوم، 'apple' از هیچ‌کدام از 'xyz' استفاده نمی‌کند، پس نتیجه باید False باشد.

برای اجرای این آزمون‌ها باید ماژول doctest را وارد کنیم و تابعی به نام run_docstring_examples را اجرا کنیم. برای اینکه استفاده از این تابع راحت‌تر شود، تابع زیر را نوشتم که یک شیء تابع را به‌عنوان آرگومان می‌گیرد.

from doctest import run_docstring_examples

def run_doctests(func):
    run_docstring_examples(func, globals(), name=func.__name__)

هنوز درباره‌ی globals و __name__ چیزی یاد نگرفته‌ایم – می‌توانید نادیده‌شان بگیرید. حالا می‌توانیم uses_any را این‌طور آزمایش کنیم.

run_doctests(uses_any)

run_doctests عبارت‌های داخل docstring را پیدا می‌کند و ارزیابی‌شان می‌کند. اگر نتیجه با مقدار مورد انتظار یکی باشد، آزمون قبول می‌شود (passes). در غیر این صورت رد می‌شود (fails).

اگر همه‌ی آزمون‌ها قبول شوند، run_doctests هیچ خروجی‌ای نمایش نمی‌دهد – در آن صورت «بی‌خبری، خوش‌خبری است.» برای دیدن اینکه وقتی یک آزمون رد می‌شود چه اتفاقی می‌افتد، این یک نسخه‌ی نادرست از uses_any است.

def uses_any_incorrect(word, letters):
    """Checks if a word uses any of a list of letters.
    
    >>> uses_any_incorrect('banana', 'aeiou')
    True
    >>> uses_any_incorrect('apple', 'xyz')
    False
    """
    for letter in word.lower():
        if letter in letters.lower():
            return True
        else:
            return False     # INCORRECT!

و این چیزی است که هنگام آزمودنش پیش می‌آید.

run_doctests(uses_any_incorrect)
**********************************************************************
File "__main__", line 4, in uses_any_incorrect
Failed example:
    uses_any_incorrect('banana', 'aeiou')
Expected:
    True
Got:
    False

(شماره‌ی خط در گزارش، بسته به محیط اجرا ممکن است متفاوت باشد.)

خروجی شامل مثالی که رد شده، مقداری که تابع باید تولید می‌کرد و مقداری که واقعاً تولید کرد است.

اگر مطمئن نیستید چرا این آزمون رد شد، در یکی از تمرین‌ها فرصت اشکال‌زدایی‌اش را خواهید داشت.

۷.۸. واژه‌نامه

متغیر حلقه (loop variable): متغیری که در سرآیند یک حلقه‌ی for تعریف می‌شود.

شیء فایل (file object): شیئی که یک فایل باز را نشان می‌دهد و پیگیری می‌کند کدام بخش‌های فایل خوانده یا نوشته شده‌اند.

متد (method): تابعی که به یک شیء وابسته است و با عملگر نقطه فراخوانی می‌شود.

به‌روزرسانی (update): یک دستور نسبت‌دهی که به متغیری که از قبل وجود دارد مقدار جدیدی می‌دهد، به‌جای اینکه متغیر تازه‌ای بسازد.

مقداردهی اولیه (initialize): ساختن یک متغیر جدید و دادن مقداری به آن.

افزایش (increment): زیاد کردن مقدار یک متغیر.

کاهش (decrement): کم کردن مقدار یک متغیر.

شمارنده (counter): متغیری که برای شمردن چیزی به کار می‌رود، معمولاً با صفر مقداردهی اولیه می‌شود و بعد افزایش می‌یابد.

جست‌وجوی خطی (linear search): الگوی محاسباتی‌ای که دنباله‌ای از عنصرها را می‌پیماید و وقتی چیزی را که دنبالش می‌گردد پیدا کرد متوقف می‌شود.

قبول شدن (pass): اگر آزمونی اجرا شود و نتیجه همان مورد انتظار باشد، آزمون قبول می‌شود.

رد شدن (fail): اگر آزمونی اجرا شود و نتیجه همان مورد انتظار نباشد، آزمون رد می‌شود.

۷.۹. تمرین‌ها

# This cell tells Jupyter to provide detailed debugging information
# when a runtime error occurs. Run it before working on the exercises.

%xmode Verbose

۷.۹.۱. از یک دستیار مجازی بپرسید

در uses_any ممکن است متوجه شده باشید که اولین دستور return داخل حلقه و دومی بیرون آن است.

def uses_any(word, letters):
    for letter in word.lower():
        if letter in letters.lower():
            return True
    return False

وقتی افراد برای اولین بار توابعی مثل این می‌نویسند، یک خطای رایج این است که هر دو دستور return را داخل حلقه می‌گذارند، مثل این.

def uses_any_incorrect(word, letters):
    for letter in word.lower():
        if letter in letters.lower():
            return True
        else:
            return False     # INCORRECT!

از یک دستیار مجازی بپرسید این نسخه چه مشکلی دارد. (Ask a virtual assistant what’s wrong with this version.)

۷.۹.۲. تمرین

تابعی به نام uses_none بنویسید که یک کلمه و یک رشته از حرف‌های ممنوعه را بگیرد و اگر کلمه از هیچ‌یک از حرف‌های ممنوعه استفاده نمی‌کند True برگرداند.

این یک طرح کلی از تابع است که دو doctest دارد. تابع را طوری کامل کنید که این آزمون‌ها را قبول شود و دست‌کم یک doctest دیگر اضافه کنید.

def uses_none(word, forbidden):
    """Checks whether a word avoid forbidden letters.
    
    >>> uses_none('banana', 'xyz')
    True
    >>> uses_none('apple', 'efg')
    False
    """
    return None
# Solution goes here
run_doctests(uses_none)

۷.۹.۳. تمرین

تابعی به نام uses_only بنویسید که یک کلمه و یک رشته از حرف‌ها را بگیرد و اگر کلمه فقط شامل حرف‌های داخل رشته است True برگرداند.

این یک طرح کلی از تابع است که دو doctest دارد. تابع را طوری کامل کنید که این آزمون‌ها را قبول شود و دست‌کم یک doctest دیگر اضافه کنید.

def uses_only(word, available):
    """Checks whether a word uses only the available letters.
    
    >>> uses_only('banana', 'ban')
    True
    >>> uses_only('apple', 'apl')
    False
    """
    return None
# Solution goes here
run_doctests(uses_only)

۷.۹.۴. تمرین

تابعی به نام uses_all بنویسید که یک کلمه و یک رشته از حرف‌ها را بگیرد و اگر کلمه همه‌ی حرف‌های داخل رشته را دست‌کم یک بار دارد True برگرداند.

این یک طرح کلی از تابع است که دو doctest دارد. تابع را طوری کامل کنید که این آزمون‌ها را قبول شود و دست‌کم یک doctest دیگر اضافه کنید.

def uses_all(word, required):
    """Checks whether a word uses all required letters.
    
    >>> uses_all('banana', 'ban')
    True
    >>> uses_all('apple', 'api')
    False
    """
    return None
# Solution goes here
run_doctests(uses_all)

۷.۹.۵. تمرین

روزنامه‌ی The New York Times هر روز معمایی به نام «Spelling Bee» منتشر می‌کند که از خوانندگان می‌خواهد با فقط هفت حرف، که یکی از آن‌ها اجباری است، تا جای ممکن کلمه بسازند. کلمه‌ها باید دست‌کم چهار حرف داشته باشند.

برای مثال، روزی که این را می‌نوشتم، حرف‌ها ACDLORT بودند و R حرف اجباری بود. پس «color» کلمه‌ی قابل‌قبولی است، اما «told» نیست چون R ندارد و «rat» هم نیست چون فقط سه حرف دارد. حرف‌ها می‌توانند تکرار شوند، پس «ratatat» قابل‌قبول است.

تابعی به نام check_word بنویسید که بررسی کند آیا یک کلمه‌ی داده‌شده قابل‌قبول است. این تابع باید به‌عنوان پارامتر، کلمه‌ای که باید بررسی شود، رشته‌ای از هفت حرف موجود و رشته‌ای شامل حرف اجباری را بگیرد. می‌توانید از توابعی که در تمرین‌های قبل نوشتید استفاده کنید.

این یک طرح کلی از تابع است که doctest دارد. تابع را کامل کنید و بعد بررسی کنید که همه‌ی آزمون‌ها قبول می‌شوند.

def check_word(word, available, required):
    """Check whether a word is acceptable.
    
    >>> check_word('color', 'ACDLORT', 'R')
    True
    >>> check_word('ratatat', 'ACDLORT', 'R')
    True
    >>> check_word('rat', 'ACDLORT', 'R')
    False
    >>> check_word('told', 'ACDLORT', 'R')
    False
    >>> check_word('bee', 'ACDLORT', 'R')
    False
    """
    return False
# Solution goes here
run_doctests(check_word)

طبق قوانین «Spelling Bee»،

  • کلمه‌های چهار حرفی هر کدام ۱ امتیاز دارند.

  • کلمه‌های بلندتر به ازای هر حرف ۱ امتیاز می‌گیرند.

  • هر معما دست‌کم یک «پانگرام» (pangram) دارد که از همه‌ی حرف‌ها استفاده می‌کند. این کلمه‌ها ۷ امتیاز اضافه دارند!

تابعی به نام score_word بنویسید که یک کلمه و یک رشته از حرف‌های موجود را بگیرد و امتیاز کلمه را برگرداند. می‌توانید فرض کنید کلمه قابل‌قبول است.

دوباره، این یک طرح کلی از تابع با doctest است. (توجه: در کد کتاب اصلی نام تابع word_score آمده است؛ برای سازگاری با آزمون‌های بعدی همان را نگه داشته‌ام.)

def word_score(word, available):
    """Compute the score for an acceptable word.
    
    >>> word_score('card', 'ACDLORT')
    1
    >>> word_score('color', 'ACDLORT')
    5
    >>> word_score('cartload', 'ACDLORT')
    15
    """
    return 0
# Solution goes here
run_doctests(word_score)

وقتی همه‌ی توابع‌تان آزمون‌هایشان را قبول شدند، از حلقه‌ی زیر استفاده کنید تا فهرست کلمه‌ها را برای کلمه‌های قابل‌قبول جست‌وجو کنید و امتیازشان را جمع بزنید.

available = 'ACDLORT'
required = 'R'

total = 0

file_object = open('words.txt')
for line in file_object:
    word = line.strip()    
    if check_word(word, available, required):
        score = word_score(word, available)
        total = total + score
        print(word, score)
        
print("Total score", total)

به صفحه‌ی «Spelling Bee» در https://www.nytimes.com/puzzles/spelling-bee بروید و حرف‌های امروز را وارد کنید. حرف وسط حرف اجباری است.

من مجموعه‌ای از حرف‌ها پیدا کردم که کلمه‌هایی با مجموع امتیاز ۵۸۲۰ می‌سازد. آیا می‌توانید از آن بهتر کنید؟ پیدا کردن بهترین مجموعه‌ی حرف‌ها شاید خیلی سخت باشد – باید «واقع‌بین» (realist) باشید.

۷.۹.۶. تمرین

شاید متوجه شده باشید که توابعی که در تمرین‌های قبل نوشتید چیزهای مشترک زیادی داشتند. در واقع آن‌قدر شبیه‌اند که اغلب می‌توانید از یک تابع برای نوشتن تابع دیگر استفاده کنید.

برای مثال، اگر کلمه‌ای از هیچ‌یک از مجموعه‌ای از حرف‌های ممنوعه استفاده نکند، یعنی از هیچ‌کدام استفاده نمی‌کند (uses_any برایش False است). پس می‌توانیم نسخه‌ای از uses_none را این‌طور بنویسیم.

def uses_none(word, forbidden):
    """Checks whether a word avoids forbidden letters.
    
    >>> uses_none('banana', 'xyz')
    True
    >>> uses_none('apple', 'efg')
    False
    >>> uses_none('', 'abc')
    True
    """
    return not uses_any(word, forbidden)
run_doctests(uses_none)

بین uses_only و uses_all هم شباهتی هست که می‌توانید از آن استفاده کنید. اگر نسخه‌ی کارآمدی از uses_only دارید، ببینید می‌توانید نسخه‌ای از uses_all بنویسید که uses_only را فراخوانی کند.

۷.۹.۷. تمرین

اگر در سؤال قبل گیر کردید، از یک دستیار مجازی بپرسید: «Given a function, uses_only, which takes two strings and checks that the first uses only the letters in the second, use it to write uses_all, which takes two strings and checks whether the first uses all the letters in the second, allowing repeats.» (یعنی: با فرض تابعی به نام uses_only که دو رشته می‌گیرد و بررسی می‌کند که رشته‌ی اول فقط از حرف‌های رشته‌ی دوم استفاده می‌کند، با استفاده از آن uses_all را بنویس که دو رشته می‌گیرد و بررسی می‌کند رشته‌ی اول از همه‌ی حرف‌های رشته‌ی دوم استفاده می‌کند، با اجازه‌ی تکرار.)

از run_doctests برای بررسی جواب استفاده کنید.

# Solution goes here
run_doctests(uses_all)

۷.۹.۸. تمرین

حالا ببینیم می‌توانیم uses_all را بر پایه‌ی uses_any بنویسیم یا نه.

از یک دستیار مجازی بپرسید: «Given a function, uses_any, which takes two strings and checks whether the first uses any of the letters in the second, can you use it to write uses_all, which takes two strings and checks whether the first uses all the letters in the second, allowing repeats.»

اگر گفت می‌تواند، حتماً نتیجه را آزمایش کنید!

# Solution goes here
# Here's what I got from ChatGPT 4o December 26, 2024
# It's correct, but it makes multiple calls to uses_any 

def uses_all(s1, s2):
    """Checks if all characters in s2 are in s1, allowing repeats."""
    for char in s2:
        if not uses_any(s1, char):
            return False
    return True
# Solution goes here