איך LeetCode עזר לי בפיתוח פיצ'ר בעבודה

איך LeetCode עזר לי בפיתוח פיצ'ר בעבודה

לאחרונה עשיתי סבב ראיונות, כחלק מההכנות לראיונות חרשתי את LeetCode עם בעיות של אלגוריתמים ומבני נתונים. מנטרה שאני רואה שחוזרת הרבה אומרת: "למה בודקים אותנו על שאלות מהתואר שלנו במדעי המחשב?", בטענה שזה לא בעיות שמשקפות Use cases אמיתיים. אז הנה סתם דוגמא למקרה אשר בו צורת המחשבה אשר יוצרות שאלות מסוג זה עוזרת בפתירת בעיות יומיומיות.

במקרה כאשר הייתי בשיא ההכנות, כאשר אני כבר שורק מבני נתונים ומדקלם רקורסיות, קיבלתי משימה בעבודה להוסיף שדה פילטר/חיפוש חדש לאזור של החיפוש המתקדם באפליקציה, המסודר בעזרת הGrid System של Bootstrap. ההוראות של המעצבת היו פשוטות: לכל היותר 4 פילטרים בשורה ולכל הפחות 2 פילטרים בשורה. זאת אומרת שלא יתכן מצב שיהיו לי כמה פילטרים בשורה ופילטר בודד בשורה בשל עצמו.

"מה הקשר רקורסיה?"

נשמע משימה פשוטה סה"כ אבל הסיפור הסתבך כאשר בדיילי התברר שגם אני וגם שני חברי צוות אחרים דיווחנו על הוספת פילטרים לאותו איזור הזה, כאשר לכל פילטר יש איזשהו ביזנס לוג'יק שמחליט אם להציג אותו. כעת מספר העמודות בGrid שכל פילטר מקבל הוא בעצם תלוי בכמה פילטרים צריך להציג. הדרך הנאיבית הייתה שכל אחד ידאג לעצמו, כל אחד ידאג להוסיף לעדכן את התנאים שמחליטים כמה עמודות תופס כל פילטר כאשר הוא לוקח בחשבון את כל הפילטרים שכבר קיימים עם התנאים שלהם. אמנם הייתה הנחה מקלה שלא יהיו יותר מ8 פילטרים ותמיד יהיו יותר מ2, עדין היו המון קומבינציות לאיזה פילטרים יכולים להופיע.

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

"עדין לא הבנתי מה הקשר רקורסיה"

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

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

const buildRow = (filters) => (
    <Row>
        {filters.map((filter) => (
            <Col size={filtersConstants.GRID_COLUMNS / filters.length}>
                {filter}
            </Col>
        ))}
    </Row>
);

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

function buildGrid(filters) {
    // if filters fit in a row
    if (filters.length <= filtersConstants.MAX_COLUMNS_IN_A_ROW) {
        return [buildRow(filters)];
    }
 
    // if not, split the array to 2 sections, and run this function again
    const topFilters = filters.slice(0, Math.floor(filters.length / 2));
    const bottomFilters = filters.slice(Math.floor(filters.length / 2));
 
    const topSection = buildGrid(topFilters);
    const bottomSection = buildGrid(bottomFilters);
 
    return topSection.concat(bottomSection);
}

איך להטמיע את זה בReact

הרעיון שלי היה שתהיה קומפוננטה גנרית, איזה Higher Order Component, שתקבל כילדים את הפילטרים שצריך להציג, ושהיא כבר תתמודד עם להציג אותם. אפשר להרחיב את זה שתקבל גם את מספר העמודות ובכך בעצם לתמוך בGrid Systems שונים ולא רק ב12 עמודות. האופן שימוש שרציתי הוא משהו כזה:

<FiltersGrid>
    <Filter />
 
    <Filter />
 
    {settings.ff1 && <Filter />}
 
    {settings.ff2 && <Filter />}
 
    {settings.ff3 && <Filter />}
 
    {settings.ff4 && <Filter />}
</FiltersGrid>

למזלנו React חושף API שנותן להתמודד עם הילדים, הchildren, של הקומפוננטה ולרוץ עליהם ולבצע איתם מה שנרצה פחות או יותר. אז הרעיון היה לרוץ על הילדים בעזרת React.Children.toArray ובאמצעות הרקורסיה ליצור מערך של מערכים המתאר את מבנה הפילטרים.

const filters = React.Children.toArray(children).filter((child) => child);
const grid = buildGrid(filters);

מצרף דוגמא עובדת של הקוד למי שרוצה לראות/לשחק/לאמץ:

רעיונות להרחיב את העניין

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

דוגמא למשהו שהתחלתי אבל הפסקתי מכיוון שלא היה לי בו צורך זה האפשרות ליצור איזורים בGRID, ליצור איזשהי תת קומפוננטה Section שתאפשר לי לתחום איזורים ובכך לשלוט איזה פילטרים יכולים לחיות ביחד באיזור/באותה השורה ואיזה לא. בגלל שזה היה Over Engineering למשימה החלטתי שלא לממש את זה אבל זה יכול להיות אחלה רעיון לסיפריה.

<FiltersGrid>
    <FiltersGrid.Section>...</FiltersGrid.Section>
 
    <FiltersGrid.Section>...</FiltersGrid.Section>
</FiltersGrid>

למי שמעוניין לתרגל קצת לוגיקה ופחות React יכול לקחת על עצמו אתגר איך לשנות את הרקורסיה ככה שלא משנה כמה פילטרים היא תקבל היא תציג אותן בצורה הכי יעילה שיש(לדוגמא אם יש 100 פילטרים, יהיו סה"כ 25 שורות ולא 32 כמו שעם הלוגיקה שאני נתתי).

סיכום

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

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

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

הבא בתור:

ניתוח פעולות אסינכרוניות בתוכניות JavaScript

ניתוח פעולות אסינכרוניות בתוכניות JavaScript