ساختارهای تکرار - بخش اول
ساختارهای تکرار (یا حلقهها) یکی از مهمترین مفاهیم در برنامهنویسی هستند. در واقع یکی از برتریهای کامپیوترها نسبت به انسانها این است که میتوانند یک عمل بخصوص را در زمانی کوتاه، میلیونها و یا میلیاردها بار انجام دهند. برای انجام چنین عملی در زبانهای برنامهنویسی از ساختارهای تکرار استفاده میشود. با استفاده از این ساختارها میتوان مجموعهی مشخصی از دستورات را به تعداد دفعات مشخص و یا تا زمان رسیدن به یک حالت خاص تکرار کرد.
ساختار تکرار while
اولین و سادهترین ساختار تکراری که معرفی میکنیم، ساختار تکرار while است. با استفاده از این ساختار میتوان مجموعهای از دستورات (یک بلاک کد) را تا زمانی که شرط خاصی برقرار باشد تکرار کرد. یعنی تکرار دستورات زمانی متوقف خواهد شد که شرط مذکور برقرار نباشد. نحوهی استفاده از این ساختار را در قطعه کد زیر مشاهده میکنید.
while(condition){
// مجموعه دستورات تکرار شونده
}
در ساختار فوق، مادامی که ارزش منطقی condition برابر با true باشد، دستورات داخل بلاک تکرار میشوند. یعنی پس از هر بار اجرای دستورات داخل بلاک، یک بار مقدار condition بررسی میشود. در صورتی که مقدار آن (ارزش منطقی آن) true باشد، یک بار دیگر دستورات اجرا میشوند. اما در صورت false بودن مقدار condition، اجرای حلقه به پایان رسیده و دستورات بعد از ساختار while اجرا خواهند شد. به قطعه کد زیر توجه کنید.
let n = 0;
while(n < 4){
console.log("n = " + n);
n++;
}
در صورت اجرای مثال فوق، خروجی زیر تولید خواهد شد. (این برنامه را میتوانید اینجا در CodePen اجرا کنید)
← n = 0
← n = 1
← n = 2
← n = 3
اما این برنامه چگونه عمل میکند و چطور خروجی بالا را تولید میکند؟
همانطور که مشاهده میکنید در اولین خط این برنامه یک متغیر به نام n و با مقدار اولیهی صفر تعریف میشود. سپس در ساختار while بررسی میشود که آیا مقدار n کوچکتر از ۴ است یا خیر؟ در صورتی که این شرط برقرار باشد (که در این حالت برقرار است)، یک بار دستورات داخل بلاک اجرا میشوند.
اگر به دستورات داخل این بلاک توجه کنید، میبینید که ابتدا یک رشته در کنسول چاپ میشود که مقدار متغیر n را نمایش میدهد. سپس یک واحد به مقدار متغیر n اضافه میشود. پس از پایان اجرای این دو دستور، شرط 4 > n مجدداً بررسی میشود. این بار مقدار متغیر n صفر نیست و به یک تغییر کرده است. اما همچنان کوچکتر از ۴ است. در نتیجه ارزش منطقی عبارت 4 > n برابر با true است. به همین دلیل یک بار دیگر دستورات داخل حلقه اجرا میشوند.
در اجرای دوم این حلقه، مقدار جدید متغیر n که 1 میباشد در خروجی چاپ شده و یک واحد به آن اضافه شده و مقدار جدید n برابر با 2 خواهد بود. به همین ترتیب دو بار دیگر این حلقه به ازای n = 2 و n = 3 اجرا میشود و دو خط دیگر در خروجی چاپ میشود. اما بعد از تکرار چهارم این حلقه، مقدار متغیر n به چهار رسیده است و به همین دلیل شرط 4 > n دیگر برقرار نیست و نتیجهی آن false است. در نتیجه اجرای حلقه خاتمه مییابد. و با توجه به اینکه پس از پایان ساختار while هیچ دستور دیگری موجود نیست، اجرای کل برنامه نیز در همین نقطه به پایان میرسد. پیشنهاد میکنم برنامه فوق را با تغییر مقدار اولیهی متغیر n و همینطور تغییر مقدار مقایسه (یعنی عدد ۴) با مقادیر مختلف اجرا کنید و نتیجه را مشاهده کنید.
اجتناب از حلقهی بینهایت
همانطور که مشاهده کردید، اجرای یک حلقهی while (و سایر انواع حلقهها) زمانی به پایان میرسد که شرط ادامهی حلقه برقرار نباشد و دارای ارزش منطقی false باشد. به همین دلیل باید در تعیین شرط پایان حلقهها بسیار دقت کنید. زیرا انتخاب شرطهای نامناسب، ممکن است منجر به تولید حلقهی بینهایت (Infinite Loop) شود. منظور از حلقهی بینهایت، حلقهای است که شرط ادامهی آن همیشه true است و هیچگاه false نخواهد شد. در چنین شرایطی برنامهی شما هیچگاه از حلقه خارج نخواهد شد. به عنوان مثال در حلقهی زیر با توجه به شرط به کار برده شده و با توجه به روند افزایشی متغیر n، این متغیر همیشه بزرگتر از صفر خواهد بود و اجرای حلقه نیز هیچگاه پایان نخواهد یافت.
let n = 10;
while(n > 0){
console.log("n = " + n);
n++;
}
البته در برخی شرایط ممکن است استفاده از شرطهایی که همیشه true هستند مفید باشد. در چنین شرایطی برای اینکه اجرای حلقه در شرایط خاصی به پایان برسد از دستور break استفاده میشود. اما فعلاً از این حالت صرفنظر میکنیم. زیرا هنوز با کاربرد break در حلقهها آشنا نشدهایم.
ساختار تکرار do-while
همانطور که اشاره شد، در ساختار تکرار while قبل از هر بار اجرای دستورات حلقه، شرط اجرای حلقه بررسی میشود و در صورت true بودن این شرط، دستورات حلقه یک بار اجرا شده، سپس مجدداً شرط اجرای حلقه بررسی میشود. در نتیجه اگر شرط اجرای حلقه از ابتدا false باشد، دستورات داخل حلقه حتی یک بار هم اجرا نخواهند شد.
گاهی اوقات لازم است تا دستوراتی را یک بار اجرا کنیم، سپس در صورت برقرار بودن یک شرط، آن دستورات را مجدداً اجرا کنیم. در چنین شرایطی میتوان از ساحتار do-while استفاده کرد. نحوهی استفاده از این ساختار را در قطعه کد زیر مشاهده میکنید.
do{
// مجموعه دستورات تکرار شونده
}while(condition)
در ساختار فوق، ابتدا یک بار دستورات داخل بلاک اجرا میشوند. سپس شرط condition بررسی میشود و در صورت true بودن آن، دستورات مجدداً اجرا میشوند. و این روند تا زمانی که condition برابر با false شود ادامه مییابد. در واقع تنها تفاوت حلقهی while و حلقهی do-while در این است که در حلقهی while شرط اجرای دستورات، قبل از شروع حلقه بررسی میشود. اما در حلقهی do-while این شرط پس از یک بار اجرا شدن دستورات بررسی میشود. به قطعه کد زیر توجه کنید.
let n = 0;
do{
console.log("n = " + n);
n++;
}while(n < 4)
این همان برنامهی قبلی است که این بار با استفاده از ساختار do-while پیادهسازی شده است. در صورت اجرای برنامهی فوق، دقیقاً همان نتیجهی قبلی را در خروجی مشاهده خواهید کرد. اما اگر مقدار اولیهی n و یا شرط 4 > n را تغییر دهید. تحت هر شرایطی دستورات داخل حلقه حداقل یک بار اجرا شده و خط اول خروجی قطعاً نمایش داده خواهد شد. اما در ساختار while در صورتی که شرط ادامهی حلقه از ابتدا false باشد، هیچ دستوری اجرا نشده و هیچ خروجی تولید نخواهد شد. برنامهی فوق را میتوانید اینجا در CodePen اجرا کنید.
ساختار تکرار for
ساختار for را میتوان پرکاربردترین نوع حلقهها در جاوا اسکریپت دانست. در قطعه کد زیر نحوهی استفاده از این ساختار را مشاهده میکنید.
for(initialization ; condition ; after){
// مجموعه دستورات تکرار شونده
}
همانطور که مشاهده میکنید برای استفاده از حلقهی for باید ۳ پارامتر مختلف را معین کنیم که عبارتند از :
- initialization : دستوری است که قبل از شروع حلقه اجرا میشود و معمولاً برای مشخص کردن مقدار اولیهی یک متغیر به کار برده میشود. توجه کنید که این دستور فقط یک بار و قبل از اولین تکرار حلقه اجرا میشود.
- condition : شرطی است که قبل از هر بار اجرای دستورات بررسی میشود و در صورت true بودن، دستورات داخل حلقه یک بار اجرا میشوند.
- after : دستوری است که بعد از هر تکرار یک بار اجرا میشود و معمولاً برای افزایش یا کاهش دادن مقدار یک متغیر (به عنوان شمارنده) به کار برده میشود.
به عنوان مثال برای پیادهسازی مثال قبلی که با ساختارهای while و do-while انجام شد. میتوانیم از ساختار for به شکل زیر استفاده کنیم.
for(let n = 0 ; n < 4 ; n++){
console.log("n = " + n);
}
در مثال فوق ابتدا قسمت initialization اجرا شده و مقدار اولیهی متغیر n را برابر با صفر قرار میدهد. سپس شرط 4 > n بررسی میشود. با توجه به اینکه در ابتدا این شرط برقرار است، دستور داخل حلقه اجرا میشود. سپس قسمت after اجرا شده و یک واحد به متغیر n اضافه میکند. دقیقاً مانند دو مثال قبلی، اینجا نیز بعد از ۴ بار تکرار شدن این حلقه، شرط 4 > n دیگر برقرار نخواهد بود و اجرای حلقه به پایان میرسد. در نتیجه، خروجی این برنامه نیز دقیقاً مانند دو برنامهی قبلی است. (این مثال را نیز میتوانید اینجا اجرا کنید)
حلقههای تو در تو
همانطور که اشاره شد، تمام ساختارهای تکرار شامل یک بلاک کد هستند که در هر تکرار، تمام دستورات این بلاک اجرا میشوند. میتوان در این بلاک کد، از هر نوع کد جاوا اسکریپتی استفاده کرد. در نتیجه میتوان در این بلاک از حلقههای دیگری استفاده کرد. چنین حلقههایی که خود شامل حلقههای دیگری باشند را حلقههای تو در تو (Nested Loops) مینامند.
فرض کنید حلقهای را ایجاد کردهاید که باید n بار اجرا شود. اگر داخل این حلقه، حلقهی دیگری را ایجاد کنید که باید m بار اجرا شود. در این صورت دستورات حلقهی داخلی در مجموع "n × m" بار اجرا خواهد شد. زیرا به ازای هر بار اجرا حلقهی خارجی، حلقهی داخلی باید m بار اجرا شود. همچنین توجه کنید که هیچ محدودیتی برای نوع حلقهی داخلی و خارجی وجود ندارد. یعنی مثلاً حلقهی خارجی میتواند از نوع while باشد و حلقهی داخلی از نوع for باشد. به مثال زیر توجه کنید.
let output = '';
for(let n = 1 ; n <= 10 ; n++){
for(let m = 1; m <= 10; m++){
output += (n * m) + '\t';
}
output += '\n';
}
console.log(output);
با توجه به اینکه این مثال نسبت به مثالهایی که پیش از این در این کتاب دیدهایم کمی پیچیدهتر است، باید نحوهی اجرای آن را کمی بیشتر توضیح دهیم. در صورتی که برنامهی فوق را اجرا کنید، خروجی زیر تولید میشود که یک جدول ضرب ۱۰ در ۱۰ را نشان میدهد. (این مثال را میتوانید اینجا اجرا کنید)
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100
در اولین خط برنامهی فوق یک متغیر به نام output از نوع رشته و با مقدار اولیهی تهی ایجاد میشود.
در خط دوم یک حلقهی for ایجاد شده است که با توجه به پارامترهای آن باید ۱۰ بار به ازای n = 1 تا n = 10 اجرا شود. بلاک کد مربوط به این حلقه، خود شامل حلقهای دیگر است که دستور داخل آن نیز با توجه به پارامترهای آن باید ۱۰ بار به ازای m = 1 تا m = 10 اجرا شوند.
اما با توجه به اینکه حلقهی داخلی در هر تکرار حلقهی خارجی باید به طور کامل (۱۰ بار) اجرا شود. لذا دستور مربوط به حلقهی داخلی (خط 4) در مجموع باید ۱۰۰ بار اجرا شود.
در هر تکرار حلقهی خارجی، یک سطر از خروجی فوق تولید میشود که نحوهی تولید این سطر به این صورت است :فرض کنید در تکرار اول هستیم و مقدار متغیر n برابر با ۱ است. حال حلقهی داخلی باید ۱۰ بار اجرا شود. در هر بار اجرای دستور داخل این حلقه، ابتدا مقدار دو متغیر m و n در هم ضرب میشود. سپس یک کاراکتر جدولبندی (کلید TAB) به نتیجهی این حاصلضرب الحاق میشود.
توجه : با توجه به اینکه عملگر "+" بین یک دادهی عددی و یک دادهی رشتهای به کار رفته است. در نتیجه عملگر "+" به عنوان عملگر الحاق دو رشته عمل کرده و ابتدا نتیجهی حاصلضرب را به رشته تبدیل میکند. سپس کاراکتر "\t" را به آن الحاق میکند.
مقدار متغیر output در ابتدای برنامه برابر با رشتهی تهی قرار گرفته بود. اما با هر بار اجرای دستور موجود در خط 4، یک رشتهی جدید به آن الحاق میشود. پس از ۱۰ بار اجرا شدن حلقهی داخلی، محتوای سطر اول خروجی فوق در متغیر output ذخیره خواهد شد. (زیرا مقدار m از ۱ تا ۱۰ تغییر کرده، در نتیجه حاصلضرب "n * m" در هر تکرار به ترتیب اعداد ۱ تا ۱۰ را تولید میکند)
پس از پایان اجرای حلقهی داخلی، دستور موجود در خط 6 اجرا میشود. این دستور یک کاراکتر خط جدید "n\" به انتهای متغیر output اضافه میکند. در نتیجه، محتوایی که از این به بعد به متغیر رشتهای output الحاق میشود در خط بعدی نمایش داده خواهد شد.
حال بخش after حلقهی خارجی اجرا شده و مقدار متغیر n را به ۲ افزایش میدهد. سپس حلقهی for خارجی برای دومین بار اجرا میشود. در این تکرار، روندی که در تکرار قبلی تشریح شد عیناً تکرار میشود. با این تفاوت که مقدار n برابر با ۲ است. بنابراین محتوای موجود در خط دوم خروجی فوق در این تکرار تولید شده و به رشتهی قبلی که در متغیر output ذخیره شده بود الحاق میشود.
همین روند تا رسیدن مقدار متغیر n به ۱۰ تکرار میشود. در نتیجه تمام ۱۰ سطر خروجی تولید میشوند و در خط 8 نیز کل رشتهی ذخیره شده در متغیر output در کنسول نمایش داده میشود.
پیشنهاد میشود برنامهی فوق را با دقت بررسی کنید تا نحوهی عملکرد آن را به طور کامل و دقیق درک کنید. زیرا از این پس برنامههایی را خواهیم نوشت که درک دقیق عملکرد آنها نیاز به درک دقیق همین برنامه دارد. همچنین از این به بعد هیچ مثالی را با این جزئیات تشریح نخواهیم کرد و فرض بر این است که خوانندهی کتاب تسلط مناسبی بر مباحث مقدماتی (مانند همین مثال) دارد.