• בלוג
  • בואו נבנה לבד פונקציית zip ב Python כדי להבין איך זה בנוי

בואו נבנה לבד פונקציית zip ב Python כדי להבין איך זה בנוי

08/02/2021

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

שאלת פייתון קצרה שהגיעה אליי היתה "איך אני עוברת על כמה רשימות במקביל וכל פעם לוקחת איבר מרשימה אחרת?". לפני שארוץ לענות עם zip של פייתון בואו נלך לבנות לבד את הפיתרון כדי להבין את האתגרים.

1. ניסיון ראשון: לולאה עם אינדקסים

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

one = [10, 20, 30]
two = ['a', 'b', 'c']

for i in range(len(one)):
    item_one = one[i]
    item_two = two[i]

    print(item_one, item_two)

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

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

one = [10, 20, 30]
two = [1, 2, 3]
three = []
for i in range(len(one)):
    three.append(one[i] + two[i])

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

2. הצעד הבא - פונקציה

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

[(10, 1), (20, 2), (30, 3)]

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

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

def my_zip(list_a, list_b):
    result = []

    for i in range(len(list_a)):
        result.append((list_a[i], list_b[i]))

    return result

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

one = [10, 20, 30]
two = [1, 2, 3]

for a, b in my_zip(one, two):
    print(a + b)

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

def my_zip(*lists):
    result = []
    length = 0 if len(lists) == 0 else len(lists[0])

    for i in range(length):
        result.append(tuple([list[i] for list in lists]))

    return result

וקוד שמשתמש בה יכול להיראות כך:

one = [10, 20, 30]
two = [1, 2, 3]
three = [1, 1, 1]

for a, b, c in my_zip(one, two, three):
    print(a + b + c)

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

3. הצעד הבא - Generator

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

def my_zip(*lists):
    length = 0 if len(lists) == 0 else len(lists[0])

    for i in range(length):
        yield tuple([list[i] for list in lists])

ומשתמשים בו מבחוץ לפי אותו ממשק בדיוק:

one = [10, 20, 30]
two = [1, 2, 3]
three = [1, 1, 1]

for a, b, c in my_zip(one, two, three):
    print(a + b + c)

4. לסיכום - zip

פונקציית zip של פייתון עושה את כל מה שאני תיארתי כאן ויותר. היא גם יותר מהירה כי היא ממומשת בשפת C כחלק משפת פייתון. את הפרטים עליה תוכלו למצוא בתיעוד בקישור: https://docs.python.org/3.3/library/functions.html#zip

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