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

رشته‌ها و عبارت‌های منظم (فصل ۸ Think Python)

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

منبع اصلی: Think Python, Chapter 8: Strings and Regular Expressions — Allen B. Downey

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

توضیح: خروجی‌های نمایش‌داده‌شده‌ی زیر کدها را با اجرای همین کدها روی فایل‌های کتاب به دست آورده‌ام. رمان Dracula (برم استوکر) اثری در مالکیت عمومی (public domain) است و از طریق پروژه‌ی گوتنبرگ در دسترس است؛ جمله‌های نقل‌شده از آن در خروجی‌ها به زبان اصلی (انگلیسی) باقی مانده‌اند.

رشته‌ها مثل عددهای صحیح، اعشاری و بولی نیستند. رشته یک دنباله (sequence) است، یعنی چندین مقدار را با ترتیبی مشخص در بر دارد. در این فصل می‌بینیم چگونه به مقدارهایی که یک رشته را می‌سازند دسترسی پیدا کنیم و از توابعی که رشته‌ها را پردازش می‌کنند استفاده می‌کنیم.

همچنین از عبارت‌های منظم (regular expressions) استفاده می‌کنیم، که ابزاری قدرتمند برای پیدا کردن الگوها در یک رشته و انجام عملیاتی مثل جست‌وجو و جایگزینی هستند.

در تمرین‌ها فرصت دارید این ابزارها را روی یک بازی کلمه‌ای به نام Wordle به کار ببرید.

۸.۱. رشته یک دنباله است

رشته دنباله‌ای از نویسه‌هاست. نویسه (character) می‌تواند یک حرف (تقریباً در هر الفبایی)، یک رقم، یک علامت نگارشی یا یک فاصله باشد.

با عملگر کروشه می‌توانید یک نویسه را از رشته انتخاب کنید. این دستور نمونه نویسه‌ی شماره‌ی ۱ را از fruit انتخاب می‌کند و به letter نسبت می‌دهد:

fruit = 'banana'
letter = fruit[1]

عبارت داخل کروشه یک اندیس (index) است که به این دلیل چنین نامیده می‌شود که نشان می‌دهد کدام نویسه از دنباله انتخاب شود. اما نتیجه ممکن است آن‌طور که انتظار دارید نباشد.

letter
'a'

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

fruit[0]
'b'

می‌توانید 'b' را حرف «صفرم» 'banana' در نظر بگیرید (در انگلیسی: “zero-eth”).

اندیس داخل کروشه می‌تواند یک متغیر باشد.

i = 1
fruit[i]
'a'

یا عبارتی که شامل متغیرها و عملگرهاست.

fruit[i+1]
'n'

اما مقدار اندیس باید عدد صحیح باشد – در غیر این صورت TypeError می‌گیرید.

fruit[1.5]
TypeError: string indices must be integers, not 'float'

همان‌طور که در فصل ۱ دیدیم، می‌توانیم از تابع داخلی len برای گرفتن طول رشته استفاده کنیم.

n = len(fruit)
n
6

برای گرفتن آخرین حرف رشته ممکن است وسوسه شوید این را بنویسید:

fruit[n]
IndexError: string index out of range

اما این باعث IndexError می‌شود، چون در 'banana' حرفی با اندیس ۶ وجود ندارد. چون شمارش را از 0 شروع کردیم، شش حرف از 0 تا 5 شماره‌گذاری شده‌اند. برای گرفتن آخرین نویسه باید 1 را از n کم کنید:

fruit[n-1]
'a'

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

fruit[-1]
'a'

اندیس -1 آخرین حرف را انتخاب می‌کند، -2 یکی مانده به آخر را، و به همین ترتیب.

۸.۲. برش‌های رشته

یک قطعه از رشته برش (slice) نامیده می‌شود. انتخاب یک برش شبیه انتخاب یک نویسه است.

fruit = 'banana'
fruit[0:3]
'ban'

عملگر [n:m] بخشی از رشته را از نویسه‌ی nام تا نویسه‌ی mام برمی‌گرداند، با احتساب اولی و بدون احتساب دومی. این رفتار برخلاف شهود است، اما شاید کمک کند تصور کنید اندیس‌ها بین نویسه‌ها اشاره می‌کنند، مثل شکل زیر:

(در کتاب اصلی اینجا نمودار رسم شده است: رشته‌ی ' b a n a n a ' با اندیس‌های 0 تا 6 که با خط‌چین به مرز بین نویسه‌ها اشاره می‌کنند.)

برای مثال، برش [3:6] حرف‌های ana را انتخاب می‌کند، یعنی 6 به‌عنوان بخشی از یک برش مجاز است، اما به‌عنوان اندیس مجاز نیست.

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

fruit[:3]
'ban'

اگر اندیس دوم را حذف کنید، برش تا انتهای رشته ادامه می‌یابد:

fruit[3:]
'ana'

اگر اندیس اول بزرگ‌تر یا مساوی اندیس دوم باشد، نتیجه یک رشته‌ی تهی (empty string) است که با دو علامت نقل‌قول نشان داده می‌شود:

fruit[3:3]
''

رشته‌ی تهی هیچ نویسه‌ای ندارد و طولش ۰ است.

در ادامه‌ی همین مثال، فکر می‌کنید fruit[:] چه معنایی دارد؟ امتحان کنید و ببینید.

fruit[:]
'banana'

۸.۳. رشته‌ها تغییرناپذیرند

وسوسه‌انگیز است که عملگر [] را در سمت چپ یک نسبت‌دهی به کار ببریم، با این قصد که یک نویسه از رشته را تغییر دهیم، مثل این:

greeting = 'Hello, world!'
greeting[0] = 'J'
TypeError: 'str' object does not support item assignment

نتیجه یک TypeError است. در پیام خطا، «object» همان رشته و «item» نویسه‌ای است که سعی کردیم نسبت دهیم. فعلاً شیء (object) همان مقدار است، اما این تعریف را بعداً دقیق‌تر می‌کنیم.

دلیل این خطا این است که رشته‌ها تغییرناپذیر (immutable) هستند، یعنی نمی‌توانید رشته‌ای را که وجود دارد تغییر دهید. بهترین کاری که می‌توانید بکنید ساختن یک رشته‌ی جدید است که گونه‌ای از رشته‌ی اصلی باشد.

new_greeting = 'J' + greeting[1:]
new_greeting
'Jello, world!'

این مثال یک حرف اول جدید را به یک برش از greeting الحاق می‌کند. روی رشته‌ی اصلی هیچ اثری ندارد.

greeting
'Hello, world!'

۸.۴. مقایسه‌ی رشته‌ها

عملگرهای رابطه‌ای روی رشته‌ها هم کار می‌کنند. برای اینکه ببینیم دو رشته برابرند، می‌توانیم از عملگر == استفاده کنیم.

word = 'banana'

if word == 'banana':
    print('All right, banana.')
All right, banana.

عملیات رابطه‌ای دیگر برای مرتب کردن کلمه‌ها به ترتیب الفبایی مفیدند:

def compare_word(word):
    if word < 'banana':
        print(word, 'comes before banana.')
    elif word > 'banana':
        print(word, 'comes after banana.')
    else:
        print('All right, banana.')
compare_word('apple')
apple comes before banana.

پایتون حروف بزرگ و کوچک را مثل آدم‌ها در نظر نمی‌گیرد. همه‌ی حروف بزرگ قبل از همه‌ی حروف کوچک می‌آیند، پس:

compare_word('Pineapple')
Pineapple comes before banana.

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

۸.۵. متدهای رشته

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

به‌جای نحو تابعی upper(word)، از نحو متدی word.upper() استفاده می‌کند.

word = 'banana'
new_word = word.upper()
new_word
'BANANA'

این استفاده از عملگر نقطه نام متد، یعنی upper، و نام رشته‌ای را که متد روی آن اعمال می‌شود، یعنی word، مشخص می‌کند. پرانتزهای خالی نشان می‌دهند این متد آرگومانی نمی‌گیرد.

فراخوانی یک متد احضار (invocation) نامیده می‌شود؛ در این مورد می‌گوییم upper را روی word احضار می‌کنیم.

۸.۶. نوشتن فایل‌ها

عملگرها و متدهای رشته برای خواندن و نوشتن فایل‌های متنی مفیدند. به‌عنوان مثال با متن رمان Dracula نوشته‌ی برم استوکر کار می‌کنیم که از پروژه‌ی گوتنبرگ (https://www.gutenberg.org/ebooks/345) در دسترس است.

download('https://github.com/AllenDowney/ThinkPython/raw/v3/pg345.txt');

کتاب را در یک فایل متنی ساده به نام pg345.txt دانلود کرده‌ام که می‌توانیم این‌طور برای خواندن بازش کنیم:

آرگومان کلیدواژه‌ای encoding='utf-8' در کتاب چاپی نیست. فقط در ویندوز لازم است.

reader = open('pg345.txt', encoding='utf-8')

این فایل علاوه بر متن کتاب، بخشی در ابتدا با اطلاعاتی درباره‌ی کتاب و بخشی در انتها با اطلاعاتی درباره‌ی مجوز دارد. قبل از پردازش متن، می‌توانیم با پیدا کردن خط‌های ویژه‌ی ابتدا و انتها که با '***' شروع می‌شوند، این مطالب اضافه را حذف کنیم.

تابع زیر یک خط را می‌گیرد و بررسی می‌کند که آیا یکی از خط‌های ویژه است یا نه. از متد startswith استفاده می‌کند که بررسی می‌کند رشته با دنباله‌ی مشخصی از نویسه‌ها شروع می‌شود یا نه.

def is_special_line(line):
    return line.startswith('*** ')

می‌توانیم از این تابع برای پیمایش خط‌های فایل و نمایش فقط خط‌های ویژه استفاده کنیم.

for line in reader:
    if is_special_line(line):
        print(line.strip())
*** START OF THE PROJECT GUTENBERG EBOOK DRACULA ***
*** END OF THE PROJECT GUTENBERG EBOOK DRACULA ***

حالا فایل جدیدی به نام pg345_cleaned.txt می‌سازیم که فقط متن کتاب را دارد. برای اینکه دوباره کتاب را پیمایش کنیم، باید دوباره برای خواندن بازش کنیم. و برای نوشتن یک فایل جدید می‌توانیم آن را برای نوشتن باز کنیم.

reader = open('pg345.txt', encoding='utf-8')
writer = open('pg345_cleaned.txt', 'w', encoding='utf-8')

open یک پارامتر اختیاری می‌گیرد که «حالت» (mode) را مشخص می‌کند – در این مثال 'w' نشان می‌دهد فایل را برای نوشتن باز می‌کنیم. اگر فایل وجود نداشته باشد ساخته می‌شود؛ اگر از قبل وجود داشته باشد، محتویاتش جایگزین می‌شود.

در قدم اول، فایل را پیمایش می‌کنیم تا اولین خط ویژه را پیدا کنیم.

for line in reader:
    if is_special_line(line):
        break

دستور break از حلقه «بیرون می‌پرد» – یعنی باعث می‌شود حلقه بلافاصله، قبل از رسیدن به انتهای فایل، تمام شود.

وقتی حلقه خارج می‌شود، line شامل خط ویژه‌ای است که شرط را درست کرد.

line
'*** START OF THE PROJECT GUTENBERG EBOOK DRACULA ***\n'

چون reader مکان خود در فایل را نگه می‌دارد، می‌توانیم از یک حلقه‌ی دوم برای ادامه از همان‌جایی که مانده‌ایم استفاده کنیم.

حلقه‌ی زیر بقیه‌ی فایل را، خط به خط، می‌خواند. وقتی خط ویژه‌ای را که پایان متن را نشان می‌دهد پیدا کند، از حلقه بیرون می‌پرد. در غیر این صورت خط را در فایل خروجی می‌نویسد.

for line in reader:
    if is_special_line(line):
        break
    writer.write(line)

وقتی این حلقه خارج می‌شود، line شامل خط ویژه‌ی دوم است.

line
'*** END OF THE PROJECT GUTENBERG EBOOK DRACULA ***\n'

در این نقطه reader و writer هنوز باز هستند، یعنی می‌توانیم همچنان از reader خط بخوانیم یا در writer خط بنویسیم. برای اینکه نشان دهیم کارمان تمام شده، می‌توانیم با احضار متد close هر دو فایل را ببندیم.

reader.close()
writer.close()

برای بررسی اینکه این فرایند موفق بوده، می‌توانیم چند خط اول فایل جدیدی را که ساختیم بخوانیم.

for line in open('pg345_cleaned.txt', encoding='utf-8'):
    line = line.strip()
    if len(line) > 0:
        print(line)
    if line.endswith('Stoker'):
        break
DRACULA
_by_
Bram Stoker

متد endswith بررسی می‌کند رشته با دنباله‌ی مشخصی از نویسه‌ها تمام می‌شود یا نه.

۸.۷. جست‌وجو و جایگزینی

در ترجمه‌ی ایسلندیِ Dracula در سال ۱۹۰۱، نام یکی از شخصیت‌ها از «Jonathan» به «Thomas» تغییر داده شد. برای انجام همین تغییر در نسخه‌ی انگلیسی، می‌توانیم کتاب را پیمایش کنیم، از متد replace برای جایگزین کردن یک نام با نام دیگر استفاده کنیم و نتیجه را در یک فایل جدید بنویسیم.

با شمردن خط‌های نسخه‌ی پاک‌شده‌ی فایل شروع می‌کنیم.

total = 0
for line in open('pg345_cleaned.txt', encoding='utf-8'):
    total += 1
    
total
15499

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

total = 0
for line in open('pg345_cleaned.txt', encoding='utf-8'):
    if 'Jonathan' in line:
        total += 1

total
199

۱۹۹ خط وجود دارد که این نام را دارند، اما این دقیقاً کل دفعاتی که ظاهر می‌شود نیست، چون ممکن است در یک خط بیش از یک بار بیاید. برای گرفتن مجموع می‌توانیم از متد count استفاده کنیم که تعداد دفعاتی را که یک دنباله در رشته ظاهر می‌شود برمی‌گرداند.

total = 0
for line in open('pg345_cleaned.txt', encoding='utf-8'):
    total += line.count('Jonathan')

total
200

حالا می‌توانیم 'Jonathan' را این‌طور با 'Thomas' جایگزین کنیم:

writer = open('pg345_replaced.txt', 'w', encoding='utf-8')

for line in open('pg345_cleaned.txt', encoding='utf-8'):
    line = line.replace('Jonathan', 'Thomas')
    writer.write(line)

نتیجه یک فایل جدید به نام pg345_replaced.txt است که نسخه‌ای از Dracula را دارد که در آن Jonathan Harker «Thomas» نامیده می‌شود.

total = 0
for line in open('pg345_replaced.txt', encoding='utf-8'):
    total += line.count('Thomas')

total
200

۸.۸. عبارت‌های منظم

اگر دقیقاً بدانیم دنبال چه دنباله‌ای از نویسه‌ها هستیم، می‌توانیم با عملگر in پیدایش کنیم و با متد replace جایگزینش کنیم. اما ابزار دیگری به نام عبارت منظم (regular expression) وجود دارد که همین عملیات – و خیلی بیشتر – را انجام می‌دهد.

برای نمایش، با یک مثال ساده شروع می‌کنیم و کم‌کم بالا می‌رویم. فرض کنید باز هم می‌خواهیم همه‌ی خط‌هایی را که یک کلمه‌ی خاص دارند پیدا کنیم. برای تنوع، دنبال اشاره به شخصیت اصلی کتاب، کنت دراکولا، می‌گردیم. این خطی است که به او اشاره می‌کند.

text = "I am Dracula; and I bid you welcome, Mr. Harker, to my house."

و این الگویی (pattern) است که برای جست‌وجو استفاده می‌کنیم.

pattern = 'Dracula'

ماژولی به نام re توابع مربوط به عبارت‌های منظم را فراهم می‌کند. می‌توانیم آن را این‌طور وارد کنیم و با تابع search بررسی کنیم که الگو در متن هست یا نه.

import re

result = re.search(pattern, text)
result
<re.Match object; span=(5, 12), match='Dracula'>

اگر الگو در متن ظاهر شود، search یک شیء Match برمی‌گرداند که نتایج جست‌وجو را دارد. در میان اطلاعات دیگر، متغیری به نام string دارد که متنِ جست‌وجوشده را دارد.

result.string
'I am Dracula; and I bid you welcome, Mr. Harker, to my house.'

متدی به نام group هم دارد که بخشی از متن را که با الگو تطابق داشته برمی‌گرداند.

result.group()
'Dracula'

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

result.span()
(5, 12)

اگر الگو در متن نباشد، مقدار بازگشتی search برابر None است.

result = re.search('Count', text)
print(result)
None

پس می‌توانیم با بررسی اینکه نتیجه None است یا نه، تشخیص دهیم جست‌وجو موفق بوده یا نه.

result == None
True

با کنار هم گذاشتن همه‌ی این‌ها، این تابعی است که خط‌های کتاب را پیمایش می‌کند تا به خطی برسد که با الگوی داده‌شده تطابق دارد و شیء Match را برمی‌گرداند.

def find_first(pattern):
    for line in open('pg345_cleaned.txt', encoding='utf-8'):
        result = re.search(pattern, line)
        if result != None:
            return result

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

result = find_first('Harker')
result.string
'CHAPTER I. Jonathan Harker’s Journal\n'

در این مثال لازم نبود از عبارت‌های منظم استفاده کنیم – می‌توانستیم همین کار را با عملگر in راحت‌تر انجام دهیم. اما عبارت‌های منظم کارهایی می‌کنند که عملگر in نمی‌تواند.

برای مثال، اگر الگو شامل نویسه‌ی خط عمودی '|' باشد، می‌تواند با دنباله‌ی سمت چپ یا دنباله‌ی سمت راست تطابق پیدا کند. فرض کنید می‌خواهیم اولین اشاره به Mina Murray را در کتاب پیدا کنیم، اما مطمئن نیستیم با نام کوچک به او اشاره شده یا با نام خانوادگی. می‌توانیم از الگوی زیر استفاده کنیم که با هر دو نام تطابق دارد.

pattern = 'Mina|Murray'
result = find_first(pattern)
result.string
'CHAPTER V. Letters—Lucy and Mina\n'

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

def count_matches(pattern):
    count = 0
    for line in open('pg345_cleaned.txt', encoding='utf-8'):
        result = re.search(pattern, line)
        if result != None:
            count += 1
    return count

حالا ببینیم Mina چند بار ذکر شده است.

count_matches('Mina|Murray')
229

نویسه‌ی ویژه‌ی '^' با ابتدای رشته تطابق دارد، پس می‌توانیم خطی را که با یک الگوی مشخص شروع می‌شود پیدا کنیم.

result = find_first('^Dracula')
result.string
'Dracula, jumping to his feet, said:--\n'

و نویسه‌ی ویژه‌ی '$' با انتهای رشته تطابق دارد، پس می‌توانیم خطی را که با یک الگوی مشخص تمام می‌شود پیدا کنیم (با نادیده گرفتن خط جدید انتها).

result = find_first('Harker$')
result.string
"by five o'clock, we must start off; for it won't do to leave Mrs. Harker\n"

۸.۹. جایگزینی در رشته

برم استوکر در ایرلند به دنیا آمد و وقتی Dracula در ۱۸۹۷ منتشر شد، در انگلستان زندگی می‌کرد. پس انتظار داریم از املای بریتانیایی کلماتی مثل «centre» و «colour» استفاده کند. برای بررسی، می‌توانیم از الگوی زیر استفاده کنیم که با «centre» یا املای آمریکایی «center» تطابق دارد.

pattern = 'cent(er|re)'

در این الگو، پرانتزها بخشی از الگو را که خط عمودی روی آن اعمال می‌شود محصور می‌کنند. پس این الگو با دنباله‌ای که با 'cent' شروع می‌شود و به 'er' یا 're' ختم می‌شود تطابق دارد.

result = find_first(pattern)
result.string
'horseshoe of the Carpathians, as if it were the centre of some sort of\n'

همان‌طور که انتظار می‌رفت، او از املای بریتانیایی استفاده کرده است.

می‌توانیم بررسی کنیم که آیا از املای بریتانیایی «colour» هم استفاده کرده است. الگوی زیر از نویسه‌ی ویژه‌ی '?' استفاده می‌کند که یعنی نویسه‌ی قبلی اختیاری است.

pattern = 'colou?r'

این الگو با «colour» که 'u' دارد یا «color» بدون آن تطابق دارد.

result = find_first(pattern)
line = result.string
line
'undergarment with long double apron, front, and back, of coloured stuff\n'

باز هم، همان‌طور که انتظار می‌رفت، از املای بریتانیایی استفاده کرده است.

حالا فرض کنید می‌خواهیم نسخه‌ای از کتاب با املای آمریکایی تولید کنیم. می‌توانیم از تابع sub در ماژول re استفاده کنیم که جایگزینی رشته (string substitution) انجام می‌دهد.

re.sub(pattern, 'color', line)
'undergarment with long double apron, front, and back, of colored stuff\n'

آرگومان اول الگویی است که می‌خواهیم پیدا و جایگزین کنیم، دومی چیزی است که می‌خواهیم با آن جایگزین شود، و سومی رشته‌ای است که می‌خواهیم در آن جست‌وجو کنیم. در نتیجه می‌بینید که «colour» با «color» جایگزین شده است.

# I used this function to search for lines to use as examples

def all_matches(pattern):
    for line in open('pg345_cleaned.txt', encoding='utf-8'):
        result = re.search(pattern, line)
        if result:
            print(line.strip())
# Here's the pattern I used (which uses some features we haven't seen)

names = r'(?<!\.\s)[A-Z][a-zA-Z]+'

all_matches(names)

۸.۱۰. اشکال‌زدایی

وقتی فایل می‌خوانید و می‌نویسید، اشکال‌زدایی می‌تواند دشوار باشد. اگر در یک نوت‌بوک Jupyter کار می‌کنید، می‌توانید از دستورهای پوسته (shell commands) کمک بگیرید. برای مثال، برای نمایش چند خط اول یک فایل می‌توانید از دستور !head استفاده کنید، مثل این:

!head pg345_cleaned.txt






                                DRACULA

                                  _by_

                              Bram Stoker

(خروجی بالا شامل خط‌های خالی ابتدای فایل است؛ ۱۰ خط اول فایل.)

علامت تعجب ابتدایی، !، نشان می‌دهد این یک دستور پوسته است که بخشی از پایتون نیست. برای نمایش چند خط آخر می‌توانید از !tail استفاده کنید.

!tail pg345_cleaned.txt
They lay in a sort of or-orderly=> They lay in a sort of orderly {pg
279}

Translyvania=> Transylvania {pg 294}

this mrrning from Dardanelles=> this morning from Dardanelles {pg 313}

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

برای ساختن یک فایل کوچک که بخشی از یک فایل بزرگ‌تر را دارد، می‌توانیم دوباره از !head همراه با عملگر تغییر مسیر > استفاده کنیم که نشان می‌دهد نتایج باید به‌جای نمایش، در یک فایل نوشته شوند.

!head pg345_cleaned.txt > pg345_cleaned_10_lines.txt

به‌طور پیش‌فرض !head ۱۰ خط اول را می‌خواند، اما یک آرگومان اختیاری می‌گیرد که تعداد خط‌هایی را که باید خوانده شود مشخص می‌کند.

!head -100 pg345_cleaned.txt > pg345_cleaned_100_lines.txt

این دستور پوسته ۱۰۰ خط اول pg345_cleaned.txt را می‌خواند و در فایلی به نام pg345_cleaned_100_lines.txt می‌نویسد.

نکته: دستورهای پوسته‌ی !head و !tail در همه‌ی سیستم‌عامل‌ها در دسترس نیستند. اگر برای شما کار نکردند، می‌توانیم توابع مشابهی را در پایتون بنویسیم. برای پیشنهادها به تمرین اول انتهای این فصل نگاه کنید.

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

دنباله (sequence): مجموعه‌ای مرتب از مقدارها که هر مقدار با یک اندیس صحیح مشخص می‌شود.

نویسه (character): عنصری از یک رشته، از جمله حرف، عدد و نماد.

اندیس (index): مقدار صحیحی که برای انتخاب یک عنصر در یک دنباله، مثل یک نویسه در رشته، به کار می‌رود. در پایتون اندیس‌ها از 0 شروع می‌شوند.

برش (slice): بخشی از رشته که با یک بازه از اندیس‌ها مشخص می‌شود.

رشته‌ی تهی (empty string): رشته‌ای که هیچ نویسه‌ای ندارد و طولش 0 است.

شیء (object): چیزی که یک متغیر می‌تواند به آن اشاره کند. یک شیء نوع و مقدار دارد.

تغییرناپذیر (immutable): اگر عناصر یک شیء قابل تغییر نباشند، شیء تغییرناپذیر است.

احضار (invocation): یک عبارت – یا بخشی از یک عبارت – که یک متد را فراخوانی می‌کند.

عبارت منظم (regular expression): دنباله‌ای از نویسه‌ها که یک الگوی جست‌وجو را تعریف می‌کند.

الگو (pattern): قاعده‌ای که شرط‌هایی را که یک رشته باید برآورده کند تا تطابق داشته باشد مشخص می‌کند.

جایگزینی رشته (string substitution): جایگزین کردن یک رشته، یا بخشی از یک رشته، با رشته‌ای دیگر.

دستور پوسته (shell command): عبارتی در یک زبان پوسته، که زبانی برای تعامل با سیستم‌عامل است.

۸.۱۲. تمرین‌ها

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

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

۸.۱۲.۱. از یک دستیار مجازی بپرسید

در این فصل فقط سطح عبارت‌های منظم را خراشیدیم. برای اینکه تصوری از امکانات بگیرید، از یک دستیار مجازی بپرسید: «What are the most common special characters used in Python regular expressions?» (رایج‌ترین نویسه‌های ویژه در عبارت‌های منظم پایتون کدام‌اند؟)

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

  • Write a Python regular expression that matches a 10-digit phone number with hyphens. (یک عبارت منظم پایتون بنویس که با شماره تلفن ۱۰ رقمی با خط تیره تطابق دارد.)

  • Write a Python regular expression that matches a street address with a number and a street name, followed by ST or AVE. (یک عبارت منظم بنویس که با نشانی خیابان – شامل عدد و نام خیابان و بعد ST یا AVE – تطابق دارد.)

  • Write a Python regular expression that matches a full name with any common title like Mr or Mrs followed by any number of names beginning with capital letters, possibly with hyphens between some names. (یک عبارت منظم بنویس که با نام کامل با هر عنوان رایج مثل Mr یا Mrs و بعد هر تعداد نام که با حرف بزرگ شروع می‌شوند، احتمالاً با خط تیره بین بعضی نام‌ها، تطابق دارد.)

و اگر می‌خواهید چیز پیچیده‌تری ببینید، عبارت منظمی بخواهید که با هر URL معتبر تطابق دارد.

عبارت منظم اغلب حرف r قبل از علامت نقل‌قول دارد که نشان می‌دهد «رشته‌ی خام» (raw string) است. برای اطلاعات بیشتر از یک دستیار مجازی بپرسید: «What is a raw string in Python?»

from doctest import run_docstring_examples

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

۸.۱۲.۲. تمرین

ببینید می‌توانید تابعی بنویسید که همان کاری را بکند که دستور پوسته‌ی !head می‌کند. این تابع باید نام فایلی که باید خوانده شود، تعداد خط‌هایی که باید خوانده شود و نام فایلی که خط‌ها در آن نوشته می‌شوند را به‌عنوان آرگومان بگیرد. اگر پارامتر سوم None بود، باید خط‌ها را به‌جای نوشتن در فایل نمایش دهد.

می‌توانید از یک دستیار مجازی کمک بگیرید، اما اگر این کار را کردید، به آن بگویید از دستور with یا دستور try استفاده نکند.

# Solution goes here

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

head('pg345_cleaned.txt', 10)
head('pg345_cleaned.txt', 100, 'pg345_cleaned_100_lines.txt')
!tail pg345_cleaned_100_lines.txt

۸.۱۲.۳. تمرین

«Wordle» یک بازی کلمه‌ای آنلاین است که هدفش حدس زدن یک کلمه‌ی پنج‌حرفی در شش تلاش یا کمتر است. هر تلاش باید به‌عنوان یک کلمه شناخته شود، نه اسم خاص. بعد از هر تلاش، اطلاعاتی می‌گیرید که کدام‌یک از حرف‌هایی که حدس زده‌اید در کلمه‌ی هدف هستند و کدام‌ها در جای درستند.

برای مثال، فرض کنید کلمه‌ی هدف MOWER است و شما TRIED را حدس می‌زنید. یاد می‌گیرید که E در کلمه هست و در جای درست قرار دارد، R در کلمه هست اما در جای درست نیست، و T، I و D در کلمه نیستند.

به‌عنوان مثالی دیگر، فرض کنید کلمه‌های SPADE و CLERK را حدس زده‌اید و فهمیده‌اید که E در کلمه هست، اما در هیچ‌یک از این جایگاه‌ها نیست، و هیچ‌یک از حرف‌های دیگر در کلمه نیستند. از کلمه‌های فهرست کلمه‌ها، چند تا می‌توانند کلمه‌ی هدف باشند؟ تابعی به نام check_word بنویسید که یک کلمه‌ی پنج‌حرفی را بگیرد و بررسی کند با توجه به این حدس‌ها می‌تواند کلمه‌ی هدف باشد یا نه.

# Solution goes here

می‌توانید از هر یک از توابع فصل قبل، مثل uses_any، استفاده کنید.

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

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

for line in open('words.txt'):
    word = line.strip()
    if len(word) == 5 and check_word(word):
        print(word)

۸.۱۲.۴. تمرین

در ادامه‌ی تمرین قبل، فرض کنید کلمه‌ی TOTEM را حدس می‌زنید و یاد می‌گیرید که E هنوز در جای درست نیست، اما M در جای درست است. چند کلمه باقی می‌ماند؟

# Solution goes here
# Solution goes here

۸.۱۲.۵. تمرین

کنت مونت‌کریستو رمانی از Alexandre Dumas است که کلاسیک به حساب می‌آید. با این حال، Umberto Eco در مقدمه‌ی یک ترجمه‌ی انگلیسی این کتاب اعتراف می‌کند که کتاب را «یکی از بدنوشته‌ترین رمان‌های تمام دوران» یافته است.

او به‌طور خاص می‌گوید در «تکرار بی‌شرمانه‌ی یک صفت» افراط شده است و به‌ویژه به دفعاتی اشاره می‌کند که «شخصیت‌هایش یا می‌لرزند یا رنگ‌پریده می‌شوند.»

برای دیدن اینکه آیا اعتراض او وارد است، تعداد خط‌هایی را که کلمه‌ی pale را به هر شکلی دارند بشمارید، شامل pale، pales، paled و paleness، و همچنین کلمه‌ی مرتبط pallor. از یک عبارت منظم واحد استفاده کنید که با هر یک از این کلمه‌ها تطابق دارد. به‌عنوان چالش اضافه، مطمئن شوید که با کلمه‌های دیگر مثل impale تطابق ندارد – ممکن است بخواهید از یک دستیار مجازی کمک بگیرید.

سلول زیر کتاب را از پروژه‌ی گوتنبرگ https://www.gutenberg.org/ebooks/1184 دانلود می‌کند.

download('https://github.com/AllenDowney/ThinkPython/raw/v3/pg1184.txt');

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

def clean_file(input_file, output_file):
    reader = open(input_file, encoding='utf-8')
    writer = open(output_file, 'w', encoding='utf-8')

    for line in reader:
        if is_special_line(line):
            break

    for line in reader:
        if is_special_line(line):
            break
        writer.write(line)
        
    reader.close()
    writer.close()

clean_file('pg1184.txt', 'pg1184_cleaned.txt')
# Solution goes here
# Solution goes here
# Solution goes here

طبق این شمارش، این کلمه‌ها در 223 خط کتاب ظاهر می‌شوند، پس شاید حق با آقای Eco باشد.