ToCode
Відкрити в Telegram
1 419
Підписники
Немає даних24 години
Немає даних7 днів
Немає даних30 день
Архів дописів
1 419
# לולאות באגים
זה התחיל כמו רעיון טוב, הם תמיד מתחילים ככה. מוציאים את זה מפה ומחברים את ההוא לשם והכל יעבוד. שלושה חודשים אחרי ואתה עדיין רודף אחרי באגים, מרגיש כמו בלולאת זמן ומשחק את אותו יום בהילוך חוזר.
ועכשיו מה? להחזיר את המפתחות? לזרוק עבודה של שלושה חודשים? ואולי אנחנו כמעט שם?
לולאות באגים כאלה הן בעיה אמיתית. הן יכולות לשאוב גם מתכנתים טובים, והן תמיד מטעות. בכל נקודה קל לראות את הבאג הבא, או את הדבר הבא שצריך לעשות כדי למצוא את הבאג הבא; אבל אף פעם אי אפשר לדעת מתי או אם בכלל נגיע לסוף הדרך. קצת כמו גירסה זדונית של ממשק הגלילה האינסופי בפייסבוק.
פיתרון שהייתי שמח לראות לסיפור הזה קשור להערכות זמנים: כמו שמנהלים מבקשים ממתכנתים הערכה כמה זמן ייקח לבנות פיצ'ר מסוים, נרצה שאותם מנהלים יוכלו לשקף כמה זמן פיצ'ר מסוים שווה להם. נגמר הזמן? שמים בצד את מה שעשינו ועוברים לדברים אחרים.
נקסט!
1 419
# טיפים לגיוס מוצלח דרך upwork
אפוורק הוא אתר לגיוס מתכנתים פרילאנסרים. בעולם פיתוח ווב המדינות המככבות שם הן הודו ואוקראינה והמחירים נעים בין 20 ל 50 דולר לשעה. מה שהופך את אפוורק לכל כך מוצלח הוא הליך הגיוס המהיר - בעוד שאם נרצה לגייס מתכנתת שכירה לעבודה נצטרך לעבור ברוב החברות שבעה מדורי גיהנום (ואז עוד קצת), באפוורק אפשר להתחיל לעבוד הרבה פעמים תוך יום או יומיים, ואם אין כימיה אפשר גם לחתוך מהיום להיום.
אם יש לכם פרויקט ורוצים לנסות לגייס אנשים אני ממש ממליץ לנסות את אפוורק. הנה כמה טיפים שעזרו לי בגיוסים ואני מקווה שיעזרו גם לכם:
1. פרסמו מודעה ברורה עם דרישות כמה שיותר מפורטות. עדיף לגייס פרילאנסר למשימה ספציפית בפרויקט ואז להמשיך איתו הלאה, מאשר לנסות לגייס "מתכנת ווב" שיעשה הכל מהכל.
2. אחרי פירסום המודעה תקבלו אינסוף פניות, כולל מאנשים לא רלוונטים. מאלה שנראים לכם רלוונטים בקשו לראות קוד. לפרילאנסרים רציניים שם יש חשבון גיטהאב או פרויקטים שעבדו עליהם כבר והם יכולים לשתף, או לפחות פרויקטי דוגמה שכתבו.
3. בקריאת הקוד עדיף לקבל קוד קטן וטוב מאשר גדול ורע. תניחו שהפרילאנסר שתיקחו לא הולך לשנות את סגנון העבודה שלו בשבילכם.
4. שיחת זום קצרה יכולה להגיד לכם המון על הבן אדם ועל התקשורת שלכם איתו. תקשורת היא אפילו יותר חשובה מאיכות הקוד, וכבר נפלתי כמה פעמים בגיוס פרילאנסר עם קוד טוב ואנגלית גרועה.
5. המחיר לא חשוב - במובן הזה שפרילאנסר טוב שווה לקחת גם במחיר יותר גבוה, ופרילאנסר גרוע לא כדאי לקחת לא משנה כמה הוא זול. זיכרו שהפרילאנסר לא הולך להשתנות בשבילכם.
6. המטרה שלנו בגיוס פרילאנסר היא למצוא כמה שיותר מהר בן אדם שכותב קוד טוב ויש לנו תקשורת טובה איתו. תמיד אפשר לחתוך כשזה לא הולך (וקרה לי גם שפרילאנסרים נעלמו לי באמצע עבודה).
7. הכינו את הפרויקט שלכם. אתם לא רוצים שהפרילאנסר יבזבז זמן בהתקנות או בלהבין מה צריך לעשות. עדיף להכין מראש קבצי דוקר או מסמך הוראות מפורט שמסביר מה צריך לעשות בשביל להתחיל לעבוד.
8. דירשו תיעוד מסודר של העבודה ורק דרך הפלטפורמה של אפוורק. שווה לזכור שאתם לא באמת יודעים מי הבן אדם שעובד אתכם, ולפחות בהתחלה תיעוד טוב של השעות מוריד את הסיכון לרמאויות.
1 419
אז מה, לוותר? לא להתכונן לשום דבר? לכתוב קוד כאילו אין מחר כי ממילא כל מה שנכתוב נצטרך לשכתב? אני לא הייתי הולך לשם. המסקנה שלי משלושת הגישות שראינו היא שכדאי להתאמץ ולכתוב קוד פשוט - קוד שקל להבין אותו וקל לשנות אותו כשנצטרך.
שווה גם להוסיף בדיקות לקוד הפשוט הזה. העובדה שהיה לי קלט לדוגמה שיכלתי להפעיל וידעתי בדיוק מה המספר שאני אמור לקבל אחרי 80 ימים עזרה לי מאוד להריץ עוד ועוד גישות ופיתרונות ולארגן מחדש את הקוד עד שמגיעים לתוצאה עובדת.
המציאות היא לא חברה. היא תמיד תפתיע עם דרישות שלא ידענו שהולכות להגיע ותמיד תקלקל בדיוק כשדברים מתחילים לעבוד. הדרך להתמודד איתה היא לא לקחת ללב, למחוק קוד ולכתוב קוד חדש שיתאים טוב יותר למציאות החדשה שגיליתם.
1 419
def grow_up(self):
if self.time_to_reproduce == 0:
self.time_to_reproduce = 6
self.fish_tank.create_fish(8)
else:
self.time_to_reproduce -= 1
def __repr__(self):
return f"{self.time_to_reproduce}"
class FishTank:
def __init__(self, fish_initial_states):
self.fish = [Fish(i, self) for i in fish_initial_states]
def create_fish(self, time_to_reproduce):
self.fish.append(Fish(time_to_reproduce, self))
def count(self):
return len(self.fish)
def play(self):
for fish in self.fish[:]:
fish.grow_up()
################
fish = []
with open('demo.txt') as f:
fish = [int(x) for x in f.read().split(',')]
tank = FishTank(fish)
for i in range(80):
tank.play()
print(tank.count())
הפיתרון מונחה העצמים יצא (כצפוי) הכי ארוך מכל השלושה. הלוגיקה המרכזית של הגדילה מצאה את מקומה במחלקה Fish והאקווריום אחראי על יצירת דגים חדשים. גם פה לא ברחנו משכפול המערך לפני האיטרציה ב play, אבל בגלל מבנה הקוד יותר קשה לראות את הצורך בשכפול הזה. שימו לב לפונקציה:
def play(self):
for fish in self.fish[:]:
fish.grow_up()
מקריאת שלושת השורות לא ברור למה אני צריך לשכפל את self.fish לפני שאני קורא ל grow_up. רק אם מדפדפים למעלה אפשר לראות שלכל דג יש בעצם חיבור לאקווריום בו הוא נמצא ושהפונקציה grow_up עלולה לשנות את מערך הדגים.
במכשול היותר רציני נתקלתי כשהגעתי לכתוב את האופטימיזציה. בעולם מונחה העצמים, כדי לחלק את הדגים לקבוצות הייתי מעדיף להשתמש במבנה הקיים - כלומר לבנות 10 אקווריומים. כל אקווריום יכיל דגים שמתאימים בגיל, ואפשר יהיה להעביר דגים בין אקווריומים שונים. נכון, אני אמחק את המחלקה Fish ואצטרך לשנות קצת את FishTank, אבל היי זה יכול אפילו לעבוד.
הניסיון הראשון שלי לפיתרון נראה כך אבל הוא לא עבד:
class FishTank:
def __init__(self, fish_count, next_tanks):
self.fish = fish_count
self.next_tanks = next_tanks
def create_fish(self):
self.fish += 1
def count(self):
return self.fish
def play(self):
for fish_tank in self.next_tanks:
fish_tank.fish += self.fish
self.fish = 0
def __repr__(self):
return f"{self.fish}"
################
fish = []
with open('demo.txt') as f:
fish = [int(x) for x in f.read().split(',')]
tanks = [FishTank(0, []) for i in range(10)]
for ti in range(9, 1, -1):
tanks[ti].next_tanks = [tanks[ti - 1]]
tanks[0].next_tanks = [tanks[6], tanks[8]]
for i in fish:
tanks[i].create_fish()
for i in range(80):
for ti in range(9, 0, -1):
tanks[ti].play()
print(sum(t.count() for t in tanks))
מה שקורה כאן זה שכשאני מעביר דגים מאקווריום אחד לשני הם בעצם מתווספים לדגים שכבר נמצאים באקווריום השני ואז אי אפשר להבדיל בינם לבין הדגים שהיו שם קודם (אותם צריך להעביר לאקווריום הבא בתור). במילים אחרות, היינו רוצים להעביר את כל הדגים מכל אקווריום לזה שאחריו באותו זמן, אבל הקוד שלי עושה את זה אקווריום אחרי אקווריום. זאת הלולאה הבעייתית:
for i in range(1):
for ti in range(9, 0, -1):
tanks[ti].play()
ואז בתוך play יש לנו:
def play(self):
for fish_tank in self.next_tanks:
fish_tank.fish += self.fish
self.fish = 0
כשמבינים את הבעיה אפשר גם לפתור אותה באותה דרך שהיא נפתרה בשיטות הקודמות, כלומר הקוד הראשי יהיה:
for i in range(80):
grown_up_fish_count = tanks[0].count()
tanks = [*tanks[1:], FishTank(0, [tanks[-2]])]
tanks[6].fish += grown_up_fish_count
tanks[8].fish += grown_up_fish_count
print(tanks)
אבל האמת שאיך שלא מסובבים את זה הפיתרון מונחה העצמים לא באמת נתן ערך. כתיבת המחלקות וחלוקת העבודה ביניהן רק קיבעה אצלי דרך מחשבה מסוימת ולא שרדה יפה את האופטימיזציה של החלק השני.
## שורה תחתונה1 419
הבעיה ששידרוג מהפיתרון שכתבתי לפיתרון שמחלק את הדגים לקבוצות אומר בעצם להחליף את כל הקוד. הדבר היחיד שאולי אוכל להשתמש בו זו הפקודה שקוראת את הקלט מהקובץ. כל השאר - המעבר על הדגים, הורדת 1 ממספר הימים של כל דג, בדיקה כמה ימים נשארו - כל זה צריך ללכת.
הקוד המעודכן שלי נראה כך:
fish = [0] * 10
with open('demo.txt') as f:
fish_ages = [int(x) for x in f.read().split(',')]
for age in fish_ages:
fish[age] += 1
def play():
global fish
new_fish = [*fish[1:], 0]
new_fish[6] += fish[0]
new_fish[8] += fish[0]
fish = new_fish
for i in range(80):
play()
print(sum(fish))
והוא כבר פותר בלי בעיה את התרגיל גם עבור 256 ימים.
## פיתרון 2 - הגישה הפונקציונאלית
היתרון של הגישה הנאיבית היה שקל לכתוב אותה. הגירסה הראשונה תאמה פחות או יותר לאיך שחשבתי על השאלה בקריאה הראשונה שלה, והאתגר היחיד היה לראות את הצורך לשכפל את המערך כדי לא לעדכן אותו באמצע הלולאה. אבל החיסרון של הגישה הנאיבית הוא שיש רק קשר קלוש בין שני החלקים של השאלה. בגלל הצורך באופטימיזציה, הייתי צריך לזרוק כמעט את כל הקוד שכתבתי.
המחשבה הבאה שאפשר להציע היא שאולי אם ניקח גישה יותר מתוחכמת לקידוד, אולי אם נארגן את הקוד אחרת למשל בתור פונקציות קטנות, אז כן נוכל להשתמש מחדש בקוד שכתבנו או לפחות בחלקים ממנו בעת מימוש האופטימיזציה. אז הלכתי לכתוב פיתרון פונקציונאלי, או לפחות כזה שעונה על האילוצים של תכנות פונקציונאלי: אין משתנים גלובאליים, אין Mutable Data, וכל הקוד כתוב בתור פונקציות קטנות. זה מה שיצא:
def flat_map(f, items):
output = []
for i in items:
res = f(i)
if type(res) == list:
output.extend(res)
else:
output.append(res)
return output
def process_fish(single_fish):
if single_fish == 0:
return [6, 8]
else:
return single_fish - 1
def play(fish):
return flat_map(process_fish, fish)
def part1():
fish = []
with open('demo.txt') as f:
fish = [int(x) for x in f.read().split(',')]
for i in range(80):
fish = play(fish)
print(len(fish))
part1()
הפונקציה process_fish מקבלת דג ומחזירה את הדג או הדגים שצריך לשים במקומו, כאשר דג מיוצג על ידי מספר שהוא כמה ימים נשארו עד שיתרבה. הפונקציה play מפעילה את flat_map שמעדכנת את מערך הדגים עם תוצאת הפעלת process_fish על כל דג, ואת הפונקציה flat_map מימשתי לבד כיוון שאין כזאת בפייתון.
הקוד עובד. מחזיר תוצאה נכונה עבור 80 ימים ונופל עם 256 ימים כצפוי. וגם אותו צריך לזרוק לפח כשרוצים לתקן את בעיית הביצועים. זאת הגירסה המתוקנת:
def play(groups):
new_groups = [*groups[1:], 0]
new_groups[8] += groups[0]
new_groups[6] += groups[0]
return new_groups
def group_fish(fish):
groups = [0] * 10
for f in fish:
groups[f] += 1
return groups
def part2():
fish = []
with open('demo.txt') as f:
fish = [int(x) for x in f.read().split(',')]
fish = group_fish(fish)
for i in range(80):
fish = play(fish)
print(sum(fish))
part2()
פונקציית ה main והמבנה הכללי נשאר כמו שהיה, אבל את play שיניתי לגמרי, את flat_map שכל כך התאמצתי לכתוב זרקתי, את process_fish וה if שבתוכה מחקתי והוספתי פונקציה חדשה שמארגנת את הדגים לפי קבוצות.
הגישה הפונקציונאלית לא הביאה לשיפור משמעותי בכמות העבודה שאני צריך בשביל לממש את האופטימיזציה. גם איתה הייתי צריך לשנות חלק משמעותי מהקוד. היא כן אפשרה לי להתעלם לגמרי מהסיפור של עדכון מערך באמצע לולאה בגלל שכל המידע הוא Immutable, שזה נחמד.
מצד שני בגלל חוסר תמיכה של השפה (אין flat_map) הייתי צריך להתאמץ ולכתוב יותר קוד בשביל להגיע לתוצאה ובסופו של דבר הפיתרון הפונקציונאלי היה ארוך יותר מהפיתרון הנאיבי.
## פיתרון 3 - פיתוח מונחה עצמים
אם קוד פונקציונאלי לא חסך לי את העבודה בשכתוב, אולי קוד מונחה עצמים יצליח להציל את המצב. מיד הלכתי לפתור את החלק הראשון בגישה מונחית עצמים עם מחלקה של דג ומחלקה נוספת לאקווריום וקיבלתי:
class Fish:
def __init__(self, time_to_reproduce, fish_tank):
self.time_to_reproduce = time_to_reproduce
self.fish_tank = fish_tank1 419
# סיפור קצת מדכא על דגים ושינויים בקוד
אם רק החיים היו יותר פשוטים. באמת. כי חיים פשוטים מובילים לקוד פשוט, ולהיפך, כשהחיים מסתבכים הקוד מסתבך איתם. יש אנשים שיגידו לכם שאם רק תכתבו קוד פונקציונאלי, או קוד מונחה עצמים, או בשפה כזאת או אחרת, אז הקוד יהיה פשוט ויתמודד יפה עם שינויים.
הם טועים.
קוד מסתבך כי החיים מסתבכים כמו שנראה תכף בדוגמה מ Advent Of Code. אבל אל תדאגו יש גם נקודת אור בסוף הסיפור. וגם פייתון, כי צריך קצת הפסקה מכל הקלוז'ר.
## מה אנחנו בונים
יום 6 של Advent Of Code האחרון התחיל בים (כל הימים עד עכשיו התחילו בים, אז זה לא מפתיע). אנחנו בצוללת ופוגשים להקה של דגים, ורוצים לגלות כמה מהר הדגים מתרבים. דג צריך יומיים כדי להגיע לבגרות, ואז עוד 6 ימים כדי להוליד דג חדש.
אה, ודגים לא מתים.
לדוגמה אם יש לנו להקה שגילאי הדגים בה הם:
3, 2, 3, 5, 4
אז הדג הראשון יוליד דג חדש בעוד שלושה ימים, ואז שוב שישה ימים לאחר מכן, ואז שוב שישה ימים לאחר מכן.
הדג השני יוליד דג חדש בעוד 4 ימים, ואז שוב שישה ימים לאחר מכן, ושוב שישה ימים לאחר מכן.
כל אחד מהדגים החדשים ייקח יומיים לגדול, ואז אחרי שישה ימים יוליד גם הוא דג חדש.
נוח במקום להסתכל על גילאי הדגים, להסתכל על כמה ימים נשארו לכל דג עד שיוליד דג חדש, ואז אפשר לדמיין מערך שמשתנה באופן הבא:
Initial state: 3,4,3,1,2
After 1 day: 2,3,2,0,1
After 2 days: 1,2,1,6,0,8
After 3 days: 0,1,0,5,6,7,8
After 4 days: 6,0,6,4,5,6,7,8,8
After 5 days: 5,6,5,3,4,5,6,7,7,8
After 6 days: 4,5,4,2,3,4,5,6,6,7
After 7 days: 3,4,3,1,2,3,4,5,5,6
After 8 days: 2,3,2,0,1,2,3,4,4,5
After 9 days: 1,2,1,6,0,1,2,3,3,4,8
After 10 days: 0,1,0,5,6,0,1,2,2,3,7,8
After 11 days: 6,0,6,4,5,6,0,1,1,2,6,7,8,8,8
After 12 days: 5,6,5,3,4,5,6,0,0,1,5,6,7,7,7,8,8
After 13 days: 4,5,4,2,3,4,5,6,6,0,4,5,6,6,6,7,7,8,8
After 14 days: 3,4,3,1,2,3,4,5,5,6,3,4,5,5,5,6,6,7,7,8
After 15 days: 2,3,2,0,1,2,3,4,4,5,2,3,4,4,4,5,5,6,6,7
After 16 days: 1,2,1,6,0,1,2,3,3,4,1,2,3,3,3,4,4,5,5,6,8
After 17 days: 0,1,0,5,6,0,1,2,2,3,0,1,2,2,2,3,3,4,4,5,7,8
After 18 days: 6,0,6,4,5,6,0,1,1,2,6,0,1,1,1,2,2,3,3,4,6,7,8,8,8,8
המשימה שלנו היא לכתוב את הקוד שמחשב כמה דגים יהיו אחרי X ימים. בשלב הראשון צריך למצוא כמה דגים יהיו אחרי 80 ימים, ואחרי שזה עובד מקבלים את משימת ההמשך שמעלה את המספר ל 256 ימים.
## פיתרון 1 - גישה פרוצדורלית
לפיתרון הראשון אני קורא הגישה הנאיבית או הגישה הפרוצדורלית. זה פיתרון שהרבה מתכנתי פייתון שאני מכיר ירגישו איתו הכי בבית. בגישה הזאת אנחנו כותבים פונקציות כשזה עוזר לנו, אנחנו משתמשים ב List Comprehension כשזה עוזר לנו, אבל גם לא מפריע לנו לשנות אוביקטים בזיכרון או לכתוב קוד מחוץ לפונקציות.
במקרה של הדגים אני צריך לקרוא את זמני ההתרבות של הדגים מקובץ קלט, אחר כך לשים הכל במערך ואז כל יום אני רץ על המערך ומוריד ב-1 את הזמן עד להתרבות של כל דג. אם הזמן הגיע לאפס אני מוסיף דג חדש למערך. זה הקוד:
fish = []
with open('demo.txt') as f:
fish = [int(x) for x in f.read().split(',')]
def play():
for index, remaining in enumerate(fish[:]):
if remaining == 0:
fish.append(8)
fish[index] = 6
else:
fish[index] -= 1
for i in range(80):
play()
print(len(fish))
יש פה פחות מ 20 שורות והתשובה שלו נכונה. צריך רק לשים לב שבלולאה אני משכפל את המערך לפני שאני רץ עליו, כדי לא להוסיף דגים למערך מתוך איטרציה (מה שיגרום לבלאגן בים וגם בפייתון).
## חלק שני - ההתרסקות והתיקון
הבעיה עם הפיתרון הנאיבי מופיעה בחלק השני, כשמעלים את מספר הימים ל 256. יש יותר מדי דגים והקוד לא מצליח להתמודד עם מערך כל כך גדול ופשוט ממשיך לרוץ המון המון זמן בלי להגיע לתוצאה.
הדרך לפתור את התרגיל למספר גדול של ימים היא לארגן את הדגים בקבוצות, כלומר כל הדגים שיש להם עוד 3 ימים עד שמייצרים דג חדש ישבו בתא אחד במערך, כל אלה שצריכים עוד 4 ימים כדי להתרבות ישבו בתא אחר במערך וכך הלאה. סך הכל יהיה לי מערך של 8 תאים שבכל תא יהיה כתוב כמה דגים יש בקבוצה הזאת. בכל יום שעובר אני מעביר את הדגים תא אחד אחורה במערך, ואלה שהיו בתא 0 נכנסים גם לתא 6 כדי להתחיל סיבוב חדש וגם לתא 8 בתור דגים חדשים.1 419
# עובד
זה יעבוד גם מחר בבוקר?
זה יעבוד גם אחרי שידרוג גירסה?
זה יעבוד גם אחרי Refactoring?
זה יעבוד גם עם נתונים של פרודקשן?
זה יעבוד גם כשנעבור למערכת הפעלה אחרת?
זה יעבוד גם עבור משתמשים מהודו?
זה יעבוד גם אחרי שנחזור מגיבוי?
זה יעבוד גם כשיהיה עומס על המערכת?
שום מערכת לא עובדת בכל המצבים. ככל שנקפיד להיות ברורים לגבי המגבלות של הקוד שלנו, כך יהיה יותר קל להשתמש בו (ולשבור אותו) במערכות אמיתיות.
1 419
# תוכנית וובינרים לרבעון הבא
הי חברים,
באיחור קל אני שמח להזמין אתכם להצטרף לאחד הוובינרים הקרובים שלנו. את הוובינרים אני מעביר כל יום חמישי בעשר בבוקר, והקלטות שלהם זמינות כאן באתר בדף הקלטות מוובינרים.
אני משתדל לבחור נושאים שקשורים לנושאי הקורסים ומרחיבים אותם. אם יש לכם רעיונות או חלומות על נושאים שהייתם רוצים ללמוד שלחו לי מייל ואנסה להוסיף אותם בחודשים הבאים. זאת הרשימה לשלושת החודשים הקרובים:
## פיצ'רים חדשים בריאקט 18 - וובינר שיתקיים ב 7.7.2022
לא מזמן יצאה גירסה חדשה של ריאקט עם תמיכה ברינדור מקבילי. בעברית זה אומר שריאקט יכול להתחיל לחשב את עץ ה Virtual DOM, ואז להחליט שיש משהו יותר חשוב, לשים הכל בצד, לחשב עץ Virtual DOM אחר חשוב יותר ואחרי שיסיים ויעדכן את המסך יחזור לעץ ששם בצד.
בוובינר אנחנו נחשוב למה הפיצ'ר הזה חשוב, איזה דברים אפשר עכשיו לעשות שקודם היו מסובכים ואיזה בעיות חדשות הוא מביא. כמובן שלכל מצב נראה דוגמאות קוד כדי שתוכלו מכאן מיד להמשיך ולשדרג את היישומים שלכם לריאקט 18.
לפרטים נוספים והרשמה:
https://www.tocode.co.il/workshops/117
## היכרות עם WSL - וובינר שיתקיים ב 3.8.2022
כבר כמה שנים ש Microsoft מאפשרים גם למשתמשי חלונות לקבל חווית פיתוח יוניקסית מלאה ובלי להתקין שום מכונה וירטואלית. הם בנו סביבה בשם WSL, שזה קיצור ל Windows Subsystem for Linux, משמשת כשכבת תרגום בין קוד לינוקס לקוד ווינדוס, וכך אפשר להריץ יישומים בינאריים שקומפלו ללינוקס על מכונת ווינדוס.
המשמעות מבחינתנו היא עצומה - אם אתם כבר מכירים Linux וצריכים לעבור למחשב Windows, תוכלו להתקין את כל הכלים שאתם מכירים ואוהבים ולהשתמש בהם בלי בעיה. אם אתם לא מכירים Linux תוכלו לקבל חווית לימוד בתוך מערכת ההפעלה שלכם ובקצב שלכם.
בוובינר אראה לכם איך לעבוד עם WSL2 כדי לקבל סביבת לינוקס מלאה בתוך ה Windows שלכם.
לפרטים נוספים והרשמה:
https://www.tocode.co.il/workshops/119
## שלום React Native World
ריאקט נייטיב היא פריימוורק לפיתוח יישומי מובייל חוצי פלטפורמות שמאפשרת לכתוב קוד פעם אחת ולהריץ אותו גם על אייפון וגם על אנדרואיד (ותכל'ס גם על מק ו Windows).
בוובינר נראה איך ליצור פרויקט React Native חדש, איך לבדוק אותו על המחשב ואיך ליצור ממנו אפליקציית אייפון, ואחרי זה נמשיך להרחיב את האפליקציה, להוסיף קומפוננטות ודפים וגם לעצב אותה.
לפרטים נוספים והרשמה:
https://www.tocode.co.il/workshops/118
כל הוובינרים יעברו בזום, בימי חמישי בשעה עשר בבוקר. נתראה בזום.
1 419
# אפשר רמז?
בהתמודדות עם אתגרים אנחנו שמים לב לשני רצונות מנוגדים ובעייתיים-
1. חלק מהזמן אנחנו מתעקשים לפתור הכל לבד, כאילו כדי להוכיח לעצמנו או לעולם שאנחנו יכולים.
2. חלק מהזמן אנחנו מיואשים לגמרי ורק רוצים להסתכל בפיתרון או שמישהו יסביר לנו מה עושים.
הבעיה בלפתור הכל לבד היא שכשאתם לא מצליחים זה יכול לייאש ולתקוע; ואם אתם מצליחים הכל זה אומר שלא בחרתם אתגרים קשים מספיק.
הבעיה בלהסתכל בפיתרונות של אחרים ולהעתיק היא שאתם לא מאמנים את השרירים הנכונים של פיתרון בעיות, ושרוב האתגרים בחיים האמיתיים לא דומים לשום דבר שכתוב בספר.
הפיתרון לאנשים שרוצים להשתפר וללמוד הוא להתרגל לחפש רמזים: לשאול איפה אפשר לקרוא יותר על XYZ, או לברר על איזה נושאים כדאי ללמוד כדי להתמודד טוב יותר עם הבעיה, או להסתכל על פיתרונות של בעיות דומות ולנסות להתאים אותם לאתגר שלנו, או כל חיפוש דומה.
רמז מאפשר לנו לשלב - גם ללמוד דברים חדשים, וגם ליישם אותם על האתגרים שעכשיו אנחנו מתמודדים איתם. שילוב כזה, לאורך זמן, מרחיב אופקים והופך אותנו לפותרי בעיות טובים יותר.
1 419
(f x2 y2)))
(defn process-line [acc line]
(let [[x1 y1 x2 y2]
(->> line
(re-seq #"(\d+),(\d+) -> (\d+),(\d+)")
(first)
(drop 1)
(map read-string))]
(cond
(= x1 x2) (reduce #(%2 %1)
acc
(fill-points-in-range y1 y2 (fn [y] #(update %1 [x1 y] (fn [v] (inc (or v 0)))))))
(= y1 y2) (reduce #(%2 %1)
acc
(fill-points-in-range x1 x2 (fn [x] #(update %1 [x y1] (fn [v] (inc (or v 0)))))))
:else acc)))
; :else (reduce #(%2 %1) acc (fill-points-in-line x1 y1 x2 y2 (fn [x y] #(update %1 [x y] (fn [v] (inc (or v 0))))))))))
(def input
(->> "input.txt"
(slurp)
(clojure.string/split-lines)
(reduce process-line {})))
(pprint (count (filter (fn [[_ v]] (> v 1)) input)))
את התרגיל המקורי תוכלו למצוא בקישור:
https://adventofcode.com/2021/day/5
ולנסות לפתור אותו בעצמכם בשפת התכנות האהובה עליכם או לשחק עם פיתרון הקלוז'ר שלי ולהציע שיפורים.1 419
# משחקים עם קלוז'ר: פיתרון Advent Of Code 2021 יום 5
תרגילי Advent Of Code הם המקום המושלם להתאמן על שפת תכנות חדשה, ובמיוחד כזאת שיודעת להתמודד יפה עם נתונים ומניפולציות עליהם. קלוז'ר בהחלט מתאימה להגדרה. את יום 5 הצלחתי לפתור עם טריק פשוט אחד - ועוד קצת קוד מסביבו. הנה הסיפור המלא.
## המשימה ביום 5
המשימה היום קשורה לבניית לוח דו-מימדי לפי רשימה של קווים שעוברים על הלוח. רשימת הקווים מגיעה בפורמט כזה:
0,9 -> 5,9
8,0 -> 0,8
9,4 -> 3,4
2,2 -> 2,1
7,0 -> 7,4
6,4 -> 2,0
0,9 -> 2,9
3,4 -> 1,4
0,0 -> 8,8
5,5 -> 8,2
בו כל שורה מציינת נקודת התחלה של הקו (עם x ו y) ונקודת סיום שלו. המשימה שלנו היא למצוא כמה נקודות יש בלוח בהן עובר יותר מקו אחד. בחלק הראשון ביקשו להסתכל רק על קווים מאוזנים או מאונכים, ובחלק השני לספור גם קווים אלכסוניים.
## איך לגשת לפיתרון
הצעד הראשון בדרך לפיתרון הוא לחשוב על דרך טובה לייצג את המידע. אנחנו מסתכלים על לוח דו-מימדי וכל שורה תוסיף אליו מידע, ולכן יהיה קל להשתמש ב Dictionary: המפתח הוא קואורדינטה (x, y) והערך הוא כמה קווים עוברים בנקודה הזאת. כל פעם שקוראים שורה ומגלים עוד קו, נגדיל ב-1 את הערך בכל הנקודות שהוא עובר בהן ובסוף נוכל לחפש את כל הערכים שגדולים מ-1.
## קוד קלוז'ר
האתגר הראשון במימוש הוא להבין איך לקבל רשימה של כל הנקודות על קו מסוים. בשביל החלק הראשון - קווים אנכיים או אופקיים - השתמשתי בקוד הבא:
(defn fill-points-in-range [start end f]
(->> (range (min start end) (inc (max start end)))
(map f)))
ופה יש טריק: במקום להחזיר לקורא רשימה של נקודות ולהכריח אותו לרוץ עליהן בלולאה, אני מעדיף לקבל עוד פונקציה ולהפעיל אותה בלולאה על כל הנקודות. זה חוסך לשכפל את הלולאה בכל קוד שיקרא לפונקציה שלי.
שאר הקוד יחסית פשוט:
(defn process-line [acc line]
(let [[x1 y1 x2 y2]
(->> line
(re-seq #"(\d+),(\d+) -> (\d+),(\d+)")
(first)
(drop 1)
(map read-string))]
(cond
(= x1 x2) (reduce #(%2 %1)
acc
(fill-points-in-range y1 y2 (fn [y] #(update %1 [x1 y] (fn [v] (inc (or v 0)))))))
(= y1 y2) (reduce #(%2 %1)
acc
(fill-points-in-range x1 x2 (fn [x] #(update %1 [x y1] (fn [v] (inc (or v 0)))))))
:else acc)))
(def input
(->> "input.txt"
(slurp)
(clojure.string/split-lines)
(reduce process-line {})))
(pprint (count (filter (fn [[_ v]] (> v 1)) input)))
שימו לב לשורה:
(fill-points-in-range y1 y2 (fn [y] #(update %1 [x1 y] (fn [v] (inc (or v 0))))))
היא לוקחת קו ובמקום להחזיר מערך של "נקודות" כמו שאולי היינו מצפים, היא מחזירה מערך של פונקציות. כל פונקציה יודעת לקחת מטריצה של לוח ולהעלות ב-1 את הערך שכתוב בנקודה ספציפית על הקו. את הפונקציות האלה אני מפעיל בתוך לולאת reduce על הלוח וככה מעדכן את כל הנקודות בקו.
אחרי שיש לי את המטריצה מוכנה עם כל הנתונים השורה:
(pprint (count (filter (fn [[_ v]] (> v 1)) input)))
מסננת רק את הנקודות שהערך בהן גדול מ-1 ומדפיסה כמה כאלה יש.
## חלק 2
בחלק השני רצינו לטפל גם בקווים אלכסוניים. בשביל זה הוספתי את הפונקציה:
(defn fill-points-in-line [x1 y1 x2 y2 f]
(conj (map f
(range x1 x2 (if (> x1 x2) -1 1))
(range y1 y2 (if (> y1 y2) -1 1)))
(f x2 y2)))
היא יותר מסובכת מהקווים האופקיים והאנכיים כי הפעם גם ה x וגם ה y משתנים והשינוי צריך להיות בהתאמה. לשמחתי map ב clojure יודעת לקבל כמה טווחים ופשוט מעבירה כמה פרמטרים לפונקציה.
אחרי שסגרנו את הפינה הזאת אפשר לשלב את הפונקציה ב process-line ולהחליף את בלוק ה else שהיה שם ב:
:else (reduce #(%2 %1) acc (fill-points-in-line x1 y1 x2 y2 (fn [x y] #(update %1 [x y] (fn [v] (inc (or v 0))))))))))
והתוכנית המלאה:
(use 'clojure.pprint)
(defn fill-points-in-range [start end f]
(->> (range (min start end) (inc (max start end)))
(map f)))
(defn fill-points-in-line [x1 y1 x2 y2 f]
(conj (map f
(range x1 x2 (if (> x1 x2) -1 1))
(range y1 y2 (if (> y1 y2) -1 1)))1 419
# מי אישר את ה Pull Request שלי?
בגיטהאב אנחנו יכולים להסתכל בממשק הגרפי אחורה ולראות את כל הדיונים שהיו על Pull Request וגם מי אישר כל PR. אבל אם נחליט לקחת את הפרויקט החוצה מגיטהאב, או אם אנחנו עובדים בגיט משורת הפקודה, המידע הזה כבר לא זמין.
לא מזמן כתבתי כאן על מנגנון הפתקים בגיט. היום אני רוצה להראות דוגמה לשימוש בהם כדי שכל פעם שמישהו מאשר Pull Request השם שלו ייכתב בתוך הקומיט בתור Note, כך שנוכל להיזכר אחרי מכל מקום.
## קוד ה Action
הטריק מבוסס על Github Actions, שזה מנגנון ה CI הפנימי של גיטהאב. אנחנו יכולים להגדיר שכל פעם שקורה משהו תופעל אוטומטית מכונה וירטואלית ותעשה משהו. במקרה שלנו נגדיר שכל פעם שמישהו משאיר Approve על Pull Request, תופעל מכונה שתשאיר פתק על הקומיט העדכני ביותר באותו PR.
הקוד של ה Action נקרא Workflow והוא שמור בקובץ yml בשם
.github/workflows/main.yml. אפשר ליצור יותר מקובץ workflow אחד, רק חשוב לשים את כולם בתיקיה הנכונה.
זה הקוד לדוגמה שלנו:
jobs:
approved:
if: github.event.review.state == 'approved'
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v2
- run: |
git config --global user.name 'Github Action'
git config --global user.email '<>'
echo "Pull Request ${{ github.event.pull_request.number }} was approved by ${{ github.event.review.user.login }}"
echo "Approved By: ${{github.event.review.user.login }}" | git notes append -F - ${{ github.event.pull_request.head.sha }}
git push origin "refs/notes/*"
בואו נראה את עיקרי הדברים:
1. בחלק הראשון אני מגדיר מתי ה Workflow ירוץ, זה יהיה כל פעם שמישהו משאיר review על Pull Request.
2. בחלק השני אני מגדיר מה לעשות.
3. הפקודה if מגדירה תנאי על האירוע, בסיפור שלנו אנחנו רוצים לטפל רק ב Reviews מסוג Approve.
4. הגדרת משתנה הסביבה GITHUB_TOKEN מאפשרת ל Action לעשות פעולות בשמנו בתוך הריפו. במקרה שלנו הפעולה היא הוספת פתק.
5. הכיף האמיתי מתחיל בתוך פקודת ה run - אני מגדיר את שם המשתמש והאימייל, מפעיל git notes append כדי ליצור פתק ודוחף את כל הפתקים למאגר.
6. גיטהאב מעביר לי את כל האינפורמציה בתור משתנים ודואג להחלפה לערכים המתאימים. המשתנים זה מה שמופיע בתוך סוגריים מסולסלים כפולים, למשל ${{github.event.review.user.login }} עבור שם המשתמש של מי שהשאיר את ה Review, ו ${{ github.event.pull_request.head.sha }} עבור ה sha של הקומיט האחרון ב PR. בתיעוד אפשר למצוא את המבנה של כל האוביקטים שלהם.
## משיכת הפתקים וצפיה בלוג
אחרי הגשת Pull Request ואישור שלו אני יכול לראות בטאב Actions את הפלט של ה Workflow. מה שיותר מעניין זה כמובן הפתק. בהרצה רגילה fetch לא מושך את הפתקים. בשביל למשוך אותם אני כותב את הפקודה:
$ git fetch origin refs/notes/commits:refs/notes/commits
ואחרי זה הפעלה של git log מראה ליד הקומיט העדכני ביותר ב PR את הפתק עם שם המשתמש שאישר אותו.
שימו לב רק שהמנגנון כאן עדיין בסיסי ובגדר טיוטה. הוא לא מטפל במחיקה או עריכה של ביקורת ובטח יש עוד כמה בעיות שלא חשבתי עליהן. אני מקווה שהוא יעזור לכם בתור בסיס ל Workflows שתרצו לכתוב.
Вже доступно! Дослідження Telegram за 2025 — головні інсайти року 
