פיתרון Advent Of Code יום 1 בעזרת יוניקס
כמו כל שנה בדצמבר החידות של Advent Of Code התחילו להתפרסם היום. אומנם עדיין לא החלטתי באיזו שפה להשתמש או אפילו אם אני מתכנן לפתור את כולן, אבל החידה הראשונה שהתפרסמה הזכירה לי שלפעמים הכי כיף לקחת כלים פשוטים למשימות פשוטות. בקיצור בואו נראה איך לפתור את Advent Of Code 2022 Day 1 בלי לצאת משורת הפקודה.
1. המשימה: חישוב קלוריות
באתגר קיבלנו רשימת מספרים שמייצגת כמה קלוריות סוחב האלף הראשון, אחריה שורה רווח ואז עוד רשימת מספרים שמתאימה לקלוריות שסוחב האלף השני, ואז עוד שורת רווח ועוד רשימת מספרים וככה הלאה. בקיצור קלט כזה:
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
ויש לנו שתי משימות:
אם סוכמים את רשימת המספרים בכל קבוצה, מהו הסכום הגבוה ביותר מכל הקבוצות (בקלט הדוגמה זה 24000, סכום הקבוצה הרביעית)
מהם שלושת הסכומים הגבוהים ביותר מכל הקבוצות (בקלט הדוגמה זה 45000, הסכום של 24000, 11000 ו-10000).
2. איך מחשבים סכומים ביוניקס
הפקודה bc
היא דרך קלה לחשב סכום של מספרים. כן צריך לעשות קצת מניפולציות על הקלט כדי שיתאים לה, אבל בגדול bc מקבלת תרגיל חשבוני דרך stdin ופותרת אותו. ככה יהיה לנו:
$ echo 2 + 3 | bc
5
$ echo 5 + 7 + 10 | bc
22
בעזרת bc אני יכול לכתוב alias קטן שלוקח רשימת מספרים מ stdin ומדפיס את סכומם:
alias sum='sed "/./s/^/+/" | tr -d "\n" | xargs echo 0 | bc '
ה alias מוסיף סימן פלוס לפני כל מספר (מדלג על שורות ריקות), אחרי זה מוחק את כל הירידות שורה כדי שכל המספרים ייכתבו לשורה אחת, מוסיף 0 בהתחלה ומדפיס את השורה ל stdout בעזרת echo, ומשם ל bc כדי להדפיס את הסכום.
3. בחזרה לתרגיל
עכשיו שאנחנו יודעים איך עובד חיבור עדיין נשאר לנו לחבר כל קבוצה בנפרד. מאחר שכל קבוצה מופרדת מהקבוצה שאחריה בשורה ריקה, נצטרך להחליף כל "רצף של שורות עד השורה הריקה" בתרגיל חיבור מתאים. במילים אחרות נרצה להפוך את הקלט ל:
0 +1000 +2000 +3000
0 +4000
0 +5000 +6000
0 +7000 +8000 +9000
0 +10000
דרך אחת להפוך קבוצות לשורות היא להפוך כל שורה ריקה לתו Null (כלומר \0
), ואז להשתמש ב xargs כדי לקחת את כל הארגומנטים עד תו ה Null. למשל בשביל להדפיס את הטקסט Elf Group לפני כל קבוצה אני יכול להפעיל:
$ cat input.txt| sed 's/^$/\x0/' | xargs -0 -n 1 echo "Elf Group: "
אבל אני לא רוצה להדפיס את התחילית Elf Group אלא לחשב סכום - ובשביל זה צריך להיות קצת יותר יצירתיים:
- נרצה להוסיף סימן
+
בתחילת כל שורה לא ריקה בקבוצה. - נרצה למחוק את הירידות שורה (כדי לקבל שורה אחת ארוכה שמייצגת תרגיל).
- נרצה להדפיס 0 בתחילת השורה.
ה Pipeline של זה נראה כך:
$ cat input.txt| sed 's/^$/\x0/' | sed '/^[0-9]/s/^/+/' | tr -d '\n' | xargs -0 -n 1 echo 0
0 +1000+2000+3000
0 +4000
0 +5000+6000
0 +7000+8000+9000
0 +10000
ואת זה אפשר לשלוח ל bc כדי להחליף כל שורה בתוצאת התרגיל, כלומר בסכום שלה:
$ cat input.txt| sed 's/^$/\x0/' | sed '/^[0-9]/s/^/+/' | tr -d '\n' | xargs -0 -n 1 echo 0 | bc
6000
4000
11000
24000
10000
השורה עם הסכום הכי גדול? זה פשוט sort ו tail:
$ cat input.txt| sed 's/^$/\x0/' | sed '/^[0-9]/s/^/+/' | tr -d '\n' | xargs -0 -n 1 echo 0 | bc | so
rt -n | tail -1
24000
סכום שלושת השורות הגדולות ביותר? פה אפשר לשלב את ה alias שיצרנו בתחילת הפוסט:
$ cat input.txt| sed 's/^$/\x0/' | sed '/^[0-9]/s/^/+/' | tr -d '\n' | xargs -0 -n 1 echo 0 | bc | sort -n | tail -3 | sum
45000
4. כיוון נוסף - awk
כלי נוסף שתמיד עוזר לפתור בעיות הוא awk. מאחר ול awk יש כבר תחביר להגדרת משתנים אני יכול פשוט לסכום את כל המספרים עד השורה הריקה, וכשאני מגיע אליה לאפס את הסכום. זאת ההתחלה:
cat input.txt| awk '/^$/ { print sum; sum = 0 } { sum += $1 }'
ושוב בשביל הסכום הגבוה ביותר מספיק להוסיף sort ו tail:
$ cat input.txt| awk '/^$/ { print sum; sum = 0 } { sum += $1 }' | sort -n | tail -1
24000
ובשביל סכום שלושת הגדולים ביותר אני משלב את ה alias מתחילת הפוסט:
$ cat input.txt| awk '/^$/ { print sum; sum = 0 } { sum += $1 }' | sort -n | tail -3 | sum
45000
היכרות עם שתי השיטות להפוך בלוקים לשורות (awk ו xargs -0) עוזרת לכתוב Pipelines טובים יותר, גם באתגרים אמיתיים מחוץ ל Advent Of Code.