מתכונת 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
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#
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.
פתרון – טבלת מעקב:
| a | b | big | small | הערה |
|---|---|---|---|---|
| 84 | 30 | – | – | ערכים התחלתיים, 84 ≠ 30 – נכנסים ללולאה |
| 54 | 30 | 84 | 30 | 84 − 30 = 54 |
| 24 | 30 | 54 | 30 | 54 − 30 = 24 |
| 6 | 24 | 30 | 24 | 30 − 24 = 6 |
| 18 | 6 | 24 | 6 | 24 − 6 = 18 |
| 12 | 6 | 18 | 6 | 18 − 6 = 12 |
| 6 | 6 | 12 | 6 | 12 − 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 , והשיר שהושמע לפני הזמן הרב ביותר נמצא בתא האחרון במערך.
כאשר המשתמש משמיע שיר:
כתבו פעולה בוליאנית חיצונית ששמה playSong בשפת Java או PlaySong בשפת C# , המקבלת מערך מלא – recent , וקוד של שיר – song , מטיפוס שלם.
הפעולה מעדכנת את המערך recent לפי הכללים שלעיל, ומחזירה true אם השיר song לא הופיע במערך לפני ההשמעה. אם השיר כבר הופיע במערך, הפעולה מחזירה false .
נתון המערך recent שלפניכם:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|---|
| recent | 512 | 87 | 340 | 19 | 765 | 203 | 48 | 931 |
דוגמה: עבור המערך recent ו־ song = 19 , הפעולה תחזיר false , והמערך ייראה כך בתום הפעולה:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|---|
| recent | 19 | 512 | 87 | 340 | 765 | 203 | 48 | 931 |
הסבר: השיר 19 כבר הופיע במערך (באינדקס 3). הוא עבר לתא 0 , השירים 512 , 87 , 340 זזו תא אחד ימינה, ושאר השירים נשארו במקומם.
דוגמה נוספת: עבור אותו המערך המקורי ו־ song = 600 , הפעולה תחזיר true , והמערך ייראה כך בתום הפעולה:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
|---|---|---|---|---|---|---|---|---|
| recent | 600 | 512 | 87 | 340 | 19 | 765 | 203 | 48 |
הסבר: השיר 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 :
recent[i] = recent[i-1] . הערך שהיה בתא pos נדרס – זה בדיוק השיר שעובר לתא 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;
}בדיקה (על המערך מהשאלה):
19 512 87 340 765 203 48 931 – כמו בדוגמה.600 512 87 340 19 765 203 48 – כמו בדוגמה.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);
כתבו פעולה בשם digitCount (ב־ C# – DigitCount) שתקבל מספר שלם num הגדול מ־ 0.
הפעולה תחזיר מערך חד־ממדי של מספרים שלמים בגודל 10. התא שהמציין שלו d (כאשר 0 ≤ d ≤ 9) יכיל את מספר הפעמים שהספרה d מופיעה במספר num.
לדוגמה, בעבור num = 2026 הפעולה תחזיר את המערך: 1,0,2,0,0,0,1,0,0,0
כתבו פעולה בשם 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) |
|---|---|
| 2026 | 1,0,2,0,0,0,1,0,0,0 |
| 3315 | 0,1,0,2,0,1,0,0,0,0 |
| 90 | 1,0,0,0,0,0,0,0,0,1 |
| 1000000 | 6,1,0,0,0,0,0,0,0,0 |
| 444 | 0,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.
מה נדרש: לקלוט מספרי כרטיסים עד כרטיס זוכה, ולהדפיס את הספרה שהופיעה הכי הרבה פעמים בכל הכרטיסים שלפני הכרטיס הזוכה (בתיקו – הקטנה).
רעיון:
פתרון: הקוד המלא – בלשונית 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 , המייצגת חנייה של רכב באחד מחניוני העירייה, ולה ארבע תכונות:
נהג שהוא תושב העיר מקבל הנחה של 40% מסכום החנייה. נוסף על כך, אם הסכום לתשלום (אחרי ההנחה, אם יש הנחה) גבוה מ־ 60.00 ש"ח – הנהג משלם 60.00 ש"ח בלבד (תעריף יומי מרבי). התעריף המרבי חל על כל הנהגים.
הניחו שקיימות פעולות get/Get ו־ set/Set לתכונות המחלקה.
כתבו במחלקה Parking פעולה בונה המקבלת את מספר החניון – lotID ואת התעריף לשעה – ratePerHour .
הפעולה תאתחל את תכונות המחלקה כך שתתקבל חנייה בחניון lotID , בתעריף של ratePerHour לשעה, למשך שעה אחת, של נהג שאינו תושב העיר.
כתבו במחלקה Parking פעולה פנימית ששמה basePrice בשפת Java או BasePrice בשפת C# , המחזירה מספר ממשי של סכום החנייה לפי התעריף לשעה ומספר השעות, ללא הנחה וללא התעריף היומי המרבי.
כתבו במחלקה 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
מה נדרש: הסכום הסופי לתשלום – אחרי הנחת התושב (אם יש) ואחרי התעריף המרבי.
רעיון:
פתרון: הקוד המלא – בלשונית Java / C#.
בדיקה:
| תעריף | שעות | תושב | basePrice | finalPrice | הסבר |
|---|---|---|---|---|---|
| 12.5 | 4 | לא | 50.0 | 50.0 | אין הנחה, 50 ≤ 60 |
| 12.5 | 4 | כן | 50.0 | 30.0 | 60% מ־ 50 |
| 12.5 | 6 | לא | 75.0 | 60.0 | 75 > 60 – תעריף מרבי |
| 12.5 | 6 | כן | 75.0 | 45.0 | אחרי ההנחה 45 ≤ 60 |
| 12 | 10 | כן | 120.0 | 60.0 | אחרי ההנחה 72 > 60 – תעריף מרבי |
| 10 | 6 | לא | 60.0 | 60.0 | 60 אינו גבוה מ־ 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 מייצגת מקום בעיר (צומת של רחוב ושדרה), ולה שתי תכונות:
הניחו שקיימות פעולות 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 מייצגת משלוח של שליח אופניים, ולה שלוש תכונות:
הניחו שקיימות פעולות get/Get ו־ set/Set עבור תכונות המחלקה, ושתכונות המחלקה אינן null .
נתון מערך חד־ממדי orders מטיפוס Delivery המכיל מידע על כל המשלוחים שביצע שליח מסוים ביום אחד. המערך אינו ממוין ואינו מכיל ערכי null .
כתבו פעולה חיצונית ששמה northmost בשפת Java או Northmost בשפת C# , המקבלת את המערך orders , ומדפיסה את קוד ההזמנה של המשלוח שנאסף מהמקום הצפוני ביותר.
הניחו שאין שני משלוחים שנאספו מאותו רחוב.
השליח מקבל 15 שקלים על כל משלוח, ובנוסף 2 שקלים על כל בלוק החל מהבלוק ה־ 11 של המשלוח (על 10 הבלוקים הראשונים אין תוספת).
כתבו פעולה פנימית במחלקה Delivery ששמה distance בשפת Java או Distance בשפת C# , המחזירה את מספר הבלוקים בין מקום האיסוף ובין מקום המסירה של המשלוח.
כתבו פעולה חיצונית ששמה 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-101 | 34/5 | 40/7 | 8 | 15 |
| B-202 | 87/2 | 80/6 | 11 | 15 + 2 = 17 |
| C-303 | 12/11 | 12/1 | 10 | 15 |
| D-404 | 95/9 | 70/3 | 31 | 15 + 42 = 57 |
| E-505 | 60/4 | 61/4 | 1 | 15 |
הפעולה החזירה 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 .
כתבו פעולה חיצונית ששמה isPrime בשפת Java או IsPrime בשפת C#, המקבלת מספר שלם – num הגדול מ־ 1 . הפעולה מחזירה true אם הוא מספר ראשוני, אחרת היא מחזירה false .
כתבו פעולה חיצונית ששמה 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
// סעיף א(1)
public static boolean isPrime(int num)
{
for (int d = 2; d * d <= num; d++)
{
if (num % d == 0)
{
return false;
}
}
return true;
}C#
// סעיף א(1)
public static bool IsPrime(int num)
{
for (int d = 2; d * d <= num; d++)
{
if (num % d == 0)
{
return false;
}
}
return 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
// סעיף א(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#
// סעיף א(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));
}
}
}בדיקה
טעות נפוצה: להריץ את הלולאה עד 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
// סעיף ב
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#
// סעיף ב
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;
}בדיקה
הפעולה מחזירה 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 לא נמצא. |
| 3 | 1 | 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.) |