שתי שיטות ערבוב ב Python

03/01/2020

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

המודול random של Python מציע שתי דרכים לערבב אלמנטים ברשימה: הראשונה היא הפונקציה sample והשניה הפונקציה shuffle. בואו נראה את ההבדל ומתי נשתמש בכל שיטה.

בפייתון ברוב הפעמים שנרצה לשנות רשימה נשתמש במתודות של אוביקט הרשימה עצמו - לדוגמא arr.append, arr.reverse ו arr.clear. אחת הפקודות המעניינות של רשימות בהקשר זה היא [].sort שממיינת את הרשימה בסדר עולה:

arr = [10, 20, 7, 30] 
arr.sort()
arr

Out[23]: [7, 10, 20, 30]

והחברה שלה היא הפונקציה sorted שמקבלת רשימה ומחזירה רשימה חדשה ממוינת:

arr = [10, 20, 7, 30]
sorted(arr)
Out[30]: [7, 10, 20, 30]

הפקודה arr.sort שינתה את הרשימה, ולעומתה sorted(arr) לא שינתה את הרשימה המקורית ורק החזירה רשימה חדשה.

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

הפונקציה הראשונה random.shuffle מקבלת רשימה ומשנה אותה במקום, כלומר:

In [31]: arr = [10, 20, 7, 30]                                           
In [32]: random.shuffle(arr)                                             
In [33]: arr                                                             

Out[33]: [20, 30, 10, 7]

זה עובד בדיוק כמו המתודה sort של מערך, אפילו שהמערך עובר כפרמטר לפונקציה.

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

In [34]: random.sample(arr, k=2)                                         
Out[34]: [10, 7]

In [35]: random.sample(arr, k=3)                                         
Out[35]: [30, 20, 10]

In [36]: random.sample(arr, k=2)                                         
Out[36]: [10, 30]
In [37]: random.sample(arr, k=2)                                         
Out[37]: [7, 30]

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

In [38]: random.sample(arr, k=len(arr))
Out[38]: [30, 7, 10, 20]

ומה לגבי זמן ריצה? לא מפתיע לגלות שהגירסא שמשנה את הרשימה In Place היא קצת יותר מהירה בעבודה על רשימות גדולות:

In [43]: a = [random.randint(0, 100) for _ in range(1_000_000)]          

In [44]: %timeit random.sample(a, k=len(a))                              
629 ms ± 24.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [45]: %timeit random.shuffle(a)                                       
538 ms ± 20.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

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