ToCode
الذهاب إلى القناة على Telegram
1 419
المشتركون
لا توجد بيانات24 ساعات
لا توجد بيانات7 أيام
لا توجد بيانات30 أيام
أرشيف المشاركات
1 419
$ bison -t -v -d calc.y
$ flex calc.l
$ gcc -o calc calc.tab.c lex.yy.c
וזה יצר לי קובץ בשם calc שהוא המחשבון ההפוך. בהינתן קובץ קלט עם תרגילי חשבון, כל תרגיל בשורה נפרדת, הקוד הבא יפתור את כולם וידפיס את התוצאות:
./calc < ../input/day18.txt
## הגישה הרגולארית: אפשר להחליף ביטוי רגולארי בתוצאה של פונקציה
גם מי שלא מכיר את flex ו bison לא חייב לעבוד קשה על התרגיל הזה. דרך נוספת קלה לפתור אותו היא שימוש בביטויים רגולאריים וביכולת של ביטוי רגולארי לעשות החלפה באמצעות פונקציה, כלומר אנחנו מזהים קטע בתוך המחרוזת, שולחים אותו לפונקציה ומחליפים את הקטע בתוצאה של הפונקציה.
במקרה של המחשבון ההפוך יש לנו 3 אופרטורים עם סדר קדימויות לקחת בחשבון:
1. סוגריים, אותם צריך לפתור לפני הכל
2. חיבור, אותו צריך לפתור כשאין יותר סוגריים
3. כפל, אותו נפתור כשסיימנו את כל תרגילי החיבור
לכן אני יכול לכתוב פונקציה שתעבוד כך:
1. אם יש סוגריים בביטוי, היא תיקח את הסוגריים הפנימיים ביותר ותנסה לפתור אותם
2. אם יש פלוס בביטוי היא תחפש את שני המספרים שסביבו ותחליף את כל השלישיה בתוצאת החיבור (אנחנו יודעים שזה מספרים, כי אם היו סוגריים מסביב לפלוס היינו נכנסים פנימה לפתור אותם לפי סעיף 1)
3. אם יש כפל בביטוי היא תחפש את שני המספרים שסביבו ותחליף את כל השלישייה במכפלה.
זאת הדרך בה אני פתרתי את התרגיל במקור וקוד הפונקציה בשפת Elixir נראה כך:
def solve_part2(expr) do
cond do
String.contains?(expr, "(") ->
Regex.replace(~r/\(([^()]+)\)/, expr, fn _, expr -> solve_part2(expr) end)
|> solve_part2
String.contains?(expr, "+") ->
Regex.replace(~r/(\d+) \+ (\d+)/, expr, fn
_, op1, op2 -> Integer.to_string(String.to_integer(op1) + String.to_integer(op2))
end)
|> solve_part2
String.contains?(expr, "*") ->
Regex.replace(~r/(\d+) \* (\d+)/, expr, fn
_, op1, op2 -> Integer.to_string(String.to_integer(op1) * String.to_integer(op2))
end)
|> solve_part2
true -> expr
end
end
## הגישה מונחית העצמים: אפשר לשנות את משמעות האופרטורים של השפה
פיתרון יצירתי שלישי וקצר ביותר לתרגיל הזה הוא העמסת אופרטורים. לא כל שפת תכנות מאפשרת את זה אבל אם יש לכם שפה שמאפשרת לשנות את הכפל כך שיחזיר למשל סכום, ואולי אופרטור אחר שיחזיר למשל כפל, אתם יכולים פשוט להחליף את המשמעות של האופרטורים אבל סדר הקדימויות יישאר כמו במקור. הנה דוגמה בשפת רובי:
class Integer
alias_method :real_plus, :+
alias_method :real_mul, :*
def -(other)
self.real_mul(other)
end
def *(other)
self.real_plus(other)
end
end
def solve(expr)
puts(eval(expr.gsub("*", "-").gsub("+", "*")))
end
solve("2 + 2 * 2")
solve("1 + (2 * 3) + (4 * (5 + 6))")
אני מחליף את סימן הכפל בסימן החיסור, ואת סימון החיבור בסימן הכפל, אבל באותו זמן גם מחליף את המשמעות של הסימנים כך שחיסור בעצם יעשה כפל, וכפל בעצם יעשה חיבור. מפעילים eval ונותנים לרובי לסיים את הקסם.1 419
# שלושה פיתרונות יצירתיים ל AoC 2020 Day 18 - סדר פעולות
לתרגיל של יום 18 היה פיתרון אחד אינטואיטיבי ומסורבל והיו עוד הרבה פיתרונות יצירתיים שאיפשרו להגיע לתוצאה הרבה יותר מהר. בפוסט זה אציג את התרגיל בעברית ונראה שלוש דרכים יצירתיות לגשת אליו.
## מה צריך לעשות
המשימה שלנו היא לעזור לחבר בבעיות במתמטיקה. אבל המתמטיקה במחברת של החבר הולכת לפי חוקים שונים ממה שאנחנו מכירים: במקום שכפל יבוא לפני חיבור המצב הפוך ויש לבצע את החיבור לפני הכפל. אין חיסור או חלקי ואפשר להשתמש בסוגריים כדי לקבוע סדר פעולות, ממש כמו אצלנו.
לכן בחשבון המוזר של החבר התרגיל הבא ייתן את התוצאה 8:
2 + 2 * 2
איך זה עובד? קודם מחברים את ה 2 + 2 כדי לקבל 4, ואז את התוצאה כופלים ב-2 ומקבלים שמונה. התרגיל הבא עם סוגריים נותן 51:
1 + (2 * 3) + (4 * (5 + 6))
נתחיל עם הסוגריים הפנימיים בצד ימין, כלומר נחבר 5 ו-6 כדי לקבל 11, אותו נכפול ב-4 כדי לקבל 44, לזה נחבר את תוצאת הסוגריים הבאים (6) ואת 1, סך הכל 44 ועוד 7 שווה 51.
הפיתרון המסורבל לתרגיל יהיה לחלק את השורה לרשימה של מספרים ופעולות ואז לרוץ בלולאה על הרשימה, כל הזמן לזכור מה הדברים הקודמים שראינו, ולהתקדם לאט לאט בפיתרון. אני לא אראה את הגישה הזאת כאן. במקום זה, בואו נראה שלושה רעיונות יותר יצירתיים לפיתרון התרגיל.
## הגישה היוניקסאית: אפשר לכתוב מחשבון עם Bison ו Flex
כיוון ראשון יהיה להשתמש בכלים bison ו flex כדי לכתוב מחשבון חדש לפי איזה חוקים שאנחנו רוצים. הכלי flex לוקח קוד מקור ובאמצעות ביטויים רגולאריים מגדיר איזה "טוקנים" יש בקוד הזה, והכלי bison לוקח את הטוקנים האלה ומגדיר מה לעשות איתם כשמצאנו אותם באמצעות כללי דיקדוק. יש תיעוד טוב על שני הכלים אז אם הקוד יעניין אתכם שווה לחפש על זה. הקוד שאני מראה כאן מבוסס על הקוד הכללי יותר למחשבון שמופיע כאן:
https://github.com/meyerd/flex-bison-example
אני מתחיל עם הקובץ calc.l שהוא הקלט ל flex, כלומר זה הקובץ שמגדיר איזה טוקנים אני הולך לראות:
%option noyywrap
%{
#include <stdio.h>
#define YY_DECL int yylex()
#include "calc.tab.h"
%}
%%
[ \t] ; // ignore all whitespace
[0-9]+ {yylval.ival = atoi(yytext);
\n {return T_NEWLINE;}
"+" {return T_PLUS;}
"*" {return T_MULTIPLY;}
"(" {return T_LEFT;}
")" {return T_RIGHT;}
"exit" {return T_QUIT;}
"quit" {return T_QUIT;}
%%
החלק החשוב בקובץ הוא הסוף שלו: אני מגדיר שמספרים יעדכנו משתנה (שמוגדר בקובץ השני) להכיל את הערך המספרי שלהם; פעולות הפלוס והכפול ייקראו T_PLUS ו T_MULTIPLY ונתתי גם שמות לפעולות הסוגריים, ירידת שורה ולמילים exit ו quit.
הקובץ השני בדוגמה וגם הארוך יותר הוא calc.y. רוב הקוד הוא Boilerplate והתוכן המלא נראה כך:
%{
#include <stdio.h>
#include <stdlib.h>
extern int yylex();
extern int yyparse();
extern FILE* yyin;
void yyerror(const char* s);
%}
%union {
unsigned long ival;
}
%token<ival> T_INT
%token T_PLUS T_MULTIPLY T_LEFT T_RIGHT
%token T_NEWLINE T_QUIT
%left T_MULTIPLY
%left T_PLUS
%type<ival> expression
%start calculation
%%
calculation:
| calculation line
;
line: T_NEWLINE
| expression T_NEWLINE { printf("%ld\n", $1); }
| T_QUIT T_NEWLINE { exit(0); }
;
expression: T_INT { $$ = $1; }
| expression T_PLUS expression { $$ = $1 + $3; }
| expression T_MULTIPLY expression { $$ = $1 * $3; }
| T_LEFT expression T_RIGHT { $$ = $2; }
;
%%
int main() {
yyin = stdin;
do {
yyparse();
} while(!feof(yyin));
return 0;
}
void yyerror(const char* s) {
fprintf(stderr, "Parse error: %s\n", s);
exit(1);
}
החלק החשוב בקובץ זה הוא השורות שמתחילות ב %left. שורות אלה קובעות את סדר הפעולות כאשר מה שמופיע נמוך יותר בקובץ הוא בעל קדימות גבוהה יותר. בדוגמה שלנו השורות:
%left T_MULTIPLY
%left T_PLUS
אומרות שאופרטור פלוס קודם לאופרטור הכפל. החלק המעניין השני הוא הבלוק הבא שקובץ מה כל אופרטור עושה:
expression: T_INT { $$ = $1; }
| expression T_PLUS expression { $$ = $1 + $3; }
| expression T_MULTIPLY expression { $$ = $1 * $3; }
| T_LEFT expression T_RIGHT { $$ = $2; }
;
והוא די מסביר את עצמו.
בשביל להשתמש בשני הקבצים יש להפעיל משורת הפקודה:1 419
# למה קשה כל כך לסיים פרויקט תוכנה
מול החלון שלי הם בונים בניין. בהתחלה עשו הרבה רעש אבל עכשיו פשוט מתקדמים לאט, קומה אחרי קומה. ואני מסתכל עליהם וסופר את הקומות עד שיסיימו וכולי קינאה.
כי כל קומה שהם מסיימים בבניין היא קומה שהם סיימו. פה זה נגמר.
אבל אצלי בקוד? כל פיצ'ר שאני כותב כאילו מזמין פנימה עוד 5 חברים, וזה עוד לפני שדיברנו על באגים. ככל שאני מתקדם בבניין ככה התוכניות ממשיכות להשתנות ובמקביל קומות ישנות מתחילות להתפורר.
בניית בניין מסתיימת כשהדיירים נכנסים, אבל פרויקט תוכנה נגמר כשהמשתמשים יוצאים. וזה אולי ההבדל הגדול בין השניים.
1 419
# מותר להתחרט
"אבל כבר כתבתי את זה", "במה שעובד לא נוגעים" ו"בשביל מה לעבוד כפול?" הם כולם טיעונים טובים נגד Refactoring. המחשבה אומרת שאם אני אשנה עכשיו משהו לא רק שאני חוזר לנקודת ההתחלה ואני לא בטוח אם בכלל הרעיון החדש טוב יותר, אלא שגם אחרי הכתיבה אני אצטרך לעבוד קשה כדי להבין אם שברתי משהו, והכי גרוע - אם הרעיון החדש ייקח יותר זמן ממה שחשבתי שייקח, אני אתקע עם קוד חצי עובד ולא אוכל להתקדם בפיתוח.
מותר להתחרט זה בדיוק הסיפור ההפוך: מותר להתחרט אומר שגם אם היה לי רעיון ועכשיו אני כבר לא בטוח שהוא כזה טוב, מותר לי לשנות את דעתי, מותר לי לנסות משהו אחר ואולי לבחור את הדבר האחר, ואולי להישאר עם הרעיון המקורי. מותר להתחרט, כי רק כשמותר להתחרט אפשר באמת לשחק עם הקוד.
בשביל שיהיה מותר להתחרט צריך מערכות תומכות כמובן: צריך מנגנון בדיקות אוטומטי שמראה לי בלחיצת כפתור אם הרעיון החדש גם עובד, ואם לא אז כמה עוד נשאר לתקן כדי שיעבוד; צריך לדעת לעבוד עם גיט כדי שאפשר יהיה תמיד לברוח חזרה לקוד הישן שעבד, לא משנה כמה שברתי. והכי חשוב צריך לעבוד בסביבה שמקבלת את זה שחלק מהזמן אנחנו הולכים לבלות ב Refactoring ובחשיבה מחדש על דברים שכבר כתבנו. רק כך המערכת כולה גדלה ואנחנו לא נתקעים עם Legacy Code שלאף אחד אין אומץ לשנות.
מותר להתחרט. אפילו שכבר יש לי קוד שעובד.
1 419
# זה לא עוזר לחשוב יותר חזק
בתור מתכנתים אנחנו אוהבים לחשוב שאנחנו יודעים הכל, יכולים הכל, גג חיפוש קצר בגוגל בשביל הסינטקס אבל בגדול - קטן עלינו. כשאתה עובד בתוך עולם תוכן שאת רובו אתה מכיר, והעבודה היא בסך הכל לכתוב עוד מאותם הדברים, קל להתרגל ולחשוב שעם קצת יותר גוגל או עם קצת יותר זמן מחשבה אפשר לפתור כל בעיה.
האמת כמובן יותר מורכבת.
בימים הטובים של CSS לפני כל ה border-radius היה פופולרי בראיונות עבודה לבקש מהמועמד לצייר div ים פינות עגולות. הפיתרון היה לבנות 4 div-ים אחד בתוך השני ולהגדיר לכל אחד תמונת רקע אחרת, ואני לא מאמין שמישהו היה מגיע לזה בלי להכיר את הטריק קודם. זה פשוט לא עוזר לשבת ולחשוב יותר חזק.
או נסו לזהות מתי שני עיגולים על המסך מתנגשים אחד בשני בלי להכיר את משפט פיתגורס. זה לא יעזור לחשוב יותר חזק, צריך לדעת מה המרחק ביניהם.
הבחירה לשבת ולחשוב על הבעיה אולי נראית רעיון טוב, אבל כמעט תמיד היכרות טובה עם עולם התוכן, עם הפיתרונות הקיימים ועם היתרונות והחסרונות של כל גישה הם הדברים שנותנים לנו את הכלים לחשוב וגם להגיע לתוצאות. זה לא עוזר לחשוב יותר חזק אם אתם לא מכירים מספיק טוב את הכלים והטכניקות הקיימים.
1 419
# תאימות אחורה
כך נראה חלק מלוג הקומיטים האחרונים בספריית ini, ספריה לעבודה עם קבצי ini ב JavaScript:
* 20e950e Use Object.create(null) to avoid default object property hazards
* a2c5da8 (tag: v1.3.8) 1.3.8
* af5c6bb Do not use Object.create(null)
* 8b648a1 don't test where our devdeps don't even work
* c74c8af (tag: v1.3.7) 1.3.7
* 024b8b5 update deps, add linting
* 032fbaf Use Object.create(null) to avoid default object property hazards
שימו לב מלמטה למעלה לקומיטים שקשורים ל Object.create:
1. קומיט 032fbaf עבר להשתמש ב Object.create(null)
2. קומיט af5c6bb חוזר ל Object.create רגיל
3. וקומיט 20e950e חוזר ל Object.create(null)
הפונקציה Object.create(null) מחזירה אוביקט חדש וריק, שלא כולל את המאפיינים הרגילים של אוביקטים ב JavaScript, בעוד שהפעלה רגילה של אותה פונקציה (בלי הפרמטר null) מחזירה אוביקט שיורש מ Object ולכן מכיל מספר מאפיינים מובנים של JavaScript. ספריית ini מחזירה אוביקט איתו אפשר לגשת למידע שב ini.
עכשיו מה קרה? אנשים שקיבלו את הספריה רצו לבדוק אם באמת יש שדה מסוים בקונפיגורציה, אבל בשביל לא להתבלבל עם המאפיינים המובנים של JavaScript השתמשו בקוד שנראה כך:
config.hasOwnProperty(blah)
כותב הספריה החליט להעלות תיקון שיגרום לזה שאוביקטי ההגדרות שחוזרים יהיו ריקים, בלי שידע שאנשים שהשתמשו בספריה כבר הגנו על עצמם עם התבנית של hasOwnProperty. התוצאה? קוד ההגנה (עם hasOwnProperty) הפסיק לעבוד, כי באוביקט שחזר גם הפונקציה hasOwnProperty כבר לא קיימת.
והקומיט האחרון שחוזר ל Object.create(null) ? נו, זאת בדיוק הסיטואציה שבגללה פותחים גירסה 2 של הספריה ושוברים תאימות אחורה.
שימו לב בעבודה עם npm לשידרוגים מהסוג הזה. מצד אחד כל ה toolchain מעודד אותנו לעדכן כל הזמן את החבילות (שזה אחלה), אבל מצד שני בכל עדכון צריך לבדוק טוב מה אנחנו שוברים ומה הסיפור סביב כל מנגנון שמשתנה. וכן זו עוד תזכורת לכמה חשוב לכתוב בדיקות יחידה ולצמצם ב mock-ים.1 419
# פיתרון מוסבר ל Advent Of Code 2020 יום 5
כמו בכל דצמבר גם החודש אריק ווסטל מפרסם את חידות התכנות שלו כל יום. בינתיים עברנו קצת יותר משבוע וזה זמן טוב לקחת אוויר ולשתף חידה לדוגמה שאולי תעשה לכם חשק להצטרף לשבועיים וחצי שנשארו. אנחנו מסתכלים על החידה והפיתרון של יום 5 ובשפת רובי (למרות שהקוד קצר וקל לדמיין את התרגום לשפות אחרות).
## החידה
מגיפת הקורונה הסתיימה וחברת התעופה היחידה ששרדה את התקופה משתמשת בשיטת הושבה ייחודית שמטרתה היחידה לשגע את הנוסעים: על כל כרטיס במקום לכתוב את מספר השורה כתובות הוראות איך להגיע לשורה המתאימה.
ההוראות מורכבות משתי האותיות F ו B כאשר F אומרת ללכת לחלק הקדמי של המטוס ו B לחלק האחורי, אבל זה לא נגמר שם. האות F או B הבאה אומרת ללכת לחלק הקדמי או האחורי של חצי המטוס אליו הגעתם מההוראה הקודמת, וכך הלאה עד שנשארה שורה אחת - היא השורה שלכם.
לדוגמה אם במטוס יש 128 שורות אז ההוראות FBFBBFF יובילו אותנו כך:
1. ה F הראשון שולח אותנו לקידמת המטוס כלומר אנחנו יודעים שצריך לשבת בין שורה 0 ל 63.
2. ה B שאחריו לוקח אותנו לחלק האחורי בחצי המטוס שלנו, כלומר צריך לשבת בין שורה 32 ל 63.
3. ה F הבא בתור אומר שצריך ללכת לחלק הקדמי של חצי המטוס שלנו, כלומר בין שורה 32 ל 47.
4. ה B הבא שולח אותנו לחלק האחורי של חצי המטוס שלנו, כלומר בין שורה 40 ל 47.
5. ה B הבא מצמצם את הטווח לשורות 44 עד 47.
6. ה F הבא מצמצם את הטווח לשורות 44 עד 45.
7. וה F האחרון שולח אותנו למקום בשורה 44.
אפשר רק לדמיין את הריבים והצעקות כשאנשים ישבו במקומות אחד של השני וינסו להסביר שזה בעצם המקום שלהם. אגב בשביל להוסיף לבלבול גם מספר הכסא בתוך השורה מקודד באותו האופן הפעם עם האותיות L ו R כש L אומרת ללכת שמאלה לחלק התחתון של המספרים ו R ימינה לחלק העליון (המושבים בכל שורה ממוספרים בין 0 ל 7).
עכשיו לתרגיל - בהינתן רשימה של כרטיסי טיסה כאלה, מיצאו את הכרטיס עבורו מספר השורה כפול שמונה ועוד מספר העמודה ייתן את הערך המקסימלי.
## הטריק
מבט ראשון על התרגיל עשוי להיות מבהיל - איך נתחיל לארגן שורות ולחלק אותן בשביל למצוא את המספר הנכון? איך בכלל מוצאים את האינדקסים ואת האמצע?
אבל במבט שני כבר אפשר לראות שכל הסיפור הזה של כרטיסי טיסה ואזורים במטוס הוא בסך הכל דרך מתוחכמת לתאר מספר בינארי ואחרי שאנחנו מבינים את זה פיתרון החידה הוא כבר עניין של דקות:
1. שורה היא מספר בייצוג בינארי בין 0 ל 127
2. כל F מייצג את הסיפרה 0
3. כל B מייצג את הסיפרה 1
הקוד מהדוגמה מייצג את המספר הבינארי 0101100 שערכו 44.
## פיתרון ברובי
נכתוב את הפונקציות הבאות לפיתרון התרגיל בשפת רובי:
def row(seat)
seat.gsub("F", "0").gsub("B", "1").to_i(2)
end
def column(seat)
seat.gsub("L", "0").gsub("R", "1").to_i(2)
end
def seat_id(seat)
row(seat[0..6]) * 8 + column(seat[7..9])
end
הפונקציה row מקבלת מחרוזת, הופכת את ה F-ים וה B-ים לספרות בינאריות ואז מתרגמת את המחרוזת שהתקבלה למספר לפי בסיס 2. הפונקציה column עושה את אותו דבר עם L ו R, והפונקציה seat_id מקבלת כרטיס טיסה מלא, לוקחת את האותיות של השורה ל row, את האותיות של העמודה ל column ומחברת את הערכים כדי לקבל את המזהה שאנחנו מחפשים.
בהנחה שיש לכם רשימת כרטיסי טיסה בתיקיית input בקובץ day5.txt הקוד הבא יקרא את כולם וידפיס את המזהה הגדול ביותר:
lines = File.read("input/day5.txt").each_line
max_id = lines.map {|line| seat_id(line) }.max
puts max_id
החלק השני של התרגיל קצת יותר מסובך ובו יהיה עליכם למצוא את כרטיס הטיסה היחיד שחסר בערימה, זה הכרטיס שלכם. לתרגיל המקורי ותיאור מלא של החלק השני כדאי לבקר ב:
https://adventofcode.com/2020/day/5
וכמובן אם אתם אוהבים חידות מהסוג הזה אריק ימשיך לפרסם אותן כל החודש, עם חידה חדשה באתר כל יום בשבע בבוקר שעון ישראל. לרשימת כל החידות בקרו ב Advent Of Code 2020.1 419
# קירות דימיוניים
אף אחד לא מוותר על צחצוח שיניים כי "לא היה זמן הבוקר" או יספר לכם שהוא כבר שבוע לא אכל כי הוא עסוק מדי בשביל ארוחות. יש דברים שאנחנו פשוט עושים.
המשחק נהיה מעניין כשזה מגיע לדברים שאנחנו רוצים או יודעים שצריך לעשות אבל לא מוצאים את הזמן. דברים כמו ספורט, לימודים או כתיבת פרויקט צד: כולן משימות של שיפור עצמי שאנחנו לא עושים ומספרים לעצמנו שהבעיה היא זמן.
פידבק שחוזר בקורסי הבוטקמפ שאני מעביר פה מדי פעם ותמיד משמח אותי מחדש (למרות שלפעמים נאמר בכעס): "בגללך אני כבר שבועיים קם שעה קודם", או "חזרתי לשבת על המחשב בלילות כדי ללמוד". כשאנחנו מוצאים את המחסום האמיתי ומצליחים לשבור אותו, יש לנו יכולת מופלאה למצוא זמן לשיפור עצמי. זמן אף פעם לא היה הבעיה.
למה אתם מתכוונים כשאתם אומרים שלא היה לכם זמן ללמוד השבוע? ועל מה צריך להתגבר כדי שתמצאו את הזמן הזה בשבוע הבא?
1 419
# במקרה הגרוע ביותר
ברגע שאנחנו מתחילים לשקול מה נעשה במקרה הגרוע ביותר כבר שמנו על השולחן את דרך הפעולה הזאת והגדלנו משמעותית את הסבירות שהיא תקרה. דוגמאות? בבקשה-
"יש לי מצגת להעביר עוד שבוע על נושא שאני לא בקיא בו. אנסה למצוא זמן להתאמן ובמקרה הגרוע ביותר פשוט אקריא מהשקפים"
"יש לנו דמו שבוע הבא והמוצר עדיין לא עובד פיקס. ננסה לתקן ובמקרה הגרוע נישאר במשרד לילה או שניים כדי להגיע מתוקתקים לדמו"
"אני צריך לכתוב בדיקות יחידה למוצר הזה שיש לו המון תלויות חיצוניות. אנסה לחשוב על רעיון ובמקרה הגרוע ביותר אכתוב פשוט Mock-ים לכל הקוד החיצוני"
בכל אחד מהמצבים רק זה שאני לוקח בחשבון או נערך למקרה הגרוע ביותר כבר שם לי אותו מול העיניים ואני מתחיל להיערך לאותו מקרה ולהכין את עצמי לכל אחת מההתמודדויות. טריק טוב שעוזר להתמודד עם החרדה ולא מייצר סיכונים מיותרים הוא להמציא תרחישים שאתם כן מוכנים לחיות איתם גם למקרה הגרוע ביותר:
"יש לי מצגת להעביר עוד שבוע על נושא שאני לא בקיא בו. אנסה למצוא זמן להתאמן ובמקרה הגרוע ביותר אגיד שאני חולה ואדחה את המצגת בכמה ימים כדי להגיע מוכן."
"יש לנו דמו שבוע הבא והמוצר עדיין לא עובד פיקס. נמשיך לנסות לתקן ובמקרה הכי גרוע נוותר על חלק מהפיצ'רים כדי להגיע מתוקתקים לדמו"
"אני צריך לכתוב בדיקות יחידה למוצר הזה שיש לו המון תלויות חיצוניות. אני עדיין לא בטוח איך לעשות את זה ובמקרה הגרוע ביותר ייקח לי עוד כמה ימים עד שאגיע לפיתרון טכנולוגי שאני שלם איתו".
בחירת הסיפור למקרה הגרוע ביותר היא לגמרי בידיים שלכם והיא עשויה להשפיע לטובה על כל תהליך הפיתוח.
1 419
# נהגי מוניות ונגני ג'אז
אם נהג מונית לוקח אותך בדרך קיצור סודית ובסוף הדרך לקחה כפול זמן (והמחיר בהתאם), קשה להאמין שתרצה לתת לו טיפ.
אבל כשנגן ג'אז מאלתר בהופעה אנחנו נצא עם הרגשה טובה, גם אם הקטע באלבום נשמע טוב יותר.
בתור פרילאנסרים אין לנו את החופש להיות נהגי מוניות מהגיהנום. כן יש לנו את החופש לבחור איזה סוג אינטרקציות אנחנו מוכרים ללקוחות ולפי זה לעזור לאנשים שמחפשים את הדבר הזה למצוא אותנו. אם אתם מסוג האנשים שאוהבים לנסוע בדרכים חדשות, זה לא הגיוני להיכנס כנהגים ב Gett. עדיף להתאמץ למצוא את הנוסעים שיש להם סטיה דומה.
1 419
# השיטה הבטוחה לבנות ביטוי רגולארי לכל טקסט
בחידה של לפני כמה ימים ב Advent Of Code קיבלנו משימת עיבוד טקסט שהזכירה לי כמה כיף לעבוד עם ביטויים רגולאריים, ואת השיטה הבטוחה לבנות ביטוי רגולארי מוצלח לכל מחרוזת טקסט. בחידה המקורית קיבלנו מחרוזת טקסט שנראית כך:
1-3 l:hello world
החלק שמשמאל לנקודותיים מתאר כלל והחלק שמימין לנקודותיים מתאר שורת טקסט. הכלל תמיד מורכב משני מספרים ואות. המספרים מגדירים כמה פעמים האות צריכה להופיע במחרוזת שבצד ימין (מינימום ומקסימום). במחרוזת הדוגמה אנחנו רוצים לוודא ש l מופיעה בין פעם אחת ל-3 פעמים בטקסט hello world.
קל מאוד לכתוב פונקציה בפייתון שעונה על השאלה, אם אני מקבל את המידע מופרד:
from collections import Counter
def valid(min_count, max_count, char, text):
min_count = int(min_count)
max_count = int(max_count)
return min_count <= Counter(text)[char] <= max_count
אבל - איך נשבור את הטקסט לארבעת הפרמטרים? כאן נכנסת השיטה הבטוחה שתמיד עובדת לכתיבת ביטויים רגולאריים:
1. מתחילים עם הטקסט המקורי
2. מחליפים כל חלק שיכול להשתנות בביטוי שמייצג את התבנית הכללית
3. נותנים שם לכל חלק
במקרה שלנו מה שיכול להשתנות זה המספרים (תבנית של סיפרה היא [0-9] ואם רוצים כמה ספרות מוסיפים + אחרי), האות (תבנית של אות היא [a-z]) והטקסט שאחרי הנקודותיים (שם יכול להיות כל דבר כלומר .+. לכן הביטוי הרגולארי יהיה:
[0-9]+-[0-9]+ [a-z]:.*
עכשיו נמשיך לתת שם לכל אחד מהחלקים:
(?P<min_count>[0-9]+)-(?P<max_count>[0-9]+) (?P<char>[a-z]):(?P<text>.*)
ואנחנו מוכנים להשתמש בקוד מתוך Python:
pattern = re.compile(r'(?P<min_count>[0-9]+)-(?P<max_count>[0-9]+) (?P<char>[a-z]):(?P<text>.*)')
if m := pattern.search("1-3 l:hello world"):
print(valid(**m.groupdict()))
else:
print("String does not match format")