تعریف حلقهها با for
گاهی لازم است که بین دستهای و محتویاتش دور بزنیم؛ مثلا بین لیستی از کلمهها، یا خطوط در یک فایل، و یا شمارهها در یک فهرست. زمانی که لیستی از چیزیهایی داریم که لازم است بینشان بگردیم، ما از یک حلقهی معین با استفاده از گزارهی for بهره میبریم. چرا حلقه «معین»؟ اگر یادتان باشد ما از گزارهی while برای حلقههای نامعین استفاده میکردیم، چرا که حلقه while به سادگی تا زمانی که شرطِ اجرای آن «غلط» شود، تکرار میشود، ولی for در بین یک دستهی «مشخص» و «معین» از چیزها حلقه میزند. به عبارتی آنقدر این حلقه را تکرار میکند که بازهی مورد نظر در لیست را گشته باشد.
متن یا سینتکس حلقهی for مشابه با حلقهی while است. به صورت کلی ما گزارهی for و بدنهی آن حلقه را با یک تورفتگی داریم:
1 2 3 4 |
friends = ['Joseph', 'Glenn', 'Sally'] for friend in friends: print('Happy New Year:', friend) print('Done!') |
مثال بالا را در نظر بگیرید. در زبان پایتونی متغیر friends یک لیستیست که شامل سه رشته میشود. حلقهی for بین این لیست دور زده و بدنهی حلقه را برای هر کدام از این سه رشته به صورت جداگانه اجرا میکند. در زیر خروجی برنامهی بالا را مشاهده کنید:
1 2 3 4 |
Happy New Year: Joseph Happy New Year: Glenn Happy New Year: Sally Done! |
ترجمهی تحتالفظی while دقیقا همان چیزی بود که در پایتون مورد استفاده قرار میگرفت، ولی برای حلقهی for اوضاع فرق میکند. بیایید حلقهی مثال قبل را با هم به زبان آدمیزاد شرح دهیم: «گزارههای موجود در بدنهی حلقه را برای هر کدام از دوستان موجود در لیست friends اجرا کن».
در کد مربوط به حلقهی for کلمههای for و in کلمات رزرو شدهاند و کلمات friend و friends متغیراند:
1 2 |
for friend in friends: print('Happy New Year:', friend) |
در این موردِ خاص، friend «متغیر تکرار» برای حلقهی for محسوب میشود. متغیر friend در هر تکرار تغییر کرده و حلقه for را تا انتها کنترل میکند. به این صورت که با رسیدن متغیر به آخرین آیتم موجود در لیست، حلقه پایان میپذیرد. در مثال بالا، متغیر تکرار که همان friend باشد، پشت سر هم و به ترتیب سه رشتهی موجود در متغیر friends را در حلقه اجرا میکند.
الگوی حلقه
اغلب ما از حلقهی for یا while استفاده کرده تا بر روی لیستی از آیتمها یا محتوای یک فایل کار کنیم. مانند زمانی که به دنبال بزرگترین یا کوچکترین مقدار در دادهای که اسکن کردهایم میباشیم.
به طور کلی، حلقهها از سه جزء تشکیل شدهاند:
- تعریف یک یا چند مقدار، قبل از شروع حلقه
- اجرای محاسبات بر روی هر کدام از آیتمها، و تغییر احتمالی متغیرهای موجود در بدنهی حلقه
- بررسی نتایج متغیرها زمانی که حلقه به پایان رسید
در ادامه ما لیستی از شمارهها را – برای نشان دادن مفهوم و ساختار الگوی حلقههای رایج – برایتان آوردهایم.
حلقههایی که میشمارند و جمع میزنند
برای نمونه، اگر قرار باشد شمارهی آیتمهای موجود در یک لیست را بشماریم، ما از حلقهی for استفاده میکنیم:
1 2 3 4 |
count = 0 for itervar in [3, 41, 12, 9, 74, 15]: count = count + 1 print('Count: ', count) |
قبل از اینکه حلقه شروع شود، مقدار متغیر count را صفر قرار میدهیم. سپس یک حلقه for مینویسیم. این حلقه تکتک آیتمهای لیست را دور زده و به ازای هر کدام از آنها یک بار گزارهی موجود در بدنه را اجرا میکند. نامِ متغیر تکرار itervar است که کنترل حلقه را در دست دارد.
در بدنهی حلقه ما عدد یک را به مقدار فعلی متغیر count به ازای هر کدام از مقادیر موجود در لیست اضافه میکنیم. زمانی که حلقه اجرا میشود، مقدار count برابر با آخرین مقداریست که داشته است. مثلا در اجرای اول count برابر با صفر است ولی در تکرار دوم حلقه با توجه به اجرای اول، برابر با یک است که با توجه به گزارهی موجود در بدنهی حلقه به دو تبدیل میشود. به همین ترتیب با هر بار اجرای حلقه، مقدار count یک عدد افزایش پیدا میکند.
در نهایت مقدار count برابر با مجموع تعداد آیتمهای موجود در لیست خواهد بود. این حلقه، خروجی خاصی را چاپ نمیکند، ولی مقداری را در نهایت به count اختصاص میدهد که هدف ما از ایجاد و ساخت آن حلقه بوده است.
در مثال زیر حلقهای را داریم که مجموع اعداد موجود در لیست را محاسبه کرده و سپس چاپ میکند:
1 2 3 4 |
total = 0 for itervar in [3, 41, 12, 9, 74, 15]: total = total + itervar print('Total: ', total) |
در این حلقه ما از متغیر تکرار استفاده کردهایم. به جای اضافه کردن عدد یک به count – به مانند مثال قبل – ما عدد موجود در لیست (۳،۴۱، ۱۲، ۹ و…) را به مقدار کل، در حین اجرای آن تکرار اضافه نمودهایم. به عبارتی متغیر total حاوی مقداری برابر با جمع مقادیر تا آنجای کار میشود. قبل از اینکه حلقه شروع شود مقدار total صفر بود، چرا که هنوز مقدار اختصاص داده شده اولیه را در خود داشت و عملیاتی بر روی آن صورت نگرفته بود. در حین اجرای حلقه، مقدار total برابر با جمع مجموع مقدارهای موجود در لیست میشود و در پایان، مقدار total برابر با جمع تمامی مقادیر موجود در لیست خواهد بود.
همینطور که حلقه اجرا میشود، total جمع عناصر موجود در معادله را حساب میکند؛ یک متغیر که به این صورت مورد استفاده قرار میگیرد، accumulator یا انباشتگر خوانده میشود.
نه حلقهی شمارش و نه حلقهی انباشتگر، در آینده به کار شما نخواهند آمد چرا که توابع توکاریشدهای به اسمهای len() و sum() به ترتیب شمارهی آیتمها، و جمع آیتمهای موجود در یک لیست را به ما میدهند و شما نیاز به حلقههای فوق ندارید.
حلقه برای یافتن مقدار بیشینه و کمینه
برای پیدا کردن بزرگترین مقدار در یک لیست یا سلسلهای از اعداد ما حلقهی زیر را ساختهایم:
1 2 3 4 5 6 7 |
largest = None print('Before:', largest) for itervar in [3, 41, 12, 9, 74, 15]: if largest is None or itervar > largest : largest = itervar print('Loop:', itervar, largest) print('Largest:', largest) |
زمانی که برنامه اجرا میشود، خروجی زیر ظاهر خواهد شد:
1 2 3 4 5 6 7 8 |
Before: None Loop: 3 3 Loop: 41 41 Loop: 12 41 Loop: 9 41 Loop: 74 74 Loop: 15 74 Largest: 74 |
متغیر largest عبارت است از بزرگترین مقداری که تا اینجای کار به آن رسیدهایم. قبل از حلقه، ما مقدار largest را None قرار میدهیم. None یک مقدار ویژهی غیرقابل تغییر است که میتواند به متغیرها اختصاص داده شود. این مقدار میگوید که متغیر «خالی» است.
قبل از شروع حلقه، بزرگترین مقداری که ما تا اینجای کار با توجه به جریان اجرا دیدهایم، None است. در حقیقت ما مقدار دیگری غیر از None را ندیدهایم. در خروجی بالا مشخص است که درست زمانیکه حلقه اجرا میشود، مقدار ۳ جایگزین مقدار اولیهی متغیر largest میشود.
بعد از اولین تکرار، largest دیگر None نیست در نتیجه قسمت دوم عبارت منطقیِ ترکیبی – که مقایسهی itervar > largest را بررسی میکند – تنها زمانی درست از آب در میآید که به عددی بزرگتر از مقدار فعلی largest برخورد کند. وقتی ما در جریان اجرا به مقداری بزرگتر از largest برخورد میکنیم، مقدار largest تغییر کرده و آن مقدار جدید را به خود میگیرد. در مثال بالا میبینید که مقدار largest از ۳ به ۴۱ و سپس به ۷۴ تغییر پیدا کرد.
در پایان حلقه، ما یک به یک مقادیر را بررسی کردهایم. largest حاوی بزرگترین مقدار موجود در لیست خواهد بود.
برای محاسبهی کمترین مقدار موجود در لیست، کد قبلی تنها یک تغییر کوچک میکند:
1 2 3 4 5 6 7 |
smallest = None print('Before:', smallest) for itervar in [3, 41, 12, 9, 74, 15]: if smallest is None or itervar < smallest: smallest = itervar print('Loop:', itervar, smallest) print('Smallest:', smallest) |
به شکل مشابه smallest حاوی کوچکترین عددِ در جریان اجراست. زمانی که حلقه پایان میپذیرد، این متغیر حاوی کوچکترین عدد کل لیست خواهد بود.
به مانند شمارش و انباشتگر، توابع توکاری شدهای در پایتون با نامهای max() و min() وجود دارد که به ترتیب بزرگترین و کوچکترین مقدار یک لیست را برمیگردانند.
کد زیر یک نسخهی ساده شده از تابع توکاری شدهی min() در پایتون است:
1 2 3 4 5 6 |
def min(values): smallest = None for value in values: if smallest is None or value < smallest: smallest = value return smallest |
تابع بالا، همان کد مربوط به محاسبهی کوچکترین عدد بود که گزارههای print آن حذف شده است. اکنون این تابع کاری را که تابع توکاری شده python برای تشخیص کوچکترین عدد موجود در لیست، انجام میدهد را اجرایی میکند.
اشکالزدایی
به محضی که شروع به نوشتن برنامههای بزرگتر میکنید، تازه میبینید که گاهی اشکالزدایی زمان بیشتری از نوشتن خودِ کد از شما میگیرد. کدهای بیشتر، به معنی احتمالِ بیشترِ خطا و احتمال مخفی شدن باگهای بیشتر در برنامه است.
یکی از راههای صرفهجویی در زمان اشکالزدایی استفاده از تکنیک دو نیم کردن است. به عنوان مثال فرض کنید که ۱۰۰ خط کد دارید. اگر قرار باشد برای بررسی کد، خط به خط جلو بروید برای بررسیِ کاملِ کد، شما صد مرحله در پیش رو خواهید داشت.
حالا روش دیگری را استفاده میکنیم. شما ابتدا مساله را به نصف میشکنید. به وسط برنامه یا یک مکان منطقی در همان حوالی میروید و یک مقدار میانی را با استفاده از گزارهی print چاپ میکنید. سپس برنامه را اجرا میکنید.
با بررسی خروجی گزاره print بررسی میکنید که مشکل از نیمهی اول کد بوده یا خیر. چگونه؟ اگه مقدار میانی یک میزانِ اشتباه را برگرداند، خب مشکل از بخش اول کد است.
هر بار که به طریق مشابه کد را بررسی کنید، مقدار خطوط مشکوک به نصف تقلیل پیدا میکند. بعد از شش مرحله، احتمالا به یک یا دو خط کد رسیدهاید. حداقل در تئوری!
در عمل، وسط یک برنامه همیشه واضح نیست و نمیشود که یک قطعه کد در بین آن جا داد و کد را بررسی کرد. در اصل شمارش خطوط و پیدا کردن نقطهی وسط، کار معقولی به نظر نمیرسد. در عوض به دنبال قطعههایی از کد که احتمالا خطا در آنها خف کرده بگردید و همانها را بررسی کنید. جایی که احساس میکنید خطا از آن ناشی شده را انتخاب کرده و سپس با استفاده از ابزارهایی مثل print سعی کنید از مشکل سر در بیاورید.
واژگان فصل
انباشتگر / Accumulator:
متغیریست در یک حلقه برای افزایش یا انباشتن یک برآیند یا نتیجه.
شمارنده / Counter:
متغیریست در یک حلقه که برای شمارش تعداد دفعاتی که اتفاقی میافتد، به کار میرود. ما «شمارنده» را در ابتدا با صفر مقداردهی میکنیم و سپس به آن مقداری را – زمانی که میخواهیم، چیزی را بشماریم – اضافه میکنیم.
نزول / Decrement:
بهروز رسانیای که مقدار یک متغیر را کاهش میدهد.
مقداردهی اولیه / Initialize:
گزارهای که در آن به یک متغیر، مقدار اولیه داده میشود. این متغیر در ادامه بهروزرسانی خواهد شد.
صعود / Increment:
بهروز رسانیای که مقدار یک متغیر را افزایش میدهد.
حلقه بینهایت / Infinite Loop:
حلقهای که شرط آن هیچگاه «غلط» نمیشود؛ به عبارتی اگر آن حلقه تنها بر مبنای شرط، تکرار شود، آن حلقه برای همیشه، و تا زمان بستن برنامه، تکرار خواهد شد.
تکرار / Iteration:
اجرای تکراری دستهای از گزارهها را Iteration یا تکرار میگویند. تکرار میتواند هم توسط یک تابع با فراخوانی خودش، هم در یک حلقه صورت پذیرد.
تمرینها
تمرین ۱: برنامهای بنویسید که تا زمانی که کاربر کلمهی “done” را تایپ کند، به صورت مکرر از او عدد در خواست کند. سپس با ورود “done” سه مقدار «مجموع اعداد»، «تعداد ورودیهای عددی»، «میانگین اعداد» را چاپ کند. برنامهی شما باید با استفاده از try و except در زمانیکه کاربر ورودی غیرعددی وارد میکند به او پیام خطای «Invalid input» را نشان دهد و به سراغ درخواست مجدد عدد برود. به خروجی برنامهی نمونهی ما دقت کنید:
1 2 3 4 5 6 7 |
Enter a number: 4 Enter a number: 5 Enter a number: bad data Invalid input Enter a number: 7 Enter a number: done 16 3 5.333333333333333 |
تمرین ۲: برنامهی دیگری بنویسید که مثل برنامهی بالا یک لیست از اعداد را درخواست کرده، سپس «بزرگترین» و «کوچکترین» آنها را به جای مقدار «میانگین» چاپ کند.