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

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

הפקודה Group_Concat ב SQLite

21/09/2019

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

הטבלאות במערכת יהיו:

  1. טבלת posts עם פרטי הפוסט ושדה id.

  2. טבלת labels עם שם התווית (אותו נרצה לחבר לשאילתה) ושדה id.

  3. טבלת label_posts היא טבלת החיבור שכוללת את השדות label_id ו post_id.

השאילתה הבאה מחזירה שורה לכל פוסט במערכת, והשדה labels של השאילתה מחזיק את כל התוויות כמחרוזת (מופרדות בפסיקים):

SELECT posts.id, posts.header, Group_Concat(labels.name) AS labels
FROM posts, label_posts, labels
ON posts.id = label_posts.post_id and labels.id = label_posts.label_id
GROUP by posts.id;

התוצאה על טבלת דוגמא שיצרתי נראית כך:

id|header|labels
1|one|event,work
2|two|tv

נ.ב. הפקודה Group_Concat ייחודית ל SQLite. בבסיסי נתונים אחרים יש פקודות מקבילות לדוגמא ב Postgresql וב SQL Server הפקודה היא STRING_AGG. אם אתם בעניין של SQL Server יש די הרבה תיעוד עליה ועוד דוגמאות בקישור הזה.

תבנית פשוטה לחיבור React ו Rails עם Webpacker

20/09/2019

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

המשך קריאה

שתי דרכים ללמוד

19/09/2019

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

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

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

כמה פונקציות ששווה להכיר מהמודולים os, sys

18/09/2019

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

המשך קריאה

היום למדתי: הפונקציה gem ב ruby

17/09/2019

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

המשך קריאה

משתנים ופונקציות גלובאליים ברובי

16/09/2019

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

המשך קריאה

טיפ Bash - בואו נבטל תיקיות אמצע עם find

15/09/2019

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

קיבלתי עץ תיקיות שכלל הרבה תיקיות אמצע ריקות, דמיינו משהו שנראה כך:

.
└── a
    └── b
        ├── c
        │   └── d
        │       └── e
        │           ├── one.txt
        │           ├── three.txt
        │           └── two.txt
        ├── f
        │   └── g
        │       ├── five.txt
        │       ├── four.txt
        │       └── six.txt
        └── y
            └── seven.txt

ורציתי להיפטר מתיקיות האמצע ולהגיע למבנה כזה:

.
└── a
    ├── c
    │   ├── one.txt
    │   ├── three.txt
    │   └── two.txt
    ├── f
    │   ├── five.txt
    │   ├── four.txt
    │   └── six.txt
    └── y
        └── seven.txt

כלומר ביטול התיקיות b, d, e ו g.

דרך קלה לבטל את תיקיות האמצע תהיה להשתמש ב find:

  1. נמצא את התיקיה העמוקה ביותר שיש להורה שלה רק ילד אחד (כלומר שהיא בן יחיד בעץ התיקיות).

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

  3. נחזור על התהליך עד שנסיים לרוץ על כל עץ התיקיות.

המתגים הרלוונטים של find הם לכן:

  1. המתג -d שיגרום ל find להתחיל מהתיקיה העמוקה ביותר ולרוץ כלפי חוץ.

  2. המתג -type d שיגרום ל find לרוץ רק על תיקיות.

  3. המתג -exec שיאפשר לנו להריץ קוד במהלך החיפוש.

ופקודה ה find שחיברה את הכל נראתה כך:

$ find -d . -type d -exec bash -c '[[ $(ls {}/.. | wc -l | tr -d " ") == 1 ]] && (mv {}/* {}/..; rmdir {})' \;

בעיה יחידה שעשויה להתעורר היא שהתיקיה העליונה ביותר בעץ היא בן יחיד ולכן תבוטל. בשביל לעקוף את זה יצרתי תיקיה זמנית ברמה העליונה ביותר בעץ (ליד a) ואחרי ה find מחקתי גם אותה.

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

מי קורא את הקוד שלך?

14/09/2019

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

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

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

  1. כתיבת קוד עם אנשים חדשים (למשל בפרויקט קוד פתוח או בהאקתון).

  2. כתיבת פרויקט קוד פתוח שלכם.

  3. השתתפות בקורס ושיתוף הפיתרונות עם המרצה והמשתתפים האחרים.

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

  5. מענה על שאלות ב Stack Overflow או פורומים אחרים.

  6. כתיבת פוסט בבלוג (בהנחה שיש קוראים).

ככל שבוחרים יותר אופציות כך מקבלים פידבק יותר מגוון שייתן לכם סיכוי לצאת מה Echo Chamber, ללמוד ולהשתפר.

טיפ פייתון: הגנה על פונקציות מקריאה דינמית

13/09/2019

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

אני מזכיר שהמחלקה שכתבתי אתמול נראתה כך:

class Counters:
    def __init__(self):
        self.counters = defaultdict(int)

    def show(self, reg):
        print(self.counters[reg])

    def inc(self, reg):
        self.counters[reg] += 1

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

        counters.__getattribute__(cmd)(*args)

נראה שלוש דרכים להגנה על הקוד.

המשך קריאה

טיפ: קריאה לפונקציה בפייתון רק לפי שמה

12/09/2019

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

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

> show x
0

> inc x
> show x
1

> inc x
> inc x
> show x 
3

> inc y
> show y
1

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

from collections import defaultdict

counters = defaultdict(int)

while True:
    try:
        next_line = input('> ')
        cmd, arg  = next_line.split()
        if cmd == "show":
            print(counters[arg])
        elif cmd == "inc":
            counters[arg] += 1
    except Exception:
        print("Invalid command. Try again")

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

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

from collections import defaultdict 

class Counters:
    def __init__(self):
        self.counters = defaultdict(int)

    def show(self, reg):
        print(self.counters[reg])

    def inc(self, reg):
        self.counters[reg] += 1

counters = Counters()
while True:
    try:
        next_line = input('> ')
        cmd, *args  = next_line.split()
        counters.__getattribute__(cmd)(*args)
    except AttributeError:
        print("Invalid command, try again")
    except TypeError:
        print("Missing required arguments to command")
    except EOFError:
        print("Bye bye")
        break

במבנה כזה יש מספר יתרונות:

  1. קל להוסיף התנהגות - פשוט מוסיפים עוד פונקציה ל Counters

  2. קל לתת טיפול מתאים לבעיות שונות, בזכות השימוש בסוגי Exceptions שונים.

  3. קל לבנות פעולות שמקבלות מספר שונה של ארגומנטים (או כלל לא), בזכות השימוש בכוכבית.