פקודות לולאה
בתכנות מתקדם, שבו אנו מטפלים בנתונים רבים או שבו התוכנית רצה פעמים רבות, אנו צריכים לחזור על פקודות בדרך מקוצרת, יעילה וקלה, במקום להעתיק את הפקודות ולשכפל אותן, דבר שיגרום לניפוח הקוד ויהפוך אותו לבלתי קריא ולבלתי מובן (וגם ידרוש מאיתנו זמן רב).
נלמד כיצד לחזור על פקודות בשתי דרכים:
- לולאת while
- לולאת for
שתי הדרכים שקולות ואפשר להשתמש בכל אחת מהן, אך יש מצבים שבהם אחת הדרכים מהירה וקלה יותר מן האחרת. נתחיל בלולאת while ואחר כך נחזור ללולאת for
6.1 לולאת while
עלינו לזכור תמיד ולהעלות על דעתנו ש-while פירושה כל עוד.
הפקודה while משמשת לחזרה על קטע פקודות מסוים מספר פעמים, שאפשר שיהיה ידוע מראש (תלוי בדרישות התוכנית או המתכנת) או שמספר החזרות אינו ידוע מראש (תלוי בקליטת נתונים מן המשתמש או בחישובים במהלך הרצת התוכנית).
מבנה לולאת while:
פקודות ערך התחלתי ;
while ( תנאי הלולאה )
{
....
....
פקודות לחזרה *
....
....
}- כדי שתנאי הלולאה יתקיים בהתחלה, עלינו לכתוב פקודות שנותנות ערך התחלתי למשתנים המשתתפים בתנאי הלולאה שנמצא בתוך הסוגריים ( )
- אם תנאי הלולאה שבתוך הסוגריים של while מתקיים, המחשב נכנס אל הפקודות המיועדות לחזרה שנמצאות בתוך הסוגריים המסולסלים ומבצע אותן.
- אחרי כל ביצוע של הפקודות המיועדות לחזרה, המחשב חוזר ובודק את תנאי הלולאה מחדש.
- אם תנאי הלולאה מתקיים (true), המחשב נכנס מחדש אל הפקודות לחזרה שנמצאות בתוך הסוגריים המסולסלים ומבצע אותן. מסיבה זו נזכור ש-while פירושה כל עוד, מפני שאנו חוזרים על הפקודות כל עוד התנאי מתקיים.
- כדי שהחזרה על ביצוע הפקודות תסתיים, התנאי צריך שלא להתקיים (להפוך ל-false), ולכן כדאי שנציב פקודה או פקודות שמשפיעות על התנאי בתוך פקודות הלולאה, כדי שמצב התנאי ישתנה מ-true ל-false
- אם התנאי אינו משתנה בשום מצב ולא הצלחנו להשפיע עליו בעזרת פקודות הלולאה כך שתנאי הלולאה יהפוך ל-false, אזי זו לולאה אינסופית, וזה מצב שאינו מקובל בדרך כלל, חוץ ממקרים מיוחדים מסוימים.
(*) פקודות הלולאה כוללות פקודה שמשפיעה על תנאי הלולאה. (פקודת קידום).
מתי נשתמש בלולאת while?:
אפשר להשתמש בלולאת while במקרים הבאים:
- פקודות לולאה המותנות בתנאי מסוים (בלי מונה), שהן בדרך כלל קליטת נתונים עם תנאי עצירה, למשל קליטת מספרים עד שנקלוט את המספר השלילי הראשון (קליטת מספר שלילי היא תנאי העצירה). (זה השימוש הנפוץ והמומלץ בלולאת while).
באופן כללי, אם השתמשנו ב-while במקרה זה (תנאי עצירה), כדאי שנקלוט נתונים פעם אחת מחוץ ללולאה, לפני ה-while, כלומר לפני שנגיע לתנאי הלולאה. - פקודות לולאה המותנות במונה מסוים ( i למשל), שבהן אנו מציבים פקודת אתחול למונה לפני while, מציבים את התנאי בסוגריים ( ) אחרי המילה while, ואז עלינו להציב פקודות שמשנות את ערך המונה (מקדמות או מחזירות אותו) בתוך הפקודות המיועדות לחזרה שנמצאות בתוך הסוגריים המסולסלים, כדי להשפיע על התנאי כך שהחזרה תיפסק בשלב מסוים (אחרי מספר מסוים של פעמים).
אתחול המונה ;
while ( תנאי הלולאה )
{
....
....
פקודות לחזרה
....
....
פקודת קידום למונה.
}- פקודות לולאה אינסופיות.
- פקודות לולאה התלויות בהתרחשות אירוע מסוים, הקשורה בהרמת דגל מסוים flag (תוצאה מסוימת).
פתרון:
static void Main(string[] args)
{
int num1, num2, i=1, result=0;
Console.Write("Insert a number: ");
num1 = int.Parse(Console.ReadLine());
Console.Write("Insert another number: ");
num2 = int.Parse(Console.ReadLine());
while(i<=num2)
{
result = result + num1; // חיבור מצטבר
i++; // i=i+1;
}
Console.WriteLine("{0} * {1} = {2}",num1,num2,result);
}תוצאת הרצת התוכנית עבור הקלטים 3 ו-7:
Insert a number: 3 Insert another number: 7 3 * 7 = 21
הערה: לא הכרחי ששני המספרים יהיו שונים, לא הכרחי שהמספר הקטן יינתן ראשון, וגם המספרים יכולים להיות שליליים.
פתרון:
static void Main(string[] args)
{
int num1, num2, i,max;
Console.Write("insert the first number: ");
num1 = int.Parse(Console.ReadLine());
Console.Write("insert the second number: ");
num2 = int.Parse(Console.ReadLine());
i = Math.Min(num1, num2);
max = Math.Max(num1, num2);
while (i<=max)
{
if(i%2==0)
Console.WriteLine(i);
i++;
}
}תוצאת פעולת התוכנית עבור הקלט 3 ו-−4
insert the first number: 3 insert the second number: -4 -4 -2 0 2
- מספר המקצועות שבהם יש אי-הצלחה (מתחת ל-55).
- מספר המקצועות שבהם יש הצלחה. (55 ומעלה).
- ממוצע התלמיד.
- הציון הגבוה ביותר שקיבל התלמיד.
- הציון הנמוך ביותר שקיבל התלמיד.
- המספר הכולל של המספרים שהוזנו בסדרה בלי המספר השלילי (כלומר כמות המספרים שאינם שליליים).
- ממוצע המספרים שאינם שליליים (כלומר ממוצע המספרים שאינם שליליים).
- המספר הגדול ביותר בסדרה זו.
הערה: לא הכרחי ששני המספרים יהיו שונים, לא הכרחי שהמספר הקטן יינתן ראשון, וגם המספרים יכולים להיות שליליים.
דוגמה: אם הוזנו 8 ו-−3 יודפס:
-2,0,2,4,6,8.פתרון:
static void Main(string[] args)
{
int num1, num2, i,max;
Console.Write("insert the first number: ");
num1 = int.Parse(Console.ReadLine());
Console.Write("insert the second number: ");
num2 = int.Parse(Console.ReadLine());
i = Math.Min(num1, num2);
max = Math.Max(num1, num2);
if (max % 2 != 0)
max = max-1;
while (i<=max)
{
if(i%2==0 && i!= max)
Console.Write("{0},",i);
else if(i%2==0 && i==max)
Console.WriteLine("{0}.", i);
i++;
}
}תוצאת הרצת התוכנית שלמעלה עבור הקלט 9 ו-−11
insert the first number: 9 insert the second number: -11 -10,-8,-6,-4,-2,0,2,4,6,8.
(מספר ראשוני הוא מספר שמתחלק חלוקה שלמה רק בעצמו וב-1).
6.2 לולאת for
לולאת for משמשת לכל המקרים שבהם אנו רוצים לחזור על קטע תוכנה מסוים, בדיוק כפי שעשינו בלולאת while. שתי הצורות שקולות, אך עדיף להשתמש בלולאת for בדרך כלל עם מונים או למספר מוגדר של פעמים.
פקודת האתחול (פקודה מספר 1) מתבצעת רק פעם אחת בהתחלה, לפני בדיקת התנאי (בלי קשר לשאלה אם התנאי מתקיים או לא), ואז אנו בודקים את התנאי (2). אם התנאי מתקיים (true), אנו נכנסים לביצוע הפקודות לחזרה (3), ואחרי ביצוע הפקודות לחזרה (3) אנו חוזרים ומבצעים את פקודת הקידום (פקודה 4, שהיא פקודת שינוי המונה), ואז בודקים שוב את התנאי (5). אם התנאי מתקיים (true) אנו נכנסים לביצוע פקודות הלולאה (6) ואחריהן מבצעים את פקודת הקידום (7). תמיד אחרי ביצוע פקודת הקידום אנו בודקים את התנאי (8), ואם התנאי מתקיים ממשיכים בלולאה וכך הלאה. אבל אם התנאי אינו מתקיים (הפך ל-false), לא ניכנס אל הסוגריים המסולסלים לביצוע פקודות הלולאה, אלא נדלג עליהם ונגיע אל הפקודות שמחוץ ללולאה (נקפוץ אל מחוץ ללולאה) ונמשיך את התוכנית (פקודות 9 * )
פתרון:
static void Main(string[] args)
{
int i, n;
Console.Write("Insert a number: ");
n = int.Parse(Console.ReadLine());
for (i =1;i<=n;i++)
Console.WriteLine("Hello Hammam");
}6.3 השוואה בין לולאת for ללולאת while
כפי שציינו קודם, שתי הצורות שקולות, וכל מה שאפשר לפתור בעזרת אחת מהן אפשר לפתור בעזרת האחרת, אך יש מקרים שבהם השימוש ב-while קל ומהיר יותר עבורנו, ומקרים אחרים שבהם for היא הקלה יותר.
באופן כללי, for משמשת ללולאה עם מונה, או לספירה, או למספר פעמים ידוע מראש עם מונה, ואילו while משמשת כאשר יש לנו לולאה עם תנאי עצירה, או לולאה שתלויה בהתרחשות של דבר מה (תוצאה מסוימת) או בקליטת נתון מסוים.
נכתוב לולאה של 10 פעמים:
int i;
for(i=1;i<=10;i++)
Console.WriteLine("Hammam");int i;
i = 1;
while(i<=10)
{
Console.WriteLine("Hammam");
i++;
}- בעזרת while
- בעזרת for
פתרון:
(א)
static void Main(string[] args)
{
int mark,i=1,sum=0;
double avg ;
while(i<=10)
{
Console.WriteLine("Insert a mark: ");
mark = int.Parse(Console.ReadLine());
sum += mark;
i++;
}
avg = (double)sum / 10;
Console.WriteLine("The average is: {0}",avg);
}(ב)
static void Main(string[] args)
{
int mark,i,sum=0;
double avg ;
for(i=1;i<=10;i++)
{
Console.WriteLine("Insert a mark: ");
mark = int.Parse(Console.ReadLine());
sum += mark;
}
avg = (double)sum / 10;
Console.WriteLine("The average is: {0}",avg);
}<number>!=<result>למשל:
אם קלטנו את המספר 5, הפלט יהיה: 5!=120
תזכורת:
פתרון:
static void Main(string[] args)
{
int n,res=1,i;
Console.Write("Insert a positive number: ");
n = int.Parse(Console.ReadLine());
if (n == 0)
Console.WriteLine("{0}!=1", n);
else
{
for (i = 1; i <= n; i++)
res = res * i;
Console.WriteLine("{0}!={1}", n, res);
}
}טבלת מעקב לתוכנית העצרת:
טבלת מעקב כאשר קולטים את הקלט n=5.
| הפקודה | n | res | i | התנאי | תנאי הלולאה | הפלט |
|---|---|---|---|---|---|---|
| int n,res=1,i; | 1 | |||||
| Console.Write("Insert a positive number: "); | 1 | Insert a positive number: | ||||
| n = int.Parse(Console.ReadLine()); | 5 | 1 | ||||
| if (n == 0) | 5 | 1 | F | |||
| else | 5 | 1 | ||||
| i = 1; | 5 | 1 | 1 | |||
| i <= n | 5 | 1 | 1 | T | ||
| res = res * i; | 5 | 1 | 1 | |||
| i++; | 5 | 1 | 2 | |||
| i <= n | 5 | 1 | 2 | T | ||
| res = res * i; | 5 | 2 | 2 | |||
| i++; | 5 | 2 | 3 | |||
| i <= n | 5 | 2 | 3 | T | ||
| res = res * i; | 5 | 6 | 3 | |||
| i++; | 5 | 6 | 4 | |||
| i <= n | 5 | 6 | 4 | T | ||
| res = res * i; | 5 | 24 | 4 | |||
| i++; | 5 | 24 | 5 | |||
| i <= n | 5 | 24 | 5 | T | ||
| res = res * i; | 5 | 120 | 5 | |||
| i++; | 5 | 120 | 6 | |||
| i <= n | 5 | 120 | 6 | F | ||
| Console.WriteLine("{0}!={1}", n, res); | 5 | 120 | 6 | 5!=120 |
6.4 הפקודה break
במצבים מסוימים אנו צריכים לעצור את החזרה (הלולאה loop) אף שהתנאי עדיין מתקיים, ולעצור את פעולת פקודות הלולאה אף שהפקודות האלה טרם הסתיימו (לא הגענו לסוף פקודות הלולאה). לשם כך קיימת הפקודה break.
הפקודה break היא פקודה שעוצרת את ביצוע הלולאה ואת ביצוע פקודות הלולאה מתוך הלולאה (loop), גם אם התנאי עדיין מתקיים וגם אם הסיבוב טרם הסתיים. כאשר מגיעים לפקודה break מצביע ביצוע הפקודות קופץ אל הפקודה הראשונה שמחוץ ללולאה loop (מחוץ לפקודות הלולאה) כדי שהמחשב ימשיך לבצע את הפקודות שנמצאות מחוץ למסגרת הלולאה.
הפקודה break מקלה עלינו את פתרון הבעיות ועוזרת לנו לפשט את הפתרונות, ואיננו חייבים להשתמש בה תמיד.
for (int i = 1; i <= 100; i++)
{
Console.WriteLine(i);
if (i == 5)
break;
}int i = 1;
while(true)
{
Console.WriteLine(i);
if (i == 5)
break;
i++;
}שני הקטעים האלה מדפיסים את המספרים מ-1 עד 5 (כולל 1 ו-5)
אם העובד הפסיק לעבוד בחברה, התוכנית לא תמשיך לחשב את סכום משכורותיו (מפני שיהיו אפס בחודשים הנותרים של השנה), ועליה להדפיס לו את סכום משכורתו במהלך החודשים שעבד בחברה.
פתרון:
static void Main()
{
double total = 0, salary;
int i;
for (i = 1; i <= 12; i++)
{
Console.Write("Insert your salary in month {0}: ", i);
salary = double.Parse(Console.ReadLine());
if (salary == 0) // העובד אינו עובד עוד בחברה
break;
total += salary;
}
if (i < 13) // i!=13
Console.WriteLine("You worked only {0} months,and you earned:{1}",i-1,total);
else
Console.WriteLine("You earned a {0} in this year.",total);
}הרצת התוכנית במקרה שבו מגיעים לביצוע הפקודה break:
Insert your salary in month 1: 50 Insert your salary in month 2: 100 Insert your salary in month 3: 350 Insert your salary in month 4: 0 You worked only 3 months,and you earned:500
6.5 הפקודה continue
לפעמים קורה שאיננו רוצים להמשיך בביצוע שאר הפקודות בלולאה (בסיבוב), אבל בלי לעצור את החזרה, אלא רוצים לעבור לתחילת הלולאה (או לפקודת הקידום ב-for). לשם כך קיימת הפקודה continue
הפקודה continue היא פקודה שעוצרת את ביצוע הסיבוב הנוכחי (החזרה שאנו נמצאים בה), אך היא אינה עוצרת את הלולאה, אלא מצביע ביצוע הפקודות קופץ אל סוף קטע פקודות הלולאה וחוזר מחדש (אם לבדיקת התנאי ב-while ואם לפקודת הקידום ב-for)
for(int i=1;i<=10;i++)
{
if (i == 5)
continue;
Console.Write("{0} ",i);
}1 2 3 4 6 7 8 9 10
מדפיס את כל המספרים מ-1 עד 10 (כולל 10) חוץ מן המספר 5
static void Main()
{
int i=1,num;
while(i<=10)
{
Console.Write("Insert a number: ");
num = int.Parse(Console.ReadLine());
if (num == 0)
continue;
Console.WriteLine("100/{0} = {1}",num,100.0/num);
i++;
}
}תוכנית זו מבטיחה שיהיו לנו 10 תוצאות של חילוק 100 במספרים. אם נקלט אפס, פעולת החילוק לא תתבצע וגם המונה לא יתקדם במקרה זה, והניסיון הזה לא ייספר מתוך עשר החזרות.
6.6 סדרות
6.6.1 סדרה חשבונית
זו סדרת מספרים שיש בה חוקיות (קשר) מוגדרת: ההפרש בין כל שני איברים עוקבים הוא הפרש קבוע ושווה ל-d . האיבר הראשון בסדרה מסומן a1 ואילו האיבר שמיקומו n מסומן באות an
נוכל להגיע לחוק הבא:
סכום האיברים עד האיבר מספר n הוא:
הסדרה הבאה:
האיבר הראשון הוא 7 והפרש הסדרה d = 3
סכום 3 האיברים הראשונים הוא: 7+10+13 כלומר 30
האיבר מספר 37 הוא: 115, וזאת לפי החוק הבא:
סכום 37 האיברים הראשונים הוא: 2257, לפי החוק הבא:
יש חוקים נוספים לסכום איברי הסדרה עד האיבר n והם:
כשאנו משתמשים בתכנות ובמחשב, אנו יכולים לפתור סדרות בעזרת לולאה בלי להשתמש בחוקים, מפני שהמחשב מהיר מאוד בביצוע פקודות, וכך נוכל למצוא את האיברים ואת הסכום.
317 , 300 , 283 …בעזרת כתיבת תוכנית מתאימה (בלי להשתמש בחוקי המתמטיקה לסדרות)
- מצא את האיבר מספר 10.
- איזה איבר הוא האיבר השלילי הראשון?
static void Main(string[] args)
{
int a1 = 317, d = -17, sum,ai=0,ai_1,flag=1;
int i;
ai_1 = a1;
for (i = 2; i <= 10 || flag ==1; i++)
{
ai = ai_1 + d; // סדרה חשבונית
if(ai<0)
{
Console.WriteLine("The first negative element is {0}",ai);
Console.WriteLine("His order is {0}",i);
flag = 0;
}
ai_1 = ai;
if(i==10)
Console.WriteLine("a10={0}", ai);
}
}תוצאת ההרצה:
a10=164 The first negative element is -6 His order is 20
הפתרון בעזרת חוקי המתמטיקה הוא:
(א) d=300 − 317 = −17 ➔ a10 = 317 + (10−1)·(−17) = 164
(ב) נפתור את אי-השוויון הבא: an < 0, כלומר נפתור את הביטוי הבא: 317+(n−1)·(−17) < 0, נסדר ונפתור ונקבל n > 19.64, ומאחר ש-n מספר שלם, התשובה היא n=20 והאיבר הוא
static void Main(string[] args)
{
int a, d, i;
Console.Write("insert the first element: ");
a = int.Parse(Console.ReadLine());
Console.Write("insert the difference: ");
d = int.Parse(Console.ReadLine());
for (i = 1; i <= 20 ; i++)
{
if(i!=20)
Console.Write("{0},",a);
else
Console.WriteLine("{0}.",a);
a = a + d;
}
}תוצאת הרצת התוכנית עבור הקלטים a1=317 ו-d = −17 היא:
insert the first element: 317 insert the difference: -17 317,300,283,266,249,232,215,198,181,164,147,130,113,96,79,62,45,28,11,-6.
static void Main(string[] args)
{
int a, d,n,sum=0,i;
Console.Write("insert the first element: ");
a = int.Parse(Console.ReadLine());
Console.Write("insert the difference: ");
d = int.Parse(Console.ReadLine());
Console.Write("insert the number of elements you want: ");
n = int.Parse(Console.ReadLine());
for (i = 1; i <= n ; i++)
{
sum += a;
if(i!=n)
Console.Write("{0},",a);
else
Console.WriteLine("{0}.",a);
a = a + d;
}
Console.WriteLine("sum={0}",sum);
}תוצאות הרצת התוכנית עבור הקלטים: a1 = −4 ו-d=5, ואנו רוצים את 5 האיברים הראשונים ( n=5 ).
insert the first element: -4 insert the difference: 5 insert the number of elements you want: 5 -4,1,6,11,16. sum=30
6.6.2 סדרה הנדסית
זו סדרת מספרים שבה כל איבר אחרי הראשון הוא מכפלת האיבר שלפניו במספר קבוע (שאינו 0) הנקרא קבוע הסדרה (או מנת הסדרה), והוא מסומן באות q.
סכום הסדרה ההנדסית הוא:
3,12,48,192,…כתוב תוכנית שמדפיסה את 10 האיברים הראשונים ומחשבת את סכומם.
static void Main()
{
int i,a1=3,q=4,ai=a1,sum=0;
for(i=1; i<=10;i++)
{
sum += ai;
if (i != 10)
Console.Write("{0},", ai);
else
Console.Write("{0}.", ai);
ai = ai * q;
}
Console.WriteLine("\nThe Sum (S10) = "+sum);
}תוצאת הרצת התוכנית:
3,12,48,192,768,3072,12288,49152,196608,786432. The Sum (S10) = 1048575
6.6.3 סדרות כלליות
סדרת פיבונאצ׳י:
סדרת פיבונאצ׳י היא סדרת מספרים שלמים וחיוביים, שבה כל איבר הוא סכום שני האיברים שלפניו. הסדרה מתחילה משני איברים התחלתיים, 1 ו-1 (יש מי שמתחילים מ-0 ו-1).
הסדרה היא:
אפשר לכתוב את חוקיותה בעזרת נוסחת נסיגה כך:
למשל, אם הזנו 1 יודפס על המסך 1, ואם הזנו 6 יודפס על המסך 8 ...
static void Main(string[] args)
{
int i, n,an=1,an_1=1,an_2=1;
Console.Write("Insert a number: ");
n = int.Parse(Console.ReadLine());
if(n==1 || n==2)
Console.WriteLine("a{0} = 1",n);
else
{
for (i = 3; i <= n; i++)
{
an = an_1 + an_2;
an_2 = an_1;
an_1 = an;
}
Console.WriteLine("a{0}={1}",n,an);
}
}תוצאת הרצת התוכנית עבור הקלט 10 היא:
Insert a number: 10 a10=55
מדפיסים את האיברים זה לצד זה, ובין איבר לאיבר פסיק, חוץ מן האיבר האחרון שאחריו מציבים נקודה.
למשל, אם קלטנו את המספר 7 נדפיס:
1,1,2,3,5,8,13.6.7 לולאה בתוך לולאה
לפעמים יש צורך בלולאה בתוך לולאה אחרת, ולפעמים הדבר קל לנו יותר לפתרון בעיות.
הלולאה הראשונה (הפנימית) היא לולאה שחוזרת על קטע פקודות פנימי, וייתכן שנצטרך לחזור על כל הלולאה הזאת פעמים רבות בעזרת לולאה חיצונית שחוזרת על כל הלולאה הפנימית.
static void Main(string[] args)
{
int i, j;
for(i=1;i<=7;i++)
{
for(j=1;j<=10;j++)
Console.Write("*");
Console.WriteLine();
}
}********** ********** ********** ********** ********** ********** **********
הפקודה Console.Write("*"); מתבצעת בלולאה הפנימית 10 פעמים (מ-j=1 עד j=10) בכל פעם שנכנסים ללולאה החיצונית (לכל i מ-i=1 עד i=7), כלומר פקודה זו מתבצעת 7·10 פעמים, כלומר 70 פעמים.
static void Main(string[] args)
{
int i, j,num;
char ch;
Console.Write("Insert a character: ");
ch = char.Parse(Console.ReadLine());
Console.Write("Insert a number: ");
num = int.Parse(Console.ReadLine());
for(i=1;i<=num;i++) // (i=0;i<num;i++)
{
for(j=1;j<=i;j++)
Console.Write("{0}",ch);
Console.WriteLine();
}
}Insert a character: $ Insert a number: 5 $ $$ $$$ $$$$ $$$$$
* ** *** **** ***** **** *** ** *
למשל, אם קלטנו 5:
1 22 333 4444 55555
הדפס שורות במספר השווה למספר שקלטת מן המשתמש
למשל, אם קלטנו 5 נדפיס
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15