10 minute read

⚠ שימו לב - העמקה



Image Description

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

הפוסט מסתיים ברשימת נקודות עיקריות - להבין אותן זה מספיק.

עד כה, הכרנו טיפוסי נתונים למספרים שלמים - בפרט, int ו-long (יש גם עוד שניים - short ו-byte - שלא מעניינים אותנו), את טיפוס הנתונים double למספרים עשרוניים (יש גם עוד אחד - float - שלא מעניין אותנו), את טיפוס הנתונים char לתווי טקסט (וראינו שמאחורי הקלעים הוא למעשה מספר שלם). הטיפוסים האלה מתחילים באות קטנה והם מה שנקרא ב-Java טיפוסים פרימיטייבים, יש שמונה כאלה, ולמי שסופר, במשפטים הקודמים מניתי שבעה כאלה (ארבעה שלמדנו ושלושה שלא מעניינים אותנו). נותר לנו אחד, והוא boolean.

למשתנה מסוג boolean יש רק שני ערכים שהוא יכול להיות בהם - true ו-false. התרגום המילולי של המילה true הוא “נכון” ושל false הוא “שגוי” או “לא נכון”, אבל לרוב נהוג לתרגם אותם “אמת” ו”שקר”. על כן, ערך בוליאני מייצג תשובה לשאלת “כן” ו”לא”. אנשים ששמעו דבר או שניים על איך עובדים מחשב ברמת החומרה (בפרט, תלמידי מגמות כמו מכטרוניקה ואלקטרוניקה) אולי מכירים את זה בתור “ביט”, יחידת הזכרון הקטנה ביותר של המחשב, ואולי מכירים את true ו-false יותר בתור 1 ו-0. אז ב-Java במקום 1 ו-0 אנחנו קוראים להם בשמות המילוליים האלה. המעבר במונחים אולי יהיה מוזר למי שכבר התרגל לחשוב על שערים לוגיים ועל 0 ו-1 בתור הדרך לומר “כן” ו”לא”, אבל מתרגלים. עבור Java, ל-0 ול-1 יש צורה של int שהוא סוג של מספר שלם, שלא מתאים לצורה של boolean.

למי שתוהה, המילה boolean נגזרת משמו של המתמטיקאי ג’ורג’ בול (George Boole), שעסק בדברים האלה ברמת התאוריה לפני שהומצא המחשב, ובכך נחשב לאחד מאבות המחשוב ומדעי המחשב. בהתאם, שמו הפך לשם של טיפוס הנתונים הזה לא רק ב-Java אלא גם בשפות תכנות רבות נוספות - כמעט בכל שפה שיש בה קונספט כזה, הקונספט נקרא boolean או bool. המילה “בוליאני” גם הפכה לביטוי שגור בקרב המתכנתים בחיי היומיום כשהם מדברים אחד עם השני, שמשעותו “דבר שיכול להיות אופציה א’ או אופציה ב’, וזהו, אלה כל האופציות ואי אפשר גם וגם” (ובכך נרדף למילה “בינארי”, שגם מגיעה מעולם התכנות ושגורה בפי מתכנתים, אך מוכרת יותר גם בקרב הציבור הכללי).

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

כאן אפשר להריץ קוד!

כאן תוכלו להריץ קוד ממש בדפדפן שלכן. מומלץ להשתמש בסביבת עבודה על המחשב במקום (IntelliJ IDEA, Visual Studio Code ודומיהם) במידה ויש - האופציה הזו עדיפה רק למי שאין לה סביבת עבודה מותקנת.

השוואות

אנחנו יכולים להשתמש במספרים ובמשתנים שיש בהם מספרים כדי להגדיר ביטוי שהתוצאה שלהם היא מספר, למשל אם x ו-y הם משתנים מסוג int, אז הביטוי הבא הוא דבר שכאשר Java תגיע אליו בקוד, היא תחשב אותו בהינתן הערכים הנוכחיים שיש במשתנים האלה ותקבל תוצאה כלשהי, שהצורה שלה הוא מספר מסוג int:

a * (b + 4)

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

             
ב-Java x < y x <= y x > y x >= y x == y x != y
בסימון אלגברי $x<y$ $x \leq y$ $x > y$ $x \geq y$ $x = y$ $x \neq y$
במילים x קטן מ-y x קטן מ-y או שווה לו x גדול מ-y x גדול מ-y או שווה לו x שווה ל-y x שונה מ-y

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

אם אתן מחפשות את המקשים < ו-> במקלדת שלכם, אם היא מקלדת סטנדרטית (QWERTY), הם נמצאים על המקשים שבעברית הם ת ו-ץ ובאנגלית הם פסיק , ונקודה . - יש להחזיק את מקש ה-Shift, ולהקיש על המקש הרלוונטי.

מוזמנות לקרוא, להבין, להריץ ולשחק עם הקוד הבא:

כאן אפשר להריץ קוד!

כאן תוכלו להריץ קוד ממש בדפדפן שלכן. מומלץ להשתמש בסביבת עבודה על המחשב במקום (IntelliJ IDEA, Visual Studio Code ודומיהם) במידה ויש - האופציה הזו עדיפה רק למי שאין לה סביבת עבודה מותקנת.

פעולות בין בוליאנים

יש שלוש פעולות עיקריות בין בוליאנים שמעניינות אותנו, והן נקראות and (וגם), or (או), ו-not (לא). גם הקונספט הזה עשוי להיות מוכר למי שלומדת מגמת מכנטרוניקה, אלקטורניקה ודומיהן - בהקשרים האלה, של חומרה, הם מכונים “שערים” או “שערים לוגיים”.

and (וגם) - &&

הפעולה הזו היא פעולה בין שני בוליאנים ותוצאתה בוליאני. היא נכתבת x && y ותוצאתה תהיה true אם גם x וגם y הם true, ואחרת, התוצאה תהיה false. בלוגיקה מציגים את זה גם בטבלה, שנקראת גם טבלת אמת:

x && y y x
true true true
false false true
false true false
false false false

or (או) - ||

הפעולה הזו היא פעולה בין שני בוליאנים ותוצאתה בוליאני. היא נכתבת x || y ותוצאתה תהיה true אם לפחות אחד משני הבוליאנים - x ו-y - הם true; ורק במקרה בו שניהם false, תוצאתה תהיה false. הנה טבלת האמת:

x || y y x
true true true
true false true
true true false
false false false

שימו לב - בלשון הדיבור, לפעמים “א’ או ב’” משמעו “או א’ או ב’, אך לא גם וגם”, למשל “עם ההמבורגר, את יכולה לקבל צ’יפס או פירה” - לרוב הם לא מתכוונים שאת יכולה לבחור בשניהם. זה לא המקרה כאן - אם גם x וגם y הם true, אז גם x || y יהיה true.

לסקרניות, נגלה שיש ב-Java פעולה שהמשמעות שלה היא “או x או y אך לא גם וגם” - שמה xor והיא משתמשת בסימן ^, אבל אני לא חושב ששווה לרשום או לזכור את זה - אני לא חושב שיהיה לכן הרבה שימוש לזה (בכלל, להרבה מתכנתים אין כמעט סיבות להשתמש בזה - בכל שנותיי כמתכנת ולא רק ב-FRC, אני חושב שאם מחלקים את מספר הפעמים שהייתי צריך להשתמש ב-xor בקוד בכמות הזמן שאני מתכנת, זה בטח xor אחד לשלושה חודשים, וגם זו עשויה להיות הערכת יתר).

not (לא) - !

הפעולה האחרונה מבין השלוש שמעניינות אותנו היא פעולה שפועלת על בוליאני אחד ותוצאתה בוליאני. היא נכתבת !x (כלומר, סימן קריאה לפני הבוליאני שעליו היא פועלת), למשל:

boolean x = true;
boolean y = !x;

התוצאה של הפעולה היא הבוליאני ההפוך מהבוליאני שעליו היא פועלת, כלומר true הופך ל-false ו-false ל-true. הנה טבלת האמת:

!x x
false true
true false

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

כאן אפשר להריץ קוד!

כאן תוכלו להריץ קוד ממש בדפדפן שלכן. מומלץ להשתמש בסביבת עבודה על המחשב במקום (IntelliJ IDEA, Visual Studio Code ודומיהם) במידה ויש - האופציה הזו עדיפה רק למי שאין לה סביבת עבודה מותקנת.

סדר פעולות

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

  1. סוגריים
  2. חזקה
  3. כפל וחילוק
  4. חיבור וחיסור

כך גם לבוליאנים יש סדר פעולות, והוא:

  1. סוגריים
  2. לא (!)
  3. וגם (&&)
  4. או (||)

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

יותר בפירוט - איך עובד סדר הפעולות

בואו נסתכל בביטוי החשבוני הבא: \(1\;+\;2\cdot 3\;+\;2\cdot 5^2\; +\;\ 3\cdot(1+2\cdot 3)^{(1+3)}\;\) לא לדאוג, אנחנו לא הולכים לחשב את זה ידנית (או בכלל). כשאנחנו נתקלים בביטוי חשבוני שיש בו יותר מפעולה אחת, אנחנו לא תמיד מחשבים אותו מימין לשמאל - למשל, שלושת הדברים הראשונים שאנחנו רואים בביטוי הזה הם $1$, סימן חיבור, ו-$2$, אבל זה לא אומר שנתחיל מלחבר 1 ו-2.

הכלל שאומר שמתחילים בסוגריים אומר שקודם כל מחשבים את $1+3$ ואת $1+2\cdot 3$. את $1+3$ זה מיידי - $4$. על $1+2\cdot 3$ צריך שוב להפעיל את הכללים האלה - סוגריים וחזקה אין לנו, אבל יש כפל וחיבור, וכפל קודם לחיבור, אז מחשבים קודם את $2\cdot 3$, מקבלים $6$, ואז $1+6$ יוצא $7$ וסיימנו לחשב את הסוגריים בבסיס החזקה. לאחר מכן ממשיכים לחזקות, וכו’ וכו’. בכל פעם שאנחנו מחשבים תת-ביטוי, אנחנו מחליפים את המקום שבו הוא כתוב בביטוי, בתוצאה שלו, כך שאחרי מה שחישבנו כאן, אנחנו נשארים עם הביטוי הבא: \(1\;+\;2\cdot 3\;+\;2\cdot 5^2\; +\;3\cdot7^4\) לאחר מכן, כשאין יותר סוגריים, אנחנו ממשיכים לחזקות, וכן הלאה.

עכשיו בואו נראה את אותו דבר לגבי ביטוי בוליאני:

false || true && false || true && !false ||
    true && !(!false && false)

סדר הפעולות בבוליאניים הוא כאמור:

  1. סוגריים
  2. לא (!)
  3. וגם (&&)
  4. או (||)

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

!false && false

ובתוכו, נתחיל מחישוב ה”לא” (!), ונחליף את הביטוי !false בתוצאתו - true. לאחר מכן נחשב את true && false ונקבל false, וכן הלאה.

כמה זה חשוב בכלל

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

boolean endAutonomous = passedAutoLine && (steppedOnRamp && isBalanced || !steppedOnRamp && timeOut);

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

  • האם עברנו את קו האוטונומי
  • האם דרכנו על הרמפה (לאו דווקא כרגע אלא מתישהו בזמן האוטונומי הזה)
  • האם הרובוט מאוזן (כלומר, או שהרמפה מאוזנת או שהוא לא על הרמפה)
  • האם עברה כמות זמן מסוימת (לא כתוב כמה, בואו נגיד 12 שניות)

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

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

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

boolean endAutonomous = passedAutoLine && (
    steppedOnRamp && isBalanced ||
    !steppedOnRamp && timeOut
);

זה מדגיש חלוקה טבעית בין ה”חלקים” של הביטוי הזה. אבל מעבר לזה, אפשר גם לחלץ תתי-ביטויים למשתנים בעלי שמות משמעותיים, וזה דבר שאפשר לעשות בהרבה מקרים וזה הרבה פעמים דבר טוב לעשות שהופך את הקוד לקריא יותר (זה נקרא “חילוץ משתנה” - “extract variable”):

boolean steppedAndIsBalanced = steppedOnRamp && isBalanced;
boolean didntStepAndIsTimedOut = !steppedOnRamp && timeOut;
boolean endAutonomous = passedAutoLine && (steppedAndIsBalanced || didntStepAndIsTimedOut);

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

boolean endAutonomous = passedAutoLine && (
    (steppedOnRamp && isBalanced) ||
    (!(steppedOnRamp) && timeOut)
);

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

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

נקודות עיקריות

  • בוליאני - boolean - הוא טיפוס נתונים שמייצג תשובה לשאלת כן/לא
  • ערכיו הם true (אמת - “כן”) ו-false (שקר - “לא”).
  • בין השאר, ניתן לקבל בוליאנים בתור תוצאה של השוואות בין ביטויים מספריים, למשל x < 3 או y == 0
  • בין בוליאנים יש שלוש פעולות עיקריות - וגם (&&), או (||), לא (!)
  • יש ביניהן סדר פעולות ומתרגלים אליו

Comments