הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

פרילאנס, בוטסטרפ וסטארט-אפ

01/07/2020

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

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

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

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

למה כל כך קשה להחזיר חוב טכני

30/06/2020

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

Springboot -The AJP Connector is configured with secretRequired=“true” but the secret attribute is either null or ""

עכשיו הלקוח עדיין צועק מלמעלה, השרת עדיין לא עובד ועכשיו נזכרת לדבר איתי על כמה פרוטוקול AJP לא מאובטח? איפה היית כשהיה לי זמן?

בקיצור שתי דקות ב Stack Overflow זה כל מה שצריך בשביל למצוא איך לגרום לספרינגבוט להפסיק לבקש secret ואנחנו ממשיכים לחבר את החוטים. נחזור לזה אתה אומר, רק שהלקוח יפסיק לצעוק.

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

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

ההבדל בין סניור לג'וניור (או: מה שלא כותבים במודעות דרושים)

29/06/2020

גיגיה מחפשים עובדים ואני אוהב את הקטע של חברות לפרסם את כל מודעות הדרושים בצורה מסודרת באתר החברה - אז הלכתי לחטט קצת בדרישות התפקיד למשרות פיתוח Front End. הנה הדרישות עבור מפתח ג'וניור:

At least 1-2 years of experience in web development (Front End). Experience with JS Frameworks such as Angular, React, Aurelia or Ember. Experience with developing Single Page Applications. Experience with Object Oriented JavaScript Experience in the design of software architecture Exceptional self-learning capabilities

והדרישות עבור מפתח סניור:

At least 4 years of experience in web development (Front End). Experience with JS Frameworks such as Angular, React, Aurelia or Ember. Experience with developing Single Page Applications. Experience with Object Oriented JavaScript. Experience in the design of software architecture. Exceptional self-learning capabilities.

חיפשתי. באמת שחיפשתי. אפילו diff הפעלתי בין שתי המודעות ותמיד אותה תשובה - שנתיים ניסיון. באמת?! שנתיים ניסיון? זה מה שעושה את ההבדל?

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

בתחום שלנו הקוד שאתם כותבים הוא מה שעושה את ההבדל. ובשביל שהקוד שאתם כותבים ילך וישתפר כל הזמן אתם צריכים לכתוב עוד ועוד ממנו, לקבל פידבק ולשפר.

כשמישהו שואל ״אני כבר יודע לעבוד עם Redux ו MobX, מה עוד צריך בשביל להיות Senior?״ זאת לא השאלה הנכונה. בשביל להתקדם צריך את האומץ להגיד ״אני לא יודע״. המעבר מ Junior ל Senior זה לא מירוץ אלא דרך חיים.

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

בוטקמפ ריאקט

בוטקמפ פייתון

שאלת SQL: איך למצוא רק את השורות עבורן עמודה מסוימת היא הגדולה ביותר?

28/06/2020

הנה חידת SQL קטנה בשביל לפתוח את השבוע שגם עזרה לי לכתוב קצת קוד היום לאתר: נניח שיש לנו טבלא בשם lessons ובה כל השיעורים של קורס מסוים, לטבלא עמודה בשם course_id עם מזהה הקורס ועמודה בשם chapter_id עם מזהה הפרק. לכל שיעור יש גם עמודה בשם duration בה מאוחסן אורך השיעור.

אנחנו רוצים למצוא מהו השיעור הארוך ביותר בכל פרק ובשאילת אחת.

בשביל לקבל תמונה של המידע בואו נתחיל עם שאילתה פשוטה:

select id, chapter_id, course_id, length from lessons where course_id = 2;

 id | chapter_id | course_id | duration
----+-------------------+-----------+--------
 17 |                 3 |         2 |     40
 18 |                 3 |         2 |     35
 20 |                 3 |         2 |     50
 22 |                 4 |         2 |     60
 21 |                 4 |         2 |     65
 19 |                 3 |         2 |     55
 23 |                 3 |         2 |     45
  9 |                 1 |         2 |      1
 24 |                 2 |         2 |     30
 13 |                 2 |         2 |     20
 70 |                 3 |         2 |     43
 71 |                 3 |         2 |     48
 11 |                 1 |         2 |     10
 10 |                 1 |         2 |      5
 12 |                 1 |         2 |     15
 14 |                 2 |         2 |     25
(16 rows)

ואנחנו רוצים לקבל רק את השורות:

 course_id | id | chapter_id | duration
-----------+----+-------------------+--------
         2 | 21 |                 4 |     65
         2 | 19 |                 3 |     55
         2 | 24 |                 2 |     30
         2 | 12 |                 1 |     15
(4 rows)

כלומר לכל פרק אנחנו רוצים רק את השיעור הארוך ביותר באותו פרק.

רעיון ראשון יהיה אולי GROUP BY, אבל מהר מאוד אתם תיזכרו שעם GROUP BY אנחנו מאבדים את כל השדות שלא בקבוצה, ולכן הוא לא יאפשר לנו לשלוף את ה id של השיעורים.

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

השליפה הראשונה לפי הפרק נראית כך:

select chapter_id, max(duration) as max_duration from lessons where course_id = 2 group by chapter_id;

 chapter_id | max_duration
------------+--------------
          1 |           15
          2 |           30
          3 |           55
          4 |           65

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

ERROR: column "lessons.id" must appear in the GROUP BY clause or be used in an aggregate function

הוא צודק כמובן. במקום זה אנחנו צריכים להשלים את הנתון בדרך אחרת:

select l.course_id, l.id, f.chapter_id, l.duration
from (
    select chapter_id, max(duration) as max_duration
    from lessons group by chapter_id
) as x
inner join
lessons as l on l.chapter_id = x.chapter_id and l.duration = x.max_duration where l.course_id = 2;

בעזרת JOIN אני משלים את הנתונים החסרים מהטבלא המקורית וכך מקבל את כל המידע.

נ.ב. השליפה הכפולה עם ה JOIN זה מנגנון שחשוב להכיר, אבל סצפיפית אם אתם על פוסטגרס שווה להכיר שיש גם אחלה קיצור דרך שנקרא DISTINCT ON (ובו אני השתמשתי בקוד האמיתי). אותה שליפה תיראה איתו כך:

=> select distinct on(chapter_id) id, chapter_id, duration from lessons where course_id = 2 order by chapter_id, duration desc;

 id | chapter_id | duration
----+-------------------+--------
 12 |                 1 |     15
 24 |                 2 |     30
 19 |                 3 |     55
 21 |                 4 |     65
(4 rows)

אז למה הפסקת?

27/06/2020

״ההסברים בקורס מעולים. אני נהנה לצפות ולומד דברים חדשים בכל שיעור״

״הקורס הולך מצוין - השיעורים ברורים, והטקסט ליד הוידאו מאוד עוזר להבנה״

״בינתיים הגעתי עד שיעור 12. בקצב הזה תוך שבועיים אני כבר יודעת Python״

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

כשמדובר בהתמדה יש דברים קטנים שעושים את ההבדל ועוזרים לנו לעבור את המשבר ולהמשיך ללמוד: האם אני רואה את הסוף? האם אני יודע בדיוק מה לעשות היום? האם אני מחויב לאנשים אחרים? האם יש מי שיעזור וימשוך אותי למעלה כשצריך?

האנשים שעוברים איתי עכשיו את מסלול הבוטקמפ עוברים בדיוק את אותם משברים, את אותו לחץ בעבודה ואת אותן הסחות דעת משפחתיות שאתם עוברים. וכך חוץ מ React ו Python הם גם לומדים לייצר שיגרת למידה ולהתמיד בה גם בתוך משבר וגם כשבכל יום אחר הם היו שמים את הקורס ב Hold. מסתבר שזו מיומנות הרבה יותר חשובה מכל יכולת טכנית ספציפית.

המחזור השני של הבוטקמפים ייפתח ב 19.7. המסלול עובד יותר טוב מכל קורס אחר שלימדתי. אם תכננתם ללמוד Python או React או שהתחלתם ללמוד ונתקעתם או לא הגעתם עדיין לרמה שאתם מרוצים ממנה - המסלול הזה בשבילכם.

פרטים נוספים על קורס Python: https://www.tocode.co.il/bootcamps/17

פרטים נוספים על קורס React: https://www.tocode.co.il/bootcamps/18

ארבעים אלף שקל

26/06/2020

באחד הפרויקטים הראשונים שלי בתור Freelancer הגיע בן אדם שחיפש אפליקציה. עכשיו אני בדיוק יצאתי ממשרת שכיר כולי בתחושה שאני המתכנת הכי טוב בעולם, ישבתי על האיפיון שהוא שלח חישבתי חישובים, הוספתי טווח ביטחון ויצא שייקח לי חודש וחצי לכתוב את האפליקציה. לקחתי את משכורת השכיר החודשית שלי, הכפלתי ב 1.5, עיגלתי ושלחתי לבחור הצעת מחיר של 40,000 ש"ח + מע"מ.

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

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

לא ידעתי שזה יכול להיות כל כך פשוט

25/06/2020

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

בדוגמא מהבוטקמפ היום - רצינו להדפיס אימוג'י מתוך תוכנית פייתון למסך הטרמינל.

דרך אחת היתה למצוא ברשת קובץ png עם האימוג'י שרצית, להוריד אותו עם requests, להשתמש ב python-resize-image כדי להקטין את התמונה ואז ב imgcat כדי לצייר אותו לטרמינל. נרצה גם לשמור תיקיה של קבצי אימוג'ים שכבר הורדנו כדי לא להוריד כל הפעלה את כל האימוג'ים מחדש, ולמחוק ממנה קבצים ישנים במנגנון LRU.

דרך שניה היא להשתמש ב print:

print('\U0001f44d')

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

קורס באיקאה

24/06/2020

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

נגרים טובים יודעים לחבר חלקים מכל מיני סוגים ולבנות רהיטים מכל מיני מבנים בסיסיים יותר. מרכיבי איקאה יודעים לבנות רק את הדבר שהם קנו. אם הייתי רוצה להיות נגר הייתי בונה סדנא ומתחיל לבנות דברים לבד. לאיקאה הולכים בשביל לקבל את הרהיט.

גם בלימודי תכנות יש פרויקטי איקאה. קורס לדוגמא שנתקלתי בו ביודמי הציע לי 21 שעות של וידאו, 111 פריטים להורדה ועשרה פרויקטים מהעולם האמיתי שמדגימים שימוש בטכנולוגיה. זה נשמע מלהיב אבל כשנכנסים לפרטים מוצאים פרק של 3 שעות על JavaScript Language Fundamentals שלא כולל בניה של שום תרגיל או משחקון אמיתי. במצב כזה אתה מבלה שעות בצפיה בוידאו בלי יכולת אמיתית ליישם את מה שלמדת על מקרים אמיתיים.

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

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

מבנים ששווה להכיר: List Comprehension

23/06/2020

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

התבנית שאני רוצה להציג היום היא List Comprehension, פעולה שמקורה בשפות פונקציונאליות אבל נמצאת היום בהרבה מאוד שפות ובכל מיני צורות. המטרה היא תמיד לקחת רשימה אחת ולהפוך אותה לרשימה אחרת: לפעמים תוך שינוי האלמנטים ולפעמים תוך מחיקה של חלק מהאלמנטים לפי פרדיקט.

את השם List Comprehension פגשתי לראשונה ב Python שם הקוד הבא לקח רשימה של מספרים והחזיר רשימה של ריבועיהם:

numbers = range(10)
squares = [i * i for i in numbers]

ולמה זה יפה? כי בעבודה עם List Comprehension יש מעט מאוד מקום ליצירתיות. איך שרואים את הסוגריים המרובעים עם ה for בתוכם מיד יש קול בתוך הראש שצועק "הנה רשימה שהופכת לרשימה אחרת", ומיד אפשר להמשיך לחפש מה הופך למה.

כמובן שזה נוח בשילוב עם פונקציות נוספות, למשל חישוב סכום ריבועי המספרים יראה כך:

sum([i * i for i in range(10)])

(*) אני לא מדבר כאן עדיין על הרכבות מתוחכמות יותר כמו Generator Comprehension, למרות שאם אתם כותבים הרבה פייתון אתם כבר יודעים שאין טעם בסוגריים המרובעים בתוך העגולים, ואם לא - אז אולי נכתוב על זה פוסט ביום אחר.

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

Enum.sum(for n <- 0..9, do: n * n)

ו Clojure:

(apply + (for [i (range 10)] (* i i)))

ברוב השפות שאינן פונקציונאליות אין כתיב מובנה להרכבה אבל אנחנו משתמשים בפונקציות map ו filter כדי לקבל בדיוק את אותה תוצאה. כך ב Ruby:

(0..9).map {|i| i * i }.sum

ב JavaScript:

// some prep work
let numbers = new Array(10).fill(0).map((val, index) => index);
Array.prototype.sum = function() {
    return this.reduce((a, b) => a + b, 0)
}

numbers.map(i => i * i).sum()

אגב ב JavaScript הייתי צריך לממש לבד את sum ואת range שקיימות מהקופסא בשפות אחרות.

ואפילו Java כבר תומכת בתחביר דומה:

public class Main {

    public static void main(String[] args) {
      IntStream stream = IntStream.range(0, 10);
      System.out.println(stream.map(i -> i * i).sum());
    }
}

מרגע שהתחלנו לחשוב על הבעיה שלנו בתור רשימה שצריכה להפוך לרשימה אחרת, לא משנה באיזה שפה אנחנו מדברים אנחנו נוכל לייצג את המבנה - בדיוק כמו שהיינו מייצגים מבנה של משתנה, לולאה, תנאי או כל מבנה בסיסי אחר בשפה. ככל שנתרגל להשתמש במבנים של map, filter ו reduce כך יהיה לנו קל יותר לכתוב ולקרוא תוכניות במגוון שפות.

איך קוד מונחה עצמים הרס לי את החיים

22/06/2020

בקורס Python היום ניסינו לעשות מירוץ שליחים בגירסת המתכנתים - כל אחד קיבל את המקלדת ל-5 דקות וניסה לכתוב קוד כשכל האחרים צועקים כל מיני רמזים (שלפעמים עזרו ולפעמים רק הפריעו). היה כיף ובסוף הצלחנו לפתור יחד תרגיל מ Advent Of Code, והפוסט היום ידבר על אחת השאלות שעלו בדרך, ועל ההבדל בין ביצועים שרואים לבין ביצועים אמיתיים. אבל קודם התרגיל.

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

הכיוון הראשון שעבד נראה כך:

input_range = range(145852, 616942)
total = 0

for candidate in input_range:
    flag_all_up = True
    flag_same = False
    cand_str = str(candidate)
    for i in range(5):
        if cand_str[i] == cand_str[i+1]:
            flag_same = True
        elif cand_str[i] > cand_str[i+1]:
            flag_all_up = False
            break

    if flag_all_up and flag_same:
        total += 1

print(total)

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

def all_up(candidate):
    cand_str = str(candidate)
    for i in range(5):
        if cand_str[i] > cand_str[i+1]:
            return False

    return True

def has_two_adjacent_digits(candidate):
    cand_str = str(candidate)
    for i in range(5):
        if cand_str[i] == cand_str[i+1]:
            return True
    return False

total = sum(
        map(lambda candidate: all_up(candidate) and has_two_adjacent_digits(candidate),
            input_range))
print(total)

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

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

class TwoAdjacentDigitsPredicate:
    def __init__(self):
        self.result = False

    def process(self, a, b):
        if a == b:
            self.result = True

class AllUpPredicate:
    def __init__(self):
        self.result = True

    def process(self, a, b):
        if a > b:
            self.result = False

def apply_predicates(val, *predicates):
    val = str(val)
    for i in range(5):
        for p in predicates:
            p.process(val[i], val[i+1])
    return all([p.result for p in predicates])

total = filter(lambda candidate: apply_predicates(
            candidate,
            TwoAdjacentDigitsPredicate(), AllUpPredicate()), input_range)

print(sum(1 if x else 0 for x in total))

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

יש לכם כבר ניחוש מה זמני הריצה לכל גירסא? הנה זה בא:

# First version - single function
python3 aoc.py  0.81s user 0.02s system 85% cpu 0.965 total
# Second version - multiple functions
python3 aoc.py  0.54s user 0.01s system 97% cpu 0.568 total
# Third version - Object Oriented
python3 aoc.py  2.52s user 0.04s system 83% cpu 3.072 total

אז כן כצפוי גירסת ה Object Oriented היא האיטית ביותר. ליצור אוביקט חדש כל פעם זו פעולה יקרה ובזבזנית. מפתיע לראות שהדבר שחשבנו שיאט אותנו (הרצה הלולאה של 5 איטרציות פעמיים) בכלל לא הפריע לפייתון והגירסא השניה היא גם הנקיה ביותר וגם המהירה ביותר.

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

והמסקנה? בביצועים כמו בביצועים כדאי לבדוק כל דבר. תכנות מונחה עצמים כמעט תמיד יאט אתכם (אבל לפעמים הוא שווה את המחיר) ובכל מקרה מומלץ לא ליצור יותר מדי אוביקטים.