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

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

חידת מהירות בפייתון

11/05/2018

שלושת הפונקציות הבאות עושות בדיוק את אותו הדבר. נסו להבין מה הן עושות ומי המהירה ביותר:

def rot1(a, n, k):
    return [a[(i+k) %n] for i in range(n)]
def rot2(a, n, k):
    for _ in range(k):
        a = a[1:] + [a[0]]
    return a
def rot3(a, n, k):                                
    res = a[:]
    for _ in range(k):
        temp = res[0]
        del(res[0])
        res.append(temp)
    return res

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

In [52]: rot1([1,2,3,4,5], 5, 4)
Out[52]: [5, 1, 2, 3, 4]

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

In [38]: timeit rot1(arr, len(arr), 10)
134 µs ± 2.7 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [37]: timeit rot2(arr, len(arr), 10)
52.9 µs ± 972 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [36]: timeit rot3(arr, len(arr), 10)
5.79 µs ± 56 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [58]: timeit rot1(arr, len(arr), len(arr) * 5)
123 µs ± 577 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [59]: timeit rot2(arr, len(arr), len(arr) * 5)
23.9 ms ± 141 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [60]: timeit rot3(arr, len(arr), len(arr) * 5)
1.26 ms ± 33.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

ככל ש k גדול יותר מגודל המערך כך המימוש הראשון מהיר יותר. זה הגיוני כי זמן העבודה שלו תלוי בגודל המערך. מצד שני כש k קטן מאוד מימוש זה הוא הגרוע ביותר.

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

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

In [62]: timeit np.roll(na, len(arr) * 5)
12.2 µs ± 190 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [63]: timeit np.roll(na, 10)
14 µs ± 76.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

מה תיקחי איתך לעבודה הבאה?

10/05/2018

שורה לקורות חיים? חברים? ידע ומיומנות? אולי קוד וכלי עבודה?

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

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

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

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

אם התשובה שלך לשאלה היא רק שורה בקורות חיים, אולי כדאי להתחיל להתאמץ יותר.

20 * 30 = ?

08/05/2018

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

עכשיו למספרים:

  1. מסלול קורס תכנות Bootcamp יכול לעלות עד עשרים אלף ש"ח (במחירים של היום כמחיר סופי ללקוח). שלושים כפול עשרים למי שתהה לגבי הכותרת נותן 600. בדקתי במחשבון. אני מזכיר - המדינה תשלם לאקספריס ולבין תחומי סכום כפול מזה.

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

  3. המסלול דורש מכל תלמיד שמונה חודשים של השקעה Full Time במסלול בלי אפשרות לעבודה תוך כדי ובמלגת קיום של 2,000 ש"ח בחודש.

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

יש לכם מיליון ומאתיים אלף ש"ח מיותרים? למה שלא תתנו אותם לואסים אבו סאלם - המייסד של מיזם לופ ללימודי תכנות לילדים במגזר הערבי. התלמידים מגיעים למרכזים שלהם כל יום אחרי בית הספר ולומדים לתכנת (וגם משלמים תשלום חודשי כמו על חוג). בראיון איתו ואסים מספר שהם לימדו באזור ה 8,000 ילדים עד כה. זה הקישור לראיון וממליץ גם לכם לקרוא: https://www.themarker.com/technation/1.6032020

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

יש בעיה עם זה

07/05/2018

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

ואז מתישבים על המחשב להתחיל לכתוב... אבל רגע, יש בעיה עם זה.

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

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

שתי סיבות ללמוד

06/05/2018

אתה יכול ללמוד מפחד- פחד להישאר מאחור, פחד שלא תמצא עבודה, פחד שלא תדע לענות כשישאלו, פחד להחמיץ.

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

זו בחירה שלכם. אני ממליץ על האפשרות השניה.

עשר שאלות לראיונות עבודה על JavaScript ES6

05/05/2018

לכבוד קורס ES6/7/8 החדש קבלו עשר שאלות שהייתי שואל בראיון עבודה על הנושא. מוזמנים להתכונן או לקחת מכאן רעיונות (תלוי באיזה צד של השולחן אתם יושבים):

  1. מה ההבדל בין var, let ו const? מה היה רע ב var? מתי נשתמש ב let ומתי ב const?

  2. מה קורה כשמריצים את הקוד הבא? מדוע? איך תיצרו מערך שאי אפשר להוסיף אליו איברים?

const arr = [10, 20, 30];
arr.push(50);
  1. מהו אוביקט Proxy? כיצד תשתמשו ב Proxy כדי לממש Private Data Members של מחלקה? מה היתרונות והחסרונות של גישה זו בהשוואה לשימוש ב Closures?

  2. הקוד הבא מושך נתוני דמות משירות הרשת swapi.co ולאחר מכן את שמות כל הסרטים בהם הדמות הופיעה:

$.get('https://swapi.co/api/people/1', function(res) {
    for (const url of res.films) {
        $.get(url, function(filmData) {
            console.log(filmData.title);
        });
    }
});

שכתבו את הקוד תוך שימוש ב Promises.

  1. כתבתי מחדש את הקוד משאלה (4) תוך שימוש ב async/await:
async function getFilms() {
    const res = await $.get('https://swapi.co/api/people/1');
    for (const url of res.films) {
        const filmData =await $.get(url);
        console.log(filmData.title);
    }
}

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

  1. מה ההבדל בין WeakMap ל Map? מתי נשתמש ב WeakMap?

  2. כתבו מחלקה בשם BindAll שכל מחלקה שיורשת ממנה מקבלת bind(this) אוטומטי לכל המתודות שלה. דוגמת שימוש:

class Person extends BindAll {
    constructor(name) {
        this.name = name;
    }

    hello() {
        console.log(this.name);
    }
}

const p = new Person('John');
// waits 500 ms and then prints 'John'
setTimeout(p.hello, 500);
  1. כתבו פונקציה בשם bindAll המקבלת מחלקה ומבצעת bind לכל המתודות שלה באופן אוטומטי.

  2. כתבו פונקציה בשם fib כך שהקוד הבא יעבוד וידפיס את המספרים הקטנים מ-100 מסדרת פיבונאצ'י (סדרת פיבונאצי היא סידרת מספרים בה כל איבר שווה לסכום שני האיברים שלפניו):

for (let n of fib()) {
    console.log(n);
    if (n > 100) break;
}
  1. כתבו מחלקה בשם Fib כך שהקוד הבא יעבוד:
const fib10 = new Fib(10);

// iterates through the first 10 numbers of a fibonacci series
for (let n of fib10) {
    console.log(n);
}

טיפ גיט: על הקשר בין בראנצ'ים לקומיטים

04/05/2018
git

השבוע דיברתי עם אליעזר במפגש תרגול git ועברנו על בראנצ'ים ועל הפקודה git branch -f שלדעתי עוזרת לעשות קצת סדר בקשר בין בראנצ'ים לקומיטים.

הבלבול מתחיל כבר כשפותחים פרויקט חדש:

$ git init
$ git log
fatal: your current branch 'master' does not have any commits yet

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

$ echo 1 > demo.txt
$ git add .
$ git commit -m 'commit 1'
$ echo 2 > demo.txt
$ git commit -a -m 'commit 2'
$ git log
localhost:project ynonperek$ git log
commit cd877d58a9885372b9b6ff93c77dabcff8b58052 (HEAD -> master)
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:51 2018 +0300

    commit 2

commit 85f0794fe33b4718dce3f02b480a119261d64115
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:40 2018 +0300

    commit 1

נראה אחלה עד שחוזרים אחורה:

$ git checkout 85f0794fe33b4718dce3f02b480a119261d64115

$ git log
commit 85f0794fe33b4718dce3f02b480a119261d64115 (HEAD)
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:40 2018 +0300

    commit 1

$ git branch
* (HEAD detached at 85f0794)
  master

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

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

בדוגמא שלנו המדבקה HEAD היתה מחוברת לקומיט cd877. אחרי ביצוע checkout הזזנו את המדבקה HEAD לקומיט הישן יותר. המדבקה master אגב עדיין נשארה מחוברת לקומיט החדש. הנה כמה פקודות שעכשיו נראות הרבה יותר הגיוניות (ממשיכים מאותו מצב):

$ git checkout master
$ git branch
* master
$ git log
commit cd877d58a9885372b9b6ff93c77dabcff8b58052 (HEAD -> master)
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:51 2018 +0300

    commit 2

commit 85f0794fe33b4718dce3f02b480a119261d64115
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:40 2018 +0300

    commit 1

ואם קומיט לא חי בתוך בראנצ' אפשר להתחיל לעשות דברים מעניינים - כמו לעקוף את master ולבצע קומיט ישירות מתוך קומיט אחר:

$ git checkout cd877
$ echo 3 > demo.txt
$ git commit -a -m 'commit 3'
$ git log
commit ff12c93ea35fce6e1583d877458f848da796a26f (HEAD)
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:37:11 2018 +0300

    commit 3

commit cd877d58a9885372b9b6ff93c77dabcff8b58052 (master)
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:51 2018 +0300

    commit 2

commit 85f0794fe33b4718dce3f02b480a119261d64115
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:40 2018 +0300

    commit 1

פעולת git checkout הזיזה את HEAD ועכשיו כל הפעולות שלנו מתבצעות על קומיט cd877 ולא "מושכות" את master קדימה. מה שיפה שמאחר ואנחנו חושבים על בראנצ'ים כמו מדבקות קל מאוד להזיז את master לקומיט אחר. הפקודות הבאות מתקנות את המצב:

$ git branch -f master HEAD
$ git checkout master
$ git log
commit ff12c93ea35fce6e1583d877458f848da796a26f (HEAD -> master)
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:37:11 2018 +0300

    commit 3

commit cd877d58a9885372b9b6ff93c77dabcff8b58052
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:51 2018 +0300

    commit 2

commit 85f0794fe33b4718dce3f02b480a119261d64115
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:40 2018 +0300

    commit 1

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

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

מה להגיד לבוס?

03/05/2018

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

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

התשובה הארוכה מפורטת בנקודות הבאות ואותה אפשר לשתף עם הבוס:

המשך קריאה

הצצה אחורה בביטוי רגולארי בפייתון

02/05/2018

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

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

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

text = 'I am :the:      walrus'
text.split(': ')
['I am :the', '     walrus']

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

import re
text = 'I am :the:      walrus'
re.split('(: )', text)
 ['I am :the', ': ', '     walrus']

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

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

import re
text = 'I am :the:      walrus'
re.split('(?<=:) ', text)

# or if you don't need the spaces before the walrus
re.split('(?<=:) +', text)
['I am :the:', 'walrus']

באופן כללי ובפרט בפייתון ארבעת סוגי ההצצות והסימנים שלהן הם:

  1. הצצה קדימה - Positive Lookahead מסומן באמצעות (?=foo). חיפוש המקום בקלט שאחריו מופיעה המילה foo.

  2. הצצה אחורה - Positive Lookbehind מסומן באמצעות (?<=foo). חיפוש המקום בקלט שלפניו מופיעה המילה foo.

  3. הצצה קדימה שלילית -Negative Lookahead מסומן באמצעות (?!foo). חיפוש מקום בקלט שאחריו לא מופיעה המילה foo.

  4. הצצה אחורה שלילית - Negative Lookbehind מסומן באמצעות (?<!foo) חיפוש המקום בקלט שלפניו לא מופיעה המילה foo.

לדוגמאות נוספות על Lookahead ו Lookbehind שווה להעיף מבט בקישור כאן:

href='http://www.rexegg.com/regex-lookaround

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