בגרות במדעי המחשב יסודות מדעי המחשב 899371 - קיץ 2024
1–2 יח"ל · שאלון 899371 · 6 שאלות · 3 שעות · פתרון מלא לכל סעיף
בחינת הבגרות במדעי המחשב, יסודות מדעי המחשב, קיץ 2024, כל 6 השאלות כמו בשאלון. לכל סעיף יש תשובה ופתרון מלא: הרעיון, הקוד או המעקב, ובדיקה קצרה. כל קוד מופיע ב-Java וב-C#. נסו לפתור לבד, ואז פתחו את הפתרון.
הוראות הבחינה (כמו בשאלון)
א. משך הבחינה: שלוש שעות.
ב. מבנה השאלון ומפתח ההערכה: בשאלון זה שני פרקים.
פרק ראשון }
פרק שני } (25×4)
סך הכול – 100 נקודות
ג. חומר עזר מותר בשימוש: כל חומר עזר, חוץ ממחשבון שיש בו אפשרות תכנות.
ד. הוראה מיוחדת:
את כל התוכניות שיש לכתוב בשפת מחשב כתבו בשפה אחת בלבד – Java או C# .
הערה: לא יורדו נקודות אם תכתבו בתוכניות אות גדולה במקום אות קטנה או להפך.
יש לכתוב במחברת הבחינה בלבד. יש לרשום "טיוטה" בראש כל עמוד המשמש טיוטה.
כתיבת טיוטה בדפים שאינם במחברת הבחינה עלולה לגרום לפסילת הבחינה.
השאלות בשאלון זה מנוסחות בלשון רבים, אף על פי כן על כל תלמידה וכל תלמיד להשיב עליהן באופן אישי.
בהצלחה!
(עמוד 2:)
השאלות
בשאלון זה שני פרקים.
יש לענות על ארבע שאלות סך הכול מן הפרקים הראשון והשני (לכל שאלה – 25 נקודות).
הערה: בכל שאלה שנדרשת בה קליטה, אין צורך לבדוק את תקינות הקלט.
לפותרים בשפת Java: בכל שאלה שנדרשת בה קליטה, הניחו שבתוכנית כתובה ההוראה:
Scanner scan = new Scanner (System.in);
פרק ראשון
יש לענות על ארבע שאלות סך הכול מן הפרקים הראשון והשני (לכל שאלה – 25 נקודות).
שאלה 1 · מעקב – ספרת העשרות והאינדקס (25 נקודות)
נתון מערך arr , המכיל מספרים שלמים חיוביים.
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | |
|---|---|---|---|---|---|---|---|
| arr | 30 | 141 | 324 | 623 | 8004 | 458 | 6 |
לפניכם קטע תוכנית, הכתוב בשפות Java ו־ C# .
Java
int count=0;
for (int i=0; i< arr.length; i++)
{
int num=arr[i]/10;
int digit=num%10;
if (digit==i)
count++;
}C#
int count=0;
for (int i=0; i< arr.Length; i++)
{
int num=arr[i]/10;
int digit=num%10;
if (digit==i)
count++;
}עקבו בעזרת טבלת מעקב אחר ביצוע קטע התוכנית עבור המערך arr , ורשמו את ערך המשתנה count לאחר ביצוע קטע התוכנית.
בטבלת המעקב יש לכלול עמודה עבור כל אחד מן המשתנים האלה: count , i , num , digit .
כתבו דוגמה למערך arr בגודל 4 המכיל מספרים שלמים חיוביים (גדולים מ־ 0) שבסיום קטע התוכנית ערכו של המשתנה count יישאר 0 .
כתבו דוגמה למערך arr בגודל 4 המכיל מספרים שלמים חיוביים (גדולים מ־ 0) שבסיום קטע התוכנית ערכו של המשתנה count יהיה שווה ל־ 4 .
◂ פתרון מלא לשאלה 1
סעיף א
count = 2 (בתאים 2 ו־ 5 ספרת העשרות שווה לאינדקס).
מה נדרש: לעקוב אחר קטע התוכנית עבור המערך הנתון ולמצוא את ערך count בסוף.
רעיון: arr[i]/10 היא חלוקה בשלמים – היא "מורידה" את ספרת האחדות. לכן num%10 היא ספרת העשרות של arr[i]. הקטע סופר כמה תאים יש שבהם ספרת העשרות של הערך שווה לאינדקס של התא.
פתרון – טבלת מעקב:
| i | arr[i] | num = arr[i]/10 | digit = num%10 | digit==i ? | count |
|---|---|---|---|---|---|
| 0 | |||||
| 0 | 30 | 3 | 3 | לא | 0 |
| 1 | 141 | 14 | 4 | לא | 0 |
| 2 | 324 | 32 | 2 | כן | 1 |
| 3 | 623 | 62 | 2 | לא | 1 |
| 4 | 8004 | 800 | 0 | לא | 1 |
| 5 | 458 | 45 | 5 | כן | 2 |
| 6 | 6 | 0 | 0 | לא | 2 |
| 7 | (התנאי i<7 אינו מתקיים – יציאה מהלולאה) | 2 |
בסיום הקטע: count = 2.
בדיקה: על המערך מתקבל count = 2.
טעות נפוצה: לחשוב ש־ num%10 היא ספרת האחדות של arr[i]. היא ספרת האחדות של num, כלומר ספרת העשרות של arr[i]. למשל עבור 6: 6/10 = 0, ולכן digit = 0 (ולא 6).
סעיף ב
למשל arr = {99, 99, 99, 99} (ספרת העשרות 9 אינה שווה לאף אינדקס 0–3).
מה נדרש: מערך בגודל 4 של מספרים חיוביים שעבורו count נשאר 0.
רעיון: count גדל רק כאשר ספרת העשרות של arr[i] שווה ל־ i (כאשר i הוא 0, 1, 2 או 3). די לבחור מספרים שספרת העשרות שלהם אינה אף אחד מן הערכים 0–3, למשל ספרת עשרות 9 בכל התאים.
פתרון: למשל
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| arr | 99 | 99 | 99 | 99 |
בכל תא: num = 9, digit = 9, ו־ 9 שונה מכל אינדקס 0–3, ולכן count נשאר 0.
בדיקה: על {99, 99, 99, 99} מתקבל count = 0. (יש עוד תשובות נכונות רבות.)
טעות נפוצה: לבחור מספרים חד־ספרתיים (למשל {5, 5, 5, 5}). עבורם num = 0 ו־ digit = 0, ובתא 0 מתקיים digit==i, ולכן count יהיה 1 ולא 0.
סעיף ג
למשל arr = {5, 12, 23, 34} (ספרות העשרות 0, 1, 2, 3 שוות לאינדקסים).
מה נדרש: מערך בגודל 4 של מספרים חיוביים שעבורו count = 4 בסוף.
רעיון: צריך שבכל אחד מארבעת התאים ספרת העשרות תהיה שווה לאינדקס: בתא 0 ספרת עשרות 0, בתא 1 ספרת עשרות 1, בתא 2 – 2, ובתא 3 – 3.
פתרון: למשל
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| arr | 5 | 12 | 23 | 34 |
| i | arr[i] | num | digit | count |
|---|---|---|---|---|
| 0 | 5 | 0 | 0 | 1 |
| 1 | 12 | 1 | 1 | 2 |
| 2 | 23 | 2 | 2 | 3 |
| 3 | 34 | 3 | 3 | 4 |
בדיקה: על {5, 12, 23, 34} מתקבל count = 4. (שימו לב: בתא 0 צריך מספר חיובי שספרת העשרות שלו 0, כמו מספר חד־ספרתי או 105.)
שאלה 2 · מערך סיסמאות (25 נקודות)
"מערך סיסמאות" במערכת ממוחשבת של בנק מסוים הוא מערך מטיפוס שלם בגודל 10 שבו הבנק שומר את עשר הסיסמאות האחרונות של הלקוח (כדי לוודא שהלקוח לא יבחר אותן שוב). הסיסמאות נשמרות לפי הסדר מן החדשה ביותר לישנה ביותר. הסיסמה החדשה ביותר (העדכנית) נמצאת בתא 0 והסיסמה הישנה ביותר נמצאת בתא האחרון במערך.
כאשר מתקבלת סיסמה חדשה, הסיסמה הישנה ביותר (הסיסמה שבתא האחרון במערך) נמחקת מן המערך, שאר הסיסמאות זזות ימינה כל אחת לתא שלידה, והסיסמה החדשה מופיעה בתא הראשון (אינדקס 0).
כתבו פעולה בוליאנית חיצונית ששמה getPass בשפת Java או GetPass בשפת C# , המקבלת מערך סיסמאות מלא – arr , וסיסמה – password , מטיפוס שלם.
אם הסיסמה שהתקבלה היא חדשה (כלומר אינה זהה לשום סיסמה שמופיעה במערך), הפעולה תוסיף אותה למערך arr בתא 0 (לאחר הזזת שאר הסיסמאות תא אחד ימינה) ותחזיר true . אחרת, מערך הסיסמאות יישאר ללא שינוי ויוחזר false .
דוגמה: עבור המערך arr שלפניכם ו־ password = 300 , המערך יישאר ללא שינוי והפעולה תחזיר false .
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
|---|---|---|---|---|---|---|---|---|---|---|
| arr | 1223 | 134 | 245 | 300 | 111 | 101 | 777 | 900 | 195 | 1234 |
הסבר: הסיסמה 300 כבר מופיעה במערך arr , באינדקס 3 .
דוגמה נוספת: עבור אותו המערך ו־ password = 8888 , הפעולה תחזיר true , והמערך ייראה כך בתום הפעולה:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
|---|---|---|---|---|---|---|---|---|---|---|
| arr | 8888 | 1223 | 134 | 245 | 300 | 111 | 101 | 777 | 900 | 195 |
הסבר: הסיסמה 8888 אינה אחת הסיסמאות שכבר מופיעות במערך.
כתבו פעולה בוליאנית חיצונית ששמה getPass בשפת Java או GetPass בשפת C# , המקבלת מערך סיסמאות מלא – arr , וסיסמה – password , מטיפוס שלם.
אם הסיסמה שהתקבלה היא חדשה (כלומר אינה זהה לשום סיסמה שמופיעה במערך), הפעולה תוסיף אותה למערך arr בתא 0 (לאחר הזזת שאר הסיסמאות תא אחד ימינה) ותחזיר true . אחרת, מערך הסיסמאות יישאר ללא שינוי ויוחזר false .
◂ פתרון מלא לשאלה 2
פתרון
חיפוש סדרתי – אם password נמצאת, return false; אחרת הזזה ימינה מהסוף (arr[i] = arr[i-1]), arr[0] = password, return true.
מה נדרש: לבדוק אם password כבר נמצאת במערך; אם לא – להכניס אותה לתא 0 אחרי הזזת כל האיברים ימינה (האחרון "נופל"), ולהחזיר true; אחרת להחזיר false בלי לשנות את המערך.
רעיון:
arr[i] = arr[i-1] עבור i מ־ arr.length-1 עד 1. הערך שהיה בתא האחרון נדרס – וזו בדיוק המחיקה של הסיסמה הישנה ביותר.פתרון:
public static boolean getPass(int[] arr, int password)
{
for (int i = 0; i < arr.length; i++)
{
if (arr[i] == password)
return false;
}
for (int i = arr.length - 1; i > 0; i--)
{
arr[i] = arr[i - 1];
}
arr[0] = password;
return true;
}בדיקה (על המערך מהשאלה):
1223 134 245 300 111 101 777 900 195 1234.8888 1223 134 245 300 111 101 777 900 195 – כמו בדוגמה.טעות נפוצה: להזיז משמאל לימין (for i = 1 ...: arr[i] = arr[i-1]). כך הערך של תא 0 מועתק לכל התאים ושאר הסיסמאות נמחקות. ההזזה ימינה חייבת להתחיל מהתא האחרון.
public static boolean getPass(int[] arr, int password)
{
for (int i = 0; i < arr.length; i++)
{
if (arr[i] == password)
return false;
}
for (int i = arr.length - 1; i > 0; i--)
{
arr[i] = arr[i - 1];
}
arr[0] = password;
return true;
}שאלה 3 · מערך מעורבל (25 נקודות)
"מערך מעורבל" הוא מערך מטיפוס שלם שהתהליך המתואר לפניכם בוצע בו 30 פעמים:
i. מוגרלים שני מספרי אינדקס תקינים (בגבולות גודל המערך).
ii. הערכים הנמצאים בתאים שמספריהם עלו בהגרלה מתחלפים ביניהם.
הערה: אם בהגרלה עלו שני מספרי אינדקס זהים, ההגרלה לא תיחשב (כלומר היא לא תיספר כחלק מן התהליך המבוצע 30 פעמים).
כתבו פעולה חיצונית ששמה shuffle בשפת Java או Shuffle בשפת C# המקבלת מערך מטיפוס שלם – arr ומערבלת אותו.
הפעולה תדפיס את ערכי התאים לפי הסדר לאחר הערבול (כל ערך בשורה חדשה).
כתבו פעולה חיצונית ששמה shuffle בשפת Java או Shuffle בשפת C# המקבלת מערך מטיפוס שלם – arr ומערבלת אותו.
הפעולה תדפיס את ערכי התאים לפי הסדר לאחר הערבול (כל ערך בשורה חדשה).
◂ פתרון מלא לשאלה 3
פתרון
לולאת while עם מונה שגדל רק כאשר שני האינדקסים המוגרלים שונים (30 החלפות אמיתיות), ואז הדפסת המערך ערך בכל שורה.
מה נדרש: לבצע 30 החלפות "אמיתיות" בין שני תאים שאינדקסיהם מוגרלים, ואחר כך להדפיס את המערך, ערך בכל שורה.
רעיון:
rnd.nextInt(arr.length) ב־ Java או rnd.Next(arr.Length) ב־ C# – מספר שלם בין 0 ל־ arr.length-1.פתרון:
public static void shuffle(int[] arr)
{
Random rnd = new Random();
int count = 0;
while (count < 30)
{
int i = rnd.nextInt(arr.length);
int j = rnd.nextInt(arr.length);
if (i != j)
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
count++;
}
}
for (int k = 0; k < arr.length; k++)
{
System.out.println(arr[k]);
}
}(ב־ Java צריך import java.util.Random; בראש הקובץ. אפשר גם להגריל בעזרת (int)(Math.random() * arr.length).)
בדיקה: על המערך {10, 20, 30, 40, 50, 60, 70} מודפסות 7 שורות, והתוצאה היא סידור מחדש של אותם ערכים (הסדר משתנה מהפעלה להפעלה). בדיקה נוספת שמוודאת שנספרות בדיוק 30 החלפות אמיתיות: במערך בן 2 תאים {1, 2} כל החלפה אמיתית מחליפה את שני התאים, ואחרי 30 החלפות (מספר זוגי) המערך חוזר להיות {1, 2}.
טעות נפוצה: לספור את הסיבוב גם כאשר i==j (לולאת for של 30 סיבובים), בניגוד להערה בשאלה. (הערה: הפעולה מניחה שבמערך יש לפחות 2 תאים, אחרת אי אפשר להגריל שני אינדקסים שונים.)
public static void shuffle(int[] arr)
{
Random rnd = new Random();
int count = 0;
while (count < 30)
{
int i = rnd.nextInt(arr.length);
int j = rnd.nextInt(arr.length);
if (i != j)
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
count++;
}
}
for (int k = 0; k < arr.length; k++)
{
System.out.println(arr[k]);
}
}פרק שני
יש לענות על ארבע שאלות סך הכול מן הפרקים הראשון והשני (לכל שאלה – 25 נקודות).
שאלה 4 · חבילת סלולר לחו"ל – TourPackage (25 נקודות)
נתונה המחלקה TourPackage – חבילת סלולר לטסים לחו"ל, ולה חמש תכונות:
תעריפי החריגה הם שקל אחד בעבור כל דקת שיחה נוספת, ו־ 2 שקלים בעבור כל Giga נוסף בנפח הגלישה.
הניחו שקיימות פעולות get/Get לתכונות המחלקה.
כתבו פעולה בונה במחלקה TourPackage , המקבלת את מספר הזיהוי של לקוח – id , את מחיר החבילה – price , את מספר דקות השיחה הכלול בחבילה – maxTime , ואת נפח הנתונים – maxData .
הפעולה מאתחלת את תכונות המחלקה. התכונה extra מאותחלת ל־ 0 .
כתבו פעולה פנימית במחלקה TourPackage ששמה setExtra בשפת Java או SetExtra בשפת C# , המקבלת את מספר דקות השיחה שבהן השתמש הלקוח בפועל בחו"ל – minutes (מטיפוס שלם), ואת נפח הגלישה שבו השתמש הלקוח בפועל בחו"ל – usage (מטיפוס שלם).
הפעולה קובעת ערך לתכונה extra בהתאם לחריגה מן החבילה הבסיסית (ככל שיש) ולתעריפי החריגה.
הערה: extra היא 0 לפני הפעולה.
נתון מערך מלא – packages מטיפוס TourPackage , שבו נתונים על לקוחות שונים (לאחר שכבר חושב ועודכן הסכום לתשלום עבור חריגה מן החבילה הבסיסית בתכונה extra).
כתבו פעולה חיצונית ששמה calculate בשפת Java או Calculate בשפת C# , המקבלת את המערך packages , ומחזירה את כמות הלקוחות שצריכים לשלם תוספת על חריגה מן החבילה הבסיסית.
כתבו פעולה חיצונית ששמה customers בשפת Java או Customers בשפת C# , המקבלת את המערך packages .
הפעולה מחזירה מערך מטיפוס שלם שבו מספרי הזיהוי של הלקוחות (id) שצריכים לשלם תוספת על חריגה מן החבילה הבסיסית (גודל המערך הוא לפי כמות האנשים שחרגו). אין חשיבות לסדר ההופעה במערך.
הערה: חובה להיעזר בפעולה שכתבתם בסעיף ב(1).
◂ פתרון מלא לשאלה 4
סעיף א(1)
public TourPackage(int id, int price, int maxTime, int maxData) – השמה לארבע התכונות ו־ extra = 0.
מה נדרש: פעולה בונה שמקבלת ארבעה פרמטרים ומאתחלת את חמש התכונות (extra ל־ 0).
רעיון: מגדירים את חמש התכונות כ־ private, ובפעולה הבונה מציבים בכל תכונה את הפרמטר המתאים. את extra, שאינה מתקבלת כפרמטר, מאתחלים ל־ 0.
פתרון (תחילת המחלקה: התכונות והפעולה הבונה):
public class TourPackage
{
private int id;
private int price;
private int maxTime;
private int maxData;
private int extra;
public TourPackage(int id, int price, int maxTime, int maxData)
{
this.id = id;
this.price = price;
this.maxTime = maxTime;
this.maxData = maxData;
this.extra = 0;
}
// ... (setExtra ופעולות get בהמשך המחלקה)
// }בדיקה: new TourPackage(111, 50, 100, 10) – getExtra() החזירה 0, ושאר ה־ get החזירו את הערכים שהתקבלו.
public class TourPackage
{
private int id;
private int price;
private int maxTime;
private int maxData;
private int extra;
public TourPackage(int id, int price, int maxTime, int maxData)
{
this.id = id;
this.price = price;
this.maxTime = maxTime;
this.maxData = maxData;
this.extra = 0;
}
// ... (setExtra ופעולות get בהמשך המחלקה)
// }סעיף א(2)
extra = (minutes > maxTime ? (minutes − maxTime)·1 : 0) + (usage > maxData ? (usage − maxData)·2 : 0).
מה נדרש: לחשב את סכום החריגה לפי השימוש בפועל ולשמור אותו ב־ extra.
רעיון:
פתרון:
public void setExtra(int minutes, int usage)
{
int sum = 0;
if (minutes > this.maxTime)
sum = sum + (minutes - this.maxTime) * 1;
if (usage > this.maxData)
sum = sum + (usage - this.maxData) * 2;
this.extra = sum;
}בדיקה (חבילה עם maxTime = 100, maxData = 10):
טעות נפוצה: לחשב (minutes - maxTime) + (usage - maxData) * 2 בלי תנאים. כאשר השימוש קטן מהחבילה, ההפרש שלילי ו"מקזז" את החריגה בחלק השני – ומתקבל סכום שגוי.
public void setExtra(int minutes, int usage)
{
int sum = 0;
if (minutes > this.maxTime)
sum = sum + (minutes - this.maxTime) * 1;
if (usage > this.maxData)
sum = sum + (usage - this.maxData) * 2;
this.extra = sum;
}סעיף ב(1)
מונים את החבילות שבהן getExtra() > 0.
מה נדרש: לספור כמה חבילות במערך יש להן extra גדול מ־ 0.
רעיון: לקוח צריך לשלם תוספת אם ורק אם extra > 0 (ה־ extra כבר חושב). עוברים על המערך וסופרים בעזרת getExtra().
פתרון:
public static int calculate(TourPackage[] packages)
{
int count = 0;
for (int i = 0; i < packages.length; i++)
{
if (packages[i].getExtra() > 0)
count++;
}
return count;
}בדיקה: מערך של 4 חבילות עם extra = 34, 0, 6, 15 (מספרי זיהוי 111, 222, 333, 444) → הוחזר 3.
public static int calculate(TourPackage[] packages)
{
int count = 0;
for (int i = 0; i < packages.length; i++)
{
if (packages[i].getExtra() > 0)
count++;
}
return count;
}סעיף ב(2)
int[] ids = new int[calculate(packages)]; ממלאים בעזרת אינדקס נפרד k את ה־ id של כל חבילה עם extra > 0.
מה נדרש: מערך חדש באורך מדויק, ובו מספרי הזיהוי של הלקוחות שחרגו.
רעיון:
פתרון:
public static int[] customers(TourPackage[] packages)
{
int[] ids = new int[calculate(packages)];
int k = 0;
for (int i = 0; i < packages.length; i++)
{
if (packages[i].getExtra() > 0)
{
ids[k] = packages[i].getId();
k++;
}
}
return ids;
}בדיקה: על אותו מערך (extra = 34, 0, 6, 15) הוחזר המערך {111, 333, 444}. על מערך שבו אף לקוח לא חרג הוחזר מערך באורך 0.
טעות נפוצה: להשתמש באותו אינדקס i גם למערך החדש (ids[i] = ...). המערך החדש קצר יותר, ולכן זה גורם לחריגה מגבולות המערך או ל"חורים" במערך.
public static int[] customers(TourPackage[] packages)
{
int[] ids = new int[calculate(packages)];
int k = 0;
for (int i = 0; i < packages.length; i++)
{
if (packages[i].getExtra() > 0)
{
ids[k] = packages[i].getId();
k++;
}
}
return ids;
}שאלה 5 · מערכת שעות – Lesson (25 נקודות)
נתונה המחלקה Lesson – שיעור, ולה ארבע תכונות:
הניחו שהפעולות get/Get ו־ set/Set קיימות עבור תכונות המחלקה.
הערה: שיעור באותו המקצוע (id) יכול להתקיים כמה פעמים באותו יום לימודים.
נתון מערך שיעורים מלא – arr מטיפוס Lesson של יום לימודים. המערך אינו ממוין.
כתבו במחלקה Lesson פעולה פנימית ששמה isLater בשפת Java או IsLater בשפת C# , המקבלת שיעור אחר – other (מטיפוס Lesson). הפעולה מחזירה true אם השיעור הנוכחי מתחיל מאוחר יותר מן הזמן שמתחיל השיעור האחר (other), ואחרת מחזירה false .
כתבו פעולה חיצונית ששמה last המקבלת את המערך arr , ומדפיסה את קוד המקצוע (id) ששיעורו התחיל אחרון ביום הלימודים.
הניחו שיש רק שיעור אחד המקיים תנאי זה.
הערה: חובה להיעזר בפעולה שכתבתם בסעיף א(1).
כתבו פעולה חיצונית ששמה sumDuration בשפת Java או SumDuration בשפת C# המקבלת את המערך arr וקוד של מקצוע – id . הפעולה מחזירה את מספר הדקות סך הכול של כל השיעורים בקוד המקצוע id המתקיימים באותו יום לימודים.
לדוגמה: אם שיעור במקצוע id נמשך 40 דקות ובאותו היום שיעור נוסף באותו מקצוע נמשך 70 דקות, יוחזר 110 .
כתבו פעולה חיצונית ששמה longest בשפת Java או Longest בשפת C# המקבלת את המערך arr . הפעולה תחזיר את קוד המקצוע (id) שמספר הדקות סך הכול של כל שיעוריו באותו יום לימודים הוא הגדול ביותר.
הניחו שיש רק מקצוע אחד המקיים תנאי זה.
הערה: חובה להיעזר בפעולה שכתבתם בסעיף ב(1).
◂ פתרון מלא לשאלה 5
סעיף א(1)
return hh·60 + mm > other.getHh()·60 + other.getMm();
מה נדרש: להשוות את שעת ההתחלה של השיעור הנוכחי (this) לשעת ההתחלה של other.
רעיון: הופכים כל שעת התחלה למספר אחד – מספר הדקות מחצות: hh × 60 + mm. אז מספיקה השוואה אחת: השיעור הנוכחי מתחיל מאוחר יותר אם המספר שלו גדול יותר (השוואה חזקה – באותה שעה בדיוק יוחזר false).
פתרון:
public boolean isLater(Lesson other)
{
int thisStart = this.hh * 60 + this.mm;
int otherStart = other.getHh() * 60 + other.getMm();
return thisStart > otherStart;
}(אפשר גם בלי המרה: hh > other.getHh() || (hh == other.getHh() && mm > other.getMm()).)
בדיקה: 13:05 לעומת 13:00 → true; 13:00 לעומת 13:05 → false; 8:00 לעומת 10:15 → false; שיעור לעומת עצמו → false.
טעות נפוצה: להשוות רק את הדקות או רק את השעות, או hh > other.hh && mm > other.mm – שנותן false עבור 13:05 לעומת 12:30.
public boolean isLater(Lesson other)
{
int thisStart = this.hh * 60 + this.mm;
int otherStart = other.getHh() * 60 + other.getMm();
return thisStart > otherStart;
}סעיף א(2)
מחפשים את השיעור המאוחר ביותר (latest) בעזרת isLater ומדפיסים latest.getId().
מה נדרש: למצוא את השיעור שמתחיל הכי מאוחר ולהדפיס את קוד המקצוע שלו.
רעיון: חיפוש "מקסימום" במערך לא ממוין: מחזיקים הפניה לשיעור המאוחר ביותר שנמצא עד עכשיו (latest), ומתחילים מ־ arr[0]. לכל שיעור אחר בודקים בעזרת isLater (סעיף א(1)) אם הוא מתחיל מאוחר יותר מ־ latest, ואם כן – מעדכנים. בסוף מדפיסים את ה־ id של latest.
פתרון (בשאלה מופיע רק השם last; ב־ C# נקרא לפעולה Last לפי המוסכמה):
public static void last(Lesson[] arr)
{
Lesson latest = arr[0];
for (int i = 1; i < arr.length; i++)
{
if (arr[i].isLater(latest))
latest = arr[i];
}
System.out.println(latest.getId());
}בדיקה: על מערך של 6 שיעורים שמתחילים ב־ 10:15, 8:00, 12:30, 13:05, 9:45, 13:00 (עם הקודים 5, 7, 5, 3, 9, 3) הודפס 3 (השיעור של 13:05). על מערך נוסף שבו המאוחר הוא האחרון הודפס הקוד שלו (2).
טעות נפוצה: להחזיר את הערך במקום להדפיס אותו – בשאלה נדרש במפורש להדפיס.
public static void last(Lesson[] arr)
{
Lesson latest = arr[0];
for (int i = 1; i < arr.length; i++)
{
if (arr[i].isLater(latest))
latest = arr[i];
}
System.out.println(latest.getId());
}סעיף ב(1)
סכום getDuration() של כל השיעורים שבהם getId() == id.
מה נדרש: סכום משכי כל השיעורים שקוד המקצוע שלהם שווה ל־ id.
רעיון: צבירה עם תנאי: עוברים על כל המערך (הוא אינו ממוין, ולכן השיעורים של אותו מקצוע יכולים להיות בכל מקום), ומוסיפים את getDuration() לכל שיעור שה־ id שלו שווה ל־ id שהתקבל.
פתרון:
public static int sumDuration(Lesson[] arr, int id)
{
int sum = 0;
for (int i = 0; i < arr.length; i++)
{
if (arr[i].getId() == id)
sum = sum + arr[i].getDuration();
}
return sum;
}בדיקה: באותו מערך – למקצוע 5 יש שיעורים של 40 ו־ 70 דקות → הוחזר 110 (כמו בדוגמה בשאלה); למקצוע 3 (45 + 20) → 65; למקצוע שאינו במערך → 0.
public static int sumDuration(Lesson[] arr, int id)
{
int sum = 0;
for (int i = 0; i < arr.length; i++)
{
if (arr[i].getId() == id)
sum = sum + arr[i].getDuration();
}
return sum;
}סעיף ב(2)
מקסימום של sumDuration(arr, arr[i].getId()) על כל i; מחזירים את ה־ id שבו התקבל.
מה נדרש: קוד המקצוע שסך דקות השיעורים שלו ביום הוא הגדול ביותר.
רעיון: לכל שיעור במערך מחשבים בעזרת sumDuration (סעיף ב(1)) את סך הדקות של המקצוע שלו, ומחפשים מקסימום. מתחילים מהמקצוע של arr[0]. כאשר נמצא סכום גדול יותר (השוואה חזקה), מעדכנים את הסכום המקסימלי ואת קוד המקצוע. מקצוע שמופיע כמה פעמים ייבדק כמה פעמים – זה לא מזיק, כי הסכום שלו זהה בכל פעם.
פתרון:
public static int longest(Lesson[] arr)
{
int maxId = arr[0].getId();
int maxSum = sumDuration(arr, maxId);
for (int i = 1; i < arr.length; i++)
{
int sum = sumDuration(arr, arr[i].getId());
if (sum > maxSum)
{
maxSum = sum;
maxId = arr[i].getId();
}
}
return maxId;
}בדיקה: במערך מסעיף א(2) הסכומים הם: מקצוע 5 – 110, מקצוע 7 – 90, מקצוע 3 – 65, מקצוע 9 – 100 → הוחזר 5. במערך שבו המקסימום שייך למקצוע של התא הראשון (1: 70 דקות; 2: 30+30) הוחזר 1.
הערה על יעילות: לכל אחד מ־ n התאים מופעלת sumDuration שעוברת על n תאים, כלומר בערך n² פעולות – זה פתרון פשוט ומתאים לשאלה, שדורשת להיעזר ב־ sumDuration.
public static int longest(Lesson[] arr)
{
int maxId = arr[0].getId();
int maxSum = sumDuration(arr, maxId);
for (int i = 1; i < arr.length; i++)
{
int sum = sumDuration(arr, arr[i].getId());
if (sum > maxSum)
{
maxSum = sum;
maxId = arr[i].getId();
}
}
return maxId;
}שאלה 6 · סכום הספרות העמוק (25 נקודות)
נתונה פעולה חיצונית ששמה digitSum בשפת Java או DigitSum בשפת C# , המקבלת מספר שלם – num1 ומחזירה את סכום כל הספרות במספר. אפשר להשתמש בפעולה בלי לממש אותה.
לדוגמה: עבור num1 = 961 , הפעולה תחזיר 16 (9 + 6 + 1) .
"סכום הספרות העמוק" של מספר כלשהו הוא מספר חד־ספרתי המתקבל באופן שלהלן:
מחשבים את סכום הספרות שוב ושוב עד שמתקבל מספר חד־ספרתי.
דוגמאות:
"סכום הספרות העמוק" של המספר 5 הוא 5 .
"סכום הספרות העמוק" של המספר 36 הוא 9 (3+6) .
"סכום הספרות העמוק" של המספר 942378 הוא 6 , כמפורט להלן:
9 + 4 + 2 + 3 + 7 + 8 = 33
3 + 3 = 6
כתבו פעולה חיצונית ששמה deepSum בשפת Java או DeepSum בשפת C# המקבלת מספר שלם num1 שאינו שלילי ומחזירה את "סכום הספרות העמוק" שלו.
"סכום הספרות העמוק" יכול להיות אי־זוגי (למשל 5 כמו בדוגמה הראשונה שלעיל) או זוגי (למשל 6 כמו בדוגמה השלישית שלעיל). יש הטוענים כי בטווח שבין 1 ל־ 999999 יש יותר מספרים בעלי "סכום ספרות עמוק" זוגי ממספרים בעלי "סכום ספרות עמוק" אי־זוגי.
כתבו פעולה חיצונית ששמה isCorrect בשפת Java או IsCorrect בשפת C# המחזירה true אם הטענה נכונה, ואחרת מחזירה false .
הערה: חובה להיעזר בפעולה שכתבתם בסעיף א(1).
נתונה פעולה חיצונית נוספת, ששמה digitExists בשפת Java או DigitExists בשפת C# , המקבלת מספר שלם – num וסִפרה – digit . הפעולה מחזירה true אם הספרה digit מופיעה במספר num לפחות פעם אחת, ואחרת מחזירה false .
אפשר להשתמש בפעולה בלי לממש אותה.
כתבו פעולה חיצונית ששמה inBoth בשפת Java או InBoth בשפת C# המקבלת שני מספרים שלמים: num1 ו־ num2 . הפעולה מחזירה true אם "סכום הספרות העמוק" של num1 מופיע במספר num2 , וגם "סכום הספרות העמוק" של num2 מופיע במספר num1 , אחרת היא מחזירה false .
למשל עבור num1 = 36 ו־ num2 = 942378 , הפעולה תחזיר true כי "סכום הספרות העמוק" של num1 (9) מופיע במספר num2 (942378), ו"סכום הספרות העמוק" של num2 (6) מופיע במספר num1 (36) .
הערה: חובה להיעזר בפעולה שכתבתם בסעיף א(1).
◂ פתרון מלא לשאלה 6
סעיף א(1)
while (num1 >= 10) num1 = digitSum(num1); return num1;
מה נדרש: להפעיל digitSum שוב ושוב עד שמתקבל מספר חד־ספרתי, ולהחזיר אותו.
רעיון: מספר אי־שלילי הוא חד־ספרתי אם ורק אם הוא קטן מ־ 10. לכן: כל עוד num1 ≥ 10 – מחליפים אותו בסכום הספרות שלו. מספר חד־ספרתי (כמו 5) לא נכנס ללולאה כלל ומוחזר כמו שהוא.
פתרון:
public static int deepSum(int num1)
{
while (num1 >= 10)
{
num1 = digitSum(num1);
}
return num1;
}בדיקה: 5 → 5; 36 → 9; 942378 → 33 → 6; 0 → 0; 999999999 → 81 → 9.
public static int deepSum(int num1)
{
while (num1 >= 10)
{
num1 = digitSum(num1);
}
return num1;
}סעיף א(2)
סופרים זוגי/אי־זוגי של deepSum(i) עבור i = 1..999999 ומחזירים countEven > countOdd. התוצאה: 444444 זוגיים לעומת 555555 אי־זוגיים, ולכן הפעולה מחזירה false.
מה נדרש: לבדוק את הטענה על כל המספרים 1 עד 999999 ולהחזיר true אם יש יותר מספרים עם "סכום ספרות עמוק" זוגי.
רעיון: עוברים בלולאה על כל i מ־ 1 עד 999999, מחשבים deepSum(i) (סעיף א(1)) וסופרים בשני מונים: זוגי ואי־זוגי. בסוף מחזירים את תוצאת ההשוואה countEven > countOdd.
פתרון:
public static boolean isCorrect()
{
int countEven = 0;
int countOdd = 0;
for (int i = 1; i <= 999999; i++)
{
if (deepSum(i) % 2 == 0)
countEven++;
else
countOdd++;
}
return countEven > countOdd;
}בדיקה: מתקבל countEven = 444444, countOdd = 555555, ולכן הפעולה מחזירה false – הטענה אינה נכונה.
הסבר מתמטי (לא נדרש בבחינה): "סכום הספרות העמוק" של מספר חיובי חוזר במחזור 1, 2, ..., 9, 1, 2, ... (הוא שארית החלוקה ב־ 9, כאשר שארית 0 נותנת 9). בתחום 1–999999 יש 999999 = 9 × 111111 מספרים, ולכן כל אחת מהספרות 1–9 מתקבלת בדיוק 111111 פעמים. הזוגיות הן 4 ספרות (2, 4, 6, 8) והאי־זוגיות 5 (1, 3, 5, 7, 9): 4 × 111111 = 444444 לעומת 5 × 111111 = 555555.
טעות נפוצה: לבדוק את הזוגיות של המספר i עצמו או של digitSum(i) במקום של deepSum(i).
public static boolean isCorrect()
{
int countEven = 0;
int countOdd = 0;
for (int i = 1; i <= 999999; i++)
{
if (deepSum(i) % 2 == 0)
countEven++;
else
countOdd++;
}
return countEven > countOdd;
}סעיף ב
return digitExists(num2, deepSum(num1)) && digitExists(num1, deepSum(num2));
מה נדרש: שני תנאים שחייבים להתקיים יחד: deepSum(num1) מופיע כספרה ב־ num2, וגם deepSum(num2) מופיע כספרה ב־ num1.
רעיון: deepSum מחזירה מספר חד־ספרתי, כלומר ספרה, ולכן אפשר להעביר אותה ישירות כפרמטר digit לפעולה הנתונה digitExists. מחברים את שתי הבדיקות ב־ && (וגם).
פתרון:
public static boolean inBoth(int num1, int num2)
{
return digitExists(num2, deepSum(num1)) && digitExists(num1, deepSum(num2));
}בדיקה:
טעות נפוצה: לבדוק רק כיוון אחד, או להחליף בין המספרים (לבדוק אם deepSum(num1) מופיע ב־ num1).
public static boolean inBoth(int num1, int num2)
{
return digitExists(num2, deepSum(num1)) && digitExists(num1, deepSum(num2));
}טבלת התשובות
| שאלה | סעיף | תשובה |
|---|---|---|
| 1 | א | count = 2 (בתאים 2 ו־ 5 ספרת העשרות שווה לאינדקס). |
| ב | למשל arr = {99, 99, 99, 99} (ספרת העשרות 9 אינה שווה לאף אינדקס 0–3). | |
| ג | למשל arr = {5, 12, 23, 34} (ספרות העשרות 0, 1, 2, 3 שוות לאינדקסים). | |
| 2 | - | חיפוש סדרתי – אם password נמצאת, return false; אחרת הזזה ימינה מהסוף (arr[i] = arr[i-1]), arr[0] = password, return true. |
| 3 | - | לולאת while עם מונה שגדל רק כאשר שני האינדקסים המוגרלים שונים (30 החלפות אמיתיות), ואז הדפסת המערך ערך בכל שורה. |
| 4 | א(1) | public TourPackage(int id, int price, int maxTime, int maxData) – השמה לארבע התכונות ו־ extra = 0. |
| א(2) | extra = (minutes > maxTime ? (minutes − maxTime)·1 : 0) + (usage > maxData ? (usage − maxData)·2 : 0). | |
| ב(1) | מונים את החבילות שבהן getExtra() > 0. | |
| ב(2) | int[] ids = new int[calculate(packages)]; ממלאים בעזרת אינדקס נפרד k את ה־ id של כל חבילה עם extra > 0. | |
| 5 | א(1) | return hh·60 + mm > other.getHh()·60 + other.getMm(); |
| א(2) | מחפשים את השיעור המאוחר ביותר (latest) בעזרת isLater ומדפיסים latest.getId(). | |
| ב(1) | סכום getDuration() של כל השיעורים שבהם getId() == id. | |
| ב(2) | מקסימום של sumDuration(arr, arr[i].getId()) על כל i; מחזירים את ה־ id שבו התקבל. | |
| 6 | א(1) | while (num1 >= 10) num1 = digitSum(num1); return num1; |
| א(2) | סופרים זוגי/אי־זוגי של deepSum(i) עבור i = 1..999999 ומחזירים countEven > countOdd. התוצאה: 444444 זוגיים לעומת 555555 אי־זוגיים, ולכן הפעולה מחזירה false. | |
| ב | return digitExists(num2, deepSum(num1)) && digitExists(num1, deepSum(num2)); |