מבנים ששווה להכיר: List Comprehension
אחד הכלים שעוזרים לנו להיות מתכנתים טובים יותר הוא לזהות תבניות בקוד, תבניות שחוזרות על עצמן בהרבה שפות תכנות, תבניות שהן בעצם מילים שכשאנחנו מצליחים לזהות ולהשתמש בהן אנחנו מיד נרגיש בבית כשנגיע לשפה חדשה.
התבנית שאני רוצה להציג היום היא List Comprehension, פעולה שמקורה בשפות פונקציונאליות אבל נמצאת היום בהרבה מאוד שפות ובכל מיני צורות. המטרה היא תמיד לקחת רשימה אחת ולהפוך אותה לרשימה אחרת: לפעמים תוך שינוי האלמנטים ולפעמים תוך מחיקה של חלק מהאלמנטים לפי פרדיקט.
את השם List Comprehension פגשתי לראשונה ב Python שם הקוד הבא לקח רשימה של מספרים והחזיר רשימה של ריבועיהם:
numbers = range(10)
squares = [i * i for i in numbers]
ולמה זה יפה? כי בעבודה עם List Comprehension יש מעט מאוד מקום ליצירתיות. איך שרואים את הסוגריים המרובעים עם ה for בתוכם מיד יש קול בתוך הראש שצועק "הנה רשימה שהופכת לרשימה אחרת", ומיד אפשר להמשיך לחפש מה הופך למה.
כמובן שזה נוח בשילוב עם פונקציות נוספות, למשל חישוב סכום ריבועי המספרים יראה כך:
sum([i * i for i in range(10)])
(*) אני לא מדבר כאן עדיין על הרכבות מתוחכמות יותר כמו Generator Comprehension, למרות שאם אתם כותבים הרבה פייתון אתם כבר יודעים שאין טעם בסוגריים המרובעים בתוך העגולים, ואם לא - אז אולי נכתוב על זה פוסט ביום אחר.
כיף לגלות שהמבנה של רשימה שהופכת לרשימה אחרת לא ייחודי לפייתון ולמעשה זמין בהרבה מאוד שפות תכנות ונראה כמעט אותו דבר בכולן. הנה אליקסיר בחישוב סכום הריבועים:
Enum.sum(for n <- 0..9, do: n * n)
ו Clojure:
(apply + (for [i (range 10)] (* i i)))
ברוב השפות שאינן פונקציונאליות אין כתיב מובנה להרכבה אבל אנחנו משתמשים בפונקציות map ו filter כדי לקבל בדיוק את אותה תוצאה. כך ב Ruby:
(0..9).map {|i| i * i }.sum
ב JavaScript:
// some prep work
let numbers = new Array(10).fill(0).map((val, index) => index);
Array.prototype.sum = function() {
return this.reduce((a, b) => a + b, 0)
}
numbers.map(i => i * i).sum()
אגב ב JavaScript הייתי צריך לממש לבד את sum ואת range שקיימות מהקופסא בשפות אחרות.
ואפילו Java כבר תומכת בתחביר דומה:
public class Main {
public static void main(String[] args) {
IntStream stream = IntStream.range(0, 10);
System.out.println(stream.map(i -> i * i).sum());
}
}
מרגע שהתחלנו לחשוב על הבעיה שלנו בתור רשימה שצריכה להפוך לרשימה אחרת, לא משנה באיזה שפה אנחנו מדברים אנחנו נוכל לייצג את המבנה - בדיוק כמו שהיינו מייצגים מבנה של משתנה, לולאה, תנאי או כל מבנה בסיסי אחר בשפה. ככל שנתרגל להשתמש במבנים של map, filter ו reduce כך יהיה לנו קל יותר לכתוב ולקרוא תוכניות במגוון שפות.