הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

היום למדתי: השוואת מחרוזות ב JavaScript בשפות אחרות

02/09/2022

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

const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
const search = 'su';

const options = days.filter(d => d.toLowerCase().includes(search.toLowerCase()));

הבעיה מתחילה כשאנחנו רוצים לסנן מילים בשפות אחרות, בהן אולי יש מאפיינים נוספים להתעלם מהם. דוגמה קלה היא שפות עם אקסנטים למשל המילה Français בצרפתית. גם אם יש משמעות לצ'ופצ'יק הקטן מתחת ל c, בשביל סינון פריטים עדיף להתעלם ממנו. הדרך לעשות את זה היא הפונקציה normalize ואז קצת עבודת ביטויים רגולאריים. הנה הקוד:

function clean(word) {
    return word
        .normalize('NFD')
        .replace(/\p{Diacritic}/gu, "")
        .toLocaleLowerCase() ;
}

const languages = ['Anglais', 'Français', 'Hébreu'];
const search = 'nc';

const options = languages.map(clean).filter(l => l.includes(clean(search)));

במבט ראשון הקריאה ל normalize נראית מיותרת:

>> 'Français'.normalize("NFD")
<- "Français" 

אבל האמת שהנורמליזציה הזו היא שמאפשרת לביטוי הרגולארי לעבוד. הנה השוואה איתה ובלעדיה:

>> "Français".replace(/\p{Diacritic}/gu, "")
<- "Français"
>> "Français".normalize('NFD').replace(/\p{Diacritic}/gu, "")
<- "Francais" 

אנחנו צריכים לנרמל מחרוזות יוניקוד בגלל שיש כמה דרכים לייצג תווי יוניקוד מיוחדים. אפשר לקרוא יותר לעומק על נורמליזציה ויוניקוד ב FAQ שלהם בקישור: http://www.unicode.org/faq/normalization.html

תרגיל תכנות - איתור המספר הראשון הייחודי

01/09/2022

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

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

# 33 is the first unique number
items = [10, 22, 33, 10, 12, 22, 9, 5, 1]

counts = items.each_with_object(Hash.new(0)) do |item, acc|
  acc[item] += 1
end

first_unique_index = items.find_index {|item| counts[item] == 1 }

if first_unique_index
  puts "Unique Index = #{first_unique_index}; Item = #{items[first_unique_index]}"
end

פייתון:

from collections import Counter

# 33 is the first unique number
items = [10, 22, 33, 10, 12, 22, 9, 5, 1]

counts = Counter(items)

index, item = next(
        filter(lambda x: counts[x[1]] == 1, enumerate(items)),
        None)

print(f"First unique index is {index}; item is {item}")

ג'אווהסקריפט:

// 33 is the first unique number
const items = [10, 22, 33, 10, 12, 22, 9, 5, 1]
const counts = {};
items.forEach(i => counts[i] ? counts[i]++ : counts[i] = 1);

const index = items.findIndex(i => counts[i] === 1);

if (index) {
  console.log(`Found at index ${index} value ${items[index]}`);
}

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

echo 10 22 33 10 12 22 9 5 1 | tr ' ' '\n' | cat -n | sed 's/^ *//' | sort -k 2 -n | uniq -f 1 -c | sed 's/^ *//' | grep -w '^1' | sort -k 2 -n | cut -c 3- | head -1

בגדול זה טריק דומה, רק עקום - אנחנו מוסיפים אינדקסים לכל השורות עם cat -n, אחרי זה ממיינים לפי הערך כדי שנוכל להפעיל uniq -c שסופר כמה פעמים מופיע כל דבר, ואז עם grep נשארים רק עם אלה שמופיעים פעם אחת, ממיינים חזרה לפי האינדקסים ולוקחים את הראשון.

מוזמנים להוסיף בתגובות פיתרונות שלכם בשפות יצירתיות יותר או פחות.

הרעיון לתרגיל הגיע ממוחמד מהגיליון האחרון של Perl Weekly Challenge בקישור: https://theweeklychallenge.org/blog/perl-weekly-challenge-180/#TASK1

רק שינוי קטן

31/08/2022

ביום חמישי עוד שבוע וחצי אעביר פה וובינר על React Native. מקווה שתבואו. בינתיים עברתי על התיעוד של הספריות שרציתי להראות ונתקלתי בדוגמה הבאה מתוך התיעוד של React Navigation:

function HomeScreen({ navigation }) {
  const [count, setCount] = React.useState(0);

  React.useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        <Button onPress={() => setCount((c) => c + 1)} title="Update count" />
      ),
    });
  }, [navigation]);

  return <Text>Count: {count}</Text>;
}

עם הכיתוב הזה מעל הדוגמה:

To be able to interact with the screen component, we need to use navigation.setOptions to define our button instead of the options prop. By using navigation.setOptions inside the screen component, we get access to screen's props, state, context etc.

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

אם ננסה לעדכן קצת את הכפתור שגם יציג את הערך של count נקבל את הקוד הבא:


function HomeScreen({ navigation }) {
  const [count, setCount] = React.useState(0);

  React.useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        <Button onPress={() => setCount((c) => c + 1)} title={`Update count. Current value = ${count}`} />
      ),
    });
  }, [navigation]);

  return <Text>Count: {count}</Text>;
}

שלא עובד. או לפחות רץ ותמיד מציג 0 בתור הערך בכפתור (למרות שהערך במסך המרכזי כן מתעדכן).

קחו כמה דקות לחשוב מה קורה שם ולמה זה לא עובד.

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

שלוש רמות של Code Review

30/08/2022

במעבר על קוד שווה לחלק את ההערות שלנו ל-3 קבוצות, או שלוש רמות של שיפורים אפשריים:

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

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

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

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

הדרך הכי קלה להתקין פרויקט Node.JS בפרודקשן

29/08/2022

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

אני יצרתי יישום נוד בגיטהאב בקישור כאן: https://github.com/ynonp/nodejs-render-demo

שמבוסס על פרויקט Hello World של render בקישור כאן: https://github.com/render-examples/express-hello-world

בתיקיית הדוגמאות שלהם אפשר לראות פרויקטים מקבילים לפייתון, אליקסיר, גו, php, ריילס, ועוד המון. הרשימה המלאה כאן: https://github.com/orgs/render-examples/repositories

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

אחרי שנכנסים לוחצים Sign In ומתחברים עם חשבון גיטהאב שלכם. ואז מוצאים למעלה את כפתור New ולוחצים עליו ובוחרים Web Service. מקלידים את הכתובת של מאגר הגיט שלכם בתיבה, בוחרים שם לפרויקט, בוחרים את התוכנית החינמית ולוחצים Create. המערכת האוטומטית של render תמשוך את כל הקבצים שלכם מהמאגר ותפעיל start. בסוף תקבלו URL עם הפרויקט שלכם באוויר, כולל תעודת SSL בסביבת פרודקשן. שלי נמצא כאן: https://express-demo-ynon.onrender.com/

מתוך התפריטים אפשר לקבל גישת SSH למכונה, להגדיר משתני סביבה וכמובן להסתכל בלוגים. אה, ויש להם גם DB שאפשר להקים ולצרף (אפשר לבחור פוסטגרס או רדיס), קרון ג'ובים ומכונות לריצה ברקע. סך הכל לקח לי פחות מחמש דקות להביא יישום Node.JS לפרודקשן וזה בהחלט כיף.

ברגע שיוצאים מהתוכנית החינמית המחירים קופצים ומכונה של 4 ג'יגה זיכרון עם שני מעבדים כבר תעלה לכם 85$ בחודש, וזה כבר תלוי בפרויקט אם בא לכם לשלם את זה על הנוחות.

מכשולים לעבודת צוות

28/08/2022

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

  1. יעד לא ברור או לא ברור מספיק

  2. כללי התנהגות לא ברורים או לא ברורים מספיק

  3. תחרות לא בריאה, מדידת הישגים אישיים במקום קבוצתיים (לדוגמה צוות שמודד לכל מתכנת כמה באגים הוא סגר ביום, וכך מעודד אנשים "לסגור" באגים מהר על חשבון באגים אחרים שנוצרים)

  4. חוסר ביטחון, כשכל אחד חושש על המקום שלו

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

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

טיפ גיט: איך לאחד מספר מאגרים למונוריפו

27/08/2022

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

המשך קריאה

הרגע שכולם עוזבים

26/08/2022

המשימה התכנותית הראשונה בקורסרה

חדר כושר, חודשיים אחרי הרישום

בלוג, אחרי השמונה פוסטים הראשונים

תואר ראשון, אחרי המבחנים של שנה א

פיתוח מוצר תוכנה, אחרי שהוא באוויר ואף אחד לא נרשם

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

מי מתאמץ יותר?

25/08/2022

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

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

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

טיפ ריילס: שתי דרכים ליצור פחות מיגרציות

24/08/2022

אחד הדברים הראשונים שאנחנו לומדים בעבודה עם ריילס הוא ליצור מיגרציות. מיגרציה היא בעצם קוד שמשנה משהו בבסיס הנתונים, למשל יוצר טבלה או משנה עמודות בטבלה. הנה דוגמה לקוד של מיגרציה:

class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.string :name
      t.text :description

      t.timestamps
    end
  end
end

שורת הפקודה של ריילס מאפשרת להריץ מיגרציות עם הפקודה:

$ ./bin/rails db:migrate

להרצת מיגרציה יש מספר השלכות:

  1. קודם כל מיגרציה משנה משהו בבסיס הנתונים. בשביל זה כתבנו אותה.

  2. בנוסף, ריילס שומר בבסיס הנתונים את המזהה של המיגרציה האחרונה שהוא הריץ, כדי שהוא יידע להריץ רק מיגרציות חדשות כל הפעלה של db:migrate.

  3. בנוסף, ריילס שומר קובץ בשם db/schema.rb שבו הוא מחזיק את ״התוצאות״ של כל המיגרציות, כלומר סוג של תמונה של בסיס הנתונים.

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

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

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

  1. הכי קל אם זו מיגרציה יחסית עדכנית להפעיל:
$ ./bin/rails db:rollback

הפקודה הזאת מבטלת את הרצת המיגרציה האחרונה, ואז אפשר לשנות את הקוד שלה ולהפעיל db:migrate מחדש.

  1. אם צריך לשנות משהו רוחבי בהרבה טבלאות או הרבה קבצי מיגרציה, אפשר ללכת בכיוון אחר ופשוט לזרוק את כל בסיס הנתונים. כל עוד לא עלינו לפרודקשן וממילא אנחנו בפיתוח נוכל להפעיל:
$ ./bin/rails db:drop

לשנות את כל קבצי המיגרציה שיתאימו למה שאנחנו צריכים ואז להפעיל מחדש:

$ ./bin/rails db:migrate
$ ./bin/rails db:seed

שימו לב שהפעלת db:migrate מעדכנת את הקובץ db/schema.rb, וקובץ זה משפיע על הפקודה db:reset. לכן אין טעם לשנות קובץ מיגרציה ולהפעיל אחרי זה ./bin/rails db:reset. פעולה כזאת פשוט תיצור את בסיס הנתונים מחדש באותו מבנה שהוא היה קודם. אחרי שמשנים קובץ מיגרציה חייבים להפעיל מחדש db:migrate כדי לשנות גם את db/schema.rb.

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