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

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

סנאי שמח ורגוע נושא לפטופ, לפי DALL-E-2
סנאי שמח ורגוע נושא לפטופ, לפי DALL-E-2

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

"נתונה מחרוזת (באורך 2 לפחות) שהתווים בה הם בתחום a-z. חובה להסיר תו אחד, ואחד בלבד, מהמחרוזת. האם ניתן לעשות זאת כך שמספר המופעים של כל ערך בתווים הנותרים יהיה זהה?"

נניח שקיבלנו את המחרוזת "xxyz". התשובה היא "כן", כי אם נבחר להסיר את התו הראשון או השני – משמאל כמובן – נקבל מחרוזת שכל אחד מערכי התווים בה (x, y, z) מופיע בדיוק פעם אחת. לעומת זאת, עבור המחרוזת "qqww" התשובה היא "לא", כי לא משנה איזה תו נסיר, יישאר אחד כמוהו ושניים עם הערך השני.

רוצים לנסות לפתור בלי רמזים? זה הזמן…

סנאי שמח כותב קוד תוכנה על לפטופ, לפי DALL-E-2
סנאי שמח כותב קוד תוכנה על לפטופ, לפי DALL-E-2

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

רוצים לפתור עכשיו? שאר הפוסט יחכה…

סנאי מתוסכל כותב קוד תוכנה על לפטופ, לפי DALL-E-2
סנאי מתוסכל כותב קוד תוכנה על לפטופ, לפי DALL-E-2

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

סנאי נסער וזועם שובר לפטופ, לפי DALL-E-2
סנאי נסער וזועם שובר לפטופ, לפי DALL-E-2

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

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

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

סנאי מותש יושב ליד לפטופ סגור, סגנון מצויר, לפי DALL-E-2
סנאי מותש יושב ליד לפטופ סגור, סגנון מצויר, לפי DALL-E-2

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

להרשמה
הודע לי על
7 תגובות
מהכי חדשה
מהכי ישנה לפי הצבעות
Inline Feedbacks
הראה את כל התגובות

נאתחל dictionary ריק – dic.
נעבור על מערך המופעים:
עבור כל תו:
אם כבר קיים key ב-dic שערכו כמספר המופעים אזי נבצע: dic[key]++
ואם לא נוסיף ל-dic ערך חדש: key שווה למספר המופעים, value = 1
נבדוק את dic:
אם יש 3 ערכים ומעלה אזי נחזיר false
אם יש ערך אחד בלבד:
אם key = 1 נחזיר true
אחרת נחזיר false
אם יש 2 ערכים:
אם ה-value של אחד ה-keys הוא 1:
אם ה-key עבורו dic[key] = 1 הוא 1 נחזיר true
אחרת אם ה-key הנ"ל גדול ב-1 מה-key השני נחזיר true
אחרת נחזיר false

הפיתרון שלי… [מזכיר קצת מיון דליים]
O(2n)
עברים על המערך הקלט וסופרים כל אות למערך המופיעם.
עוברים על מערך המופיעם אם יש הפרש של 1 בין מינימום למקסימום אז זה אפשרי [ לא ביקשו לעשות אלא רק לדעת אם זה אפשרי]

כפי שאני מכיר אותך [לטובה] אשמח למצוא את הטעות אצלי.

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

האם שאלת את Chat GPT ?