ToCode
Ir al canal en Telegram
1 419
Suscriptores
Sin datos24 horas
Sin datos7 días
-530 días
Archivo de publicaciones
1 419
זמן לימוד ארוך
בבית ספר לימדו אותי שילדים חכמים לומדים דברים מהר יותר. שעדיף ללמוד את החומר בפחות זמן ואם שני ילדים מקבלים את אותו ציון אז זה שלמד פחות הוא היותר חכם.
החיים האמיתיים לימדו אותי את ההיפך. היום אני מבין שמה שאנחנו צריכים זה את היכולת ללמוד משהו לאורך זמן ארוך. את היכולת להתרכז, להעמיק, לשאול עוד שאלות. בהינתן מסלול מספיק ארוך התמדה מנצחת מהירות בכל פעם.
1 419
הכל בסדר טייפסקריפט?
הקוד הבא בטייפסקריפט הצליח לבלבל אותי לרגע-
type MyItem = {type: 'a', url: string, id: number}
function go(item: MyItem) {}
const f = {type: 'a', url: 'url', id: 0}
go(f)
הוא לא מתקמפל.
הפונקציה לא מוכנה לקבל את f כי היא טוענת שהוא מטיפוס לא מתאים. הכל בסדר טייפסקריפט? תסתכל בטיפוס MyItem, זה בדיוק מתאים.
האמת שטייפסקריפט צודק (לפחות חלקית). הדרך בה טייפסקריפט עובד כשיש לי השמה של אוביקט למשתנה היא לנסות להסיק אוטומטית מה הטיפוס הכי הגיוני של אותו משתנה, ולפי הטיפוס הזה הוא מגביל את הפעולות הבאות שאפשר לעשות עם אותו משתנה. במקרה של f הטיפוס שטייפסקריפט הסיק הוא:
const f: {
type: string;
url: string;
id: number;
}
כלומר ה type הוא מסוג string ולא מהסוג הספציפי 'a'. מבחינת טייפסקריפט מותר לשנות את ה type לערך אחר, כלומר הקוד הבא כן מתקמפל:
type MyItem = {type: 'a', url: string, id: number}
function go(item: MyItem) {}
const f = {type: 'a', url: 'url', id: 0}
f.type = 'b';
כשטייפסקריפט רואה את הקריאה:
go(f)
הוא מסתכל רק על הטיפוס של f ועל הטיפוס שהפונקציה צריכה לקבל ומבין שאין התאמה. טייפסקריפט לא יכול להתחייב שאף אחד לא שינה את הערך של type כי מבחינתו מותר לשנות את הערך שם. לכן הוא גם לא יכול להתחייב שמה שנמצא כרגע במשתנה f באמת מתאים לחתימה של הפונקציה.
איך פותרים את זה? אז מסתבר שיש כמה דרכים - קודם כל אפשר להגיד לטייפסקריפט בצורה מפורשת מה הטיפוס של f:
type MyItem = {type: 'a', url: string, id: number}
function go(item: MyItem) {}
const f: MyItem = {type: 'a', url: 'url', id: 0}
עכשיו אפשר יהיה להעביר אותו לפונקציה go ואי אפשר יהיה לשנות את הערך של f.type.
עוד אופציה היא להגדיר ש type הוא קבוע בתוך הגדרת האוביקט:
const f = {type: 'a' as const, url: 'url', id: 0}
ודרך שלישית לקבל את אותו טיפוס היא להגדיר את type ממש להיות a:
const f = {type: 'a' as 'a', url: 'url', id: 0}
מכירים רעיונות נוספים? שתפו בתגובות אשמח לשמוע.1 419
מה map מחזירה
עוד דרך להסתכל על ההבדל בין תכנות מונחה עצמים לתכנות פונקציונאלי היא להסתכל על פונקציות וערכי ההחזר שלהן. ניקח את הפונקציה map לדוגמה ונשווה -
סקאלה, שהיא שפה מונחית עצמים, תגדיר לכל Collection פונקציית map שונה שמחזירה Collection חדש מאותו הסוג. בגלל זה כשאני מריץ map על קבוצה אני מקבל חזרה קבוצה:
scala> Set(1, 2, 3).map(_ * 2)
val res1: Set[Int] = Set(2, 4, 6)
scala> List(1, 2, 3).map(_ * 2)
val res4: List[Int] = List(2, 4, 6)
קלוז'ר לעומתה שמה את הדגש על הפונקציה ולכן הפעלה של map על כל דבר מחזירה תמיד LazySeq:
user=> (type (map (partial * 2) #{1 2 3}))
clojure.lang.LazySeq
user=> (type (map (partial * 2) [1 2 3]))
clojure.lang.LazySeq
ו JavaScript מסכימה להגדיר map רק על רשימה ולכן:
new Set([1, 2, 3]).map(i => i * 2)
TypeError: new Set([1, 2, 3]).map is not a function. (In 'new Set([1, 2, 3]).map(i => i * 2)', 'new Set([1, 2, 3]).map' is undefined)
מה עדיף? לא נעים להגיד אבל אני מצליח לטעות בכל השפות. הדבר החשוב הוא תמיד לזכור באיזה שפה אנחנו ומה המאפיינים של אותה שפה. אפילו (ואולי במיוחד) כשלפונקציות יש את אותו שם.
נ.ב. ומה מחזיר הקוד הבא ברובי? נסו לבדוק ותראו אם הבנתם למה:
3.1.1 :002 > [1, 2, 3].map { _ * 2 }1 419
אופטימיזציית זנב הרקורסיה ב JavaScript ב 2024
לפני 6 שנים כתבתי על אופטימיזציית זנב הרקורסיה בדפדפנים:
https://www.tocode.co.il/blog/2018-09-javascript-tco
מאז הרבה קרה בדפדפן אבל דווקא הפיצ'ר הזה נשאר מאחור. ספארי, כרום וגם פיירפוקס לא מצליחים לחשב רקורסיבית מספרים גדולים.
אבל נקודת אור אחת כן הצטרפה בעולם של JavaScript בצד שרת מהכיוון של bun (נוד ודינו, כמו הדפדפנים עדיין לא תומכים באופטימיזציה זו).
הנה התוכנית:
function factors_of(number, i=2) {
if (number < i) {
return [];
}
if (number % i === 0) {
return [i, ...factors_of(number / i, i)];
}
return factors_of(number, i+1);
}
console.log(factors_of(909090909090909090909090));
והרצה בצד שרת ב node:
node a.js
/Users/ynonp/tmp/a.js:1
function factors_of(number, i=2) {
^
RangeError: Maximum call stack size exceeded
at factors_of (/Users/ynonp/tmp/a.js:1:20)
at factors_of (/Users/ynonp/tmp/a.js:10:12)
at factors_of (/Users/ynonp/tmp/a.js:10:12)
at factors_of (/Users/ynonp/tmp/a.js:10:12)
at factors_of (/Users/ynonp/tmp/a.js:10:12)
at factors_of (/Users/ynonp/tmp/a.js:10:12)
at factors_of (/Users/ynonp/tmp/a.js:10:12)
at factors_of (/Users/ynonp/tmp/a.js:10:12)
at factors_of (/Users/ynonp/tmp/a.js:10:12)
at factors_of (/Users/ynonp/tmp/a.js:10:12)
Node.js v21.7.1
ב Deno:
deno run a.js
error: Uncaught (in promise) RangeError: Maximum call stack size exceeded
function factors_of(number, i=2) {
^
at factors_of (file:///Users/ynonp/tmp/a.js:1:20)
at factors_of (file:///Users/ynonp/tmp/a.js:10:12)
at factors_of (file:///Users/ynonp/tmp/a.js:10:12)
at factors_of (file:///Users/ynonp/tmp/a.js:10:12)
at factors_of (file:///Users/ynonp/tmp/a.js:10:12)
at factors_of (file:///Users/ynonp/tmp/a.js:10:12)
at factors_of (file:///Users/ynonp/tmp/a.js:10:12)
at factors_of (file:///Users/ynonp/tmp/a.js:10:12)
at factors_of (file:///Users/ynonp/tmp/a.js:10:12)
at factors_of (file:///Users/ynonp/tmp/a.js:10:12)
ורק bun מפתיע לטובה:
$ bun a.js
[
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
71, 101, 251, 401, 9384251
]1 419
למה בנית את זה ככה?
נתונה קומפוננטת ריאקט:
function ItemData() {
const { id } = useParams();
const { data, error, isLoading } = useSWR('/api/user', fetcher);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
return <ItemView item={data} />
}
הקומפוננטה לוקחת את הפרמטר id מהגדרות הנתיב, פונה לשרת כדי למשוך את המידע על הפריט ואז מציגה את המידע ב ItemView. התצוגה בקומפוננטה נפרדת כדי שאפשר יהיה לבדוק אותה בלי קשר למשיכת המידע וגם זה יותר נוח לתחזק ככה את הקוד, אבל למה לפנות לשרת רק אחרי שהקומפוננטה מתרנדרת כדי למשוך את המידע?
תשובה 1 - כי זה הכי מהיר
פניה שניה לשרת? מה פתאום, פניתי לשרת רק פעם אחת. ה HTML וה JavaScript שמגיעים ללקוח נשלחים מ CDN ולא מהשרת שלי. רק אחרי טעינת העמוד המערכת פונה לשרת שלי כדי למשוך את כל המידע הרלוונטי ולהציג את העמוד.
מה אפשר לעשות אחרת? האמת שטוב ששאלת. אם רשימת הפריטים ידועה מראש אפשר היה לבנות ב Offline עמודי HTML שונים עבור כל id ולשמור את הכל ב CDN, וכך הדפדפן היה יכול להציג את המידע הכי מהר - מיד איך שהוא מקבל את העמוד אפילו בלי להריץ קוד ריאקט. תכל'ס נקסט יודע לעשות את זה אוטומטית.
אם הרשימה דינמית אפשר להשתמש ב Server Components כדי למשוך את כל המידע מבסיס הנתונים לפני ששולחים את העמוד ללקוח וכך לצמצם קריאות. באצת יהיה מעניין לבדוק מבחינת ביצועים איזה גישה נותנת תוצאות טובות יותר.
תשובה 2 - כי זה לא היה מספיק חשוב
פניה שניה לשרת? נו בסדר יש לי עוד 5 מאיפה שזאת באה. למי אכפת כמה פניות לשרת אתה שולח בשביל להציג את התוכן? אני שומר ב DB סטטיסטיקה שמראה של 95% מהלקוחות העמוד נטען תוך פחות משניה וזה כל מה שחשוב. כל עוד אנחנו לא חורגים מתקציב הביצועים שלנו עדיף להתמקד בפיצ'רים ולא באופטימיזציות.
תשובה 3 - כי זה מה שהיה כתוב בתיעוד
כל מי שכותב ריאקט יודע ש react-query ו swr הן הדרך הכי טובה למשוך מידע מהשרת. מה רצית שאכתוב useEffect שם? חוץ מזה הכל עובד אז לא נוגעים.
תשובה טובה ל"למה בנית את זה ככה" צריכה לפתוח דיון. צריכה לחשוף עוד אופציות ואת מערך השיקולים שהוביל לבחירה בדרך מסוימת. אם אין לכם עדיין תשובה טובה זה אולי זמן טוב להתחיל לחפש.1 419
שלושה יתרונות של ניהול הרשאות מבוסס Policy
ניהול הרשאות במערכת זה המנגנון שמאפשר לנו מרגע שמשתמש התחבר להבין מה מותר לאותו משתמש לעשות. באנגלית זה נקרא Authorization ובדרך כלל אנחנו מפרידים את מנגנון ניהול ההרשאות ממנגנון ההזדהות (Authentication). ההזדהות מאפשרת לי להגיד שמי שנכנס הרגע הוא משתמש X וניהול ההרשאות זה מה שמאפשר לי להגיד שלמשתמש X מותר להיכנס לדף מסוים.
במנגנון ניהול ההרשאות אנחנו מוצאים שתי גישות מרכזיות (עם ספריות קוד תואמות). הגישה הראשונה היא ניהול הרשאות מבוסס משתמש. פה יש לנו ב Rails את cancancan וב JavaScript/Typescript את casl. בגישה זאת אנחנו מתחילים את התוכנית בלקחת אוביקט משתמש ו"להדביק" לו הרשאות:
import { defineAbility } from '@casl/ability';
export default (user) => defineAbility((can) => {
can('read', 'Article');
if (user.isLoggedIn) {
can('update', 'Article', { authorId: user.id });
can('create', 'Comment');
can('update', 'Comment', { authorId: user.id });
}
});
הרבה פעמים אני מחלק את קטע הקוד הזה לפונקציות ותהיה לי פונקציה אחת שמטפלת במשתמש מסוג "גולש רגיל" פונקציה אחרת ל"גולש מחובר" פונקציה שלישית ל"מנוי" ורביעית ל"מנהל האתר". בכל פונקציה אני מדביק למשתמש את ההרשאות המתאימות.
פה באתר למשל אני משתמש בספריה כזאת בקוד ריילס ויש לי הרשאות כמו:
def guest_user(user)
can :read, BlogPost do |post|
post.published_at <= Time.now
end
can :read, Lesson, free: true
end
גישה כזאת עובדת מאוד טוב בתחילת הפרויקט או בפרויקטים קטנים, כל עוד כל ההרשאות נכנסות בקובץ אחד.
גישה שנייה לניהול הרשאות היא ניהול מבוסס מדיניות. בגישה זו אני מגדיר Policy לגישה ל"דבר" מסוים ובתוך המדיניות אני מגדיר מי רשאי לעשות מה עם אותו דבר. זאת הגישה שאנחנו פוגשים בספריות ריילס כמו pundit ו action_policy, פאנדיט גם זמינה ב JavaScript. הנה קטע מתוך התיעוד של action_policy כדי שנראה את ההבדל:
class PostPolicy < ApplicationPolicy
# everyone can see any post
def show?
true
end
def update?
# \user\ is a performing subject,
# \record\ is a target object (post we want to update)
user.admin? || (user.id == record.user_id)
end
end
או ב JavaScript מתוך התיעוד של pundit:
import { Policy } from 'pundit'
export default class PostPolicy extends Policy {
constructor(user, record) {
super(user, record)
this.setup.apply(this)
}
edit() {
return this.user.id === this.record.userId
}
destroy() {
return this.user.isAdmin
}
}
שלושה יתרונות מהירים של מדיניות הרשאות מבוססת מודלים הם:
1. כל ההרשאות של מודל מסוים מרוכזות במקום אחד.
2. אפשר לשתף Policy בין כמה מודלים.
3. ניהול שינויים - אם אני רוצה לקבוע ממחר שתוכן מסוג מסוים כבר לא חופשי בגישה הראשונה אני צריך לשנות בשני מקומות (גם בפונקציה שמגדירה הרשאות לאורחים וגם בפונקציה שמגדירה הרשאות למשתמשים רשומים). בגישה השנייה מספיק לעדכן את פונקציית הצפייה ב Policy שמתאים לדבר אותו אני מזיז.1 419
כמעט פרידה מ nodemon
נודמון היה חבר נאמן במשך הרבה שנים. מספיק קשה לזכור לשמור את הקובץ כל פעם אחרי שינויים, בכתיבת אפליקציית ווב צד שרת באקספרס בלעדיו היה צריך גם לסגור ולפתוח מחדש ידנית את השרת. נודמון נתן פיתרון כל כך טוב שכבר שכחנו שפיצ'ר בסיסי כמו לטעון מחדש את המערכת אחרי שמשנים קבצים זה משהו שצריך להיות מובנה בכלי העבודה.
והנה הגיע נוד 20 והפך את כל העסק להיסטוריה.
במקום להתקין ולהפעיל nodemon אנחנו יכולים כבר לכתוב:
node --watch ./bin/www
וכשיהיה שינוי אוטומטית נוד יטען מחדש את האפליקציה.
אז למה רק כמעט? לצערי מצב watch של node עדיין לא יודע לעבוד עם קבצי TypeScript ו ts-node לא מכיר עדיין את המתג הזה, לכן אם האפליקציה היא בטייפסקריפט אנחנו עדיין נצטרך את nodemon, שמקמפל אוטומטית את הטייפסקריפט עם כל שינוי.1 419
חווית המתכנת
כן יש מושג כזה ואפילו עם ראשי תיבות - DX, Developer Experience.
קו המחשבה כאן הוא שאם אנחנו כותבים כלים שלמתכנתים ולמתכנתות יהיה קל להשתמש בהם אז התוכנות שאותם אנשים יכתבו יהיו טובות יותר, מהירות יותר ובטוחות יותר. מצד שני כשאנשים צריכים לעבוד קשה כדי ללמוד איך להשתמש "נכון" בכלי שלך הם יעשו יותר טעויות והתוצאה (האפליקציה שהם כותבים) תהיה פחות טובה.
דוגמה טובה ל DX היא כשאנחנו בונים API. אם קל להשתמש ב API הזה וקל למשוך ממנו רק את המידע שהמתכנתים צריכים אז האפליקציות שייכתבו עם אותו API יצאו טובות יותר. אם ה API מכריח את המשתמשים למשוך הרבה יותר מידע ממה שהם צריכים אז למפתחים שישתמשו באותו API לא תהיה ברירה והם יאלצו לכתוב מערכות פחות טובות.
או אם אני כותב פריימוורק עם תיעוד טוב אז מתכנתים ישתמשו בפריימוורק כמו שתכננתי והמערכת שהם כותבים תהיה מהירה יותר. אם אני לא כותב תיעוד הם ישתמשו בה בצורה שלא חשבתי עליה והתוצאה תהיה באגים מוזרים ומערכת איטית ולא מאובטחת.
הבעיה ב DX מתחילה כש DX מתחיל לבוא על חשבון UX, וכשאנחנו מבלבלים בין כלים לכתיבת מערכות טובות ל"כלים שכיף לנו להשתמש בהם". פריזמה מפורסמת בתור ה ORM עם ה DX הטוב ביותר בעולם, אבל בלי אפשרות לעשות JOIN בין טבלאות כל יישום שישתמש בפריזמה יצטרך לעשות הרבה יותר עבודה (נכון אוטומטית בלי לערב את המתכנתים, אבל עדיין) ולכן יישומים רבים יהיו איטיים בהרבה בהשוואה לכאלה שייכתבו עם דריזל.
עוד דוגמה מפורסמת היתה פונגאפ (ואחריה קורדובה) שנתנה למפתחים אפשרות לכתוב אפליקציות מובייל בכלים של פיתוח ווב שהם כבר מכירים, אבל ייצרה אפליקציות שעבדו לאט והיו מוגבלות מבחינת היכולות והאינטגרציה עם הטלפון.
כשעובדים בצוות האתגר הוא כפול: אותם כלים שמספקים DX מהאגדות הרבה פעמים עברו מספיק ליטוש כדי שלא נראה את הבעיות באפליקציות POC קטנות. לפעמים יש תחושה שאותם כלים נכתבו ספציפית עבור אותן אפליקציות קטנות. הרבה פעמים החברים בצוות לא להוטים לוותר על חווית המפתח המושקעת מתוך הרגשה שבעיות הביצועים הם דאגה של העתיד או שאולי בכלל לא יגיעו אלינו.
אין לי פיתרון קל למצבים האלה. ככלל ככל שאנחנו מבינים יותר טוב את הכלים ואת המגבלות המובנות שלהם קל לנו יותר לבחור את אותם כלים שמשלבים חווית מפתחים טובה עם מוצר טוב ללקוח.
1 419
פה כמעט התייאשתי עד שמצאתי את הפוסט הזה:
https://aymarino.github.io/polygon-area/
שמסביר על נוסחה בשם נוסחת שרוך הנעל שמאפשרת לחשב שטח של פוליגון רק לפי הקודקודים שלו. בשביל להשתמש בנוסחה תחילה הפכתי את רשימת ההוראות לרשימה של קודקודים:
def coordinates(input: Source, parseLine: (line: String) => PaintingInstruction): List[(Long, Long, Long)] =
input
.getLines()
.scanLeft((0L, 0L, 0L)) { (acc, line) =>
val (row, column, distance) = acc
val paintingInstruction = parseLine(line)
paintingInstruction.dir match
case "L" => (row, column - paintingInstruction.count, distance + paintingInstruction.count)
case "R" => (row, column + paintingInstruction.count, distance + paintingInstruction.count)
case "U" => (row - paintingInstruction.count, column, distance + paintingInstruction.count)
case "D" => (row + paintingInstruction.count, column, distance + paintingInstruction.count)
}.toList
הערך השלישי ברשימה הוא המרחק הכולל של הצורה, וזה יעזור לנו בהמשך החישוב. כתבתי שתי פונקציות לקריאת שורות להתאים לשני חלקי התרגיל:
def parsePart1(line: String): PaintingInstruction =
val lineRE = """(\w) (\d+) \((#\w+)\)""".r
val List(d, count, color) = lineRE.findFirstMatchIn(line).get.subgroups
PaintingInstruction(d, count.toInt, color)
def parsePart2(line: String): PaintingInstruction =
val lineRE = """\w \d+ \(#(\w\w\w\w\w)(\w)\)""".r
val List(count, d) = lineRE.findFirstMatchIn(line).get.subgroups
val distance = java.lang.Long.parseLong(count, 16)
val direction = d match
case "0" => "R"
case "1" => "D"
case "2" => "L"
case "3" => "U"
PaintingInstruction(direction, distance, "...")
ואז את הנוסחה לשטח פוליגון:
def shoelace(coordinates: List[(Long, Long, Long)]): Long =
coordinates.zip(coordinates.tail).map { pair =>
val ((x1, y1, d1), (x2, y2, d2)) = pair
x1 * y2 - y1 * x2
}
.sum
.abs / 2
זה הקוד לחלק הראשון:
def day18part1(): Unit =
val coords = coordinates(Source.fromResource("day18.txt"), parsePart1)
val distance = coords.last._3
val area = shoelace(coords)
val poolSize = area + distance / 2 + 1
println(poolSize)
וזה החלק השני:
def day18part1(): Unit =
val coords = coordinates(Source.fromResource("day18.txt"), parsePart2)
val distance = coords.last._3
val area = shoelace(coords)
val poolSize = area + distance / 2 + 1
println(poolSize)
אני מודה שבמבט ראשון התרגיל הזה נראה לי משעמם ולכן לקח לי הרבה זמן לגשת אליו. בסופו של דבר היה טוויסט ולמדתי כמה נוסחאות חדשות כך שיצא מוצלח.1 419
פיתרון Advent Of Code יום 18 בסקאלה
מזמן לא כתבנו Advent Of Code וזו הזדמנות מצוינת להיזכר ולהתקדם. יש לנו 25 חידות סך הכל אז אנחנו ממש מתקרבים לסיום. בואו נראה את יום 18, את הניסיון הראשון הארוך והלא מוצלח שלי ואז את הפיתרון האמיתי.
מה צריך לחשב
היום אנחנו הולכים לחפור בריכה. בדרך כלל כשמציירים מטריצה דו-מימדית אנחנו מדמיינים שאנחנו מסתכלים על המשטח מלמעלה, אבל בתרגיל היום אנחנו מסתכלים על הבריכה מהצד. זה הציור להמחשה:
#######
#.....#
###...#
..#...#
..#...#
###.###
#...#..
##..###
.#....#
.######
איך חופרים את הבריכה אתם שואלים? אין בעיה זה בדיוק הקלט לפאזל. בשביל לחפור אנחנו מקבלים רשימת הוראות שנראית כך:
R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)
האותיות R, D, L ו U קובעות לאיזה כיוון לחפור (שמאלה, למטה, ימינה או למעלה) והמספר קובע כמה מטר. נתעלם בינתיים מהמספר האחרון בשורה.
אחרי שחפרנו את הבריכה ממלאים אותה במים וזה נראה ככה:
#######
#######
#######
..#####
..#####
#######
#####..
#######
.######
.######
ועכשיו צריך לגלות כמה משבצות עם סולמית יש בציור. בדוגמה יש 64 (אתם יכולים לספור).
איך לא לספור סולמיות
כשראיתי את המטריצה הדבר הראשון שרציתי לעשות היה לשמור אותה במפה בסקאלה כשהמפתח הוא הקואורדינטות והערך הוא סולמית או מחרוזת הציור או true או מה שלא יהיה.
אחרי זה רציתי לקחת נקודה בתוך הבריכה ולהתחיל לחפש ממנה את כל הנקודות שסביבה כלפי חוץ עד שאני מגיע לקו הבריכה וכך אני יכול לדעת כמה משבצות נמצאות בתוך שטח הבריכה. מוסיפים את זה להיקף ומצאנו את מספר הסולמיות. אפילו כתבתי את הקוד.
שתי הפונקציות האלה חופרות את הבריכה:
case class PaintingInstruction(dir: String, count: Long, color: String)
def dig(input: Source): Canvas =
val lineRE = """(\w) (\d+) \((#\w+)\)""".r
input
.getLines()
.foldLeft(((0, 0), Map[(Int, Int), PaintingInstruction]())) { (acc, line) =>
Try {
val (pos, canvas) = acc
val List(d, count, color) = lineRE.findFirstMatchIn(line).get.subgroups
val paintingInstruction = PaintingInstruction(d, count.toInt, color)
paint(pos, canvas, paintingInstruction)
}.recover { err =>
// skip line in case of error
println(s"Error in line ${line} - ${err}")
acc
}.get
}._2
def paint(pos: Point, canvas: Canvas, cmd: PaintingInstruction): (Point, Canvas) =
0L.until(cmd.count).foldLeft((pos, canvas)) { (acc, count) =>
val ((row, column), canvas) = acc
val nextCanvas = canvas.updated((row, column), cmd)
cmd.dir match
case "R" => ((row, column + 1), nextCanvas)
case "L" => ((row, column - 1), nextCanvas)
case "U" => ((row - 1, column), nextCanvas)
case "D" => ((row + 1, column), nextCanvas)
}
והפונקציה הזאת מחפשת את כל הנקודות בתוך הבריכה:
@tailrec
def floodFill(canvas: Canvas, finished: Set[Point], inProgress: List[Point]): Set[Point] =
inProgress match
case head :: tail =>
val (row, column) = head
println(head)
val neighbors = List(
(row-1, column),
(row+1, column),
(row, column-1),
(row, column+1),
).filterNot { p => canvas.contains(p) }
.filterNot(finished.contains)
.filterNot(inProgress.contains)
floodFill(canvas, finished + head, tail ++ neighbors)
case Nil => finished
הקוד פותר את החלק הראשון אבל נכשל בצורה כואבת בחלק השני והוא גם לא יעיל.
ניסיון שני
בחלק השני של התרגיל מספרים לנו שבעצם קראנו את השורה לא נכון כל הזמן. המספר האחרון הוא הדבר החשוב - חמש הספרות הראשונות שלו הן מספר בבסיס 16, והסיפרה האחרונה מייצגת את הכיוון 0 עבור ימינה, 1 עבור למטה 2 עבור שמאלה ו 3 אומר למעלה. המספרים גדולים ולכן כבר לא ריאלי לשמור את כל המשבצות במטריצה. אם היינו שומרים ומחשבים קלט הדוגמה היה מסתכם ב 952408144115 משבצות.
¡Ya disponible! Investigación de Telegram 2025 — los principales insights del año 
