ToCode
Open in Telegram
1 423
Subscribers
+124 hours
+27 days
+630 days
Posts Archive
1 423
בתרגום לעברית - קח את כל הערכים של המטריצה (המספרים והסימנים), סנן מהם רק את הסימנים, קח את כל רשימת השכנים של כל הסימנים לתוך רשימה אחת ארוכה ואז תהפוך כל קואורדינטות של שכן לסימן שנמצא שם מתוך db. אם אין ערך במטריצה לקואורדינטה הזו נשתמש בערך הריק. אחרי זה תשמור את כל הסימנים שמצאת לתוך Set, מה שימחק את הכפילויות, ואז תהפוך חזרה לרשימה כדי שנוכל לקחת את הערכים המספריים שלהם, לסכום את כולם ולהדפיס.
שימוש במידע המפוענח לפיתרון החלק השני
את החלק השני אפשר למצוא מאותה מטריצה באופן הבא:
db
.values
.collect { case n: SignSigil if n.value == "*" => n }
.map(s => s.neighbors())
.map(s => s.map(p => db.getOrElse(p, NullSigil)))
.map(s => s.toSet)
.map(s => s.collect { case n: NumberSigil => n })
.filter(s => s.size == 2)
.map(g => g.foldLeft(1)(_ * _.value))
.sum
.pipe(println)
הפעם זה מתורגם לעברית בתור - קח את כל הסימנים, סנן מהם רק את הכוכביות, לכל כוכבית קח את הקואורדינטות של השכנים שלה, משוך מהמטריצה את המידע על השכנים, הפוך כל רשימת שכנים לקבוצה כדי לסנן את הכפילויות ואז סנן רק את אלה באורך 2, הפוך כל זוג כזה למכפלה שלו וסכום את המכפלות.1 423
פיתרון Advent Of Code 2023 יום 3 בסקאלה
כבר תקופה שאני לומד סקאלה ובסמיכות זמנים משמחת הגיע חודש דצמבר ואיתו החידות של Advent Of Code. וכן במקור תכננתי לפתור את כל החידות בזמן אמת ביום שהן מתפרסמות, אבל לחיים היו תוכניות אחרות. בכל מקרה אני אמשיך לפתור בקצב שלי ומקווה לסיים את כל 25 המשימות לפני סוף 2024. בינתיים אנחנו ביום השלישי ועדיין בסקאלה.
האתגר
הסיפור בגדול הוא פיענוח של קלט שמגיע בצורת מטריצה. הקלט לדוגמה הוא:
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
והמשימה היא למצוא את כל המספרים שלא צמודים לסימן ולסכום אותם. בחלק השני המשימה תהיה למצוא סימני כוכבית שצמודים אליהם בדיוק שני מספרים, ואז למצוא את מכפלת שני מספרים אלה ולסכום את כל המכפלות.
פיענוח הקלט בסקאלה
האינטואיציה הראשונה שלי כשראיתי את הקלט היתה להגדיר שלוש מחלקות, אחת עבור מספרים, שניה עבור הסימנים ושלישית עבור המקומות הריקים. בגלל שהמשימה כללה דרישה להסתכל על הדברים שמסביב לסימנים הוספתי למחלקה שמתאימה לסימן גם פונקציה שמחזירה את כל השכנים שלו. זה הקוד שיצא לשלושת המחלקות:
sealed trait Sigil
case class NumberSigil(line: Int, start: Int, end: Int, value: Int) extends Sigil
case class NullSigil(value: Int = 0) extends Sigil
case class SignSigil(line: Int, column: Int, value: String) extends Sigil {
def neighbors(): Seq[(Int, Int)] =
for
ii <- Range(line-1, line+2)
jj <- Range(column-1, column+2)
yield (ii, jj)
}
אחרי זה חשבתי שיהיה מועיל לשמור את כל הסימנים והמספרים במטריצה כשהמפתח הוא הזוג "שורה-עמודה" והערך זה האוביקט שמתאים למה שנמצא שם. זה אומר שיהיו משבצות במטריצה שיחזיקו את אותו אוביקט (בקלט הדוגמה בשורה הראשונה שלושת העמודות הראשונות מכילות את אותו מספר) ולכן נצטרך לשים לב לא לסכום דברים פעמיים.
הפונקציה הבאה מקבלת מטריצה מהסוג שתיארתי ואוביקט של סימן או מספר ומוסיפה אותו למטריצה:
def addSigilsToDB(sigils: List[Sigil])(db: Map[(Int, Int), Sigil]): Map[(Int, Int), Sigil] =
sigils.foldLeft(db)((acc, value) =>
value match
case n: NumberSigil =>
Range(n.start, n.end).foldLeft(acc)((acc, col) =>
acc.updated((n.line, col), n))
case s: SignSigil => acc.updated((s.line, s.column), s)
)
כתבתי את הפונקציה בתור חישוב מושהה (Curry) כלומר היא מקבלת קודם את הסימן ואז מחזירה פונקציה שמקבלת את המטריצה ומחזירה מטריצה חדשה עם תוספת הסימן. הסיבה היא הפונקציה האחרונה parse שמקבלת את הקלט ובונה ממנו את המטריצה. זה הקוד שלה:
def parse(input: Source): Map[(Int, Int), Sigil] =
input.getLines().zipWithIndex.foldLeft(Map(): Map[(Int, Int), Sigil])((acc, value) =>
val (line, lineIndex) = value
val numberSigils =
"""(\d+)""".r.findAllMatchIn(line).map(m =>
NumberSigil(line = lineIndex, start = m.start, end = m.end, value = m.toString.toInt))
.toList
val signSigils =
"""([^\d.])""".r.findAllMatchIn(line).map(m =>
SignSigil(line = lineIndex, column = m.start, value = m.toString()))
.toList
acc
.pipe(addSigilsToDB(numberSigils))
.pipe(addSigilsToDB(signSigils))
)
המבנה של פונקציה שמחזירה פונקציה הוא שאפשר להפעיל את pipe כדי לקבל תחביר קצת יותר קריא לשירשור פונקציות.
שימוש במידע המפוענח לפיתרון החלק הראשון
אחרי חישוב המטריצה אפשר להמשיך לפיתרון החלק הראשון. אנחנו צריכים את כל המספרים שצמודים לסימן ולכן נכתוב:
db
.values
.collect { case s: SignSigil => s }
.flatMap(s => s.neighbors())
.map(pos => db.getOrElse(pos, NullSigil()))
.toSet
.toList
.collect { case s: NumberSigil => s.value }
.sum
.pipe(println)1 423
"רק צריך להתאמץ יותר" זו מלכודת כפולה
הבעיה הראשונה עם "הייתי צריך להתאמץ יותר" היא שזה משפט שגורם לך להרגיש רע, הוא מכניס אותך לסטרס לקראת הפעם הבאה, כשרוב הזמן תוספת סטרס לא עוזרת לתפקד טוב יותר.
והבעיה השניה עם "הייתי צריך להתאמץ יותר", שאפילו יותר גרועה, היא ש"הייתי צריך להתאמץ יותר" רומז לפיתרון - לעשות עוד ממה שאני כבר יודע - כשאולי עדיף למצוא דרך אחרת או מנגנון אחר טוב יותר.
אם לא הצלחת להתרכז בקורס פייתון או לפתור את התרגיל, לא בטוח שהבעיה היא חוסר מאמץ. אולי צריך להכריח את עצמך לשבת לצפות שוב בוידאו, אבל אולי גם צריך לחפש דרך לימוד אחרת, או אולי זו לא תקופה טובה ללמוד פייתון עכשיו או שאולי עדיף ללמוד משהו אחר לגמרי. לפעמים עוזר להתאמץ יותר, הרבה פעמים שווה להתאמץ למצוא פיתרון טוב יותר.
1 423
למה בדיקות לא מזהות באגים
לגישת TDD יש המון הבטחות - הם הבטיחו שדרך הבדיקות נוכל לתכנן טוב יותר את הארכיטקטורה, שכתיבת בדיקות תצמצם את מספר הבאגים, שיהיה קל יותר להגיע ל Deployment, שיהיה קל יותר לתקן באגים כשיתגלו בפרודקשן וגם לבנות קוד חדש.
אבל כמו שיודע רוב מי שעבד בגישה הזאת בפרויקט אמיתי, המציאות מורכבת יותר.
כן אנחנו כותבים בדיקות, אבל עדיין מגלים "פתאום" המון מקרי קצה שלא חשבנו עליהם, ושדורשים ריפקטורינג כולל של רכיבים בסיסיים במערכת.
כן אנחנו כותבים בדיקות, אבל אז בפרודקשן מגלים בעיית ביצועים שמכריחה אותנו לארגן המון קוד מחדש, כולל את קוד הבדיקות.
כן אנחנו כותבים בדיקות, אבל לעתים קרובות מגלים שהן היו מיותרות.
והכי גרוע - כן אנחנו כותבים בדיקות אבל עדיין יש באגים.
אחת הסיבות שבדיקות לא מוצאות באגים היא שיש פחות באגים בקוד שכולל בדיקות. הבאגים מסתתרים באותם חלקים בקוד שעליהם אין בדיקות. במקום לדמיין שהבדיקות מיותרות (כי אף פעם אין באגים בקוד שקשור אליהן), שווה לנצל את התכונה הזאת של בדיקות ולהוסיף אותן ליותר חלקים בקוד.
1 423
היום למדתי: לא כזה פשוט לבנות נתיב ארוך
הפונקציה os.path.join של פייתון מחברת שמות של כמה נתיבים לנתיב גדול:
>>> import os
>>> os.path.join("/foo", "bar", "buz")
'/foo/bar/buz'
שפות רבות כוללות פונקציה בשם דומה כדי לחבר כמה URL-ים לנתיב אחד ארוך, אבל ההתנהגות הרבה יותר מבלבלת. בפייתון:
>>> import urllib.parse
>>> urllib.parse.urljoin("http://localhost", "blog", "items")
'http://localhost/blog'
בגלל שהפונקציה urljoin מקבלת רק שני פרמטרים עבור החלקים של הנתיב, והפרמטר השלישי הוא בכלל בוליאני שמדבר על היחס לפרגמנטים כמו הדברים שבאים אחרי סולמית או סימן שאלה. אפילו אם נישאר במסגרת של שני פרמטרים זה עדיין יהיה מבלבל:
>>> urllib.parse.urljoin('http://localhost/blog', 'items')
'http://localhost/items'
בגלל שהמילה blog בשם הנתיב לא מסתיימת ב / אז הוא "מוחלף" במילה items. זה כאילו שהיינו מסתכלים על דף אינטרנט בכתובת הראשונה ומקבלים לינק לכתובת השניה, אז התוצאה היתה החלפה של blog ל items. כשאני מוסיף / ההתנהגות יותר דומה למה שהיה לנו בעבודה עם קבצים:
>>> urllib.parse.urljoin('http://localhost/blog/', 'items')
'http://localhost/blog/items'
גם JavaScript תומכת בחיבור URL-ים רק משני חלקים, אבל שם סדר הפריטים הפוך:
> new URL("items", "http://localhost/blog").toString()
'http://localhost/items'
> new URL("items", "http://localhost/blog/").toString()
'http://localhost/blog/items'
רובי כבר מאפשרת להדביק כמה חלקים שאני רוצה ל URL, אבל גם פה יש לנו את ההתנהגות המוזרה עם הלוכסנים:
3.1.1 :006 > URI.join("http://localhost", "blog/", "items").to_s
=> "http://localhost/blog/items"
3.1.1 :007 > URI.join("http://localhost", "blog", "items").to_s
=> "http://localhost/items"
וגם קלוז'ר במימוש של ספריית lambdaisland/uri תואמת להתנהגות של רובי עם הלוכסנים בסוף מילים:
; "http://localhost/items"
(str (uri/join "http://localhost" "blog" "items"))
; "http://localhost/blog/items"
(str (uri/join "http://localhost" "blog/" "items"))
מסקנות -
1. שימו לב לחתימה של URI/join בשפה שבחרתם, האם אפשר לקבל כמה פרמטרים שרוצים או רק שניים ומה הסדר שלהם.
2. שימו לב שבחיבור נתיבים ל URL יש לסיים בלוכסן כל נתיב שאתם רוצים שיישאר בתוצאה הסופית.
מכירים עוד מוזרויות של המנגנון בשפות אחרות? אל תתביישו לספר בתגובות או בטלגרם.1 423
טיפ פייתון: סידור פלט עם format
כולנו מכירים את מנגנון ה format string של פייתון שמאפשר לכתוב f בתחילת מחרוזת כדי "לשתול" בתוך המחרוזת תוצאות של ביטויים בקוד פייתון לדוגמה:
print(f"10 / 3 = {10 / 3}")
יותר מזה, אנחנו יכולים להוסיף בכל סוגריים מסולסלים נקודותיים ואחריהם פקודת תבנית כדי שהפלט יוצג לפי התבנית שנבחר, בדוגמה של 10 חלקי שלוש אפשר לכתוב:
>>> print(f"{10 / 3:.2f}")
3.33
ויש עוד למשל אפשר להפוך מספר לבסיס 16 אם נכתוב אחרי הנקודותיים את האות x:
>>> print(f"16 is base 16 is {16:x}")
16 is base 16 is 10
מה שאולי לא ידעתם זה שהמנגנון מבוסס על פונקציה מובנית בפייתון בשם format, ולפעמים יותר נוח להשתמש בה מאשר בכל ההמרה למחרוזת. הפונקציה format מקבלת ערך ותיאור תבנית וכותבת את הערך לפי התבנית שהעברתם. בדוגמאות שלנו אם מה שמעניין אותנו זה רק המחרוזת שבתוך הסוגריים המסולסלים בלי הטקסט שלפניה ואחריה נוכל לכתוב:
>>> format(16, 'x')
'10'
>>> format(10 / 3, '.2f')
'3.33'
>>> format('hello', '^20s')
' hello '
שזה לא תמיד יותר קצר מ f"..." אבל יש בו משהו יותר מדויק כשעובדים על ערך בודד.1 423
מה את פוחדת לאבד?
חברה שואלת - יש לי אחלה עבודה אבל קצת משעממת. אני לא מרגישה שאני מתקדמת מקצועית ופוחדת שבעצם הולכת אחורה. ניסיתי ללכת לכמה ראיונות אבל בגלל שלא היה לי זמן ללמוד ברצינות לא התקבלתי. האם שווה לעזוב את העבודה כדי לעבור למקום אחר, או להתאמץ וללמוד בשעות הערב תוך כדי עבודה ולהתקדם בקצב שלי?
ואין ספק שזאת אחת הדילמות הקשות בתעשייה שלנו, ובמיוחד בתקופה זו כשיותר קשה למצוא משרות.
דרך אחת שיכולה לעזור היא לפרק את השאלה לשני חלקים: מה את באמת פוחדת לאבד? ומה את מפסידה כבר עכשיו כשאת נשארת?
לגבי החלק הראשון ובהנחה שחסכת מספיק כסף לחודשים הקרובים (ייקח לך משהו כמו חצי שנה ללמוד טוב לראיונות ולמצוא עבודה חדשה), הפחדים יהיו בעיקר החשש לא למצוא עבודה טובה יותר או לא למצוא עבודה אחרת כלל. אבל מה זה בעצם אומר למצוא עבודה פחות טובה? עבודה עם פחות כסף? (לא נראה לי, אחרת העלאה היתה מספיקה כדי לגרום לך להישאר), עבודה שדורשת יותר שעות? (שוב לא נשמע נכון). אם הדבר שאת מחפשת הוא עבודה יותר מאתגרת שתתן לך הזדמנות לבנות דברים מועילים ולהשתפר מקצועית, ואת מוכנה להתפשר על הכסף והשעות כדי להשיג אותה וגם להשקיע זמן עבודה בבית בללמוד, הסיכוי לא למצוא הוא מאוד נמוך.
לא, אני לא חושב שאת צריכה לפחד שלא תמצאי עבודה יותר מאתגרת או יותר מתגמלת מקצועית. פחד יותר הגיוני הוא הפחד להתחרט, הפחד להתעורר באותה עבודה תובענית עם קידום מקצועי ויותר שעות ולגלות שבעצם חסרה לך הגמישות והאדישות של העבודה הנוכחית. את חוששת לשאול את עצמך בעוד חצי שנה "בשביל מה הייתי צריכה את זה? מה היה לי חסר בחיים?"
בחלק השני נרצה להבין מה את מפסידה כבר עכשיו כשאת נשארת במקום "נוח", והאם אפשר לפצות על זה בעבודה מאומצת יותר בשעות הערב או בשילוב קורס מקצועי? היתרון של עבודה נוחה הוא שאת יודעת איפה תהיי מבחינה מקצועית בעוד שנתיים (בדיוק איפה שאת היום). וזה גם החיסרון. יכול להיות שתצליחי לקחת קורס ערב ולהתמיד בו, אבל בלי הלחץ של הצורך למצוא עבודה חדשה, ובלי החיכוך של "לא להצליח" דברים בעבודה היום יומית, יהיה קשה מאוד לקחת ברצינות את הקורס. זה תמיד יראה כמו תחביב, שאפשר להתמיד בו כשיש זמן ולהפסיק איך שדברים מתחילים להסתבך.
חוסר נוחות, לחץ, חיכוך, אלה כולם תנאים הכרחיים לתנועה קדימה אותה את מחפשת.
אז כן הפחד להתחרט תמיד שם, והחרטה אולי גם תגיע, אבל לצערי אנחנו יודעים בדיוק איפה תהיי בעוד שנתיים אם תבחרי להישאר. בדיוק באותו מקום בו את נמצאת עכשיו. במצב הזה יש רק דרך אחת קדימה.
1 423
נקודת עצירה
הנה כמה דברים כלליים שאנחנו יודעים על סוף יום העבודה-
1. אנחנו אוהבים "לסיים" ולהגיע לקוד שעובד לפני שמסיימים יום עבודה (כולל אפיזודות של להישאר כל הלילה במשרד רק כי לא מצאת את הבאג).
2. אנחנו חוששים מדף לבן ומסתבכים כשצריך להתחיל משימה מאפס, עד שנסגרים על הכיוון הכללי לפיתרון.
3. אחרי שיש פיתרון עובד אנחנו לא אוהבים לבזבז זמן על לבדוק אותו ומעדיפים לעבור לבעיה הבאה.
אם ניתן לשעון לקבוע באיזו נקודה נסיים את יום העבודה יש סיכוי טוב שניפול למלכודת. שעת הסיום תגיע, אנחנו נישאר במשרד יותר ממה שתכננו כי יש בדיוק באג שצריך לפתור, אחרי חריגה של שעתיים נגיע למשהו שבגדול עובד, לא נישאר כדי לבדוק אותו עד הסוף ולמחרת נבזבז את כל הבוקר בלחשוב איך לגשת לבעיה הבאה.
אבל אולי עם קצת מחשבה אפשר לשפר את המצב.
הטריק הוא לסיים את הפיתוחים החדשים שעתיים לפני שנגמר היום, ולהשקיע את השעתיים האחרונות של היום בתכנון השלב הבא של הפיתרון (בין אם זה הפיצ'ר הבא או השלב הבא של אותו פיצ'ר). אני אוהב בסוף היום לכתוב תיעוד ובדיקות, גם אם הבדיקות נכשלות או אפילו לא מתקמפלות כי התשתית להרצה שלהן לא קיימת. בצורה כזאת אפשר להתחיל את הבוקר שלמחרת בהמשך קידוד מאותה נקודה, ואת שעות הצהריים להשקיע בבדיקות וחיזוק הפיתרון. בחירת נקודת עצירה טובה לפיתוח יכולה לעזור לאיזון טוב בין בית לעבודה ולחסוך לנו שעות נוספות, בלי לפגוע בפרודוקטיביות בזכות ניצול טוב יותר של שעות הבוקר.
יש לכם גם טריקים איך לסדר את הקידוד במהלך היום כדי לשפר את הפרודוקטיביות? אשמח לשמוע בתגובות או בטלגרם.
1 423
מדריך next.js חלק 8 - העלאה לשרת
נסיים את המדריך בחיבור עם ורסל לצורך העלאת האתר שכתבנו לאינטרנט.
תיקון שגיאות בניה
לפני שאפשר להעלות את הקוד לשרת צריך לוודא שהוא נבנה בלי שגיאות, ואם עקבתם אחרי המדריך עד כאן תצטערו לשמוע שזה לא המקרה. במהלך העבודה עשיתי מספר קיצורי דרך שאומנם היו קלים יותר לכתיבה והסבר אבל הרגיזו את next. אלה הבעיות המרכזיות:
1. בכל הקומפוננטות ייצאתי פונקציה אנונומית. נקסט אוהב שיש שם לכל קומפוננטה ולכן אני מחליף כל יצירת קומפוננטה לפונקציה ממש עם שם, כלומר במקום לכתוב:
export default () => { ... }
אני כותב:
export default function AboutPage() { ... }
2. בקומפוננטת התפריט היה לי מערך של פריטים שכללו נתיב על השרת. ההגדרה המקורית של המערך היתה:
items: Array<{href: string, text: string}>
אבל נקסט רוצה לעשות בדיקת טיפוסים גם על הנתיבים שאני מעביר לאלמנט Link ולכן ה href לא יכול להיות כל string אלא צריך להיות מחרוזת שהיא גם נתיב תקני על השרת. בשביל לתקן את שגיאת הבניה החלפתי את ההגדרה ל:
export default function Menu<T extends string>({ items }: {
items: Array<{href: Route<T>, text: string}>
}) {
3. הוספתי מאפיין key לכל רשימה.
זו רשימת השגיאות המלאה שתיקנתי:
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
index cf82120..f697b7c 100644
--- a/src/app/about/page.tsx
+++ b/src/app/about/page.tsx
@@ -1,8 +1,8 @@
-export default () => {
+export default function About() {
return (
<div>
<h1>About Us</h1>
- <p>This is an example lesson for using next.js router</p>
+ <p>This is an example lesson for using next.js</p>
</div>
)
}
\ No newline at end of file
diff --git a/src/app/components/menu.tsx b/src/app/components/menu.tsx
index 936ca44..468dfcc 100644
--- a/src/app/components/menu.tsx
+++ b/src/app/components/menu.tsx
@@ -1,16 +1,17 @@
"use client";
import Link from 'next/link';
+import type { Route } from 'next'
import { usePathname } from 'next/navigation'
-export default ({ items }: {
- items: Array<{href: String, text: String}>
-}) => {
+export default function Menu<T extends string>({ items }: {
+ items: Array<{href: Route<T>, text: String}>
+}) {
const pathname = usePathname()
return (
<nav className='flex my-4 border-4 border-indigo-200 border-l-indigo-500'>
{items.map(item => (
- <div className='flex-1 px-2'>
+ <div className='flex-1 px-2' key={item.href}>
<Link href={item.href} >{item.text}</Link>
{pathname == item.href &&
<span className="ml-2 w-2 h-2 bg-blue-500 rounded-full inline-block"></span>
diff --git a/src/app/counter.tsx b/src/app/counter.tsx
index 50da28f..c4bb5a4 100644
--- a/src/app/counter.tsx
+++ b/src/app/counter.tsx
@@ -2,7 +2,7 @@
import { useState } from 'react';
import Header from './header';
-export default () => {
+export default function Counter() {
const [count, setCount] = useState(0);
console.log('counter');
return (
diff --git a/src/app/header.tsx b/src/app/header.tsx
index 0ff3c50..a9e22f5 100644
--- a/src/app/header.tsx
+++ b/src/app/header.tsx
@@ -1,4 +1,4 @@
-export default () => {
+export default function Header() {
console.log('header');
return <h1>Counter Header</h1>
}
\ No newline at end of file
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 5cd02da..b27557b 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -2,7 +2,7 @@ import type { Metadata } from 'next'
import { UserProvider } from '@auth0/nextjs-auth0/client';
import { Inter } from 'next/font/google'
-import TopMenu from './servermenu';
+import TopMenu from './menu';
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
diff --git a/src/app/menu.tsx b/src/app/menu.tsx
index 1e40b9c..2043559 100644
--- a/src/app/menu.tsx
+++ b/src/app/menu.tsx
@@ -2,7 +2,7 @@ import Menu from './components/menu';
import { readdir } from 'node:fs/promises';
import { existsSync } from 'node:fs';
-export default async () => {