فصل ۴: ساختارهای داده — list، tuple، dict، set و collections

comprehension ها و sorted با key و lambda (و مرتب‌سازی الفبای فارسی)

ساختن مجموعه در یک خط خوانا

comprehension روشی فشرده برای ساختن list، dict یا set از روی یک iterable است. الگوی کلی: «عبارت برای هر عضو در مجموعه، اگر شرط». مقایسه کنید:

prices = [8_500_000, 12_000_000, 4_200_000, 15_700_000]

# روش حلقه‌ای
expensive = []
for p in prices:
    if p > 10_000_000:
        expensive.append(p // 10)

# comprehension: همان کار، یک خط
expensive = [p // 10 for p in prices if p > 10_000_000]
نوعنحونمونه
list[expr for x in it if cond][s * 2 for s in sizes]
dict{k: v for x in it}{c: len(c) for c in cities}
set{expr for x in it}{w.strip() for w in words}
generator(expr for x in it)sum(p for p in prices)

دقت کنید «if در انتها» فیلتر می‌کند، اما «if/else در ابتدا» تبدیل است: [p if p > 0 else 0 for p in values] هیچ عضوی را حذف نمی‌کند، فقط منفی‌ها را صفر می‌کند.

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

sorted و پارامتر key

sorted(iterable) همیشه یک لیست جدید برمی‌گرداند و روی هر iterableی کار می‌کند. پارامتر key تابعی است که برای هر عضو «کلید مقایسه» تولید می‌کند. lambda تابع کوچک بی‌نامی است که فقط یک عبارت برمی‌گرداند و برای همین موقعیت‌ها ساخته شده است:

orders = [("رضا", 12, 9_500_000), ("مریم", 6, 7_800_000), ("سارا", 12, 8_200_000)]

by_price = sorted(orders, key=lambda o: o[2])
by_size_desc_then_price = sorted(orders, key=lambda o: (-o[1], o[2]))
names_ci = sorted(["b", "A", "c"], key=str.casefold)   # بدون حساسیت به حروف بزرگ

ترفند تاپل در key: مرتب‌سازی چندسطحی را در یک خط انجام می‌دهد؛ اول بر اساس عضو اول تاپل، در صورت تساوی بر اساس دومی. منفی کردن عدد، ترتیب همان سطح را نزولی می‌کند.

مرتب‌سازی درست فارسی

پایتون رشته‌ها را بر اساس کد یونیکد مرتب می‌کند و در یونیکد، حروف «پ چ ژ ک گ ی» بعد از «و» و «ه» آمده‌اند (چون به الفبای عربی اضافه شده‌اند). نتیجه: «پارسا» بعد از «وحید» می‌آید! راه‌حل، کلید سفارشی بر اساس الفبای فارسی است:

FA_ALPHABET = "آابپتثجچحخدذرزژسشصضطظعغفقکگلمنوهی"
ORDER = {ch: i for i, ch in enumerate(FA_ALPHABET)}

def fa_key(word: str) -> list[int]:
    return [ORDER.get(ch, 100 + ord(ch)) for ch in word]

names = ["یاسمن", "پارسا", "وحید", "چنگیز", "علی", "گلناز", "آرش"]
print(sorted(names))              # ['آرش', 'علی', 'وحید', 'پارسا', ...]  غلط
print(sorted(names, key=fa_key))  # ['آرش', 'پارسا', 'چنگیز', 'علی', 'گلناز', 'وحید', 'یاسمن']

نکته‌هایی که کمتر کسی می‌داند

  • متغیر حلقه‌ی comprehension به بیرون نشت نمی‌کند (برخلاف حلقه‌ی for معمولی)؛ x داخل [x for x in ...] بعد از آن تعریف نشده است.
  • sum([x * x for x in data]) اول کل لیست را می‌سازد؛ sum(x * x for x in data) بدون کروشه، عضوبه‌عضو و با حافظه‌ی ثابت کار می‌کند.
  • operator.itemgetter(2) معادل سریع‌تر و خواناتر lambda o: o[2] است و با چند اندیس هم کار می‌کند: itemgetter(1, 2).
  • در comprehension تودرتو ترتیب حلقه‌ها مثل حلقه‌های تودرتوی معمولی است: [(r, c) for r in rows for c in cols]؛ حلقه‌ی بیرونی اول می‌آید.
  • برای مرتب‌سازی حرفه‌ای‌تر فارسی (نادیده گرفتن نیم‌فاصله، اعراب و «ي» عربی)، قبل از fa_key متن را یکسان‌سازی کنید؛ کتابخانه‌ی PyICU هم collation کامل فارسی دارد.

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