16.1 לוח שחמט בגודל N × N: חידת המלכות הכללית
איך אנחנו מרחיבים את פתרון חידת 8 המלכות בשני כיוונים (לוח דו-ממדי כללי N×N ולוח תלת-ממדי N×N×N), מהי ההגדרה המדויקת של חידת המלכות הכללית, ואיפה מסתתרים הערכים הקשיחים (Hard-coded) של הספרה 8 שעלינו להחליף בפרמטר.
מודל מנטלי ועיקרון מנחה: משחררים את הקוד מכבלי הקבוע הקשיח 8 אל פרמטר גמיש N
בפרק הקודם בנינו מנגנון משומן של שרשרת מלכות שיודעות לתקשר זו עם זו, אבל "רתכנו" את המספר 8 לתוך פס הייצור של הלוח ולתוך תקרת התנועה של כל מלכה. הכללה אמיתית פירושה להפוך את גודל הלוח לפרמטר דינמי!
הגדרה 16.1 (חידת המלכות הכללית): יש להציב N מלכות על גבי לוח שחמט בגודל N × N, כך שאף מלכה אינה מאיימת על אף מלכה אחרת (אין שתי מלכות באותה שורה, באותה עמודה או באותו אלכסון).
כדי להכליל את הפתרון לכל N, עלינו לאתר את שתי הנקודות שבהן קובע המספר 8 באופן קשיח (Hard-coded): לולאת יצירת המלכות ב-Board>>initialize ובדיקת גבול הלוח ב-Queen>>advance.
1. שני כיווני ההרחבה שלנו בפרק 16 והגדרת חידת המלכות הכללית
מעבר מלוח קבוע של 8×8 אל לוח דו-ממדי כללי N×N ואל לוח מרחבי תלת-ממדי N×N×N
שלום לך! אחרי שבפרק 15 בנינו יחד את הפתרון המבוזר והאלגנטי לחידת 8 המלכות (באמצעות שרשרת של מלכות ששולחות הודעות זו לזו ומבצעות חיפוש עם נסיגה – Backtracking), בפרק 16 אנחנו לוקחים את הפתרון הזה ומרחיבים אותו בשני כיוונים מרתקים:
- כיוון ראשון (סעיף 16.1): הכללה ללוח דו-ממדי כללי בגודל
N × N. - כיוון שני (סעיף 16.2): הכללה ללוח שחמט תלת-ממדי (מרחבי) בגודל
N × N × N.
נתחיל בכיוון הראשון שלנו. ההכללה של חידת 8 המלכות ללוח דו-ממדי כללי מנוסחת כך:
הצב N מלכות על לוח שחמט N × N כך שאף מלכה אינה מאיימת על מלכה אחרת.
🔍 במילים אחרות – פירוק ההגדרה לתנאים גיאומטריים על הלוח:
יש להציב N מלכות על גבי לוח שחמט בגודל N × N, כך שלא תהיינה שתי מלכות:
2. האתגר התכנותי בהכללה: איפה קובע המספר 8 באופן קשיח (Hard-coded)?
איתור שתי נקודות הקיבוע בפתרון של פרק 15 ב-Board>>initialize וב-Queen>>advance
כשאנחנו חוזרים אל הקוד שכתבנו בפרק 15 עבור 8 מלכות ושואלים את עצמנו: "מה בעצם מונע מהקוד שלנו לפתור לוח בגודל 5×5 או 10×10?", אנחנו מגלים שבפתרון של פרק 15 קובעו ערכים קשיחים (Hard-coded) של הספרה 8 בשני מקומות קריטיים:
בלולאת יצירת המלכות ב-Board>>initialize
הלוח יצר תמיד בדיוק 8 מלכות באמצעות הלולאה הקשיחה (1 to: 8) do: [...].
lastQueen := QueenTerminator new.
(1 to: 8) do: [:i |
lastQueen := Queen new setColumn: i neighbor: lastQueen]
בבדיקת גבול הלוח ב-Queen>>advance
כל מלכה בדקה האם הגיעה לשורה האחרונה באמצעות התנאי הקשיח row = 8 ifTrue: [...].
row = 8 ifTrue: [
neighbor nextSolution ifFalse: [^ false].
row := 0].
row := row + 1.
^ true
לכן, כדי להכליל את הפתרון לכל N, יש לאפשר ללוח לקבל את גודלו כפרמטר, ולהבטיח שכל מלכה על הלוח תדע מהו גבול השורות של הלוח שבו היא נמצאת!
משימת מימוש בעקבות ההגדרה:
ממשו פתרון בסמולטוק (Smalltalk) לחידת N המלכות.
איום בשורה, בעמודה ובשני האלכסונים על לוח N × N והסרת הקבוע הקשיח 8
מיני-סימולציה 16.1: סייר ההכללה ל-N × N ותחומי האיום של מלכה
ארגז החול של N המלכות: התאמת גודל הלוח N × N, הצבת מלכות וזיהוי קווי איום
בחר את גודל הלוח N (מ-1 ועד 8), לחץ על משבצות בלוח כדי להציב או להסיר מלכות, או לחץ על "מצא פתרון אוטומטי (solve)". המערכת תצבע באדום עדין את כל תחומי האיום (שורה, עמודה ואלכסונים) ותתריע מיד אם שתי מלכות מאיימות זו על זו לפי הגדרה 16.1!
board solve.
הגשר הנרטיבי: רגע, איך שומרים את הפרמטר N כך שגם הלוח וגם כל המלכות יכירו אותו?
ראינו שגם המחלקה Board (בלולאת האתחול) וגם המחלקה Queen (במתודה advance) זקוקות למספר N.
סטודנטים רבים מתפתים לפתרון שנראה קצר ונוח במבט ראשון: להגדיר את N כמשתנה מחלקה (classVariableNames: 'N') ב-Board ולחשוף אותו דרך מתודת המחלקה Board N!
מה קורה כשמריצים את ההצעה הזו על שני לוחות במקביל ב-Workspace? בלשונית הבאה ננתח יחד את מלכודת הבחינה הקלאסית הזו!
מקרה בוחן ושאלת בחינה מרכזית: מלכודת משתנה המחלקה N בהכללת הלוח
מה מתרחש כאשר מגדירים את גודל הלוח N כמשתנה מחלקה משותף (classVariableNames: 'N'), מדוע הרצת board1 solve על לוח בגודל 5×5 מדפיסה פתאום No solution.!, וכיצד מתכננים את הפתרון התקני מונחה העצמים.
מודל מנטלי ועיקרון מנחה: תכונה שמשתנה ממופע למופע אסור שתישמר כמשתנה מחלקה משותף!
להגדיר את גודל הלוח N כמשתנה מחלקה זה כמו לתלות שלט אחד ויחיד בכניסה לבניין שכתוב עליו "מספר החדרים בכל דירה". ברגע שדייר בדירה של חדר אחד מעדכן את השלט ל-1, גם דירת ה-5 חדרים בטוחה שיש לה רק חדר אחד!
משתנה מחלקה (classVariableNames) הוא עותק יחיד המשותף לכל המופעים של אותה המחלקה! כל אתחול של לוח חדש דורס את הערך של הלוחות הקודמים שנוצרו לפניו.
כדי לאפשר קיומם של מספר לוחות בגדלים שונים בו-זמנית, חל איסור להשתמש במשתנה מחלקה: יש להגדיר את boardSize (או n) כמשתנה מופע ולהעבירו לכל מלכה כמשתנה מופע פרטי משלה (או כקישור למופע הלוח שלה).
1. ההצעה המפתה להכללת הלוח ל-N × N באמצעות משתנה מחלקה
בחינת הקוד המוצע שבו N מוגדר ב-classVariableNames של המחלקה Board
בוא נבחן יחד הצעה נפוצה מאוד להכללת בעיית המלכות ללוח בגודל N × N.
מי שכתב את ההצעה הזו אמר לעצמו: "כדי שלא אצטרך לשנות את חתימת האתחול של כל מלכה ולהעביר לה את גודל הלוח, אשמור את N כמשתנה מחלקה ב-Board, ואכתוב מתודת מחלקה Board class >> N שכל מלכה תוכל לקרוא לה מתוך advance!"
הנה הקוד המוצע במלואו — קרא אותו בעיון:
Object subclass: #Board
instanceVariableNames: 'lastQueen'
classVariableNames: 'N'
poolDictionaries: ''
category: 'OOD-NQueens'
Board >> initialize: queensNum
N := queensNum.
lastQueen := QueenTerminator new.
(1 to: N) do: [:i |
lastQueen := Queen new setColumn: i neighbor: lastQueen]
Board class >> N
^ N
Queen >> advance
row = Board N
ifTrue: [
neighbor nextSolution ifFalse: [^ false].
row := 0].
row := row + 1.
^ true
2. ניתוח הכשל והתוצאה המפתיעה בחלון ה-Workspace
למה לוח בגודל 5×5 (שיש לו 10 פתרונות חוקיים!) מחזיר פתאום No solution.!?
אם נבדוק את הקוד המוצע על לוח בודד בלבד, נדמה שהכל עובד מצוין. אבל תכנות מונחה עצמים נבחן בעולם שבו מספר מופעים חיים ופועלים במקביל! בוא נבחן מה מתרחש כאשר מריצים את חמש השורות הבאות בחלון ה-Workspace:
board1 := Board new.
board2 := Board new.
board1 initialize: 5.
board2 initialize: 1.
board1 solve.
למרות שללוח בגודל 5×5 יש פתרונות חוקיים רבים, המערכת מדפיסה No solution.! — מדוע?!
בוא נפרק יחד, צעד אחר צעד, את ארבעת שלבי הכשל המבני שגרמו לתוצאה הזו:
המשתנה N הוגדר כמשתנה מחלקה (classVariableNames: 'N'). כפי שלמדנו בעקרונות השפה, משתנה מחלקה הוא עותק יחיד המשותף לכל המופעים של אותה המחלקה.
כאשר נוצר ואותחל הלוח הראשון (board1 initialize: 5), ערכו של משתנה המחלקה N נקבע ל-5, ונוצרה ב-board1 שרשרת של 5 מלכות.
מיד לאחר מכן, כאשר אותחל הלוח השני (board2 initialize: 1), הוא ניגש לאותו משתנה מחלקה משותף N, דרס את ערכו הקודם ושינה אותו מ-5 ל-1!
כאשר מופעלת המתודה board1 solve, 5 המלכות של הלוח הראשון פונות במתודה advance אל Board N ומקבלות כולן את הערך המעודכן: 1! לכן ברגע שמלכה מגיעה לשורה 1 היא מניחה שהגיעה לקצה הלוח ונסוגה — ואי אפשר להציב 5 מלכות בשורה אחת בלבד!
3. הפתרון התקני מונחה העצמים: משתני מופע פרטיים לכל לוח ולכל מלכה
איך מאפשרים למספר לוחות בגדלים שונים להתקיים ולפעול בו-זמנית ללא התנגשות?
✅ כללי הפתרון התקני מונחה העצמים:
- כדי לאפשר קיומם של מספר לוחות בגדלים שונים בו-זמנית, חל איסור להשתמש במשתנה מחלקה משותף עבור גודל הלוח.
- במקום זאת, יש להגדיר את
boardSize(אוn) כמשתנה מופע (instanceVariableNames) של המחלקהBoard. - בעת יצירת המלכות, גודל הלוח מועבר לכל מלכה כמשתנה מופע פרטי משלה (למשל במתודת אתחול מורחבת
setColumn:neighbor:boardSize:), או לחלופין כל מלכה מחזיקה קישור למופע הלוח שלה.
דריסת משתנה המחלקה המשותף N מול עצמאות מוחלטת במשתני מופע (boardSize)
מיני-סימולציה 16.2: מעקב שורה-אחר-שורה אחרי ריצת ה-Workspace
מעבדת הבחינה החיה: השוואת משתנה מחלקה משותף (N) מול משתנה מופע פרטי (boardSize)
החלף בין המימוש השגוי (משתנה מחלקה N) לבין המימוש התקני מונחה העצמים (משתנה מופע boardSize), בחר גדלים עבור board1 ועבור board2, והרץ את board1 solve כדי לראות את ההבדל בזמן אמת:
🔍 מצב המופעים בזיכרון ופלט ה-Transcript בעת הרצת board1 solve:
הגשר הנרטיבי: פתרנו במלואה את ההכללה הדו-ממדית ל-N × N – עכשיו בוא נמריא אל הממד השלישי!
אחרי שהבנו איך להכליל את הלוח הדו-ממדי לכל N × N באמצעות משתני מופע תקניים, אנחנו מוכנים לכיוון ההכללה השני והמרתק של פרק 16: לוח שחמט תלת-ממדי (מרחבי) בגודל N × N × N!
כמה מלכות עלינו להציב במרחב תלת-ממדי כזה? איך מיוצגות הקואורדינטות של שתי מלכות Q1(i,j,k) ו-Q2(row,col,lev)? ואיך 7 שורות קוד קצרות בסמולטוק (איור 16.1) מזהות כל איום מרחבי באחד המישורים או האלכסונים? בוא נעבור ללשונית 3!
16.2 לוח שחמט תלת-ממדי (מרחבי) ובדיקת איום מרחבי בסמולטוק
הצבת N² מלכות על לוח שחמט תלת-ממדי בגודל N × N × N, ייצוג מלכות באמצעות שלשה סדורה של שורה, עמודה ומפלס Q1(i,j,k) ו-Q2(row,col,lev), ופירוק מעמיק של קוד הסמולטוק באיור 16.1.
מודל מנטלי ועיקרון מנחה: משלושה מרחקים מוחלטים (dx, dy, dz) לארבעת תנאי האיום המרחביים
דמיין בניין קומות של לוחות שחמט: N מפלסים שמונחים זה מעל זה ויוצרים קובייה של N × N × N. מיקומה של כל מלכה נקבע על ידי שורה, עמודה ומפלס (קומה): (row, col, lev).
הגדרה 16.2 (חידת המלכות המרחבית): הצב N² מלכות על לוח שחמט תלת-ממדי N × N × N כך שבכל אחד מהמישורים אף מלכה אינה מאיימת על מלכה אחרת.
חישוב הערך המוחלט (abs) של ההפרשים בשלושת הצירים (dx, dy, dz) מתרגם את בדיקת האיום הגיאומטרית לארבע השוואות אלגבריות פשוטות של איפוס צירים או שוויון ביניהם!
1. הגדרה 16.2: חידת המלכות המרחבית בלוח N × N × N
כמה מלכות מציבים במרחב תלת-ממדי ואיך מיוצג מיקומה של כל מלכה?
הכללה פחות ידועה — אך מרתקת במיוחד — של חידת המלכות היא החידה התלת-ממדית (המרחבית), המנוסחת כך:
הצב N² מלכות על לוח שחמט תלת ממדי N × N × N כך שבכל אחד מהמישורים אף מלכה אינה מאיימת על מלכה אחרת.
📐 במילים אחרות – ייצוג קואורדינטות של מלכה במרחב:
יש להציב N² מלכות על גבי לוח שחמט תלת-ממדי בגודל N × N × N כך שכל שתי מלכות Q1, Q2 אינן מאיימות זו על זו. כל מלכה ממוקמת במרחב באמצעות שלשה סדורה של שורה, עמודה ומפלס:
Q1(i, j, k)
Q2(row, col, lev)
2. איור 16.1: הקוד בסמולטוק לבדיקת איום מרחבי ופירוק הלוגיקה שלו
איך מחשבים את המרחקים המוחלטים dx, dy, dz ומה בודק כל אחד מארבעת תנאי ה-ifTrue?
הקוד בסמולטוק שמזהה איום באחד המישורים או האלכסונים המרחביים בין שתי מלכות מוצג באיור 16.1. בוא נקרא תחילה את הקוד המקורי במדויק:
dx := (row - i) abs. dy := (col - j) abs. dz := (lev - k) abs. ((dx = 0) and: [ dy = 0 ]) ifTrue: [ ^ true ]. ((dx = 0) and: [ dy = dz ]) ifTrue: [ ^ true ]. ((dx = dz) and: [ dy = 0 ]) ifTrue: [ ^ true ]. ((dx = dy) and: [ dx = dz ]) ifTrue: [ ^ true ]
🧠 איך זה עובד מתחת לפני השטח? פירוק מלא של איור 16.1:
כדי לקבוע האם שתי מלכות Q1(i,j,k) ו-Q2(row,col,lev) מאיימות זו על זו באחד המישורים או האלכסונים המרחביים, הקוד פועל בשני שלבים:
abs:
dx := (row - i) abs.— המרחק המוחלט בציר השורות (ביןrowל-i).dy := (col - j) abs.— המרחק המוחלט בציר העמודות (ביןcolל-j).dz := (lev - k) abs.— המרחק המוחלט בציר המפלסים/הקומות (ביןlevל-k).
^ true אם מתקיים איום):
1. ((dx = 0) and: [ dy = 0 ]) ifTrue: [ ^ true ].
שתי המלכות חולקות את אותה שורה ואותה עמודה — כלומר הן נמצאות זו מעל זו באותו עמוד אנכי לאורך ציר המפלסים!
2. ((dx = 0) and: [ dy = dz ]) ifTrue: [ ^ true ].
המלכות נמצאות באותה שורה (dx = 0), ונמצאות על אלכסון במישור עמודה-מפלס (Y-Z) שבו המרחק בעמודות שווה למרחק במפלסים (dy = dz).
3. ((dx = dz) and: [ dy = 0 ]) ifTrue: [ ^ true ].
המלכות נמצאות באותה עמודה (dy = 0), ונמצאות על אלכסון במישור שורה-מפלס (X-Z) שבו המרחק בשורות שווה למרחק במפלסים (dx = dz).
4. ((dx = dy) and: [ dx = dz ]) ifTrue: [ ^ true ]
המלכות נמצאות על אלכסון מרחבי תלת-ממדי מלא, שבו כל שלושת ההפרשים שווים זה לזה (dx = dy = dz)!
משימת מימוש לחידה התלת-ממדית:
ממשו פתרון בסמולטוק (Smalltalk) לחידת המלכות התלת־ממדית.
קואורדינטות Q1(i,j,k) ו-Q2(row,col,lev) במרחב וארבעת וקטורי האיום המרחביים
מיני-סימולציה 16.3: חוקר 4 תנאי האיום המרחביים באיור 16.1
מחשבון וסימולטור מעקב ביצוע לבדיקת איום מרחבי (איור 16.1)
הזן קואורדינטות עבור המלכה הראשונה Q1(i, j, k) ועבור המלכה השנייה Q2(row, col, lev) (או בחר אחד מתרחישי הבדיקה המוכנים). הסימולטור יחשב בזמן אמת את הערכים המוחלטים של dx, dy, dz ויצבע בירוק זוהר את שורת התנאי הראשונה באיור 16.1 שהחזירה ^ true ועצרה את הריצה!
Q1(i, j, k):Q2(row, col, lev):הגשר הנרטיבי: סיימנו את סעיפים 16.1 ו-16.2 – בוא נסכם את פרק 16 ואת חלק VI כולו!
ראינו כיצד מכלילים את חידת המלכות הן ללוח דו-ממדי N × N (תוך הימנעות ממלכודת משתנה המחלקה N) והן ללוח תלת-ממדי מרחבי N × N × N עם N² מלכות ובדיקת האיום המרחבי באיור 16.1.
בלשונית הבאה נרכז את כל ההגדרות, הנוסחאות והקוד בכרטיסיות זיכרון תלת-ממדיות (3D Flashcards) ונתכונן לפרק 17 (הסביבה הגרפית Morphic)!
מילון מושגים אינטראקטיבי וסיכום פרק 16 (הכללה של חידת המלכות)
חיבור שני כיווני ההכללה שלמדנו בפרק — מלוח דו-ממדי N × N (והפתרון התקני מונחה העצמים ללא משתנה מחלקה) ועד ללוח תלת-ממדי מרחבי N × N × N וקוד בדיקת האיום באיור 16.1.
מודל מנטלי ועיקרון מנחה: סיכום שני צירי ההכללה של פרק 16
הגדרה 16.1 מציבה N מלכות על לוח N × N ללא איום בשורה, בעמודה או באלכסון, תוך החלפת הקבוע הקשיח 8 בפרמטר ב-Board>>initialize וב-Queen>>advance.
אסור לשמור את גודל הלוח כמשתנה מחלקה N (שנדרס בין board1 ל-board2 וגורם ל-No solution.!), אלא רק כמשתנה מופע פרטי (boardSize).
הגדרה 16.2 מציבה N² מלכות בקואורדינטות Q1(i,j,k) ו-Q2(row,col,lev), ואיור 16.1 בודק איום מרחבי באמצעות dx, dy, dz בערך מוחלט (abs) ו-4 תנאי ifTrue.
מהכללת הלוח הדו-ממדי (16.1) ומלכודת משתנה המחלקה ועד להכללה המרחבית (16.2)
מיני-סימולציה 16.4: בוחן אבחנה מהיר – בדוק את שליטתך בפרק 16!
כרטיסיות זיכרון תלת-ממדיות (3D Flashcards) לכל ההגדרות והקוד של פרק 16
לחץ על כל כרטיסייה כדי להפוך אותה ולבחון את עצמך על כל 9 מושגי המפתח של פרק 16:
נחשפו: 0 / 9בכך סיימנו במלואו את פרק 16 ואת חלק VI (סימולציה)! מה מחכה לנו בפרק 17?
בפרק 16 השלמנו את ההכללה של חידת המלכות הן למימד הדו-ממדי הכללי N × N (תוך הבנה עמוקה של ההבדל בין משתנה מחלקה משותף למשתנה מופע פרטי) והן למימד המרחבי התלת-ממדי N × N × N עם בדיקת האיום המרחבי באיור 16.1.
stepping וה-Threads!