כשהמחשב שוכח להגיד שיש בעיה
הבעיות שהכי מעצבן למצוא הן אלה בהן נראה שהמחשב מרמה אותנו בכוונה. נראה כאילו הוא יודע שהוא לא עושה מה שהתכוונתי אבל בכל זאת הוא יושב שם בצד ומגחך בזמן שאני מנסה להבין למה מה שביקשתי זה לא מה שבאמת התכוונתי.
הסיפור שלי היום הוא על התרגיל הכי קל בינתיים ב Advent Of Code 2024 הוא יום מספר 3. בתרגיל הזה הקלט מורכב מרצפים כאלה:
xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))
ואנחנו צריכים לחשב את סכום המכפלות אבל כשיש פקודת don't()
מפסיקים לספור עד שמוצאים פקודת do()
ואז ממשיכים בחישוב, כלומר בקלט לדוגמה המכפלות הרלוונטיות הן:
mul(2,4), mul(8,5)
והסכום הוא 48. קל? בטח. אפשר למחוק את כל מה שבין don't ל do ולסכום את מה שנשאר. הנה ה Ruby הראשון שניסיתי לכתוב:
str = File.read('input.txt').gsub(/don't\(\).*?(?:do\(\)|$)/, '')
pp str.scan(/mul\((\d{1,3}),(\d{1,3})\)/).map { _1.to_i * _2.to_i }.sum
רואים את הבעיה? קחו רגע לחשוב על זה. לי זה לקח אפילו יותר מרגע.
הסיפור כאן הוא שהקלט יכול להכיל יותר משורה אחת. במצב כזה אולי יהיה don't()
בשורה אחת אבל ה do()
יגיע רק בשורה הבאה. סימן הדולר בביטוי הרגולארי וגם הנקודה יודעים לטפל בקלט של שורה אחת בלבד ולכן הקוד נשבר. המוקש הוא ש gsub לא מתלונן כשהוא מקבל כמה שורות ופשוט מחליף את הטקסט בכל השורות, לדוגמה:
3.3.5 :010 > str = "one\ntwo\nthree\n"
=> "one\ntwo\nthree\n"
3.3.5 :011 > puts str.gsub(/t/, 'T')
one
Two
Three
=> nil
בגלל זה הקוד המקורי שכתבתי כן עושה משהו ואפילו מוחק את כל ה don't
-ים מהקלט, אבל לא עושה את העבודה בצורה מספיק מדויקת בגלל אותה בעיה של מעבר שורות.
פיתרון? נו ברגע שרואים את זה אפילו ChatGPT יכול לתקן - רק צריך לעבור לגירסת השורות-רבות של הביטוי הרגולארי כלומר להוסיף מאפיין m ולהפוך את הדולר, שמסמן סוף שורה, ל \z
שמסמן את סוף המחרוזת. זה הקוד:
str = File.read('input.txt').gsub(/don't\(\).*?(?:do\(\)|\z)/m, '')
pp str.scan(/mul\((\d{1,3}),(\d{1,3})\)/).map { _1.to_i * _2.to_i }.sum
או בגירסת שורה-אחת כמו שאוהבים ברובי:
pp File.read('input.txt')
.gsub(/don't\(\).*?(?:do\(\)|\z)/m, '')
.scan(/mul\((\d{1,3}),(\d{1,3})\)/)
.map { _1.to_i * _2.to_i }
.sum