ES2019 – שימוש ב flatMap

flatmap היא דרך מעולה לעשות מוטציה למערכים וגם לפלטר אותם בג'אווהסקריפט

תקן ES2019 כולל בחובו כמה תוספות מעניינות ולא דרמטיות במיוחד – התוספת המעניינת ביותר היא flatMap שלמרות שהיא לא מהפכנית היא שימושית במיוחד בלא מעט הזדמנויות.

אז מה היא עושה? בואו ונעבור על map. שהיא מתודה של מערך בג'אווהסקריפט. איך עובד map, אני מריץ אותו כשאני רוצה לעבור על כל אברי המערך ולהחזיר מערך בגודל זהה עם מוטציה (מוטציה – או Mutation זה לשנות את איברי המערך). למשל:

const myArray = ['Ran', 'Moshe', 'Yaakov', 'Yossi'];

const result = myArray.map((name) => {
   return `${name} is nice!`
});

console.log(result); 
//["Ran is nice!", "Moshe is nice!", "Yaakov is nice!", "Yossi is nice!"]

מה יש לי פה? מערך של שמות שעליהם אני מפעיל פונקצית map. הפונקציה הזו היא פונקציה שיש אותה לכל המערכים והיא מקבלת פונקציה אנונימית (במקרה הזה פונקצית חץ – זוכרים? אם לא – קראו פה) שמחזירה את השם לאחר מוטציה פשוטה שמוסיפה לו טקסט.

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

['Carrot,Cabbage,Salt,Mayonnaise', 'Bread,Oil,Eggs,Cream,Sugar']

אני רוצה להגיע למצב שבו יש:

["Carrot", "Cabbage", "Salt", "Mayonnaise", "Bread", "Oil", "Eggs", "Cream", "Sugar"]

איך עושים את זה? הדבר הראשון שקופץ לנו הוא map, לעבור על כל המערך ולבצע split כדי לבודד את כל הרכיבים. מה הבעיה? שבסוף יש לי שני מערכים. map מתמפה בדיוק למספר האיברים המקורי. היו לי שני איברים בהתחלה? יהיו לי גם שני איברים בסוף.

const myArray = ['Carrot,Cabbage,Salt,Mayonnaise', 'Bread,Oil,Eggs,Cream,Sugar'];

const result = myArray.map((name) => {
   return name.split(',');
});

console.log(result); 
//[["Carrot Cabbage Salt Mayonnaise"], ["Bread Oil Eggs Cream Sugar"]]

וזה מאוד מבאס, כי אני רוצה מערך אחד שלם שיש בו את כל הרכיבים. מה עושיןם? אפשר להשתמש ב-flat על המערך או… להשתמש פשוט ב-flatMap:

const myArray = ['Carrot,Cabbage,Salt,Mayonnaise', 'Bread,Oil,Eggs,Cream,Sugar'];

const result = myArray.flatMap((name) => {
   return name.split(',');
});

console.log(result); 
//["Carrot", "Cabbage", "Salt", "Mayonnaise", "Bread", "Oil", "Eggs", "Cream", "Sugar"]

אלגנטי מאוד. זה פשוט חוסך שימוש ב-flat. כיוון ש-flatMap לא מחויב להחזיר לנו אותו מספר של איברים, אפשר להשתמש בו גם למוטציה וגם לפילטור. למשל, בואו נעמיד פנים שאני טוקבקיסט מגעיל מ-Ynet ואני נדרש לממש אלגוריתם של תגובות שמקבל מערך של גילאים. כל מה שהוא מתחת לגיל 35 אני כותב שהוא מתאים להייטק וכל מה שהוא מעל גיל 35 אני מעיף אותו החוצה. איך אני עושה את זה?

אם אני משתמש ב-map, אני מקבל תוצאה לא צפויה:

const highTechWorkers = ['20', '25', '41', '9'];
const result = highTechWorkers.map((worker) => {
  const age = parseInt(worker);
  if (age < 35)
   return `age ${worker} is OK`;
});

console.log(result); 
//["age 20 is OK", "age 25 is OK", undefined, "age 9 is OK"]

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

const highTechWorkers = ['20', '25', '41', '9'];
highTechWorkersFiltered = highTechWorkers.filter((worker) => {
    const age = parseInt(worker);
    if (age < 35) {
      return true;
    } else {
      return false;
    }
});
const result = highTechWorkersFiltered.map((worker) => {
   return `age ${worker} is OK`;
});

console.log(result); 
//["age 20 is OK", "age 25 is OK", "age 9 is OK"]

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

const highTechWorkers = ['20', '25', '41', '9'];
const result = highTechWorkers.flatMap((worker) => {
  const age = parseInt(worker);
  if (age < 35) {
    return [`age ${worker} is OK`]; // Keep it, in array
  } else {
    return []; // Delete it!
  }
});

console.log(result); 
//["age 20 is OK", "age 25 is OK", "age 9 is OK"]
https://codepen.io/barzik/pen/BbxWev

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

פוסטים נוספים שכדאי לקרוא

תמונה מצוירת של רובוט שמנקה HTML
יסודות בתכנות

סניטציה – למה זה חשוב

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

פתרונות ומאמרים על פיתוח אינטרנט

נגישות טכנית – פודקאסט ומבוא

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

גלילה לראש העמוד