פיתרון Advent Of Code יום 1 בעזרת יוניקס

02/12/2022

כמו כל שנה בדצמבר החידות של Advent Of Code התחילו להתפרסם היום. אומנם עדיין לא החלטתי באיזו שפה להשתמש או אפילו אם אני מתכנן לפתור את כולן, אבל החידה הראשונה שהתפרסמה הזכירה לי שלפעמים הכי כיף לקחת כלים פשוטים למשימות פשוטות. בקיצור בואו נראה איך לפתור את Advent Of Code 2022 Day 1 בלי לצאת משורת הפקודה.

1. המשימה: חישוב קלוריות

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

1000
2000
3000

4000

5000
6000

7000
8000
9000

10000

ויש לנו שתי משימות:

  1. אם סוכמים את רשימת המספרים בכל קבוצה, מהו הסכום הגבוה ביותר מכל הקבוצות (בקלט הדוגמה זה 24000, סכום הקבוצה הרביעית)

  2. מהם שלושת הסכומים הגבוהים ביותר מכל הקבוצות (בקלט הדוגמה זה 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 אלא לחשב סכום - ובשביל זה צריך להיות קצת יותר יצירתיים:

  1. נרצה להוסיף סימן + בתחילת כל שורה לא ריקה בקבוצה.
  2. נרצה למחוק את הירידות שורה (כדי לקבל שורה אחת ארוכה שמייצגת תרגיל).
  3. נרצה להדפיס 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.