דילוג לתוכן המרכזי

מתכונת 1 במדעי המחשב - יסודות מדעי המחשב 899371

1–2 יח"ל · שאלון 899371 · 6 שאלות · 2:30 שעות · פתרון מלא לכל סעיף

מבחן מתכונת במדעי המחשב, יסודות מדעי המחשב, במבנה של בחינת הבגרות. כל שאלה בו בנויה כמו שאלה מבגרות אחרת. לכל סעיף יש תשובה ופתרון מלא, וכל קוד מופיע ב-Java וב-C#. פתרו בזמן של הבחינה, ורק אחר כך פתחו את הפתרונות.

הוראות הבחינה

א. משך הבחינה: שעתיים וחצי.
ב. מבנה השאלון ומפתח ההערכה: בשאלון זה שני פרקים.
פרק ראשון – (20×2) – 40 נקודות
פרק שני – (30×2) – 60 נקודות
סך הכול – 100 נקודות
ג. חומר עזר מותר בשימוש: כל חומר עזר, חוץ ממחשבון שיש בו אפשרות תכנוּת.
ד. הוראה מיוחדת:
את כל התוכניות שיש לכתוב בשפת מחשב כתבו בשפה אחת בלבד – Java או C#.
הערה: לא יוּרדוּ נקודות אם תכתבו בתוכניות אות גדולה במקום אות קטנה או להפך.

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

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

בהצלחה!

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

הערה: בכל שאלה שנדרשת בה קליטה, אין צורך לבדוק את תקינות הקלט.
לפותרים בשפת Java: בכל שאלה שנדרשת בה קליטה, הניחו שבתוכנית כתובה ההוראה:
Scanner scan = new Scanner (System.in);

פרק ראשון (40 נקודות)

ענו על שתיים מן השאלות 1–3 (לכל שאלה – 20 נקודות).

שאלה 1 · מעקב – חיסור חוזר של הקטן מן הגדול (20 נקודות)

לפניכם קטע תוכנית, הכתוב בשפות Java ו־ C#.

Java

Java
int a = 84;
int b = 30;
while (a != b) {
    int big = Math.max(a, b);
    int small = Math.min(a, b);
    a = big - small;
    b = small;
}
System.out.println(a);

C#

C#
int a = 84;
int b = 30;
while (a != b) {
    int big = Math.Max(a, b);
    int small = Math.Min(a, b);
    a = big - small;
    b = small;
}
Console.WriteLine(a);
א.

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

ב.

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

◂ פתרון מלא לשאלה 1

סעיף א

תשובה

6

מה נדרש: לעקוב אחרי ביצוע הקטע (עמודות a, b, big, small) ולכתוב מה יודפס.

רעיון: בכל סיבוב בוחרים את הגדול (big) ואת הקטן (small) מבין a ו־ b, מציבים ב־ a את ההפרש big − small וב־ b את הקטן. הלולאה נמשכת כל עוד a ≠ b. זהו אלגוריתם אוקלידס בגרסת החיסור, ולכן הערך המודפס הוא המחלק המשותף הגדול ביותר של 84 ו־ 30.

פתרון – טבלת מעקב:

abbigsmallהערה
8430––ערכים התחלתיים, 84 ≠ 30 – נכנסים ללולאה
5430843084 − 30 = 54
2430543054 − 30 = 24
624302430 − 24 = 6
18624624 − 6 = 18
12618618 − 6 = 12
6612612 − 6 = 6; a = b – יציאה מן הלולאה

הקטע מדפיס: 6

בדיקה: הפלט של הקטע הוא 6, והוא אכן המחלק המשותף הגדול ביותר של 84 ו־ 30.

טעות נפוצה: להציב ב־ b את big במקום את small, או להתבלבל בסדר: a מקבל את ההפרש, ו־ b מקבל את הקטן – גם כאשר הקטן היה a.

סעיף ב

תשובה

למשל a = 9 , b = 4 (כל שני מספרים זרים – שאין להם מחלק משותף גדול מ־ 1)

מה נדרש: ערכים התחלתיים שונים, גדולים מ־ 1, שבעבורם יודפס 1.

רעיון: חיסור הקטן מן הגדול אינו משנה את המחלקים המשותפים של שני המספרים, לכן בסוף a = b = המחלק המשותף הגדול ביותר של הערכים ההתחלתיים. יודפס 1 בדיוק כאשר למספרים אין מחלק משותף גדול מ־ 1 (מספרים זרים).

פתרון: למשל a = 9, b = 4: (9,4) → (5,4) → (1,4) → (3,1) → (2,1) → (1,1) – יודפס 1.
(מתאימים גם 5 ו־ 3, 8 ו־ 15, 10 ו־ 7 וכו'.)

בדיקה: עם a = 9, b = 4 הפלט הוא 1. בדיקה על כל הזוגות השונים בתחום 2–40 הראתה שמודפס 1 בדיוק בעבור הזוגות הזרים.

שאלה 2 · רשימת "הושמעו לאחרונה" (20 נקודות)

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

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

כתבו פעולה בוליאנית חיצונית ששמה playSong בשפת Java או PlaySong בשפת C# , המקבלת מערך מלא – recent , וקוד של שיר – song , מטיפוס שלם.
הפעולה מעדכנת את המערך recent לפי הכללים שלעיל, ומחזירה true אם השיר song לא הופיע במערך לפני ההשמעה. אם השיר כבר הופיע במערך, הפעולה מחזירה false .

נתון המערך recent שלפניכם:

01234567
recent512873401976520348931

דוגמה: עבור המערך recent ו־ song = 19 , הפעולה תחזיר false , והמערך ייראה כך בתום הפעולה:

01234567
recent195128734076520348931

הסבר: השיר 19 כבר הופיע במערך (באינדקס 3). הוא עבר לתא 0 , השירים 512 , 87 , 340 זזו תא אחד ימינה, ושאר השירים נשארו במקומם.

דוגמה נוספת: עבור אותו המערך המקורי ו־ song = 600 , הפעולה תחזיר true , והמערך ייראה כך בתום הפעולה:

01234567
recent600512873401976520348

הסבר: השיר 600 לא הופיע במערך, ולכן השיר 931 שבתא האחרון נמחק.

כתבו פעולה בוליאנית חיצונית ששמה playSong בשפת Java או PlaySong בשפת C# , המקבלת מערך מלא – recent , וקוד של שיר – song , מטיפוס שלם.
הפעולה מעדכנת את המערך recent לפי הכללים שלעיל, ומחזירה true אם השיר song לא הופיע במערך לפני ההשמעה. אם השיר כבר הופיע במערך, הפעולה מחזירה false .

◂ פתרון מלא לשאלה 2

פתרון

תשובה

חיפוש סדרתי של המקום pos של song (אם אינו נמצא – pos הוא התא האחרון); הזזה ימינה מ־ pos עד 1 (recent[i] = recent[i-1]); recent[0] = song; מחזירים true רק אם song לא נמצא.

מה נדרש: לעדכן את רשימת "הושמעו לאחרונה" ולהחזיר אם השיר היה חדש.

רעיון: שני המקרים הם בעצם אותה פעולה – "להוציא" תא אחד, להזיז ימינה את כל התאים שלפניו, ולשים את השיר בתא 0 :

1.חיפוש סדרתי: מחפשים את האינדקס pos שבו נמצא song (נעצרים כשנמצא).
2.אם השיר לא נמצא – התא ש"יוצא" הוא התא האחרון (השיר שנמחק), ולכן קובעים pos = recent.length-1 .
3.מזיזים ימינה מ־ pos אחורה עד 1: recent[i] = recent[i-1] . הערך שהיה בתא pos נדרס – זה בדיוק השיר שעובר לתא 0 (או השיר הישן שנמחק).
4.מציבים את song בתא 0 , ומחזירים true אם השיר לא נמצא בשלב 1 .

פתרון:

public static boolean playSong(int[] recent, int song)
{
    int pos = -1;
    for (int i = 0; i < recent.length && pos == -1; i++)
    {
        if (recent[i] == song)
            pos = i;
    }
    boolean isNew = (pos == -1);
    if (isNew)
        pos = recent.length - 1;
    for (int i = pos; i > 0; i--)
    {
        recent[i] = recent[i - 1];
    }
    recent[0] = song;
    return isNew;
}

בדיקה (על המערך מהשאלה):

•song = 19 → הוחזר false , והמערך הפך ל־ 19 512 87 340 765 203 48 931 – כמו בדוגמה.
•song = 600 → הוחזר true , והמערך הפך ל־ 600 512 87 340 19 765 203 48 – כמו בדוגמה.
•song = 512 (כבר בתא 0) → false , והמערך לא השתנה; song = 931 (בתא האחרון) → false , ו־ 931 עבר לתא 0 .
•רצף השמעות 48 , 700 , 931 על המערך המקורי → 931 700 48 512 87 340 19 765 .

טעות נפוצה: להזיז משמאל לימין (for i = 1 ...: recent[i] = recent[i-1]) – כך הערך של תא 0 מועתק לכל התאים. טעות נוספת: כאשר השיר כבר נמצא, להזיז את כל המערך ימינה – כך השיר מופיע פעמיים והשיר שבתא האחרון נמחק בטעות.

הקוד המלא
public static boolean playSong(int[] recent, int song)
{
    int pos = -1;
    for (int i = 0; i < recent.length && pos == -1; i++)
    {
        if (recent[i] == song)
            pos = i;
    }
    boolean isNew = (pos == -1);
    if (isNew)
        pos = recent.length - 1;
    for (int i = pos; i > 0; i--)
    {
        recent[i] = recent[i - 1];
    }
    recent[0] = song;
    return isNew;
}

שאלה 3 · כרטיסי הגרלה – מערך מוני ספרות (20 נקודות)

בפסטיבל קיץ מוכרים כרטיסי הגרלה. על כל כרטיס מודפס מספר שלם וגדול מ־ 0.

כרטיס זוכה הוא כרטיס שכל הספרות במספר שלו שוות זו לזו (למשל: 7, 444, 88888).

הערה: אין צורך לבדוק את תקינות הקלט.

לפותרים בשפת Java: הניחו שבתוכנית כתובה ההוראה:
Scanner input = new Scanner(System.in);

1.

כתבו פעולה בשם digitCount (ב־ C# – DigitCount) שתקבל מספר שלם num הגדול מ־ 0.
הפעולה תחזיר מערך חד־ממדי של מספרים שלמים בגודל 10. התא שהמציין שלו d (כאשר 0 ≤ d ≤ 9) יכיל את מספר הפעמים שהספרה d מופיעה במספר num.
לדוגמה, בעבור num = 2026 הפעולה תחזיר את המערך: 1,0,2,0,0,0,1,0,0,0

2.

כתבו פעולה בשם raffle (ב־ C# – Raffle) שתקלוט את מספרי הכרטיסים שנמכרו בפסטיבל, לפי סדר מכירתם.
הקלט מסתיים כאשר נקלט כרטיס זוכה.
הפעולה תדפיס את הספרה שהופיעה הכי הרבה פעמים בכל הכרטיסים שנקלטו לפני הכרטיס הזוכה. אם יש כמה ספרות כאלה, תודפס הקטנה מביניהן.
הניחו כי לפני הכרטיס הזוכה נקלט לפחות כרטיס אחד.
דוגמה: אם נקלטו המספרים 1203, 3315, 90, 444 – יודפס 3.
ניתן להשתמש בפעולה שכתבתם בסעיף 1.

◂ פתרון מלא לשאלה 3

סעיף 1

תשובה

int[10]; כל עוד num > 0: counts[num % 10]++, num = num / 10; מחזירים counts.

מה נדרש: פעולה שמקבלת מספר חיובי ומחזירה מערך בגודל 10 של "מוני ספרות": בתא d – כמה פעמים הספרה d מופיעה במספר.

רעיון: מפרקים את המספר לספרות מהסוף להתחלה: num % 10 היא ספרת האחדות, ו־num / 10 מוריד אותה. הספרה עצמה משמשת מציין במערך המונים, ולכן counts[num % 10]++ מוסיף 1 בדיוק לתא של הספרה הזאת. ממשיכים כל עוד num > 0. מערך חדש מאותחל אוטומטית לאפסים.

פתרון: הקוד המלא – בלשונית Java/C#.

בדיקה:

numהמערך המוחזר (תאים 0–9)
20261,0,2,0,0,0,1,0,0,0
33150,1,0,2,0,1,0,0,0,0
901,0,0,0,0,0,0,0,0,1
10000006,1,0,0,0,0,0,0,0,0
4440,0,0,0,3,0,0,0,0,0

טעות נפוצה: לכתוב counts[i]++ עם מונה לולאה במקום להשתמש בספרה עצמה כמציין, או לעצור כש־num % 10 == 0 – אז האפסים במספר כמו 1000000 לא ייספרו.

הקוד המלא
public static int[] digitCount(int num)
{
    int[] counts = new int[10];   // כל התאים מאותחלים ל-0
    while (num > 0)
    {
        counts[num % 10]++;       // ספרת האחדות הנוכחית
        num = num / 10;
    }
    return counts;
}

סעיף 2

תשובה

לולאת זקיף: כל עוד במערך digitCount(ticket) יש יותר מתא אחד שאינו 0 – total[d] += counts[d]; בסוף מדפיסים את מציין המקסימום הראשון ב־total.

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

רעיון:

1.מערך total בגודל 10 צובר את מוני הספרות של כל הכרטיסים.
2.כרטיס הוא זוכה בדיוק כאשר במערך שמחזירה digitCount יש תא אחד בלבד שאינו 0 (רק ספרה אחת מופיעה בו). לכן לכל כרטיס סופרים את התאים שאינם 0 (kinds).
3.לולאת זקיף: קולטים כרטיס ראשון; כל עוד kinds > 1 (הכרטיס אינו זוכה) – מוסיפים את counts ל־total תא־תא, קולטים את הכרטיס הבא ומחשבים לו counts ו־kinds מחדש. הכרטיס הזוכה עצמו אינו נצבר.
4.מחפשים את מציין המקסימום ב־total: מתחילים ב־best = 0, ומחליפים רק כאשר total[d] גדול ממש מ־total[best]; כך בתיקו נשארת הספרה הקטנה.

פתרון: הקוד המלא – בלשונית Java/C#.

בדיקה: בדוגמה מהשאלה: 1203 (ספרות 1,2,0,3), 3315 (3,3,1,5), 90 (9,0), ואז 444 – זוכה. total: לספרה 3 – 3 הופעות, ל־0 ול־1 – 2, ל־2, 5, 9 – 1. מודפס 3. מקרים נוספים: 12, 21, 5 ⇒ תיקו בין 1 ל־2 ⇒ מודפס 1; 505, 6 ⇒ מודפס 5.

טעות נפוצה: לבדוק "כרטיס זוכה" בעזרת num % 11 == 0 או השוואה לספרה הראשונה בלבד. הבדיקה הנכונה היא שבמערך המונים יש תא אחד בלבד שאינו 0.

הקוד המלא
public static void raffle()
{
    int[] total = new int[10];
    int ticket = input.nextInt();
    int[] counts = digitCount(ticket);
    int kinds = 0;                       // כמה ספרות שונות יש בכרטיס
    for (int d = 0; d < 10; d++)
        if (counts[d] > 0)
            kinds++;
    while (kinds > 1)
    {
        for (int d = 0; d < 10; d++)
            total[d] = total[d] + counts[d];
        ticket = input.nextInt();
        counts = digitCount(ticket);
        kinds = 0;
        for (int d = 0; d < 10; d++)
            if (counts[d] > 0)
                kinds++;
    }
    int best = 0;
    for (int d = 1; d < 10; d++)
        if (total[d] > total[best])
            best = d;
    System.out.println(best);
}

פרק שני (60 נקודות)

ענו על שתיים מן השאלות 4–6 (לכל שאלה – 30 נקודות).

שאלה 4 · המחלקה Parking – חניוני העירייה (30 נקודות)

נתונה המחלקה Parking , המייצגת חנייה של רכב באחד מחניוני העירייה, ולה ארבע תכונות:

•lotID – מספר החניון, מטיפוס שלם בין 0 ל־ 29 (כולל)
•ratePerHour – התעריף לשעת חנייה, מטיפוס מספר ממשי
•hours – מספר שעות החנייה, מטיפוס שלם
•resident – האם הנהג הוא תושב העיר, מטיפוס בוליאני (true – בעבור תושב העיר, false – בעבור נהג שאינו תושב העיר)

נהג שהוא תושב העיר מקבל הנחה של 40% מסכום החנייה. נוסף על כך, אם הסכום לתשלום (אחרי ההנחה, אם יש הנחה) גבוה מ־ 60.00 ש"ח – הנהג משלם 60.00 ש"ח בלבד (תעריף יומי מרבי). התעריף המרבי חל על כל הנהגים.

הניחו שקיימות פעולות get/Get ו־ set/Set לתכונות המחלקה.

א.

כתבו במחלקה Parking פעולה בונה המקבלת את מספר החניון – lotID ואת התעריף לשעה – ratePerHour .
הפעולה תאתחל את תכונות המחלקה כך שתתקבל חנייה בחניון lotID , בתעריף של ratePerHour לשעה, למשך שעה אחת, של נהג שאינו תושב העיר.

ב.(1)

כתבו במחלקה Parking פעולה פנימית ששמה basePrice בשפת Java או BasePrice בשפת C# , המחזירה מספר ממשי של סכום החנייה לפי התעריף לשעה ומספר השעות, ללא הנחה וללא התעריף היומי המרבי.

(2)

כתבו במחלקה Parking פעולה פנימית ששמה finalPrice בשפת Java או FinalPrice בשפת C# , המחזירה מספר ממשי של הסכום הסופי שהנהג צריך לשלם, לפי ההנחה לתושבי העיר והתעריף היומי המרבי שתוארו לעיל.
הערה: יש להשתמש בפעולה שכתבתם בתת־סעיף ב(1).

ג.

נתון מערך – dayParkings מטיפוס Parking המכיל את כל החניות שהיו ביום מסוים בכל חניוני העירייה. המערך אינו מסודר לפי סדר כלשהו ואין בו ערכי null .
כתבו פעולה חיצונית ששמה incomePerLot בשפת Java או IncomePerLot בשפת C# , המקבלת את המערך dayParkings . הפעולה מחזירה מערך חדש מטיפוס מספר ממשי בגודל 30 , שבכל תא מופיעה ההכנסה הכוללת של העירייה באותו יום מן החניון שמספרו זהה לאינדקס של התא. ההכנסה מכל חנייה היא הסכום הסופי שהנהג שילם.
למשל, אם בחניון מספר 3 היו באותו יום שלוש חניות, והסכומים הסופיים שלהן הם 20.00 ש"ח, 60.00 ש"ח ו־ 45.00 ש"ח – יופיע בתא של אינדקס 3 המספר 125.0 .

◂ פתרון מלא לשאלה 4

סעיף א

תשובה

lotID ו־ ratePerHour מהפרמטרים, hours = 1, resident = false

מה נדרש: פעולה בונה Parking(lotID, ratePerHour) שיוצרת חנייה של שעה אחת של נהג שאינו תושב העיר.

רעיון: את lotID ו־ ratePerHour מעתיקים מהפרמטרים; hours מקבלת 1 ו־ resident מקבלת false ("אינו תושב העיר"). (בקוד מוצגות גם הגדרות התכונות של המחלקה.)

פתרון: הקוד המלא – בלשונית Java / C#.

בדיקה: new Parking(7, 12.5) – מתקבלים lotID = 7, ratePerHour = 12.5, hours = 1, resident = false.

הקוד המלא
private int lotID;
private double ratePerHour;
private int hours;
private boolean resident;

public Parking(int lotID, double ratePerHour)
{
    this.lotID = lotID;
    this.ratePerHour = ratePerHour;
    this.hours = 1;
    this.resident = false;
}

סעיף ב(1)

תשובה

return ratePerHour * hours;

מה נדרש: סכום החנייה הבסיסי, בלי הנחה ובלי תקרה.

רעיון: הסכום הוא התעריף לשעה כפול מספר השעות: ratePerHour * hours (התוצאה ממשית כי ratePerHour ממשי).

פתרון: הקוד המלא – בלשונית Java / C#.

בדיקה: תעריף 12.5 ו־ 4 שעות – הוחזר 50.0; תעריף 12 ו־ 10 שעות – הוחזר 120.0.

הקוד המלא
public double basePrice()
{
    return ratePerHour * hours;
}

סעיף ב(2)

תשובה

sum = basePrice(); אם resident – sum = sum * 0.6; אם sum > 60.00 – sum = 60.00

מה נדרש: הסכום הסופי לתשלום – אחרי הנחת התושב (אם יש) ואחרי התעריף המרבי.

רעיון:

1.sum = basePrice() (שימוש בפעולה מ־ ב(1)).
2.אם resident == true – מורידים 40%, כלומר משלמים 60%: sum = sum * 0.6.
3.אחרי ההנחה בודקים את התקרה: אם sum גבוה מ־ 60.00 – sum = 60.00. התקרה חלה על כולם, ולכן התנאי השני אינו בתוך התנאי הראשון.

פתרון: הקוד המלא – בלשונית Java / C#.

בדיקה:

תעריףשעותתושבbasePricefinalPriceהסבר
12.54לא50.050.0אין הנחה, 50 ≤ 60
12.54כן50.030.060% מ־ 50
12.56לא75.060.075 > 60 – תעריף מרבי
12.56כן75.045.0אחרי ההנחה 45 ≤ 60
1210כן120.060.0אחרי ההנחה 72 > 60 – תעריף מרבי
106לא60.060.060 אינו גבוה מ־ 60

טעות נפוצה: לבדוק את התקרה לפני ההנחה (על basePrice). תושב שחנה 6 שעות בתעריף 12.5 ישלם אז 60 * 0.6 = 36 במקום 45 – ההנחה מחושבת קודם, ורק אחר כך התקרה.

הקוד המלא
public double finalPrice()
{
    double sum = basePrice();
    if (resident)
        sum = sum * 0.6;
    if (sum > 60.00)
        sum = 60.00;
    return sum;
}

סעיף ג

תשובה

double[30]; לכל חנייה: income[getLotID()] += finalPrice()

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

רעיון: מספר החניון (0–29) משמש ישירות כאינדקס. יוצרים double[30] (כל התאים מאותחלים ל־ 0), ועוברים פעם אחת על dayParkings: לכל חנייה מוסיפים את finalPrice() לתא שהאינדקס שלו הוא getLotID(). הצבירה היא של הסכום הסופי (פעולה ב(2)), לא של basePrice.

פתרון: הקוד המלא – בלשונית Java / C#.

בדיקה: חניות: (חניון 3, 10 ש"ח × 2 שעות, לא תושב) → 20, (חניון 0, 8 × 3, תושב) → 14.4, (חניון 3, 15 × 5, לא תושב) → 60 (תקרה), (חניון 29, 9 × 4, לא תושב) → 36, (חניון 3, 12.5 × 6, תושב) → 45. התקבל מערך בגודל 30 ובו תא 3 = 125.0 (כמו בדוגמה), תא 0 ≈ 14.4, תא 29 = 36.0, ושאר התאים 0.

טעות נפוצה: להגדיר מערך int – ההכנסות הן מספרים ממשיים (למשל 14.4), ולכן המערך חייב להיות מטיפוס double.

הקוד המלא
public static double[] incomePerLot(Parking[] dayParkings)
{
    double[] income = new double[30];
    for (int i = 0; i < dayParkings.length; i++)
    {
        int lot = dayParkings[i].getLotID();
        income[lot] = income[lot] + dayParkings[i].finalPrice();
    }
    return income;
}

שאלה 5 · שליח אופניים – משלוחים בעיר

בעיר מסוימת הרחובות ממוספרים מדרום לצפון (רחוב 1 הוא הדרומי ביותר), והשדרות ממוספרות ממערב למזרח.

המחלקה Location מייצגת מקום בעיר (צומת של רחוב ושדרה), ולה שתי תכונות:

•street – מספר שלם בין 1 ל־ 120 (כולל), המייצג את מספר הרחוב
•avenue – מספר שלם בין 1 ל־ 12 (כולל), המייצג את מספר השדרה

הניחו שקיימות פעולות get/Get ו־ set/Set עבור תכונות המחלקה, ופעולה בונה המקבלת ערכים עבור תכונות המחלקה.

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

תיאור הפעולהכותרת הפעולה
הפעולה מחזירה true אם המקום הנוכחי נמצא צפונית למקום other (כלומר מספר הרחוב שלו גדול יותר), אחרת הפעולה מחזירה false .Java – public boolean northOf (Location other)<br>C# – public bool NorthOf (Location other)
הפעולה מחזירה את מספר הבלוקים בין המקום הנוכחי ובין המקום other (ההפרש בערך מוחלט בין מספרי הרחובות ועוד ההפרש בערך מוחלט בין מספרי השדרות).Java – public int blocks (Location other)<br>C# – public int Blocks (Location other)

המחלקה Delivery מייצגת משלוח של שליח אופניים, ולה שלוש תכונות:

•orderId – קוד ההזמנה, מטיפוס מחרוזת
•pickup – המקום שממנו נאסף המשלוח, מטיפוס Location
•dropoff – המקום שאליו נמסר המשלוח, מטיפוס Location

הניחו שקיימות פעולות get/Get ו־ set/Set עבור תכונות המחלקה, ושתכונות המחלקה אינן null .

נתון מערך חד־ממדי orders מטיפוס Delivery המכיל מידע על כל המשלוחים שביצע שליח מסוים ביום אחד. המערך אינו ממוין ואינו מכיל ערכי null .

א.

כתבו פעולה חיצונית ששמה northmost בשפת Java או Northmost בשפת C# , המקבלת את המערך orders , ומדפיסה את קוד ההזמנה של המשלוח שנאסף מהמקום הצפוני ביותר.
הניחו שאין שני משלוחים שנאספו מאותו רחוב.

ב.(1)

השליח מקבל 15 שקלים על כל משלוח, ובנוסף 2 שקלים על כל בלוק החל מהבלוק ה־ 11 של המשלוח (על 10 הבלוקים הראשונים אין תוספת).

כתבו פעולה פנימית במחלקה Delivery ששמה distance בשפת Java או Distance בשפת C# , המחזירה את מספר הבלוקים בין מקום האיסוף ובין מקום המסירה של המשלוח.

(2)

כתבו פעולה חיצונית ששמה totalPay בשפת Java או TotalPay בשפת C# , המקבלת את המערך orders , ומחזירה את הסכום הכולל שהשליח מקבל על כל המשלוחים.
אפשר להשתמש בפעולה שכתבתם בסעיף ב(1).

◂ פתרון מלא לשאלה 5

סעיף א

תשובה

חיפוש מקסימום עם northOf על getPickup(); מדפיסים orders[maxIndex].getOrderId().

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

רעיון: חיפוש מקסימום, שבו ההשוואה בין שני מקומות נעשית בפעולה northOf של Location. שומרים את האינדקס של המשלוח ה"צפוני" ביותר עד כה (מתחילים מ־0), ועוברים על שאר התאים: אם מקום האיסוף של orders[i] צפוני למקום האיסוף של המשלוח השמור – מעדכנים. בסוף מדפיסים את קוד ההזמנה.

פתרון: הקוד המלא – בלשונית Java/C#. בשרשרת orders[i].getPickup().northOf(orders[maxIndex].getPickup()) מקבלים קודם את עצם ה־Location של מקום האיסוף, ועליו מפעילים את northOf.

בדיקה: 5 משלוחים שמקומות האיסוף שלהם ברחובות 34 , 87 , 12 , 95 , 60. המשלוח D-404 נאסף מרחוב 95 – מודפס D-404. במערך של משלוח אחד מודפס הקוד שלו.

טעות נפוצה: להשוות את מקומות המסירה (dropoff) במקום מקומות האיסוף, או לכתוב orders[i].getPickup() > ... – אי אפשר להשוות עצמים באופרטור >.

הקוד המלא
    // סעיף א
    public static void northmost(Delivery[] orders)
    {
        int maxIndex = 0;
        for (int i = 1; i < orders.length; i++)
        {
            if (orders[i].getPickup().northOf(orders[maxIndex].getPickup()))
                maxIndex = i;
        }
        System.out.println(orders[maxIndex].getOrderId());
    }

סעיף ב(1)

תשובה

return pickup.blocks(dropoff);

מה נדרש: פעולה פנימית ב־Delivery שמחזירה את מספר הבלוקים מהאיסוף למסירה.

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

פתרון:

public int distance()
{
    return pickup.blocks(dropoff);
}

(אפשר גם getPickup().blocks(getDropoff()).)

בדיקה: איסוף ברחוב 34 שדרה 5, מסירה ברחוב 40 שדרה 7 → 6 + 2 = 8; איסוף 95/9, מסירה 70/3 → 25 + 6 = 31.

הקוד המלא
    // סעיף ב(1) – פעולה פנימית במחלקה Delivery
    public int distance()
    {
        return pickup.blocks(dropoff);
    }

סעיף ב(2)

תשובה

לכל משלוח: 15 + (אם distance() > 10 אז 2·(distance() − 10)); מחזירים את הסכום.

מה נדרש: הסכום שהשליח מקבל על כל המשלוחים במערך.

רעיון: לכל משלוח מוסיפים 15 שקלים. מחשבים d = distance(); אם d > 10, הבלוקים החל מה־11 הם d - 10 בלוקים, ועל כל אחד מוסיפים 2 שקלים. סוכמים על כל המערך.

פתרון: הקוד המלא – בלשונית Java/C#.

בדיקה:

משלוחאיסוף (רחוב/שדרה)מסירהבלוקיםתשלום
A-10134/540/7815
B-20287/280/61115 + 2 = 17
C-30312/1112/11015
D-40495/970/33115 + 42 = 57
E-50560/461/4115

הפעולה החזירה 119 = 15 + 17 + 15 + 57 + 15.

טעות נפוצה: לשלם 2 שקלים על כל הבלוקים כשהמשלוח ארוך מ־10 בלוקים, או לשכוח את 15 השקלים של משלוח קצר (משלוח של 10 בלוקים בדיוק מקבל 15 שקלים בלבד).

הקוד המלא
    // סעיף ב(2)
    public static int totalPay(Delivery[] orders)
    {
        int sum = 0;
        for (int i = 0; i < orders.length; i++)
        {
            sum += 15;                        // תשלום בסיס לכל משלוח
            int d = orders[i].distance();
            if (d > 10)
                sum += (d - 10) * 2;          // 2 שקלים לכל בלוק החל מהבלוק ה־11
        }
        return sum;
    }

שאלה 6 · ראשוניים תאומים והשערת גולדבך (30 נקודות)

מספר שלם num הגדול מ־ 1 ייקרא "מספר ראשוני" אם אין לו מחלקים חיוביים ושלמים חוץ מ־ 1 ומעצמו.
שני מספרים ראשוניים שההפרש ביניהם הוא 2 נקראים "זוג ראשוניים תאומים".

דוגמה למספר ראשוני: המספר 13 , כי המחלקים החיוביים שלו הם רק 1 ו־ 13 .
דוגמה למספר שאינו ראשוני: המספר 15 , כי הוא מתחלק גם ב־ 3 וגם ב־ 5 .
דוגמה לזוג ראשוניים תאומים: 17 , 19 – שניהם ראשוניים, וההפרש ביניהם הוא 2 .

א.(1)

כתבו פעולה חיצונית ששמה isPrime בשפת Java או IsPrime בשפת C#, המקבלת מספר שלם – num הגדול מ־ 1 . הפעולה מחזירה true אם הוא מספר ראשוני, אחרת היא מחזירה false .

(2)

כתבו פעולה חיצונית ששמה printTwins בשפת Java או PrintTwins בשפת C#, המקבלת שני מספרים שלמים הגדולים מ־ 1 : low , high . הפעולה תדפיס את כל זוגות הראשוניים התאומים ששני המספרים שבהם נמצאים בין low ל־ high (כולל). כל זוג יודפס בשורה נפרדת, המספר הקטן תחילה.
הניחו ש־ low קטן מ־ high או שווה לו.
הערה: אפשר להיעזר בפעולה שכתבתם בתת־סעיף (1).
דוגמה: בעבור low=10 ו־ high=45 הפעולה תדפיס:
11 13
17 19
29 31
41 43

ב.

יש השערה שכל מספר זוגי הגדול מ־ 2 הוא סכום של שני מספרים ראשוניים (לא בהכרח שונים זה מזה). למשל: 4 = 2 + 2 , 10 = 3 + 7 , 28 = 5 + 23 .
כתבו פעולה חיצונית ששמה goldbach בשפת Java או Goldbach בשפת C#, המחזירה true אם ההשערה מתקיימת בעבור כל המספרים הזוגיים 4 עד 10,000 (כולל). אחרת היא מחזירה false .
הערה: אפשר להיעזר בפעולה שכתבתם בתת־סעיף א(1).

◂ פתרון מלא לשאלה 6

סעיף א(1)

תשובה

for (d = 2; d * d <= num; d++) if (num % d == 0) return false; return true;

מה נדרש
לבדוק אם ל-num יש מחלק בין 2 ל-num − 1.

רעיון
מחפשים מחלק d החל מ-2. מספיק לבדוק עד √num (התנאי d * d <= num): אם num = d · e ושני הגורמים גדולים מ-√num, המכפלה הייתה גדולה מ-num. ברגע שנמצא מחלק – מחזירים false; אם לא נמצא – true. (גם לולאה עד num − 1 נכונה, רק איטית יותר.)

פתרון
Java

Java
// סעיף א(1)
public static boolean isPrime(int num)
{
    for (int d = 2; d * d <= num; d++)
    {
        if (num % d == 0)
        {
            return false;
        }
    }
    return true;
}

C#

C#
// סעיף א(1)
public static bool IsPrime(int num)
{
    for (int d = 2; d * d <= num; d++)
    {
        if (num % d == 0)
        {
            return false;
        }
    }
    return true;
}

בדיקה

•isPrime(2) ← הלולאה לא מתבצעת (4 > 2) ← true ; isPrime(3) ← true
•isPrime(4) ← 4 % 2 = 0 ← false ; isPrime(49) ← 49 % 7 = 0 ← false ; isPrime(91) ← 91 = 7 · 13 ← false
•isPrime(97) ← true

טעות נפוצה: לכתוב d * d < num – אז ריבוע של ראשוני (למשל 49 או 9) מזוהה בטעות כראשוני.

הקוד המלא
// סעיף א(1)
public static boolean isPrime(int num)
{
    for (int d = 2; d * d <= num; d++)
    {
        if (num % d == 0)
        {
            return false;
        }
    }
    return true;
}

סעיף א(2)

תשובה

for (p = low; p + 2 <= high; p++) if (isPrime(p) && isPrime(p + 2)) מדפיסים p ו-(p + 2).

מה נדרש
להדפיס כל זוג p, p + 2 ששניהם ראשוניים ושניהם בתחום [low, high].

רעיון
עוברים על כל p מ-low. כדי שגם p + 2 יהיה בתחום, הלולאה רצה כל עוד p + 2 <= high. לכל p בודקים בעזרת סעיף א(1) אם p ראשוני וגם p + 2 ראשוני, ואם כן – מדפיסים את הזוג.

פתרון
Java

Java
// סעיף א(2)
public static void printTwins(int low, int high)
{
    for (int p = low; p + 2 <= high; p++)
    {
        if (isPrime(p) && isPrime(p + 2))
        {
            System.out.println(p + " " + (p + 2));
        }
    }
}

C#

C#
// סעיף א(2)
public static void PrintTwins(int low, int high)
{
    for (int p = low; p + 2 <= high; p++)
    {
        if (IsPrime(p) && IsPrime(p + 2))
        {
            Console.WriteLine(p + " " + (p + 2));
        }
    }
}

בדיקה

•printTwins(10, 45) ← 11 13 / 17 19 / 29 31 / 41 43 (כמו בדוגמה; 43 45 אינו זוג כי 45 אינו ראשוני)
•printTwins(2, 13) ← 3 5 / 5 7 / 11 13 (המספר 5 שייך לשני זוגות)
•printTwins(24, 28) ← לא מודפס דבר

טעות נפוצה: להריץ את הלולאה עד p <= high – אז עלול להיות מודפס זוג שהמספר השני שבו גדול מ-high (למשל בעבור high = 41 יודפס 41 43).

הקוד המלא
// סעיף א(2)
public static void printTwins(int low, int high)
{
    for (int p = low; p + 2 <= high; p++)
    {
        if (isPrime(p) && isPrime(p + 2))
        {
            System.out.println(p + " " + (p + 2));
        }
    }
}

סעיף ב

תשובה

לכל n זוגי מ-4 עד 10,000 מחפשים p בין 2 ל-n/2 כך ש-isPrime(p) וגם isPrime(n − p); אם לא נמצא – return false; בסוף return true. (התוצאה: true.)

מה נדרש
לבדוק את ההשערה בעבור כל מספר זוגי בתחום 4–10,000, ולהחזיר false אם יש מספר שאין לו פירוק לסכום של שני ראשוניים.

רעיון
לולאה חיצונית על המספרים הזוגיים n = 4, 6, ..., 10000 (קפיצות של 2). לכל n – חיפוש סדרתי של ראשוני p כך ש-n − p גם ראשוני. מספיק לחפש p עד n / 2 (אם p > n/2, אז n − p < n/2 והזוג כבר נבדק בסדר ההפוך). משתנה found עוצר את החיפוש כשנמצא פירוק. אם בסוף החיפוש found הוא false – זו דוגמה נגדית, ומחזירים false מיד. אם כל המספרים עברו – מחזירים true.

פתרון
Java

Java
// סעיף ב
public static boolean goldbach()
{
    for (int n = 4; n <= 10000; n = n + 2)
    {
        boolean found = false;
        for (int p = 2; p <= n / 2 && !found; p++)
        {
            if (isPrime(p) && isPrime(n - p))
            {
                found = true;
            }
        }
        if (!found)
        {
            return false;
        }
    }
    return true;
}

C#

C#
// סעיף ב
public static bool Goldbach()
{
    for (int n = 4; n <= 10000; n = n + 2)
    {
        bool found = false;
        for (int p = 2; p <= n / 2 && !found; p++)
        {
            if (IsPrime(p) && IsPrime(n - p))
            {
                found = true;
            }
        }
        if (!found)
        {
            return false;
        }
    }
    return true;
}

בדיקה

•n = 4: p = 2, n − p = 2 ← שניהם ראשוניים ✓
•n = 10: p = 2 ← 8 לא ראשוני; p = 3 ← 7 ראשוני ✓
•n = 28: p = 2, 3 לא מתאימים (26, 25); p = 5 ← 23 ✓

הפעולה מחזירה true.

טעות נפוצה: להתחיל את החיפוש של p מ-3 ("ראשוני אי-זוגי") – אז 4 = 2 + 2 לא נמצא והפעולה מחזירה false. או לכתוב return true בתוך הלולאה החיצונית מיד אחרי המספר הראשון שנמצא לו פירוק.

הקוד המלא
// סעיף ב
public static boolean goldbach()
{
    for (int n = 4; n <= 10000; n = n + 2)
    {
        boolean found = false;
        for (int p = 2; p <= n / 2 && !found; p++)
        {
            if (isPrime(p) && isPrime(n - p))
            {
                found = true;
            }
        }
        if (!found)
        {
            return false;
        }
    }
    return true;
}
טבלת התשובות
שאלהסעיףתשובה
1א

6

ב

למשל a = 9 , b = 4 (כל שני מספרים זרים – שאין להם מחלק משותף גדול מ־ 1)

2-

חיפוש סדרתי של המקום pos של song (אם אינו נמצא – pos הוא התא האחרון); הזזה ימינה מ־ pos עד 1 (recent[i] = recent[i-1]); recent[0] = song; מחזירים true רק אם song לא נמצא.

31

int[10]; כל עוד num > 0: counts[num % 10]++, num = num / 10; מחזירים counts.

2

לולאת זקיף: כל עוד במערך digitCount(ticket) יש יותר מתא אחד שאינו 0 – total[d] += counts[d]; בסוף מדפיסים את מציין המקסימום הראשון ב־total.

4א

lotID ו־ ratePerHour מהפרמטרים, hours = 1, resident = false

ב(1)

return ratePerHour * hours;

ב(2)

sum = basePrice(); אם resident – sum = sum * 0.6; אם sum > 60.00 – sum = 60.00

ג

double[30]; לכל חנייה: income[getLotID()] += finalPrice()

5א

חיפוש מקסימום עם northOf על getPickup(); מדפיסים orders[maxIndex].getOrderId().

ב(1)

return pickup.blocks(dropoff);

ב(2)

לכל משלוח: 15 + (אם distance() > 10 אז 2·(distance() − 10)); מחזירים את הסכום.

6א(1)

for (d = 2; d * d <= num; d++) if (num % d == 0) return false; return true;

א(2)

for (p = low; p + 2 <= high; p++) if (isPrime(p) && isPrime(p + 2)) מדפיסים p ו-(p + 2).

ב

לכל n זוגי מ-4 עד 10,000 מחפשים p בין 2 ל-n/2 כך ש-isPrime(p) וגם isPrime(n − p); אם לא נמצא – return false; בסוף return true. (התוצאה: true.)

כל האתר במבט אחד

קיצורי דרך לכל הכלים, הבגרויות והקורסים

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