פתרון לתרגיל הלולאות

[8 באוגוסט 2009] [17 תגובות]

בסוף השיעור לולאות ב-Java התבקשתם לפתור את התרגיל הבא:

כתבו תוכנית הקולטת מספרים מהמשתמש עד שהוא מקיש את המספר 0 ולכל היותר 10 מספרים. לאחר מכן התוכנית מציגה את כמות המספרים, סכום המספרים (תוצאת חיבור כל המספרים) והממוצע של המספרים (אם המספר 0 הוזן, הוא אינו נכלל בחישוב המספרים).

הפתרון

נחלק את התרגיל למשימות קטנות יותר. אלו הן המשימות:

  1. בניית המעטפת
  2. קליטת מספרים עד שמופיעה הספרה אפס
  3. קליטת 10 מספרים לכל היותר
  4. חישוב הסכום והממוצע של המספרים שנקלטו

1. בניית המעטפת

כפי שכבר למדנו, ישנה מעטפת בה יש לכתוב את הקוד שלנו. כרגע אין עדיין צורך בהסברים על המעטפת, לכן ניקח אותה מכאן כמו שהיא:

class loops
{
        public static void main(String args[])
        {
        }
}

2. קליטת מספרים עד שמופיעה הספרה אפס

אנחנו אמורים לקלוט מספרים מהמשתמש, מכיוון שזוהי פעולה שחוזרת על עצמה - עלינו להשתמש בלולאות. השאלה היא: באיזה סוג לולאה נשתמש?

לולאת FOR היא בעייתית, מכיוון שעלינו לדעת מראש כמה מספרים המשתמש עומד להקליד. עם זאת, ניתן להשתמש בלולאת FOR על מנת לפתור את התרגיל, אך זאת לא תהיה הדרך הפשוטה ביותר כעת.

לולאת WHILE מתאימה מכיוון שאנו יכולים לקבוע מתי היא תעצר וזה מתאים לנו. אנחנו רוצים שהיא תפסיק להתבצע כאשר המשתמש מקליד את הספרה 0.

לולאת DO-WHILE אפילו מתאימה יותר למקרה שלנו מכיוון שאנו נקלוט לפחות מספר אחד (אפילו אם זה יהיה המספר 0) ולכן נשתמש בה.).

הביטו בקוד הבא ובהסברים שאחריו:

import java.util.Scanner;

class loops
{
        public static void main(String args[])
        {		
                Scanner console = new Scanner(System.in);
                
                int currentNumber;
                do
                {
                	System.out.print("Please enter a number: ");
                	currentNumber = console.nextInt();
                } while(currentNumber != 0);         
                
                console.close();  
        }
}

את שורות 1, 7 ו-16 אתם כבר מכירים. אנחנו מגדירים את המשתנה console על מנת שיקלוט מספרים מהמשתמש ובסוף התוכנית מודיעים ל-console שסיימנו לקלוט מספרים מהמשתמש.

בשורה 9 נגדיר משתנה מסוג Integer על מנת שהוא תמיד יזכור את הערך האחרון שהמשתמש הקליד. אנחנו לא מאתחלים אותו לערך כלשהו בתחילת התוכנית.

שורות 10-14 הן הלולאה שלנו. בתוך הלולאה, אנחנו מבצעים שתי פעולות:

  • שורה 12: מודיעים למשתמש "אנא הכנס מספר:".
  • שורה 13: קולטים מספר מהמשתמש לתוך המשתנה currentNumber

תנאי הלולאה בשורה 14 בודק האם currentNumber שונה מ-0. כלומר: אם הוא שונה מ-0, יש לבצע את הלולאה פעם נוספת ואם הוא שווה ל-0 יש להפסיק את הלולאה ולא לקלוט עוד מספרים.

בשלב זה התוכנית שלנו מסוגלת לקלוט מספרים עד שמופיעה הספרה 0

3. קליטת 10 מספרים לכל היותר

עד שלב זה, המשתמש יכול להקליד אינסוף מספרים ואנחנו לא מגבילים אותו. כעת עלינו להגביל את כמות המספרים שהמשתמש יכול להקליד לעשרה מספרים לכל היותר. הוא כמובן יכול להקליד פחות אם יבחר להקליד את הספרה 0.

נעשה זאת על ידי הוספת משתנה בשם counter. זהו משתנה מונה, כלומר משתנה שהמטרה שלו היא לספור פעולות שאנו עושים. אנחנו נשתמש בו כדי לספור את כמות המספרים שהמשתמש הקליד.

הביטו בקוד הבא ובהסברים שאחריו:

import java.util.Scanner;

class loops
{
        public static void main(String args[])
        {		
                Scanner console = new Scanner(System.in);
                
                int currentNumber;
                int counter = 0;
                
                do
                {
                	System.out.print("Please enter a number: ");
                	currentNumber = console.nextInt();
                	if (currentNumber != 0)
                	{
                	    counter++;
                	}
                } while(currentNumber != 0 && counter < 10);         
                
                console.close();  
        }
}

נביט בשורות שנוספו:

שורה 10: אנחנו מגדירים משתנה מסוג integer בשם counter ומאפסים אותו. כלומר ברגע זה, המשתמש עדיין לא הקליד אף מספר ולכן counter שווה ל-0.

שורות 16-18: מיד לאחר שאנו קולטים מהמשתמש את המספר לתוך currentNumber נבדוק האם הספרה היא 0 או לא. אם המספר אינו שווה ל-0, עלינו לזכור שהמשתמש הכניס עוד מספר. נקדם את המונה שלנו counter ב-1 על ידי הפעולה counter++. זכרו שאם המשתמש הכניס את הספרה 0, הוא התכוון לסיים את הקלדת המספרים ולכן לא נקדם את המונה counter.

זכרו שהפעולה counter++ זהה לפעולה: counter = counter + 1

שורה 20: על מנת לקלוט לכל היותר עשרה מספרים, הוספנו תנאי להמשך ריצת הלולאה. הוספנו את התנאי על ידי הסימן "וגם" (&&). התנאי קובע שיש להמשיך לבצע את הלולאה במידה ו-counter קטן מ-10. כלומר: כאשר counter לא יהיה קטן מ-10 , הרי שהמשתמש הקליד עשרה מספרים ויש לסיים את הלולאה.

כעת התנאי של הלולאה קובע כי יש להמשיך לבצע אותו ולקלוט מספרים נוספים מהמשתמש רק אם המספר האחרון שהוקלט הוא לא 0 וגם שלא נקלטו עדיין עשרה מספרים.

4. חישוב הסכום והממוצע של המספרים שנקלטו

כעת התוכנית קולטת את המספרים לפי הדרישות. עלינו להדפיס בסיום קליטת המספרים את הערכים הבאים: כמות המספרים שנקלטה, סכום המספרים והממוצע שלהם.

חישוב הסכום הוא פשוט, בכל פעם שנקלוט מספר, נוסיף אותו לסכום המספרים שקלטנו עד עכשיו.

חישוב הממוצע הוא הסכום לחלק לכמות המספרים, מכיוון שיש לנו את שתי הנתונים האלו זה יהיה פשוט לביצוע.

import java.util.Scanner;

class loops
{
        public static void main(String args[])
        {		
                Scanner console = new Scanner(System.in);
                
                int currentNumber;
                int counter = 0;
                int sum = 0;
                
                do
                {
                	System.out.print("Please enter a number: ");
                	currentNumber = console.nextInt();
                	if (currentNumber != 0)
                	{
                	    counter++;
                	    sum = sum + currentNumber;

                	}
                } while(currentNumber != 0 && counter < 10);         

                double average = average = sum / counter;
                
                System.out.println("Numbers: " + counter);
                System.out.println("Sum: " + sum);
                System.out.println("Average: " + average);
                
                console.close();  
        }
}

נביט בשורות שנוספו:

שורה 11: אנחנו מגדירים משתנה בשם sum אשר יזכור את סכום המספרים בכל שלב של התוכנית. כמו כן אנחנו מאפסים אותו.

שורה 20: הוספנו שורה לתוך תנאי ה-IF. השורה הזאת מתבצעת במידה והמשתמש הקליד מספר שאינו 0 ולכן עלינו לסכום אותו. אנחנו מוסיפים את הערך של currentNumber לערך של sum. כלומר, אם sum היה שווה -5 ו-currentNumber שווה -4, כעת sum יהיה שווה ל-9.

השורה sum = sum + currentNumber אומרת שהערך החדש של sum יהיה שווה לערך הישן של sum בתוספת הערך של currentNumber;

שורה 25: בשורה זאת אנחנו מגדירים משתנה חדש בשם average. משתנה זה יחזיק את הממוצע של המספרים שנקלטו. אנחנו מבצעים את החישוב: סכום המספרים שהוא sum לחלק לכמות המספרים שהיא counter.

שימו לב שהמשתנה average הוא מסוג double וזאת מכיוון שממוצע לא חייב להיות מספר שלם, הוא יכול להיות 3.5 למשל.

שורות 27-29: שורות אלו מדפיסות למסך את הנתונים שחישבנו: כמות המספרים שנקלטה, הסכום והממוצע שלהם.

בשלב זה התוכנית עובדת, אבל האם היא עובדת כמו שצריך? האחריות לבדוק זאת מוטלת עלינו.

מציאת ותיקון בעיות

נסו לבצע את התרחיש הבא: הריצו את התוכנית, הקלידו את הספרה 0 ואז לחצו על מקש ה-Enter. התוכנית הפסיקה לפעול! מה קרה?

קיבלנו את השגיאה הבאה:

Exception in thread "main" java.lang.ArithmeticException: / by zero at loops.main(loops.java:25)

ישנה בעיה בשורה 25. אם נקרא את הודעת השגיאה, נבין כי ביצענו חלוקה ב-0, דבר שאסור לעשות בחשבון. המחשב אינו יודע כיצד לבצע את הפעולה הזאת ולכן הפסיק את ריצת התוכנית שלנו.

מדוע התבצעה חלוקה ב-0? היא התבצע מכיוון שהמשתנה counter הוא אפס - מכיוון שהמשתמש בחר לא להקליד אף מספר.

זהו מקרה מיוחד שבו אין מספרים כלל. אם לא נקלטו מספרים, לא יכול להיות ממוצע. נחליט שאם המשתמש לא הקליד אף מספר, הממוצע יהיה 0.

נבצע את התיקון כך:

import java.util.Scanner;

class loops
{
        public static void main(String args[])
        {		
                Scanner console = new Scanner(System.in);
                
                int currentNumber;
                int counter = 0;
                int sum = 0;
                
                do
                {
                	System.out.print("Please enter a number: ");
                	currentNumber = console.nextInt();
                	if (currentNumber != 0)
                	{
                	    counter++;
                	    sum = sum + currentNumber;

                	}
                } while(currentNumber != 0 && counter < 10);         

                double average = 0;
                if (counter > 0)
                {
                        average = sum / counter;
                }
                
                System.out.println("Numbers: " + counter);
                System.out.println("Sum: " + sum);
                System.out.println("Average: " + average);
                
                console.close();  
        }
}

שורה 25: קבענו ש-average יהיה שווה בהתחלה לערך 0.

שורה 26-29: רק במידה וישנם מספרים, נשנה את הערך average שיהיה הממוצע של המספרים. אם אין מספרים, הערך של average ישאר מאופס.

השינוי הזה פתר בעית החלוקה באפס. עלינו להמשיך ולבדוק האם התוכנית עובדת כמו שצריך.

ישנה עוד בעיה חמורה! נסו לבצע את התרחיש הבא:

הריצו את התוכנית והקלידו את המספר 3 ואז Enter, 2 ואז Enter, 0 ואז Enter.

התוצאות שנראה הן שנקלטו 2 מספרים, הסכום שלהם הוא 5 והממוצע שלהם הוא 2.0!

אם נבצע את החישוב של הממוצע בעצמנו, נוכל לראות שהתוכנית שלנה מחזירה לנו ערך ממוצע שגוי. הרי הממוצע של 2 מספרים שהסכום שלהם הוא 5 אמור להיות 2.5 - מה קרה כאן?

נביט בשורה 26 בה אנחנו מחלקים את הסכום sum בכמות המספרים שנקלטה counter. מכיוון ש-counter הוא מסוג integer, גם תוצאת החילוק תהיה integer. כלומר, תוצאת החילוק תהיה מספר שלם.

לכן כאשר המחשב חילק 5 ל-2, התוצאה שהוא קיבל היתה אכן 2.5, אך הוא שינה את המספר ל-2.0 מכיוון שהתוצאה חייבת להיות גם מספר שלם.

זכרו: כאשר מחלקים מספר כלשהו במשתנה מסוג integer, גם תוצאת החילוק תהיה integer.

הפתרון הוא להגיד למהדר (קומפיילר) להתייחס ל-counter כאילו שהיה מסוג אחר. אנחנו רוצים שהמהדר יתחייחס אליו כמו ל-double. כך התוצאה תוכל להיות עם מספר עשרוני ולא רק מספר שלם. נוכל להגיד למהדר לבצע זאת על ידי ביצוע Casting (המרה).

import java.util.Scanner;

class loops
{
        public static void main(String args[])
        {		
                Scanner console = new Scanner(System.in);
                
                int currentNumber;
                int counter = 0;
                int sum = 0;
                
                do
                {
                	System.out.print("Please enter a number: ");
                	currentNumber = console.nextInt();
                	if (currentNumber != 0)
                	{
                	    counter++;
                	    sum = sum + currentNumber;

                	}
                } while(currentNumber != 0 && counter < 10);         

                double average = 0;
                if (counter > 0)
                {
                        average = sum / (double) counter;
                }
                
                System.out.println("Numbers: " + counter);
                System.out.println("Sum: " + sum);
                System.out.println("Average: " + average);
                
                console.close();  
        }
}

שורה 28: לפני המילה counter הוספנו Casting על ידי כתיבת הסוג double בתוך סוגריים. פעולה זאת אומרת למהדר להתייחס ל-counter כאילו היה משתנה מסוג double. כך תוצאת החלוקה תהיה נכונה.

אתם מאמינים שזה פועל? אל תאמינו - תבדקו.

סיכום

בתרגיל זה שילנו את כל מה שלמדנו עד כאן: קלט ופלט, פעולות על משתנים, תנאי IF ולולאות והצלחנו לפתור את המשימות שלנו.

כמו כן, למדנו כמה דברים חדשים:

  • התוכניות שלנו עלולות להפסיק לרוץ בגלל בעיות בקוד. בתוכנית זאת התוכנית הפסיקה לרוץ לאחר חלוקה במספר 0.
  • למדנו כיצד לבצע המרה (Casting) על מנת לשנות את הדרך שהמהדר מתייחס למשתנה.
  • למדנו שיש לבדוק האם התוכנית שלנו עובדת כמו שרצינו שהיא תעבוד. ישנם דברים שקשה לחזות מראש, אך עם הזמן נכיר את הבעיות שעלולות לקרות ונוכל לפתור את חלקן כבר בשלב כתיבת התוכנית.

ישנם המון דרכים לפתור את התרגיל ובחרתי בדרך זאת מכיוון שהיא פשוטה להסבר וניתן לבנות אותה בשלבים.

אתם מוזמנים לשאול שאלות.

אם אהבת את הפוסט, אני שולח מדי פעם למייל עוד פוסטים מעניינים שלא תמיד מגיעים להתפרסם באתר. אתה מוזמן להצטרף לרשימה:
(אני שונא ספאם. אני מבטיח לעולם לא לשלוח משהו לא מעניין)

17 תגובות

  1. נדב | 8/8/2009 7:14:55 AM

    אפשר לעשות את החלק הראשון ככה גם:

            int x = 1, times = 0;
            Scanner console = new Scanner(System.in);
            while (x!=0 && times<10)
            {
            x = console.nextInt();
            times++;

  2. טל | 8/8/2009 8:30:24 AM

    נדב, אתה צודק וכל הכבוד על פרסום הקטע שכתבת. זה באמת יעבוד ויכול להיות שאם הייתי מתכנת את זה במקום אחר הייתי משתמש בדרך זאת.

    בפתרון שלך, על מנת לגרום ללולאת ה-while להיכנס בפעם הראשונה, נאלצת לאתחל את הערך של X להיות שווה ל-1, דבר שאין לו משמעות מבחינת הקוראים של המדריך הזה. לכן דרך זאת דורשת יותר הסברים ולכן פחות פשוטה למי שלא מתמצא.

    ניתן כמובן לעשות דבר דומה עם לולאת for. מי שמעוניין לתרגל עוד, מוזמן לפתור את התרגיל עם לולאת for ללא לולאות while.

  3. bugale | 8/9/2009 1:10:12 AM
    יש לך טעות בתנאי של לולאת הDO WHILE... אני חושב שאתה יכול למצוא אותו לבד..
  4. איתי | 8/9/2009 7:15:02 AM

    סתם מתוך היגיון, התנאי שעשית הוא currentNumber != 0 && currentNumber < 10, אבל למעשה לא עדיף לבדוק אם המספר הנוכחי גדול מ-0? זה ימנע מקרים של מינוסים, ומה עם לבדוק האם המספר אכן מספר טבעי (מספר שלם הגדול מ-0, חוסף את הבדיקה האם המספר שקיבלת לא שווה ל-0).

     

  5. טל | 8/9/2009 7:24:43 AM

    @bugale: תודק. טעות בהקלדה. המשתנה שצריך להיות קטן מעשר הוא counter. תוקן.

    @איתי, ניתן להכניס גם מספרים שליליים.. אין הגבלה על כך.

    בקשר לבדיקה האם המספר שלם: ניתן לבדוק עוד המון דברים, כמו האם הוכנסה מילה במקום מספר וכדומה. לטעם התרגיל והפתרון, השארתי את הדברים פשוטים.

  6. איתי | 8/10/2009 7:05:57 AM

    אוקיי, לאחר השינוי הבנתי שהמטרה שלך לא הייתה להגביל את ההקלדה של המספרים עד 10 (בגלל הטעות בהקלדה חשבתי שכן, מודה שלא קראתי את כל ההסבר).

     אז מה שאמרתי לא רלוונטי, חוץ מזה שכן כדאי לבדוק אם הקלידו מספר שלם.

  7. yotam | 2/27/2010 4:31:03 AM

    טל יש לי שאלה לגביי ה-"JCreator" 

    א.לדעתי הוא לא פשוט כמו שאמרת

    ב.תכנס דקה לnotepad תלחץ אנטר 3 פעמים תחזור להתחלה תכתוב משהו בשורה השנייה ואז תלחץ שוב אנטר. למה אין דבר כזה גם ב JCreator? כי כשאני מנסה לרדת שורה כשכתוב משהו בשורה מתחתייה זה מוחק לי את כל השורה שהייתה כתובה מקודם. עזרה!!! 

  8. yotam | 2/27/2010 4:38:55 AM
    יש לי עוד שאלה קטנה בחלק 3 איך הדרתי כבר שמה ואיך שהוא גרמת ללולאה ב"please enter a number" איך עשית את זה?
  9. יותם | 3/1/2010 12:46:40 AM

    ואתה שוב אומר ואתה שוב משקר!!! אני מודה לך מאוד על כל מה שעזרתה לי אבל... אמרת שיש לנו את כל הכלים לפתור את זה ובעצם לא היו לנו...

    אתה לא לימדתה אותנו את הפקודות

    currentNumber

    sum

    avarage

    אז אל תגיד את זה סתם... 

  10. bugale | 3/1/2010 1:41:04 AM

    אלה לא פקודות O_O

    אלה שמות שהוא נתן למשתנים....

  11. יותם | 3/1/2010 7:06:02 AM
    מה? מזאת אומרת
  12. bugale | 3/1/2010 7:40:53 AM

    זאת אומרת שאלה שמות של משתנים.....לא פקודות.

  13. אלכס | 9/30/2012 2:31:22 PM

    יש עוד בעיה שאני מזהה.

     כאשר אנו מכניסים מספרים, המספר 0 גם כן נכלל בחישוב של הממוצע. 

    נסו להכניס את המספרים 3 ואז 2 ואז 0 (כדי לעצור את התכנית) והממוצע שהוא יתן זה 1.666667 ולא 2.5 כמצופה כיוון שהוא לא מחלק את הסכום (5) ב 2 אלא הוא מחלק ב 3 כיוון שהוא מחשיב גם את ה 0 שהכנסנו.

     בשום מקום בקוד לא הגדרנו לו להתעלם מהמספר 0.

    איך גורמים לו להתעלם מהספרה 0 ? 

  14. יהודה | 10/2/2012 5:48:33 AM

    אלכס - כן הגדרנו, המשתנה sum מתחלק במשתנה counter שמתקדם רק במקרה שלא נכתב 0.

    ניסיתי לכתוב את התוכנית לפני שראיתי את הפתרון ובחרתי בלולאת For, הסיבה שאתה מציין לא לבחור ב while מפני שאיננו יודעים כמה פעמים זה ירוץ לא מדויקת. בכל מקרה צריך כאן 2 תנאים, אחד שלא יעבור את ה 10 והשני שלא כתבו 0, מה זה משנה אם נשתמש ב for או ב while? העיקר שיציית ל 2 התנאים

  15. אלכס | 10/3/2012 4:02:30 AM

    תודה יהודה, אני באמת רואה שהגדרנו את זה אבל בפועל זה משום מה לא עובד.

    אשמח אם תעתיק את הקוד מכאן ותנסה להריץ אותו, הוא תמיד מחשיב את המספר אפס במונה המספרים ואני לא מבין למה.

     

  16. נועה | 4/29/2014 10:47:21 AM

    היי! אני מבינה שזה פורום ישן יחסית, אבל אלו באמת ההסברים הטובים ביותר שיש (אתה אדיר טל!) ואם מישהו בכל זאת עדיין מסתובב פה ויכול לעזור לי אני ממש אשמח!

    לא כל כך הבנתי למה שכהגדרת משתנה, כלומר די סתם משתנה (כי ממה שאני הבנתי זו לא איזו פונקציה חדשה שיש לג'אבה ולא לימדת אותנו, אלא סתם משתנה int) זה סופר את הפעמים. לא הבנתי איך זה קורה - נכון שעשית ++ אבל כל מה שזה עושה זה מעלה משתנה ב1. לא ממש הבנתי איפה הקשר בין המשתנה שצריכים להקליד המשתמשים לבין int counter.

    שאלה נוספת: אני עשיתי את זה בדרך אחרת. הרבה יותר ארוכה אני מניחה... נתתי שמות ל10 משתנים. בwhile כתבתי - a*b*c*d*e*f*g*h*i*j !=0

    אבל הוא לא נעצר כשאחד המספרים 0... הוא נעצר אחרי 10 מספרים ומציג לי את הסכומים והממוצע שהגדרתי לפי ה-0 הראשון שהיה לו. אני לא ממש מבינה למה... ברגע שאחד מהם 0 זה כבר false לא? 

  17. טל | 4/29/2014 2:57:29 PM

    נועה - קיימות כמובן יותר מדרך אחת לפתור את התרגיל הזה.

    קשה להעיר על הקוד הספציפי שכתבת בלי לראות אותו (זאת לא המלצה לכתוב אותו כאן). תחשבי מה היית עושה אם היה צריך לקלוט 1,000,000 מספרים ולא רק 10. האם היית מגדירה מיליון משתנים?

    בקשר לקשר בין המשתנה counter והלולאה:

    את צודקת לחלוטין, counter הוא פשוט משתנה רגיל של java. למשתנה שמשמש לספור מספר מסוים של פעולות אנחנו קוראים ״מונה״. ממש כמו כשנוסעים במונית. 

    המשתמש מתבקש להכניס מספר בכל פעם שמגיעים לשורה: console.nextInt ואם המספר אינו אפס, אנחנו מעלים את המונה שלנו (counter) באחד. כלומר, מונים (סופרים) את כמות המספרים שהכניס המשתמש.

    לולא הגדרנו את counter כיצד היינו יודעים מתי להפסיק לבקש עוד מספרים מהמשתמש? 

התגובות נעולות