זה שיעור המשך על לולאות for. הוא כבר לא עוסק רק בתחביר הבסיסי, אלא בדפוסים שמופיעים שוב ושוב בפתרון שאלות: קפיצות של האינדקס, בדיקת ראשוניות, שימוש בדגל, break, מציאת מינימום/מקסימום, ערך נלווה, ושמירת ערך מהסיבוב הקודם.
קפיצות בלולאה
השיעור מתחיל משאלה קטנה אבל חשובה: אם רוצים להדפיס רק מספרים זוגיים, אפשר לרוץ על כל המספרים ולבדוק בכל פעם i % 2 == 0, אבל זו לא הדרך הכי נקייה. במקום זה אפשר לשלוט בקפיצה של i ולהגדיל אותו בכל פעם ב־2.
for (int i = 0; i < 100; i += 2)
{
Console.WriteLine(i);
}
זו אותה חשיבה גם לקפיצות אחרות:
for (int i = 0; i < 100; i += 3)
{
Console.WriteLine(i);
}
המסר הפדגוגי כאן הוא לא “חייבים להיות הכי יעילים בבגרות”, אלא שכדאי ללמד פתרון אלגנטי כשאפשר. אם אנחנו יודעים מראש שאנחנו צריכים רק ערכים מסוימים, אין סיבה לרוץ על כולם ולסנן.
בדיקת ראשוניות: מתחילים מהרעיון הפשוט
המעבר הבא הוא לדוגמה קלאסית: איך בודקים אם מספר הוא ראשוני. השאלה נפתחת דרך ההגדרה הבסיסית: מספר ראשוני מתחלק רק ב־1 ובעצמו, ולכן מתחילים לבדוק מחלקים מ־2.
int n = 79;
for (int i = 2; i < n; i++)
{
if (n % i == 0)
{
Console.WriteLine("not prime");
}
}
זו התחלה טובה כדי להבין את הרעיון, אבל היא עדיין לא פתרון מלא: אם מצאנו מחלק, אין סיבה להמשיך לבדוק; ואם לא מצאנו מחלק, צריך לדעת להדפיס בסוף שהמספר כן ראשוני.
דגל ו־break
כאן נכנס הדפוס החשוב של דגל בוליאני. אנחנו מניחים שהמספר ראשוני, ורק אם מצאנו מחלק אנחנו “מכבים” את הדגל. בשיעור זה מוצג דרך הבאגים הטבעיים שעולים בכיתה, ואז מתייצב לדפוס ברור.
int n = 79;
bool isPrime = true;
for (int i = 2; i < n; i++)
{
if (n % i == 0)
{
isPrime = false;
break;
}
}
if (isPrime)
Console.WriteLine("prime");
else
Console.WriteLine("not prime");
שתי נקודות חשובות יש כאן:
breakעוצר את הבדיקה ברגע שכבר אין צורך להמשיך.if (isPrime)עדיף עלif (isPrime == true), כי המשתנה עצמו כבר בוליאני.
ההסבר על הדגל ועל הכתיבה הנקייה של תנאי בוליאני מופיע כאן.
מציאת מינימום ומקסימום
אחרי break ודגלים, השיעור עובר לדפוס מרכזי נוסף: מציאת מינימום ומקסימום בלולאה. הטעות הנפוצה היא לאתחל את המינימום ל־0. אם כל המספרים חיוביים, 0 יישאר בטעות המינימום, למרות שהוא בכלל לא נקלט. לכן צריך אתחול נכון.
int min = int.MaxValue;
for (int i = 0; i < 4; i++)
{
int num = int.Parse(Console.ReadLine());
if (num < min)
min = num;
}
Console.WriteLine(min);
למקסימום עושים את ההפך:
int max = int.MinValue;
for (int i = 0; i < 4; i++)
{
int num = int.Parse(Console.ReadLine());
if (num > max)
max = num;
}
העיקרון הוא להתחיל מערך קצה שלא “ינצח” בטעות את הנתונים האמיתיים.
ערך נלווה: לא רק מה המינימום, אלא איפה הוא הופיע
לפעמים לא מספיק לדעת מה הערך הקטן ביותר. רוצים לדעת גם באיזה סיבוב הוא הופיע. זה נקרא כאן “ערך נלווה”: כשמעדכנים את המינימום, מעדכנים יחד איתו גם את האינדקס או המיקום. הדוגמה הזאת נבנית בחלק של האינדקס הנלווה.
int min = int.MaxValue;
int minIndex = -1;
for (int i = 0; i < 10; i++)
{
int num = int.Parse(Console.ReadLine());
if (num < min)
{
min = num;
minIndex = i + 1; // מספר הקלט בספירה מ־1
}
}
Console.WriteLine("min = " + min);
Console.WriteLine("place = " + minIndex);
זה דפוס שיחזור בהמשך הרבה, במיוחד במערכים: לא רק מוצאים ערך, אלא שומרים עוד מידע עליו.
גלגול ושמירת היסטוריה
בחלק הבא השיעור עובר לדפוס יותר מתקדם: שמירת ערך מהסיבוב הקודם. אם רוצים להשוות כל מספר לקודם שלו, חייבים לזכור מה היה המספר הקודם לפני שקולטים או מעבדים את הבא. הרעיון מוצג דרך שמירת היסטוריה בלולאה.
int previous = int.Parse(Console.ReadLine());
int changes = 0;
for (int i = 1; i < 10; i++)
{
int current = int.Parse(Console.ReadLine());
if (current != previous)
changes++;
previous = current;
}
המשפט החשוב הוא: רגע לפני שממשיכים לסיבוב הבא, שומרים את הערך הנוכחי בתוך previous. בסיבוב הבא הוא כבר יהיה “הערך מהפעם הקודמת”.
בדיקת סדרה עולה
אותו דפוס של היסטוריה מאפשר לבדוק אם סדרת קלט עולה. שומרים את הערך הקודם, ובכל פעם בודקים שהערך החדש גדול ממנו. אם מצאנו הפרה, מכבים דגל. הדוגמה הזאת מופיעה במעבר לסדרה עולה.
int previous = int.Parse(Console.ReadLine());
bool rising = true;
for (int i = 1; i < 5; i++)
{
int current = int.Parse(Console.ReadLine());
if (current <= previous)
rising = false;
previous = current;
}
if (rising)
Console.WriteLine("rising");
else
Console.WriteLine("not rising");
כאן הדגל לא אומר “מצאתי משהו”, אלא “עד עכשיו הכל תקין”. ברגע שיש הפרה, הוא נסגר.
תחביר חוקי לא תמיד אומר קוד טוב
בסוף השיעור יש דיון חשוב על גבולות התחביר של for. אפשר לכתוב for עם כמה משתנים, בלי חלק מהחלקים, או עם משחקים שונים באינדקס. אבל השורה התחתונה היא ש־for נולדה כדי להיות פשוטה. הדברים האלה חוקיים אבל לא רצויים בדרך כלל.
הדוגמה הבולטת היא שינוי של i בתוך גוף הלולאה. זה יכול לפתור בעיות מסוימות, למשל “לקלוט שלושה מספרים חיוביים ואם נקלט שלילי לא לספור אותו”, אבל המחיר הוא קריאות נמוכה. הדיון על זה מופיע בסוף השיעור: מי שקורא את הקוד מצפה שהאינדקס ינוהל בכותרת ה־for, לא יתחבא באמצע גוף הקוד.
הכלל המעשי:
for (int i = 0; i < count; i++)
{
// אל תשנו את i כאן בלי סיבה ממש טובה
}
קוד טוב לא רק עובד. הוא גם מספר לקורא מה הולך לקרות.
סיכום קצר
- אם אפשר לקפוץ ישירות לערכים הרצויים, השתמשו ב־
i += 2,i += 3וכדומה. - בבדיקת ראשוניות, דגל בוליאני ו־
breakנותנים פתרון ברור. - למינימום מאתחלים ב־
int.MaxValue; למקסימום ב־int.MinValue. - כשצריך לדעת גם “איפה”, שומרים ערך נלווה כמו אינדקס.
- בהשוואה בין שכנים שומרים היסטוריה:
previous. - תחביר מתוחכם של
forקיים, אבל בכיתה ובבגרות עדיף קוד ברור ופשוט.
להקשר הבסיסי יותר של for: פרק 4 - לולאת for קצרה.