• בלוג
  • עמוד 272
  • השוואה: עיבוד רשימות בשפות פרל, פייתון ורובי

השוואה: עיבוד רשימות בשפות פרל, פייתון ורובי

06/04/2015

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

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

1. המשימה: עיבוד נתונים וחישוב

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

data = [
  { name: 'WhatsApp', tags: ['IM', 'Social'], size: 30 },
  { name: 'Facebook', tags: ['Social'],       size: 10 },
  { name: 'Mr Jump',  tags: ['Game'],         size: 40 },
  { name: 'Twitter',  tags: ['social'],       size: 2 },
]

נרצה לקבל את הסכום 42, הוא סכום הגדלים של כל היישומים הכוללים את התג Social.
שימו לב שכמו בחיים האמיתיים, מערך התגים לא אחיד. אנו נרצה שהקוד שלנו יקבל את Twitter כאפליקציה חברתית, למרות שהיא סומנה בתור social, כלומר עם אות קטנה בהתחלה.

2. נתחיל עם פייתון

import operator

data = [
    { 'name' : 'WhatsApp', 'tags' : ['IM', 'Social'], 'size' : 30 },
    { 'name' : 'Facebook', 'tags' : ['Social'],       'size' : 10 },
    { 'name' : 'Mr Jump',  'tags' : ['Game'],         'size' : 40 },
    { 'name' : 'Twitter',  'tags' : ['social'],       'size' :  2 },
]

size = reduce(operator.add,
       map   (lambda x:   x['size'], 
       filter(lambda x:   'social' in map(str.lower, x['tags']), data)))

print "Total size = {}".format(size)

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

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

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

כדי לבדוק אם המילה social נמצאת ברשימת התגיות היה צריך תחילה ״לסדר״ את הרשימה כך שלא יהיה הבדל בין אותיות קטנות לגדולות. זו מטרת ה map השני בשורה 12.

3. נעבור לרובי

data = [
  { name: 'WhatsApp', tags: ['IM', 'Social'], size: 30 },
  { name: 'Facebook', tags: ['Social'],       size: 10 },
  { name: 'Mr Jump',  tags: ['Game'],         size: 40 },
  { name: 'Twitter',  tags: ['social'],       size: 2 },
]

size = data.select { |app| app[:tags].any? { |tag| tag =~ /social/i }  }
           .map    { |app| app[:size] }
           .reduce :+

puts "Total size = #{size}"

הגדרת הרשימה כמעט זהה לקוד הפייתון, אך הפעם אין צורך להשתמש במרכאות סביב המפתחות. מתחת לפני השטח העניינים מורכבים יותר, שכן המפתח באובייקט כזה הוא מסוג Symbol ולא String.
קוד החישוב לעומת זאת מחליף את הגישה הפונקציונאלית בגישת Object Oriented, כך שגם סדר הפעולות מתחלף. הפעם אנו מתחילים בסינון, ממשיכים בשליפת הגודל ומסיימים בחישוב הסכום. העברת הבלוק לפונקציה קומפקטית יותר ואין שימוש במילה lambda, למרות שעדיין צריך לבחור שם למשתנה שייצג אלמנט בתוך הבלוק.
הדפסת ערך בתוך מחרוזת ברובי הרבה יותר קריאה מאשר בפייתון, עם סימן הסולמית-סוגריים בתוך מרכאות כפולות שמציין פענוח של קוד רובי.

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

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

4. ונסיים עם פרל

use v5.18;
use List::Util qw/reduce/;
no warnings 'experimental::smartmatch';

my @data = (
	{ name => 'WhatsApp', tags => ['IM', 'Social'], size => 30 },
	{ name => 'Facebook', tags => ['Social'],       size => 10 },
	{ name => 'Mr Jump',  tags => ['Game'],         size => 40 },
	{ name => 'Twitter',  tags => ['social'],       size =>  2 },
);

# Total size for social apps
my $size = reduce { $a + $b }
           map    { $_->{size} }
           grep   { /social/i ~~ $_->{tags} }
                  @data;
                  
say "Total size = $size";

פרל עברה לא מעט בחייה, כפי שנראה בבירור מקוד התחלת התוכנית. הפונקציה reduce הצטרפה לשפה בשלב מאוחר יותר, ולכן עלינו לייבא אותה בנפרד (אל חשש, המודול List::Util הינו מודול ליבה ונמצא בכל התקנת פרל). חוץ מזה אנו נוהגים לציין בפרל את מספר הגירסא של השפה אליה נכתבה התוכנית לטובת מהדרי פרל ישנים שאולי יצטרכו להריץ את הסקריפט.

ברמת התחביר פרל משתמשת בתחיליות מיוחדות למשתנים כדי לציין את סוגם. כך הרשימה @data תתחיל בסימן אחר מהמשתנה היחיד $size.
ביחד ל-3 השפות, קריאת קוד פרל היא משימה שדורשת להכיר היטב את השפה. כך הפונקציה map לא מאפשרת לכם להגדיר איך ייקרא המשתנה בבלוק: הוא תמיד ייקרא $_. הפונקציה reduce גם היא לא מאפשרת בחירה של שמות ותמיד השמות יהיו $a ו $b. לפונקציה filter קוראים בפרל grep. הייתרון כאן הוא האחידות: בכל מקום שיש map אנו יודעים מהו שם המשתנה הרץ.

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

5. סיכום וקריאת המשך

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

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

פרל: באתר Perl Maven תוכלו למצוא מדריכים מעולים בעברית ללימוד השפה
http://he.perlmaven.com/perl-tutorial

פייתון: ספר אונליין בחינם עם תרגול מובנה
http://www.davekuhlman.org/python_book_01.html

רובי: מדריך רובי ב20 דקות
https://www.ruby-lang.org/en/documentation/quickstart/