ToCode
1.43K subscribers
3.42K links
טיפים קצרים למתכנתים מאת ינון פרק
Download Telegram
קבצים סטטיים מאקספרס על Deno Deploy
אם תהיתם מאיפה הגיעו כל הפוסטים על node ו deno בימים האחרונים אז תשמחו לשמוע שאני עובד עכשיו על רענון קורס Node שבאתר ומקווה שעד פסח אתם תקבלו קורס מעודכן על Node שכבר כולל אינסוף דוגמאות קוד ב TypeScript ותואם גם ל Node וגם ל Deno. רוב הזמן זה לא נורא מסובך אבל בלי קצת בעיות תאימות החיים לא היו מעניינים. בפוסט היום אני רוצה לדבר על בעיית תאימות קטנה כזאת בשירות Deno Deploy.

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

בהינתן תיקיית פרויקט Deno אפשר לכתוב משורת הפקודה:

$ deployctl deploy


והפרויקט עולה לאוויר.

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

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

את קוד הפרויקט אתם יכולים למצוא בגיטהאב שלי בקישור:
https://github.com/ynonp/deno-deploy-files

כאן אציג רק את עיקרי הדברים.

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

import express, {Request, Response, NextFunction} from 'express';
import process from 'node:process';
import root from './routes/root.ts';
import path from 'node:path';
import url from 'node:url';
import createError from 'http-errors';

const PORT = process.env.PORT || 3000;
const app = express();

const dirname = path.dirname(url.fileURLToPath(import.meta.url));
app.set('view engine', 'ejs');
app.set('views', path.join(dirname, '/views'));

app.use('/', root);
app.use(express.static('./public'));

app.use((req: Request, res: Response, next: NextFunction) => {
next(createError(404, 'Not Found'));
})

app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err);
res.status(500).send({ errors: [{ message: "Something went wrong" }] });
});

app.listen(PORT, () => {
console.log(\Server listening on port ${PORT}\);
});


כאשר השורה הרלוונטית היא:

app.use(express.static('./public'));


מה קורה כשדוחפים ל Deno Deploy
אחרי העלאה ל Deno Deploy התוכנית עובדת אבל הקובץ הסטטי (בדוגמה שלי זה קובץ CSS) לא נטען. בדיקה בלוג מראה את השגיאה הבאה:

TypeError: Cannot read properties of null (reading 'toUTCString')
at SendStream.setHeader (file:///node_modules/.deno/send@0.18.0/node_modules/send/index.js:874:31)
at SendStream.send (file:///node_modules/.deno/send@0.18.0/node_modules/send/index.js:620:8)
at onstat (file:///node_modules/.deno/send@0.18.0/node_modules/send/index.js:725:10)
at Deno.stat.then.denoErrorToNodeError.syscall (ext:deno_node/_fs/_fs_stat.ts:85:32)
at eventLoopTick (ext:core/01_core.js:153:7)


פה כבר התחלתי לפחד. זה נראה ש stat של דינו לא עובד בדיוק כמו stat של node.js ולכן משהו נשבר בשידור הקובץ.

במקום לריב איתם הלכתי לכתוב מידלוור משלי שישלח את הקובץ בלי stat. הוא נראה ככה:

app.use(async (req: Request, res: Response, next: NextFunction) => {
try {
const userInput = path.join('./public', req.path);
const safeInput = path.normalize(userInput).replace(/^(\.\.(\/|\\|$))+/, '');
const mime = contentType(path.extname(safeInput))
console.log(\File: ${safeInput}. content type = ${mime}\);
const content = await Deno.readFile(safeInput);
res.set('Content-Type', mime);
res.send(buffer.Buffer.from(content));
} catch (err) {
console.log(\file not found ${req.path}\);
next();
}
});


ואת התוצאה אפשר לראות אונליין על Deno Deploy עם ה CSS בקישור:

https://ynonp-deno-deploy-files-yfge7qm6f97y.deno.dev/
👍2
אבל אני לא משתמש ב xz
כמה בעיות של העולם בעקבות הניסיון האחרון לשתול דלת אחורית בתוכנה שכולם משתמשים בה-

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

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

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

נראות אולי הסיכוי היחיד שלנו לצאת טוב וקצת לשפר את האבטחה של העולם. פרויקט xz הוא פרויקט בקוד פתוח אבל בניית הבינארים להפצה נעשתה על המכונה של המפתח הזדוני. אחת ההבטחות של קוד פתוח היא שיותר עיניים יכולות לראות יותר באגים. בסיפור של xz מישהו ידע את זה ועבד קשה בשביל להסתיר את הפריצה.
👍4
דינו או באן
כמו כל תוכנה שהיתה איתנו הרבה זמן, גם node.js מושך איתו הרבה קוד ישן ופיצ'רים שאף אחד כבר לא יודע שקיימים. הדברים האלה מאטים את הפיתוח וגם את הזמן הריצה וזה לא סוד שיישומי node היו יכולים להיות מהירים יותר.

נוסף על כך נוד מוגבל על ידי מבנה וארכיטקטורה שאנשים כבר התרגלו אליהם ונטועים עמוק בתוך האקוסיסטם של הפרויקט. דברים כמו ניהול תלויות דרך package.json, הגירסה שלהם ל Buffer-ים והגירסה שלהם ל Stream וכמובן CommonJS ועוד המון פיתוחים שעברו סטנדרטיזציה אחרי שנכנסו ל Node.

בכל ההשוואות שמצאתי בין שלושת הכלים באן היה המהיר ביותר, עם יעד להיות כמה שיותר תואם ל node. בגירסה 1.1 שיצאה עכשיו הם מספרים בהתלהבות על ה APIs הלא מתועדים של node שהם משכפלים.

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

לאן זה הולך?

קשה לי לדמיין עתיד בו node.js ממשיך להוביל. אם באן יעבדו נכון מהר מאוד אנשים יתחילו להחליף את ה node שלהם ב bun בלי להרגיש בהבדל. ובדיוק כמו שאף אחד לא מתגעגע לוובפאק גם לא נראה געגועים ל node. הדבר החדש יעבוד בדיוק אותו דבר רק מהר יותר.

ועדיין אני מקווה לראות את דינו לוקח את ההובלה. יש להם קהילה מפרגנת ומיתוג טוב וכוונה אמיתית ליצור משהו טוב יותר. אם זה יקרה זה לא יהיה Drop In Replacement אלא בחירה חדשה שנצטרך לעשות. אני אשמח להתעורר יום אחד ולגלות שכמו שלא הייתי צריך יותר את jQuery אני גם לא אצטרך את כל ה require, ה Buffer וה APIs הלא מתועדים.
👍1
שלוש תחנות בדרך למציאת עבודה בהייטק
קודם כל גילוי נאות, בתור פרילאנסר ב 12 שנים האחרונות עבר הרבה זמן מאז הפעם האחרונה שחיפשתי עבודה. אני היום מכיר את העולם הזה יותר דרך חברים או מניסיונות שלי לעזור ללקוחות לגייס.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

type DeepPartial<T> = T | 
(T extends Array<infer U>
? DeepPartial<U>[]
: T extends Map<infer K, infer V>
? Map<DeepPartial<K>, DeepPartial<V>>
: T extends Set<infer M>
? Set<DeepPartial<M>>
: T extends object ? {
[K in keyof T]?: DeepPartial<T[K]>;
} : T);


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

בואו נקרא את ההגדרה יחד-

1. מגדירים טיפוס חדש בשם DeepPartial שהוא טיפוס גנרי. הוא יכול לקבל כל טיפוס טייפסקריפט קיים בתור בסיס.

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

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

4. גם לא מפה? לא להיבהל אולי זה Set. אם כן אז בעצם אנחנו צריכים סט מטיפוס DeepPartial של הפריטים ב T.

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

6. לא אוביקט? טוב אז נשארים עם T כי זה בטח משתנה פשוט.

בעברית הטיפוס הזה הוא שידרוג של Partial הרגיל של טייפסקריפט. בעוד ש Partial הופך את כל המפתחות של אוביקט לאופציונאליים בצורה רדודה, ה DeepPartial הופך את כולם לאופציונאליים בצורה מקוננת. כשחושבים על זה הוא אפילו לא נשמע כל כך מיותר או מסובך. פשוט דורש היכרות עם אותם מבנים שלרוב אנחנו אוהבים לטאטא מתחת לשטיח.
🤮2👍1
מה שעשינו פעם קודמת
אחת ההטיות שקשה להתגבר עליהן היא הטיית ה"פעם קודמת עשינו את זה". במיוחד אם פעם קודמת הגענו לתוצאה סבירה.

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

אבל אני לא בטוח שזה כל כך פשוט. הנה עוד כמה נקודות למחשבה:

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

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

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

כשאנחנו מוכנים לבחון מחדש החלטות שעשינו אנחנו מפרידים את עצמנו מההחלטה. אני כבר לא "מתכנת ריאקט" או "מתכנתת Java". אני פה בשביל לפתור בעיות ואני אשמח לבחור את הדרך הטובה ביותר לפתור את הבעיה הבאה.
👍6
מה המילה שמופיעה הכי הרבה פעמים במובי דיק?
לא צריך יותר מכמה שורות פייתון וספר חופשי או שניים כדי למצוא מילים מעניינות באנגלית. במקרה של מובי דיק החיים קלים כי הספר ללא זכויות יוצרים ואפשר למצוא את כל הטקסט המקורי בפרויקט גוטנברג בקישור:

https://www.gutenberg.org/cache/epub/2701/pg2701.txt

עכשיו בואו נלך לקרוא אותו, אבל בהילוך מהיר.

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

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

הקוד הזה לדוגמה ייקח משפט וידפיס את כל הטוקנים והתפקיד של כל טוקן במשפט:

import spacy

nlp = spacy.load("en_core_web_trf")
doc = nlp("This is such a long sentence that I cannot read it so go on please.")

print([(w.text, w.pos_) for w in doc])


והפלט:

[('This', 'PRON'), ('is', 'AUX'), ('such', 'DET'), ('a', 'DET'), ('long', 'ADJ'), ('sentence', 'NOUN'), ('that', 'SCONJ'), ('I', 'PRON'), ('can', 'AUX'), ('not', 'PART'), (
'read', 'VERB'), ('it', 'PRON'), ('so', 'CCONJ'), ('go', 'VERB'), ('on', 'ADP'), ('please', 'INTJ'), ('.', 'PUNCT')]


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

בעיה 1 - מאיפה משיגים את הטקסט
הטקסט של מובי דיק זמין אונליין ולכן בשביל לקבל אותו אני יכול להשתמש בקוד פייתון הבא:

import urllib.request
import ssl

if __name__ == "__main__":
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

with urllib.request.urlopen("https://www.gutenberg.org/cache/epub/2701/pg2701.txt",
context=ctx) as f:
text = f.read().decode('utf8')
print(len(text))


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

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

def chunks(text: str, max_size: int):
while len(text) > max_size:
space_index = text[:max_size].rfind(' ')
yield text[:space_index]
text = text[space_index:]
print(f"{len(text)} chars left")
yield text


עכשיו אנחנו מוכנים לספור את המילים:

def count_words(text: str):
nlp = spacy.load("en_core_web_trf")
word_count = Counter()
for chunk in chunks(text, 100_000):
doc = nlp(chunk)
word_tags = {'ADV', 'VERB', 'NOUN', 'ADJ'}
weird_tokens = {"'s", "so", "then", "there"}
word_count.update([w.text.lower()
for w in doc
if (w.pos_ in word_tags) and (w.text not in weird_tokens)])

return word_count




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

סך הכל זאת היתה כל התוכנית:

import spacy
from collections import Counter
import urllib.request
import ssl

def chunks(text: str, max_size: int):
while len(text) > max_size:
space_index = text[:max_size].rfind(' ')
yield text[:space_index]
text = text[space_index:]
print(f"{len(text)} chars left")
yield text
1
def count_words(text: str):
nlp = spacy.load("en_core_web_sm")
word_count = Counter()
for chunk in chunks(text, 100_000):
doc = nlp(chunk)
word_tags = {'ADV', 'VERB', 'NOUN', 'ADJ'}
weird_tokens = {"'s", "so", "then", "there"}
word_count.update([w.text.lower()
for w in doc
if (w.pos_ in word_tags) and (w.text not in weird_tokens)])

return word_count


if __name__ == "__main__":
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

with urllib.request.urlopen("https://www.gutenberg.org/cache/epub/2701/pg2701.txt",
context=ctx) as f:
text = f.read().decode('utf8')
word_count = count_words(text)
print(f"Book has {len(word_count)} words")

print("--- most common 200 words:")
print(word_count.most_common(200))

print("--- least common 200 words:")
print(word_count.most_common()[:-201:-1])

print("--- least common 200 words that appear more than once:")
word_count_greater_than_1 = sorted({k: v for k, v in word_count.items() if v > 1}.items(),
key=lambda x: x[1])

print(word_count_greater_than_1[:200])


תוצאות? בטח. רשימה ראשונה - המילים הכי נפוצות בספר מובי דיק:
[('whale', 894), ('now', 781), ('ship', 515), ('more', 507), ('man', 504), ('old', 440), ('other', 432), ('sea', 431), ('’s', 416), ('only', 378), ('head', 333), ('boat', 331), ('time', 329), ('long', 327), ('very', 322), ('here', 316), ('ye', 315), ('still', 311), ('great', 300), ('said', 296), ('most', 286), ('seemed', 279), ('last', 275), ('way', 269), ('chapter', 267), ('see', 265), ('again', 258), ('have', 256), ('yet', 247), ('whales', 246), ('little', 246), ('_', 243), ('men', 239), ('say', 233), ('round', 230), ('first', 225), ('much', 223), ('same', 213), ('such', 208), ('hand', 207), ('side', 206), ('never', 206), ('ever', 205), ('own', 205), ('good', 202), ('look', 200), ('almost', 196), ('even', 192), ('go', 192), ('deck', 188), ('thing', 187), ('water', 186), ('all', 185), ('as', 183), ('too', 182), ('made', 177), ('come', 177), ('away', 175), ('world', 174), ('white', 174), ('day', 171), ('thou', 170), ('life', 167), ('far', 165), ('seen', 164), ('do', 163), ('many', 161), ('well', 159), ('line', 158), ('let', 157), ('eyes', 156), ('had', 156), ('fish', 154), ('part', 153), ('sort', 152), ('cried', 150), ('thought', 148), ('know', 148), ('back', 147), ('once', 147), ('night', 147), ('boats', 145), ('so', 144), ('air', 140), ('crew', 137), ('whole', 136), ('full', 135), ('take', 134), ('thus', 134), ('things', 133), ('tell', 133), ('small', 130), ('soon', 129), ('feet', 127), ('hands', 125), ('came', 123), ('whaling', 122), ('mast', 121), ('has', 121), ('captain', 119), ('think', 118), ('half', 118), ('found', 117), ('just', 117), ('place', 117), ('called', 116), ('make', 114), ('saw', 112), ('times', 112), ('right', 110), ('body', 110), ('work', 110), ('poor', 108), ('high', 106), ('heard', 106), ('moment', 105), ('sight', 104), ('sperm', 104), ('end', 102), ('aye', 101), ('stand', 100), ('one', 100), ('sail', 98), ('strange', 98), ('hold', 98), ('years', 96), ('however', 95), ('face', 95), ('sun', 95), ('down', 94), ('voyage', 94), ('few', 94), ('went', 94), ('also', 93), ('dead', 93), ('get', 92), ('certain', 91), ('is', 90), ('oil', 90), ('going', 89), ('heart', 89), ('perhaps', 89), ('stood', 89), ('indeed', 89), ('give', 88), ('ships', 88), ('eye', 87), ('sometimes', 87), ('heads', 86), ('days', 86), ('seems', 86), ('like', 86), ('true', 85), ('matter', 85), ('arm', 85), ('iron', 85), ('hard', 84), ('set', 84), ('black', 83), ('soul', 82), ('death', 81), ('seem', 81), ('wild', 81), ('standing', 81), ('cabin', 81), ('known', 80), ('tail', 80), ('always', 80), ('present', 80), ('seas', 79), ('large', 79), ('mind', 79), ('young', 79), ('light', 79), ('length', 78), ('land', 78), ('instant', 77), ('least', 76), ('open', 76), ('harpooneer', 76), ('enough', 76), ('bed', 76), ('at', 76), ('fire', 75), ('mate', 75), ('harpoon', 75), ('leg', 75), ('word', 74), ('morning', 74), ('vast', 73), ('living', 73), ('board', 73), ('put', 73), ('did', 73), ('lay', 73), ('done', 73), ('often', 73), ('-', 72), ('point', 71), ('deep', 70)]


רשימה שניה - המילים הכי פחות נפוצות בספר מובי דיק:
[('newsletter', 1), ('subscribe', 1), ('includes', 1), ('confirmed', 1), ('volunteer', 1), ('network', 1), ('originator', 1), ('checks', 1), ('addresses', 1), ('donation', 1), ('web', 1), ('treatment', 1), ('gratefully', 1), ('international', 1), ('donors', 1), ('accepting', 1), ('prohibition', 1), ('solicitation', 1), ('www.gutenberg.org/donate', 1), ('locations', 1), ('paperwork', 1), ('charities', 1), ('outdated', 1), ('widespread', 1), ('www.gutenberg.org/contact', 1), ('deductible', 1), ('identification', 1), ('corporation', 1), ('educational', 1), ('501(c)(3', 1), ('sections', 1), ('ensuring', 1), ('goals', 1), ('financial', 1), ('formats', 1), ('synonymous', 1), ('c', 1), ('deletions', 1), ('additions', 1), ('modification', 1), ('alteration', 1), ('employee', 1), ('indemnify', 1), ('provisions', 1), ('void', 1), ('unenforceability', 1), ('invalidity', 1), ('maximum', 1), ('violates', 1), ('types', 1), ('implied', 1), ('disclaimers', 1), ('elect', 1), ('distributor', 1), ('1.f.3', 1), ('warranty', 1), ('remedies', 1), ('disclaim', 1), ('codes', 1), ('virus', 1), ('disk', 1), ('infringement', 1), ('transcription', 1), ('data', 1), ('inaccurate', 1), ('defects', 1), ('stored', 1), ('proofread', 1), ('expend', 1), ('employees', 1), ('manager', 1), ('discontinue', 1), ('notifies', 1), ('periodic', 1), ('legally', 1), ('owed', 1), ('taxes', 1), ('%', 1), ('exporting', 1), ('hypertext', 1), ('processing', 1), ('proprietary', 1), ('nonproprietary', 1), ('compressed', 1), ('binary', 1), ('redistribute', 1), ('detach', 1), ('unlink', 1), ('redistributing', 1), ('indicating', 1), ('texts', 1), ('accessed', 1), ('1.e.', 1), ('representations', 1), ('downloading', 1), ('govern', 1), ('unprotected', 1), ('compilation', 1), ('1.e', 1), ('1.c', 1), ('indicate', 1), ('1.a.', 1), ('renamed', 1), ('orphan', 1), ('retracing', 1), ('sheathed', 1), ('padlocks', 1), ('dirgelike', 1), ('liberated', 1), ('ixion', 1), ('suction', 1), ('halfspent', 1), ('forth?—because', 1), ('thrill', 1), ('etherial', 1), ('intercept', 1), ('incommoding', 1), ('tauntingly', 1), ('backwardly', 1), ('touched;—at', 1), ('coincidings', 1), ('ironical', 1), ('intermixingly', 1), ('whelmings', 1), ('inanimate', 1), ('animate', 1), ('lookouts', 1), ('infatuation', 1), ('gaseous', 1), ('mediums', 1), ('bewildering', 1), ('bowstring', 1), ('mutes', 1), ('voicelessly', 1), ('grooves;—ran', 1), ('grapple', 1), ('unconquering', 1), ('comber', 1), ('foregone', 1), ('prow,—death', 1), ('bullied', 1), ('uncracked', 1), ('unsurrendered', 1), ('flume', 1), ('dislodged', 1), ('buttress', 1), ('predestinating', 1), ('inactive', 1), ('coppers', 1), ('though;—cherries', 1), ('gulping', 1), ('assassins', 1), ('brushwood', 1), ('mattrass', 1), ('unwinking', 1), ('unappeasable', 1), ('fidelities', 1), ('plaid', 1), ('gap', 1), ('splashing', 1), ('persecutions', 1), ('evolution', 1), ('crashing', 1), ('cracks!—’tis', 1), ('sinew', 1), ('tug', 1), ('ungraduated', 1), ('unprepared', 1), ('foreknew', 1), ('writhed', 1), ('fiercer', 1), ('tell”—he', 1), ('rowlocks', 1), ('pertinaciously', 1), ('abate', 1), ('busying', 1), ('staved', 1), ('judicious', 1), ('seekest', 1), ('again.—aye', 1), ('breath—“aye', 1), ('befooled!”—drawing', 1), ('befooled', 1), ('frayed', 1), ('flailed', 1), ('knitted', 1), ('tiers', 1), ('combinedly', 1), ('creamed', 1), ('brokenly', 1), ('swamping', 1), ('bedraggled', 1), ('berg', 1), ('upheaved', 1), ('ahab!—shudder', 1), ('it!—where', 1), ('soars', 1), ('vane”—pointing', 1), ('again!—drive', 1), ('whale!—ho', 1)]


רשימה שלישית - המילים הכי פחות נפוצות אבל שעדיין מופיעות יותר מפעם אחת:
[('restrictions', 2), ('updated', 2), ('*', 2), ('dick', 2), ('loomings', 2), ('postscript', 2), ('historically', 2), ('diminish?—will', 2), ('aloft.—thunder', 2), ('chase.—third', 2), ('combination', 2), ('defunct', 2), ('indebted', 2), ('dusting', 2), ('grammars', 2), ('vaulted', 2), ('entertaining', 2), ('affording', 2), ('rosy', 2), ('sadness', 2), ('tuileries', 2), ('gulp', 2), ('hoary', 2), ('paunch', 2), ('biggest', 2), ('verbal', 2), ('gulf', 2), ('insomuch', 2), ('parmacetti', 2), ('boil', 2), ('quid', 2), ('pikes', 2), ('fry', 2), ('troops', 2), ('caution', 2), ('discoverer', 2), ('fence', 2), ('abode', 2), ('conceal', 2), ('boldness', 2), ('revenue', 2), ('momentary', 2), ('serves', 2), ('impetus', 2), ('enemies', 2), ('swords', 2), ('finny', 2), ('mightier', 2), ('flounders', 2), ('gateway', 2), ('monument', 2), ('sprout', 2), ('resounds', 2), ('rushes', 2), ('neglected', 2), ('opportunities', 2), ('witnessing', 2), ('formidable', 2), ('displays', 2), ('employ', 2), ('a.d.', 2), ('inevitable', 2), ('national', 2), ('rebounds', 2), ('totally', 2), ('ex', 2), ('arches', 2), ('entrances', 2), ('alcoves', 2), ('whites', 2), ('manage', 2), ('cheery', 2), ('giant', 2), ('regulating', 2), ('warehouses', 2), ('surrounds', 2), ('waterward', 2), ('battery', 2), ('cooled', 2), ('seaward', 2), ('pent', 2), ('benches', 2), ('extremest', 2), ('suffice', 2), ('caravan', 2), ('metaphysical', 2), ('employs', 2), ('hermit', 2), ('woodlands', 2), ('overlapping', 2), ('spurs', 2), ('bathed', 2), ('sighs', 2), ('shepherd', 2), ('wade', 2), ('lilies', 2), ('cataract', 2), ('poet', 2), ('pedestrian', 2), ('deity', 2), ('tormenting', 2), ('rag', 2), ('tribulations', 2), ('judiciously', 2), ('mummies', 2), ('grasshopper', 2), ('touches', 2), ('indignity', 2), ('orchard', 2), ('thieves', 2), ('entailed', 2), ('cheerfully', 2), ('wholesome', 2), ('leaders', 2), ('secretly', 2), ('magnificent', 2), ('tragedies', 2), ('delusion', 2), ('inducements', 2), ('gates', 2), ('inmost', 2), ('amazingly', 2), ('monopolising', 2), ('sally', 2), ('concernment', 2), ('grapnels', 2), ('expensive', 2), ('congealed', 2), ('tinkling', 2), ('building', 2), ('stumble', 2), ('porch', 2), ('dilapidated', 2), ('carted', 2), ('ruins', 2), ('cheap', 2), ('pea', 2), ('palsied', 2), ('judging', 2), ('writer', 2), ('lookest', 2), ('improvements', 2), ('copestone', 2), ('curbstone', 2), ('tatters', 2), ('drinks', 2), ('tepid', 2), ('frosted', 2), ('thoroughly', 2), ('diligent', 2), ('systematic', 2), ('contemplation', 2), ('oft', 2), ('unwarranted', 2), ('yeast', 2), ('sublimity', 2), ('deceptive', 2), ('combat', 2), ('hyperborean', 2), ('yielded', 2), ('aggregated', 2), ('opinions', 2), ('dismantled', 2), ('purposing', 2), ('sickle', 2), ('segment', 2), ('mown', 2), ('mower', 2), ('wondered', 2), ('imbedded', 2), ('decanters', 2), ('bottles', 2), ('withered', 2), ('dearly', 2), ('sells', 2), ('pours', 2), ('villanous', 2), ('cheating', 2), ('rudely', 2), ('surround', 2), ('skrimshander', 2), ('objections', 2), ('liked', 2), ('adjoining', 2), ('nightmare', 2), ('complexioned', 2), ('seed', 2), ('coats', 2), ('comforters', 2), ('icicles', 2), ('molasses', 2), ('sovereign', 2), ('capering', 2), ('interested', 2), ('ordained', 2), ('partner', 2), ('brawn', 2), ('reminiscences', 2), ('stature', 2), ('orgies', 2)]
👍2