درس ۷ از ۴۹ · جریان برنامه

حلقه‌ها

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

تکرار

تکرار ۳: بدنه را سه بار اجرا می‌کند. با در ک شمارنده‌ای به نام ک می‌گیرید که از صفر شروع می‌شود؛ پس تکرار ۴ در ک مقدارهای ۰، ۱، ۲ و ۳ را می‌دهد. تعداد می‌تواند هر عبارت صحیحی باشد، مثل یک متغیر یا و۱.طول()، و اگر صفر باشد بدنه اصلا اجرا نمی‌شود. تکرار ۱ تا ۱۰ روی بازه می‌چرخد (دو سر بازه هم جزو آن‌اند)، هر ۳ گام را تعیین می‌کند و اگر آغاز بزرگ‌تر از پایان باشد، حلقه رو به پایین می‌شمارد.

loop-count.salam
تابع ریشه:
    تکرار ۳:
        سرچاپ "سلام"
    پایان
    تکرار ۴ در ک:
        چاپ ک, ""
    پایان
    سرچاپ ""
    تکرار ۱۰ تا ۰ هر ۵ در ک:
        سرچاپ "شمارش معکوس", ک
    پایان
پایان
خروجیسلام سلام سلام 0 1 2 3 شمارش معکوس 10 شمارش معکوس 5 شمارش معکوس 0

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

بازه و گام

گام پس از هر همیشه مثبت نوشته می‌شود، حتی وقتی حلقه رو به پایین می‌شمارد: جهت را فقط دو سر بازه تعیین می‌کنند، پس تکرار ۱۰ تا ۱ هر ۳ مقدارهای ۱۰، ۷، ۴ و ۱ را می‌دهد. گام می‌تواند متغیر هم باشد. گام ۰، یا عدد منفی‌ای که مستقیم در کد نوشته شود مثل هر -۲، خطای کامپایل است (E063). گامی که در یک متغیر است فقط هنگام اجرا معلوم می‌شود و بررسی نمی‌شود: اگر منفی از آب دربیاید، حلقه بی‌صدا هیچ باری اجرا نمی‌شود.

loops.salam
تابع ریشه:
    تکرار ۱ تا ۱۰ هر ۳ در ک:
        سرچاپ "دور"، ک
    پایان
    تکرار ۱۰ تا ۱ هر ۳ در ک:
        چاپ ک، ""
    پایان
    سرچاپ ""
    گام := ۱۵
    تکرار ۰ تا ۶۰ هر گام در دقیقه:
        چاپ دقیقه، ""
    پایان
    سرچاپ ""
پایان
خروجیدور 1 دور 4 دور 7 دور 10 10 7 4 1 0 15 30 45 60

جهت شمارش هنگام آغاز حلقه و از مقدار آن لحظه‌ی دو سر بازه تعیین می‌شود. وقتی یکی از دو سر متغیر باشد، همین یک دام می‌سازد. تکرار از تا ۱ با ۳ همان‌طور که انتظار دارید از ۳ رو به پایین می‌شمارد، اما وقتی از صفر باشد، آغاز از پایان کوچک‌تر است؛ پس حلقه رو به بالا می‌شمارد و به‌جای هیچ، 0 1 چاپ می‌کند:

loop-range-trap.salam
تابع شمارش(از: صحیح):
    تکرار از تا ۱ در ک:
        چاپ ک، ""
    پایان
    سرچاپ "حرکت"
پایان

تابع شمارش امن(از: صحیح):
    اگر از >= ۱:
        تکرار از تا ۱ در ک:
            چاپ ک، ""
        پایان
    پایان
    سرچاپ "حرکت"
پایان

تابع ریشه:
    شمارش(۳)
    شمارش(۰)
    شمارش امن(۰)
پایان
خروجی3 2 1 حرکت 0 1 حرکت حرکت

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

حلقه‌ی تا

تا شرط: تا وقتی شرط برقرار است تکرار می‌شود و همین که نادرست شد می‌ایستد؛ همان حلقه‌ای که در زبان‌های دیگر while نام دارد. پس شرط را همان‌طور بنویسید که می‌خواهید حلقه ادامه پیدا کند: تا ن != ۰ تا وقتی ن صفر نشده پیش می‌رود. هر جا از پیش نمی‌دانید حلقه چند دور لازم دارد، تا را به کار ببرید.

loop-until.salam
تابع جمع رقم‌ها(عدد: صحیح): صحیح:
    ناپایا ن := عدد
    ناپایا جمع := ۰
    تا ن != ۰:
        جمع += ن % ۱۰
        ن /= ۱۰
    پایان
    برگشت جمع
پایان

تابع ریشه:
    سرچاپ جمع رقم‌ها(۱۴۰۵)، جمع رقم‌ها(۰)
    ناپایا پس‌انداز := ۱۰۰۰.۰
    ناپایا سال‌ها := ۰
    تا پس‌انداز < ۲۰۰۰.۰:
        پس‌انداز *= ۱.۰۸
        سال‌ها++
    پایان
    سرچاپ "دو برابر شد پس از"، سال‌ها، "سال"
پایان
خروجی10 0 دو برابر شد پس از 10 سال

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

حلقه‌هایی که با بشکن تمام می‌شوند

گاهی جای تصمیم برای ایستادن وسط یا انتهای بدنه است، نه بالای آن. در این حالت تا درست: بنویسید و هر جا تصمیم گرفته شد با بشکن بیرون بیایید. حلقه‌ای که دست‌کم یک بار اجرا می‌شود (do-while در زبان‌های دیگر) هم همین‌طور نوشته می‌شود. مثال زیر ۲۳ نتیجه‌ی جستجو را صفحه‌ای ده‌تا نشان می‌دهد و پس از صفحه‌ای که آخرین نتیجه در آن است می‌ایستد.

loop-forever.salam
تابع ریشه:
    کل := ۲۳
    اندازه صفحه := ۱۰
    ناپایا صفحه := ۱
    تا درست:
        نخست := (صفحه - ۱) * اندازه صفحه + ۱
        ناپایا واپسین := صفحه * اندازه صفحه
        اگر واپسین > کل:
            واپسین = کل
        پایان
        سرچاپ "صفحه"، صفحه، "مورد"، نخست، "تا"، واپسین
        اگر واپسین == کل:
            بشکن
        پایان
        صفحه++
    پایان
پایان
خروجیصفحه 1 مورد 1 تا 10 صفحه 2 مورد 11 تا 20 صفحه 3 مورد 21 تا 23

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

هر ... در

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

loop-each.salam
تابع ریشه:
    شهرها := ["تهران", "اصفهان", "شیراز"]
    هر شهر در شهرها:
        سرچاپ شهر
    پایان
    هر شماره، شهر در شهرها:
        سرچاپ شماره + ۱, شهر
    پایان
پایان
خروجیتهران اصفهان شیراز 1 تهران 2 اصفهان 3 شیراز

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

loop-each-map.salam
تابع ریشه:
    ناپایا انبار := نگاشت {} برگردان نگاشت<رشته، صحیح>
    دیرکن انبار.آزادکن()
    انبار.درج("خودکار"، ۱۲)
    انبار.درج("جوهر"، ۰)
    انبار.درج("کاغذ"، ۲۵۰)
    انبار.درج("گیره"، ۳)
    ناپایا کل := ۰
    ناپایا کم := ۰
    هر _، تعداد در انبار:
        کل += تعداد
        اگر تعداد < ۵:
            کم++
        پایان
    پایان
    سرچاپ "کل موجودی:"، کل
    سرچاپ "کالاهای نیازمند سفارش:"، کم
    ناپایا بهاها := [۱۰۰، ۲۵۰، ۴۰]
    هر ش، بها در بهاها:
        بهاها[ش] = بها + بها / ۱۰
    پایان
    سرچاپ بهاها
پایان
خروجیکل موجودی: 265 کالاهای نیازمند سفارش: 2 [110, 275, 44]

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

هر روی چه چیزهایی می‌گردد

هر روی همه‌ی دنباله‌ها یک‌جور کار می‌کند. نام نخست اختیاری است و جایگاه را می‌دهد (و در نگاشت، کلید را)؛ نام آخر خود عضو است.

رویبنویسیدعضو چیستنام نخست چیست
آرایه یا برشهر ع در فهرست:، هر ش، ع در فهرست:عضوشماره، از ۰
وکتورهر ع در و:، هر ش، ع در و:عضوشماره، از ۰
نگاشتهر ک، م در نگاشت: (هر دو نام لازم است)مقدارکلید
نویسه‌های یک رشتههر ح در متن:، هر ش، ح در متن:یک نویسه، به شکل رشتهشماره‌ی نویسه، از ۰
بایت‌های یک رشتههر ب در متن.بایت ها():، هر ش، ب در متن.بایت ها():یک بایت، به شکل نویسهجایگاه بایت، از ۰
loop-each-all.salam
تابع ریشه:
    نام‌ها := ["سارا"، "رضا"]
    هر ش، نام در نام‌ها:
        سرچاپ ش، نام
    پایان
    ناپایا نمره‌ها := وکتور {} برگردان وکتور<صحیح>
    نمره‌ها.بیفزا(18)
    نمره‌ها.بیفزا(20)
    ناپایا جمع := 0
    هر نمره در نمره‌ها:
        جمع += نمره
    پایان
    سرچاپ "جمع:"، جمع
    ناپایا سن‌ها := نگاشت {} برگردان نگاشت<رشته، صحیح>
    سن‌ها.درج("سارا"، 30)
    هر نام، سن در سن‌ها:
        سرچاپ نام، سن
    پایان
    هر ش، ح در "سلام":
        سرچاپ ش، ح
    پایان
    ناپایا سطر := ""
    هر ب در "Hi!".بایت ها():
        سطر = سطر + (ب برگردان صحیح) + " "
    پایان
    سرچاپ سطر
پایان
خروجی0 سارا 1 رضا جمع: 38 سارا 30 0 س 1 ل 2 ا 3 م 72 105 33

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

حلقه‌های تودرتو

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

loop-nested.salam
واردسازی قالب بندی

تابع ریشه:
    تکرار ۱ تا ۴ در سطر:
        ناپایا خط := ""
        تکرار ۱ تا ۶ در ستون:
            خط += قالب بندی.پرکردن چپ("" + سطر * ستون، ۴)
        پایان
        سرچاپ خط
    پایان
پایان
خروجی 1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24

بشکن و گذر

بشکن از حلقه بیرون می‌رود و گذر به دور بعدی می‌پرد. در حلقه‌های تودرتو، بشکن ۲ از دو حلقه با هم بیرون می‌رود، بشکن ۳ از سه حلقه و همین‌طور تا آخر؛ این عدد نمی‌تواند از شمار حلقه‌های پیرامون بیشتر باشد.

loop-break.salam
تابع ریشه:
    تکرار ۱ تا ۱۰ در ک:
        اگر ک % ۲ == ۰:
            گذر
        پایان
        اگر ک > ۷:
            بشکن
        پایان
        چاپ ک, ""
    پایان
    سرچاپ ""
    تکرار ۳ در الف:
        تکرار ۳ در ب:
            اگر الف + ب == ۳:
                بشکن ۲
            پایان
            سرچاپ الف, ب
        پایان
    پایان
پایان
خروجی1 3 5 7 0 0 0 1 0 2 1 0 1 1

در حلقه‌ی تا مراقب گذر باشید: یکراست به سنجش شرط برمی‌گردد؛ پس اگر خطی که حلقه را جلو می‌برد پس از گذر آمده باشد، از رویش می‌پرد و حلقه هرگز تمام نمی‌شود. شمارنده را پیش از گذر به‌روز کنید.

کدام حلقه؟

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

قاعده‌هایی که باید به خاطر سپرد

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

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