הרבה אנשים אוהבים להשתמש בשם flatten בתור שם של פונקציה שמקבלת רשימה של רשימות ומחזירה רשימה בלי רשימות מקוננות (רשימה שטוחה), אבל מה קורה כשעומק הרקורסיה גדול מאחד? מה אם הרשימה הראשית מכילה רשימות של רשימות של רשימות? פה החיים מתחילים להיות מעניינים.
לפייתון אין פונקציית ספריה בשם flatten אבל בתיעוד של itertools אפשר למצוא את המימוש הבא:
def flatten(list_of_lists):
"Flatten one level of nesting."
return chain.from_iterable(list_of_lists)
כלומר מבחינתם ההשטחה היא רק ברמה אחת.
סקאלה לקחה גישה דומה וכבר הכניסה את המנגנון כפונקציית ספריה רשמית כך שאפשר לכתוב:
Range(1, 10).map(i => List(List(i), 1, 2, 3)).flatten
ולקבל את התוצאה שכוללת רשימה פנימית:
val res5: IndexedSeq[List[Int] | Int] = Vector(List(1), 1, 2, 3, List(2), 1, 2, 3, List(3), 1, 2, 3, List(4), 1, 2, 3, List(5), 1, 2, 3, List(6), 1, 2, 3, List(7), 1, 2, 3, List(8), 1, 2, 3, List(9), 1, 2, 3)
בקלוז'ר רשימה שטוחה זאת רשימה שאין בה רשימות מקוננות בכלל ולכן תרגום הקוד לקלוז'ר מחזיר תוצאה שונה:
user=> (->> (range 10) (map (fn [i] [[i] 2 3])) flatten)
(0 2 3 1 2 3 2 2 3 3 2 3 4 2 3 5 2 3 6 2 3 7 2 3 8 2 3 9 2 3)
אליקסיר כמו קלוז'ר לא כוללת רשימות מקוננות בתוצאה:
iex(6)> (1..10) |> Enum.map(fn i -> [[i], 2, 3] end) |> List.flatten
[1, 2, 3, 2, 2, 3, 3, 2, 3, 4, 2, 3, 5, 2, 3, 6, 2, 3, 7, 2, 3, 8, 2, 3, 9, 2,
3, 10, 2, 3]
ו JavaScript? בהתחלה זה נראה שהיא לוקחת את הגישה של סקאלה ופייתון ומשטיחה רק רמה אחת:
> new Array(10).fill(0).map((_, i) => [[i], 2, 3]).flat()
[
[ 0 ], 2, 3, [ 1 ], 2, 3,
[ 2 ], 2, 3, [ 3 ], 2, 3,
[ 4 ], 2, 3, [ 5 ], 2, 3,
[ 6 ], 2, 3, [ 7 ], 2, 3,
[ 8 ], 2, 3, [ 9 ], 2, 3
]
אבל אז אנחנו נזכרים ש flat מקבלת פרמטר אופציונאלי שקובע את עומק הרקורסיה (בדיוק כמו ב ruby אגב):
> new Array(10).fill(0).map((_, i) => [[i], 2, 3]).flat(Infinity)
[
0, 2, 3, 1, 2, 3, 2, 2, 3,
3, 2, 3, 4, 2, 3, 5, 2, 3,
6, 2, 3, 7, 2, 3, 8, 2, 3,
9, 2, 3
]
מסקנה? מכל הסיפור הכי אהבתי את הגישה של פייתון - כשיש מילה טעונה שכל שפה נותנת לה משמעות אחרת, עדיף להשאיר אותה מחוץ לספריה הסטנדרטית.