ToCode
رفتن به کانال در Telegram
1 420
مشترکین
+124 ساعت
+17 روز
-430 روز
آرشیو پست ها
1 420
במקום לפנות זמן נסו להוסיף אנרגיה
דרך אחת להספיק יותר דברים היא לפנות זמן לדברים החדשים. זה מפתה כי אנחנו תמיד בהרגשה שחסר לנו זמן, ואז אם נצליח להוריד מהלו"ז משימות שלוקחות זמן יהיה יותר זמן לדברים מועילים.
הבעיה עם הטכניקה הזאת היא שפינוי זמן לא מבטיח שהוא יתמלא בדברים שרצית. לרדת לחצי משרה כדי להשקיע בלימודים זה מפתה, אבל הרבה פעמים אתה מגלה שירדת לחצי משרה ואתה עדיין לא מוצא זמן ללימודים.
שיטה אחרת שעובדת לי יותר טוב היא להסתכל על מוטיבציה ואנרגיה, ולנסות להבין איזה משימות אפשר להוסיף ללו"ז כדי לקבל יותר אנרגיה לפעולה. בדוגמה של אנרגיה ללימודים זה יכול להיות הקשבה להרצאות על הנושא שאותו רוצים ללמוד, השתתפות בכנסים או שילוב של חלקים קטנים מאותו הנושא בחיי היום יום שמחוץ ללימודים.
כשנמצאים במצוקת זמן זה נשמע דמיוני לחשוב שהפיתרון הוא להוסיף עוד משימות, אבל למרבה ההפתעה לפעמים זה עובד.
1 420
טיפ ביטויים רגולאריים - לא חוזרים על Capturing Group
טעות נפוצה בעבודה עם ביטויים רגולאריים היא הרצון להשתמש ב Capturing Groups בלי לדעת מראש מה אנחנו צריכים לתפוס. לדוגמה קחו את הטקסט:
values: 10 20 30 40
ובהנחה שרשימת המספרים יכולה להיות בכל אורך, אפשר היה לדמיין קוד פייתון שיקרא את הרשימה עם ביטוי רגולארי:
import re
text = "values: 10 20 30 40"
m = re.search(r'values: (?:(\d+)\s*)+', text)
אבל מהר מאוד נגלה את הטעות כשננסה להציג את הקבוצות שתפסנו דרך הביטוי:
>>> m.groups()
('40',)
מאיפה הוא הגיע ל 40? דרך טובה לחשוב על Capture Groups היא כמו בעבודה עם מילון, כך שאם יש יותר מערך אחד לקבוצה כל ערך דורס את זה שלפניו ונקבל רק את הערך האחרון. נכון שבדוגמה שלנו גם 10, 20 ו 30 היו התאמות של הקבוצה, אבל בגלל שזו קבוצה אחת רק 40 נשאר בערך שחזר מההתאמה.
במקום להכניס יותר מדי ערכים לקבוצה, במקרים כאלה יש להשתמש ב findall יחד עם ביטוי רגולארי לתוכן של קבוצה כדי לקבל את כל התוצאות:
>>> re.findall(r'\b(\d+)\b', text)
['10', '20', '30', '40']1 420
בתרגום לעברית - קח את כל הערכים של המטריצה (המספרים והסימנים), סנן מהם רק את הסימנים, קח את כל רשימת השכנים של כל הסימנים לתוך רשימה אחת ארוכה ואז תהפוך כל קואורדינטות של שכן לסימן שנמצא שם מתוך 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 420
פיתרון 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 420
"רק צריך להתאמץ יותר" זו מלכודת כפולה
הבעיה הראשונה עם "הייתי צריך להתאמץ יותר" היא שזה משפט שגורם לך להרגיש רע, הוא מכניס אותך לסטרס לקראת הפעם הבאה, כשרוב הזמן תוספת סטרס לא עוזרת לתפקד טוב יותר.
והבעיה השניה עם "הייתי צריך להתאמץ יותר", שאפילו יותר גרועה, היא ש"הייתי צריך להתאמץ יותר" רומז לפיתרון - לעשות עוד ממה שאני כבר יודע - כשאולי עדיף למצוא דרך אחרת או מנגנון אחר טוב יותר.
אם לא הצלחת להתרכז בקורס פייתון או לפתור את התרגיל, לא בטוח שהבעיה היא חוסר מאמץ. אולי צריך להכריח את עצמך לשבת לצפות שוב בוידאו, אבל אולי גם צריך לחפש דרך לימוד אחרת, או אולי זו לא תקופה טובה ללמוד פייתון עכשיו או שאולי עדיף ללמוד משהו אחר לגמרי. לפעמים עוזר להתאמץ יותר, הרבה פעמים שווה להתאמץ למצוא פיתרון טוב יותר.
1 420
למה בדיקות לא מזהות באגים
לגישת TDD יש המון הבטחות - הם הבטיחו שדרך הבדיקות נוכל לתכנן טוב יותר את הארכיטקטורה, שכתיבת בדיקות תצמצם את מספר הבאגים, שיהיה קל יותר להגיע ל Deployment, שיהיה קל יותר לתקן באגים כשיתגלו בפרודקשן וגם לבנות קוד חדש.
אבל כמו שיודע רוב מי שעבד בגישה הזאת בפרויקט אמיתי, המציאות מורכבת יותר.
כן אנחנו כותבים בדיקות, אבל עדיין מגלים "פתאום" המון מקרי קצה שלא חשבנו עליהם, ושדורשים ריפקטורינג כולל של רכיבים בסיסיים במערכת.
כן אנחנו כותבים בדיקות, אבל אז בפרודקשן מגלים בעיית ביצועים שמכריחה אותנו לארגן המון קוד מחדש, כולל את קוד הבדיקות.
כן אנחנו כותבים בדיקות, אבל לעתים קרובות מגלים שהן היו מיותרות.
והכי גרוע - כן אנחנו כותבים בדיקות אבל עדיין יש באגים.
אחת הסיבות שבדיקות לא מוצאות באגים היא שיש פחות באגים בקוד שכולל בדיקות. הבאגים מסתתרים באותם חלקים בקוד שעליהם אין בדיקות. במקום לדמיין שהבדיקות מיותרות (כי אף פעם אין באגים בקוד שקשור אליהן), שווה לנצל את התכונה הזאת של בדיקות ולהוסיף אותן ליותר חלקים בקוד.
1 420
היום למדתי: לא כזה פשוט לבנות נתיב ארוך
הפונקציה 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 420
טיפ פייתון: סידור פלט עם 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 420
מה את פוחדת לאבד?
חברה שואלת - יש לי אחלה עבודה אבל קצת משעממת. אני לא מרגישה שאני מתקדמת מקצועית ופוחדת שבעצם הולכת אחורה. ניסיתי ללכת לכמה ראיונות אבל בגלל שלא היה לי זמן ללמוד ברצינות לא התקבלתי. האם שווה לעזוב את העבודה כדי לעבור למקום אחר, או להתאמץ וללמוד בשעות הערב תוך כדי עבודה ולהתקדם בקצב שלי?
ואין ספק שזאת אחת הדילמות הקשות בתעשייה שלנו, ובמיוחד בתקופה זו כשיותר קשה למצוא משרות.
דרך אחת שיכולה לעזור היא לפרק את השאלה לשני חלקים: מה את באמת פוחדת לאבד? ומה את מפסידה כבר עכשיו כשאת נשארת?
לגבי החלק הראשון ובהנחה שחסכת מספיק כסף לחודשים הקרובים (ייקח לך משהו כמו חצי שנה ללמוד טוב לראיונות ולמצוא עבודה חדשה), הפחדים יהיו בעיקר החשש לא למצוא עבודה טובה יותר או לא למצוא עבודה אחרת כלל. אבל מה זה בעצם אומר למצוא עבודה פחות טובה? עבודה עם פחות כסף? (לא נראה לי, אחרת העלאה היתה מספיקה כדי לגרום לך להישאר), עבודה שדורשת יותר שעות? (שוב לא נשמע נכון). אם הדבר שאת מחפשת הוא עבודה יותר מאתגרת שתתן לך הזדמנות לבנות דברים מועילים ולהשתפר מקצועית, ואת מוכנה להתפשר על הכסף והשעות כדי להשיג אותה וגם להשקיע זמן עבודה בבית בללמוד, הסיכוי לא למצוא הוא מאוד נמוך.
לא, אני לא חושב שאת צריכה לפחד שלא תמצאי עבודה יותר מאתגרת או יותר מתגמלת מקצועית. פחד יותר הגיוני הוא הפחד להתחרט, הפחד להתעורר באותה עבודה תובענית עם קידום מקצועי ויותר שעות ולגלות שבעצם חסרה לך הגמישות והאדישות של העבודה הנוכחית. את חוששת לשאול את עצמך בעוד חצי שנה "בשביל מה הייתי צריכה את זה? מה היה לי חסר בחיים?"
בחלק השני נרצה להבין מה את מפסידה כבר עכשיו כשאת נשארת במקום "נוח", והאם אפשר לפצות על זה בעבודה מאומצת יותר בשעות הערב או בשילוב קורס מקצועי? היתרון של עבודה נוחה הוא שאת יודעת איפה תהיי מבחינה מקצועית בעוד שנתיים (בדיוק איפה שאת היום). וזה גם החיסרון. יכול להיות שתצליחי לקחת קורס ערב ולהתמיד בו, אבל בלי הלחץ של הצורך למצוא עבודה חדשה, ובלי החיכוך של "לא להצליח" דברים בעבודה היום יומית, יהיה קשה מאוד לקחת ברצינות את הקורס. זה תמיד יראה כמו תחביב, שאפשר להתמיד בו כשיש זמן ולהפסיק איך שדברים מתחילים להסתבך.
חוסר נוחות, לחץ, חיכוך, אלה כולם תנאים הכרחיים לתנועה קדימה אותה את מחפשת.
אז כן הפחד להתחרט תמיד שם, והחרטה אולי גם תגיע, אבל לצערי אנחנו יודעים בדיוק איפה תהיי בעוד שנתיים אם תבחרי להישאר. בדיוק באותו מקום בו את נמצאת עכשיו. במצב הזה יש רק דרך אחת קדימה.
1 420
נקודת עצירה
הנה כמה דברים כלליים שאנחנו יודעים על סוף יום העבודה-
1. אנחנו אוהבים "לסיים" ולהגיע לקוד שעובד לפני שמסיימים יום עבודה (כולל אפיזודות של להישאר כל הלילה במשרד רק כי לא מצאת את הבאג).
2. אנחנו חוששים מדף לבן ומסתבכים כשצריך להתחיל משימה מאפס, עד שנסגרים על הכיוון הכללי לפיתרון.
3. אחרי שיש פיתרון עובד אנחנו לא אוהבים לבזבז זמן על לבדוק אותו ומעדיפים לעבור לבעיה הבאה.
אם ניתן לשעון לקבוע באיזו נקודה נסיים את יום העבודה יש סיכוי טוב שניפול למלכודת. שעת הסיום תגיע, אנחנו נישאר במשרד יותר ממה שתכננו כי יש בדיוק באג שצריך לפתור, אחרי חריגה של שעתיים נגיע למשהו שבגדול עובד, לא נישאר כדי לבדוק אותו עד הסוף ולמחרת נבזבז את כל הבוקר בלחשוב איך לגשת לבעיה הבאה.
אבל אולי עם קצת מחשבה אפשר לשפר את המצב.
הטריק הוא לסיים את הפיתוחים החדשים שעתיים לפני שנגמר היום, ולהשקיע את השעתיים האחרונות של היום בתכנון השלב הבא של הפיתרון (בין אם זה הפיצ'ר הבא או השלב הבא של אותו פיצ'ר). אני אוהב בסוף היום לכתוב תיעוד ובדיקות, גם אם הבדיקות נכשלות או אפילו לא מתקמפלות כי התשתית להרצה שלהן לא קיימת. בצורה כזאת אפשר להתחיל את הבוקר שלמחרת בהמשך קידוד מאותה נקודה, ואת שעות הצהריים להשקיע בבדיקות וחיזוק הפיתרון. בחירת נקודת עצירה טובה לפיתוח יכולה לעזור לאיזון טוב בין בית לעבודה ולחסוך לנו שעות נוספות, בלי לפגוע בפרודוקטיביות בזכות ניצול טוב יותר של שעות הבוקר.
יש לכם גם טריקים איך לסדר את הקידוד במהלך היום כדי לשפר את הפרודוקטיביות? אשמח לשמוע בתגובות או בטלגרם.
اکنون در دسترس! پژوهش تلگرام ۲۰۲۵ — مهمترین بینشهای سال 
