برنامه‌نویسی فانکشنال در پایتون - قسمت ۱


برنامه‌نویسی فانکشنال چی هست؟

با استفاده از این روش (Functional Programming) شما می‌تونید خیلی کدهاتون رو کوتاه‌تر و بهینه‌تر بکنید. به طور مثال فرض کنید که می‌خواین تمام عناصر یک لیست رو به عدد صحیح تبدیل کرده و در نهایت مربع آنها را حساب کنید. در یک خط می‌توان چنین کاری را انجام داد. بدون اینکه توضیح اضافه‌ای بدیم بریم سراغ آموزش کاربردی این پست :)

۱. تابع Map

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

۱.۱. مثال | یک لیست از رشته بگیرید و داده‌های آن را ابتدا به عدد صحیح تبدیل کنید.

data = ['1', '2', '3', '4']
data = map(int, data)
print(list(data)) # [1, 2, 3, 4]

تابع int یک ورودی می‌گیرد و عدد صحیح آن را برمی‌گرداند.

۱.۲. مثال | یک لیست از عدد بگیرید و مربع اعداد را حساب کنید.

def square(n): return n * n
data = [1, 2, 3, 4]
data = map(square, data)
print(list(data)) # [1, 4, 9, 16]

۱.۳. مثال | میخوایم یه لیست از کاراکترهای انگلیسی کوچیک رو یکی ببریم جلوتر.

def encrypt(c):
    code = ord(c) + 1
    if code == 123: code = 97
    return chr(code)
chars = ['a', 'b', 'z']
data = map(encrypt, chars)
print(list(data)) # ['b', 'c', 'a']


دقت کنید که برای تبدیل یک کاراکتر به کد اسکی از تابع ord استفاده می‌کنیم. کد اسکی حرف a برابر ۹۷ می‌باشد و کد اسکی حرف z برابر ۱۲۲ می‌باشد. پس از افزایش کد اسکی باید بررسی کنیم که اگر از ۱۲۲ گذشته باشد آن را به حرف a یا همان کد ۹۷ برگردانیم. در نهایت با استفاده از تابع chr کد را به حرف تبدیل می‌کنیم. این مثال را می‌توانیم کمی کوتاه‌تر کنیم.

def encrypt(c):
    code = (ord(c) - 96) % 26 + 97
    return chr(code)
chars = ['a', 'b', 'z']
data = map(encrypt, chars)
print(list(data)) # ['b', 'c', 'a']

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

print(*map(lambda c: chr((ord(c) - 96) % 26 + 97), input().split()))

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

۲. تابع Filter

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

۲.۱. مثال | اعداد زوج یک لیست را استخراج کنید.

number = [1, 2, 3, 4, 5]
even = filter(lambda n: n%2 == 0, number)
print(list(even)) # [2, 4]


به ازای عناصری که تابع به برای آنها مقدار صحیح برگرداند آن عنصر در لیست باقی می‌ماند وگرنه حذف می‌شود. اگه دقت کنید خیلی کاربرد داره. مثلا بخوایم که رشته‌های یه لیست که ویژگی‌های خاصی دارن رو استخراج کنیم :)

۲.۲. مثال | حروف صدادار یک لیست را استحراج کنید.

chars = ['a', 'c', 'r', 'e', 'o']
vowels = filter(lambda c: c in 'aeiou', chars)
print(list(vowels)) # ['a', 'e', 'o']

به ترتیب خروجی دقت کنید. دقیقا از چپ به راست عناصر را فیلتر می‌کند. در کل وقتی بتوانید یک تابع که خروجی آن True و False است را پیاده‌سازی کنید به راحتی می‌توانید از فیلتر استفاده کنید.

۳. معرف لیست

به راحتی می‌توانید لیست‌های خود را در یک خط تعریف کنید. به طور مثال فرض کنید می‌خواهیم اعداد ۱ تا ۱۰۰ را درون یک لیست قرار دهیم.

[ i for i in range(1, 101)) ] # Method 1
list(range(1, 101)) # Method 2

حال فرض کنید که بخواهیم مجذور اعداد یک تا پنجاه را درون یک لیست قرار دهیم.

[ i ** 2 for i in range(1, 51) ]

در نهایت یک سری عدد از ورودی می‌گیریم و علامت آن‌ها را تعیین می‌کنیم. اگر منفی باشند با عدد ۱- و اگر مثبت باشند با عدد ۱ نشان می‌دهیم. خیلی سادس :)

[ (-1 if n < 0 else 1) for n in map(int , input().split()) ]

۴. مرتب‌سازی

با استفاده از تابع sorted می‌توانیم یک لیست را به هر نحوی مرتب کنیم. این تابع یک پارامتر مهم به نام key دارد که با استفاده از آن به تابع می‌فهمانیم چطور مرتب‌سازی را انجام دهد. فرض کنید می‌خواهیم یک سری تاپل را درون یک لیست مرتب کنیم. فرض کنید یک لیست داریم که هر عنصر شامل نام یک ماشین و قیمت آن می‌باشد. حال می‌خواهیم بر اساس قیمت به صورت نزولی و در صورت برابری بر اساس نام ماشین به صورت صعودی داده‌ها را مرتب کنیم.

cars = [ ('toyota', 17000), ('benz', 25000), ('audi', 25000) ]
result = sorted(cars, key = lambda car: (-car[1], car[0]))
print(result)
#output -> [('audi', 25000), ('benz', 25000), ('toyota', 17000)]

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

۵. توابع کاربردی

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

  • تابع sum برای جمع کردن مقادیر عددی یک شی قابل پیمایش به کار می‌رود.
  • تابع max برای پیدا کردن بزرگترین عضو یک شی قابل پیمایش به کار می‌رود.
  • تابع min برای پیدا کردن کوچیکترین عضو یک شی قابل پیمایش به کار می‌رود.
sum( [1, 2, 3, 4] ) # 10
min( [1, 2, 3, 4] ) # 1
max( [1, 2, 3, 4] ) # 4

۶. چالش نهایی

به شما یک آرایه از اعداد داده می شود. ما به یک عدد گوگولی میگیم اگه هم خودش بر ۶ بخش پذیر باشه و هم شماره‌ی جایگاهی که در آرایه ظاهر شده بر ۶ بخش پذیر باشه :)

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

راهنمایی ۱ : به این شکل می‌توانید اعدادی را که اندیسشان بر ۶ بخش پذیر است را چاپ کنید.

print(*list(map(int, input().split()))[5::6])

راهنمایی ۲: به این شکل می‌توانید اعدادی را که زوج هستند را چاپ کنید.

print(*(x for x in map(int, input().split()) if x % 2 == 0))

در نهایت کد شما باید چنین چیزی باشد.

*sorted(i for i in list(map(int, input().split()))[5::6] if x%6 == 0)