ToCode
Открыть в Telegram
1 419
Подписчики
Нет данных24 часа
-17 дней
+230 день
Архив постов
1 419
# היום למדתי: שינוי שם ב git
שאלה של לקוחה שלחה אותי לחפש מה גיט עושה כשהוא כותב לנו שקובץ שינה את השם - והתשובה הפתיעה אפילו אותי. בואו נפתח מאגר בשביל לראות יחד.
אני בתיקיה חדשה כותב:
$ git init .
$ echo hello > file1.txt
$ git add .
$ git commit -m 'initial commit'
ועכשיו נשנה לקובץ את השם:
$ git mv file1.txt file2.txt
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
renamed: file1.txt -> file2.txt
$ git commit -m 'rename'
ואפשר לראות את שינוי השם גם בצפיה בלוג:
$ git log -p --oneline
25ccbd7 (HEAD -> main) rename
diff --git a/file1.txt b/file2.txt
similarity index 100%
rename from file1.txt
rename to file2.txt
0e6c0e3 initial commit
diff --git a/file1.txt b/file1.txt
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/file1.txt
@@ -0,0 +1 @@
+hello
עכשיו אנחנו יודעים שכל קומיט ב git הוא בסך הכל Snapshot של כל הקבצים בפרויקט. אנחנו יודעים שגיט לא שומר שינויים בין קומיטים וכשאנחנו מנסים למצוא שינויים הוא משווה בזמן אמת בין הקומיטים כדי לזהות את השינויים. במילים אחרות אנחנו יודעים שמבחינת גיט כל קומיט הוא פתית שלג ייחודי שמכיל את כל המידע של הפרויקט.
אז איך גיט יודע שקובץ שינה את השם בין קומיטים? איפה המידע הזה נשמר? ... מסתבר שבשום מקום. המפתח הוא ה similiarity index של הקבצים. כשאינדקס זה גבוה גיט מניח בסבירות גבוהה שמדובר באותו קובץ שפשוט שינה את השם.
זאת גם הסיבה שגיט יכול לזהות לבד כשקובץ שינה את שמו, ושלפעמים הוא טועה בזיהוי. כל זה הוא בסך הכל טקסט יפה שגיט משתמש בו כדי להסביר לנו דברים שהוא חושב שאולי קרו, אבל בעצם אין לו דרך מדויקת לדעת את זה.
והטריק להיום הוא המתג --no-renames שאפשר לצרף להרבה פקודות גיט כדי לכבות את מנגנון זיהוי שינויי השם. כך אותו הלוג יראה בלי המנגנון:
$ git log -p --oneline --no-renames
25ccbd7 (HEAD -> main) rename
diff --git a/file1.txt b/file1.txt
deleted file mode 100644
index ce01362..0000000
--- a/file1.txt
+++ /dev/null
@@ -1 +0,0 @@
-hello
diff --git a/file2.txt b/file2.txt
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/file2.txt
@@ -0,0 +1 @@
+hello
0e6c0e3 initial commit
diff --git a/file1.txt b/file1.txt
new file mode 100644
index 0000000..ce01362
--- /dev/null
+++ b/file1.txt
@@ -0,0 +1 @@
+hello
הפעם במקום שינוי שם יש לנו מחיקה ויצירה של קובץ חדש. עבור אותו לוג.
גם diff יכול לקבל אותו טיפול והנה שתי האפשרויות:
$ git diff --name-status HEAD~
R100 file1.txt file2.txt
$ git diff --name-status --no-renames HEAD~
D file1.txt
A file2.txt
הבחירה להשתמש במנגנון זיהוי שינוי השמות של גיט היא שלכם, והיא לא קשורה לתוכן הקבצים עצמם שנשמרים במאגר. לפעמים מנגנון זה נוח ועוזר לראות מה קרה בלוג בצורה יותר מדויקת, אבל כשהוא לא עוזר לכם אל תתביישו לכבות אותו.1 419
# גבולות
ברירת המחדל של שרת Express היא לאפשר העלאה של קבצים עד 1 מגה. זה אומר שאם אתם כותבים קוד ובודקים אותו בעצמכם רק עם קבצים קטנים, כשתגיעו לפרודקשן כנראה תגלו תקלות מפתיעות.
אבל אם אתם מכירים את הגבול אתם יודעים שביישום Express שמאפשר העלאת קבצים תמיד כדאי לבדוק על קובץ של מגה וחצי או שניים - רק ליתר ביטחון.
ובאופן כללי, כשאנחנו מכירים גבול ויודעים שפיצ'ר מסוים בטכנולוגיה מסוימת דורש טיפול מיוחד (סדר קונטיינרים בדוקר, סימנים מיוחדים ב SQL, רשימות ארוכות בריאקט, גיבוי שמפסיק באמצע בתוכנת גיבוי) אנחנו יכולים לבדוק את התנאי המיוחד בכל מערכת חדשה שנכתוב מהסוג הזה, ולוודא שהתנאי הזה טופל כשאנחנו עוברים על קוד של אחרים ב Code Review.
כשאתם לומדים טכנולוגיה חדשה חפשו את הגבולות. היכרות איתם תחסוך הרבה בעיות במעבר מ Tutorial לעולם האמיתי.
1 419
$ pip install -r requirements.txtואז להפעיל את שרת הפייתון עם:
$ python main.py
ולבסוף להפעיל את שרת הפיתוח לצד לקוח עם:
$ cd frontend
$ yarn dev
נסו להיכנס ל localhost:3000 ממספר חלונות ולכתוב טקסט בתיבה. אם הכל עובד כמו שצריך אתם תראו את הטקסט מועתק לכל החלונות האחרים.1 419
# הדגמת פיתוח תקשורת דו-כיוונית בין שרת פייתון ללקוח ריאקט
ספריית socket.io היא עדיין הדרך הכי פשוטה לבנות חיבור תקשורת אסינכרוני בין קוד צד לקוח לקוד צד שרת. בדוגמה היום אני לוקח שרת פייתון ולקוח ריאקט כדי לבנות תיבת טקסט שמראה את אותו הטקסט בין כמה חלונות - כלומר אנחנו משנים את הטקסט בחלון אחד ואוטומטית בחלונות אחרים ובמחשבים אחרים הטקסט בתיבה משתנה בהתאמה.
## צד השרת: פייתון ו Socket IO
הספריה python-socketio מספקת מימוש נוח לפרוטוקול SocketIO בצד שרת בפייתון. זו ספריית מעטפת שיכולה להשתמש במספר ספריות ניהול אירועים בשביל התקשורת, ואני הלכתי על מימוש ב aiohttp.
החלק המעניין בקוד צד השרת הוא בלוקים מהצורה הזו:
@sio.on('message')
async def print_message(sid, message):
print("Socket ID: " , sid)
print(message)
await sio.emit('message', message, broadcast=True);
כל מתודה שמסומנת עם ה Decorator של SocketIO תהיה מתודה שמאזינה לאירוע. בקוד הדוגמה מאזינים לאירוע בשם message. הפונקציה emit שולחת אירועים החוצה והפרמטר broadcast גורם לפרמטר להישלח לכל הלקוחות. זה נוח לתיבת טקסט שצריכה להיות מסונכרנת בין כמה מכונות כדי שכל שינוי במכונה אחת אוטומטית יישלח לכל המכונות האחרות.
קוד השרת המלא הוא בסך הכל קובץ פייתון אחד עם התוכן הבא:
from aiohttp import web
import socketio
static_files = {
'/static': './frontend/dist',
}
sio = socketio.AsyncServer(cors_allowed_origins='*', aync_mode='aiohttp')
app = web.Application()
app.add_routes([web.static('/static', './public')])
sio.attach(app)
@sio.on('message')
async def print_message(sid, message):
print("Socket ID: " , sid)
print(message)
await sio.emit('message', message, broadcast=True);
if __name__ == '__main__':
web.run_app(app)
אני הוספתי CORS Headers כדי שיהיה נוח במצב פיתוח להפעיל שרת Webpack ולהתחבר ממנו לשרת ה SocketIO (כל אחד מהם רץ על פורט אחר). במצב ייצור אפשר לוותר על זה ולהגיש את הקבצים מאותו דומיין דרך nginx או באמצעות מנגנון ה Static Files של שרת aiohttp.
## צד הלקוח: ריאקט ו socket.io-react-hook
בצד הלקוח הקוד מחולק לשני קבצים: בקובץ ה index.js או main.js (תלוי בתבנית הפרויקט שלכם) אני צריך להוסיף אלמנט בשם IoProvider מסביב לכל האפליקציה שלי:
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import { IoProvider } from 'socket.io-react-hook';
ReactDOM.render(
<React.StrictMode>
<IoProvider>
<App />
</IoProvider>
</React.StrictMode>,
document.getElementById('root')
)
והחלק המעניין הוא הקומפוננטה עצמה: שם אני משתמש ב Hooks הבאים:
1. הפונקציה useSocket מתחברת לשרת Socket IO מרוחק.
2. הפונקציה useSocketEvent מתחברת לאירוע שהשרת יכול לשלוח, וכל פעם שהשרת שולח את האירוע אוטומטית תגרום ל Render מחדש של הקומפוננטה.
בעזרת useEffect אני מחבר בין האירוע שחוזר מ useSocketEvent לטקסט שמוצג: כל פעם שקיבלנו הודעה מהשרת נבדוק אם היא שונה מהטקסט ששמור אצלנו נציג אותה בעזרת שמירתה למשתנה ה State.
הקוד המלא בקובץ App.jsx נראה כך:
import './App.css'
import { useState, useEffect } from 'react';
import { useSocket, useSocketEvent } from 'socket.io-react-hook';
function App() {
const [text, setText] = useState('');
const { socket, error } = useSocket('http://localhost:8080');
const { lastMessage } = useSocketEvent(socket, 'message');
useEffect(function() {
if (lastMessage && lastMessage !== text) {
setText(lastMessage);
}
}, [lastMessage]);
if (error) {
return <p>{String(error)}</p>
}
function handleChange(e) {
setText(e.target.value);
socket.emit('message', e.target.value);
}
return (
<div>
<input type="text" value={text} onChange={handleChange} />
<p>Last message = {lastMessage}</p>
</div>
);
}
export default App
## איך מפעילים
אם אתם רוצים לשחק עם הפרויקט תשמחו לשמוע ששמתי את כל הקוד בגיטהאב במאגר https://github.com/ynonp/python-socketio-demo.
בשביל להפעיל אותו צריך להריץ קודם כל את ההתקנות בפייתון עם:1 419
# אתגר ריפקטורינג ב JavaScript
יניב היה צריך פונקציה שמחזירה קוד HTML לאלמנט קישור לפי מספר מאפיינים. הפונקציה מקבלת אוביקט בשם props ותומכת במאפיינים הבאים:
1. אם האוביקט מכיל את המאפיין href אז ל a יהיה href עם הערך המתאים. אחרת לא יהיה לו.
2. אם האוביקט מכיל את האלמנט title אז ל a יהיה title עם הערך המתאים. אחרת לא יהיה לו.
3. אם האוביקט מכיל את האלמנט text אז ל a יהיה טקסט עם הערך שעבר.
זאת הפונקציה שיניב כתב:
function showLink(props) {
return (
`<a
${props.href ? `href="${props.href}"` : ''}
${props.title ? `title="${props.title}"` : ''}
>${props.text ? props.text : ''}
</a>`
);
}
כשדנה הסתכלה על הקוד היא הרגישה שהפונקציה מסורבלת מדי. היא הציעה לשכתב אותה בעזרת שתי פונקציות עזר לקוד הבא:
function showLinkBetter(props) {
const { href, title } = attrs(props);
const { text } = values(props);
return (
`<a ${href} ${title}>${text}</a>`
);
}
"ויותר מזה", התלהבה דנה, "עם פונקציות העזר שלי אתה יכול לתמוך במאפיינים חדשים בצורה קלה בלי לשנות את הפונקציות attrs ו values שכתבתי - רק תוך שינוי הפונקציה showLinkBeter".
איך נראה המימוש של attrs ו values שדנה כתבה?
## פיתרון
הבעיה שדנה היתה צריכה להתמודד איתה היא לא טריוויאלית: יש לשנות את כל הערכים באוביקט, כולל כאלה שבכלל לא נמצאים בו! האוביקט ש attrs מחזירה צריך לדעת להחזיר מחרוזת ריקה עבור כל מפתח שיבקשו ממנו ושלא נמצא באוביקט ה props.
דרך אחת לגשת לפתור את זה היא לדחות את החישוב של ערך ההחזר עד שבאמת מישהו מבקש מפתח כלשהו. ב JavaScript אנחנו יכולים לעשות את זה עם הגדרת Getter (אבל אז נצטרך לדעת איך קוראים למפתח), או עם הגדרת Proxy.
אני לא יודע מה דנה כתבה, אבל זה הפיתרון שאני הייתי כותב בעזרת proxy כדי לממש את attrs ו values:
function returnTextProxy(props, withPrefix) {
return new Proxy(props, {
get: function(target, prop, _receiver) {
if (target[prop]) {
return withPrefix ? `${prop}="${target[prop]}"` : target[prop];
} else {
return "";
}
}
});
}
function attrs(props) {
return returnTextProxy(props, true);
}
function values(props) {
return returnTextProxy(props, false);
}
יש לכם רעיונות אחרים? אל תתביישו ושתפו בתגובות.1 419
# היום למדתי: ארכיטקטורות ו Docker Images
אחד הדברים שמבלבלים בעבודה עם דוקר זה הדמיון (וההבדל) בין קונטיינרים למכונות וירטואליות. היינו מאוד רוצים לחשוב על קונטיינר בתור משהו שאפשר להריץ בכל מקום, אבל מצד שני אנחנו גם יודעים שאימג' הוא לא מחשב מלא ויש דברים שהוא לוקח ממערכת ההפעלה. איך שני הדברים האלה מסתדרים יחד? הנה עיקרי הדברים:
1. בבניית Docker Image אנחנו בונים אימג' ספציפי לארכיטקטורת המעבד ולמערכת ההפעלה עליה אנחנו מריצים את תהליך הבניה.
2. המנוע שמריץ את ה build לא חייב להיות זהה למערכת ההפעלה שיש לכם על המחשב, וזה החלק המבלבל. ב Windows אנחנו בדרך כלל מריצים את ה build ממנוע שרץ ב WSL כלומר במכונת לינוקס וירטואלית. דוקר על המק משתמש במכונה וירטואלית אחרת כדי להריץ את המנוע, אבל גם היא מריצה לינוקס. לכן אם לא עשיתם משהו מוזר תהליך ה build יבנה אימג' ל Linux.
3. הרבה אימג'ים בדוקר האב נבנו כמה פעמים, עבור מספר מערכות הפעלה וארכיטקטורות מעבד. בבניה כזאת מי שמפרסם את האימג' בעצם בונה ומפרסם כמה אימג'ים ובסוף מאחד את כולם לשם אחד ב Dockerhub. תראו למשל את האימג' של פייתון. התג 3.9.8 מכיל רשימה של 11 אימג'ים שונים, כל אחד לזוג אחר של ארכיטקטורת מעבד ומערכת הפעלה.
4. כל פעם שאתם כותבים
docker run או docker pul המנוע של דוקר מחפש אימג' שמתאים לארכיטקטורה ולמערכת הפעלה שהוא רץ בה כרגע. זה אומר שכשאני אכתוב docker run python על מערכת לינוקס ועל מערכת Windows, אני אקבל אימג' שונה בכל מערכת. אפשר לראות את זה אם תפעילו docker image ls בכל אחת משתי המכונות ותראו את ה Digest השונה.
5. כל שלושת מערכות ההפעלה יודעות לבנות אימג'ים ל Linux ולכן רוב הזמן ההבדלים נשארים מוסתרים. אבל נקודה אחת שקשה להסתיר היא ארכיטקטורת המעבד. דוקר שרץ על מחשב Arm יבנה ויריץ אימג'ים ל Arm, ודוקר שרץ על מחשב אינטל יבנה ויריץ אימג'ים של אינטל.
6. קלאסטרים וקוברנטס כן רצים על ארכיטקטורת מעבד ספציפית ושם יש לנו בחירה כשמקימים את הקלאסטר. ל AWS יש לדוגמה מעבד שנקרא Graviton2 המבוסס ARM ולפי טענתם יכול לחסוך בעלות הקלאסטר בהשוואה למעבד אינטל. אבל, אם האימג' שלי בנוי רק בארכיטקטורת amd64 אני לא יכול להשתמש בקלאסטר כזה. אמזון אפילו מסבירים איך לבנות אימג' לארכיטקטורת ARM למי שכן רוצה לחסוך.
7. אפשר לבדוק מה הארכיטקטורה שה Docker Engine שלכם כרגע מריץ עם docker version. חפשו את המפתח OS/Arch.
8. משתנה הסביבה DOCKER_DEFAULT_PLATFORM קובע לאיזה ארכיטקטורת יעד אנחנו בונים את האימג'ים כשמריצים docker build. בשביל לבנות למעבדי אינטל כשאני רץ על מכונת ARM אני קובע את הערך ל linux/amd64. בשביל לבנות סכימות יותר מתוחכמות יש מנגנון שנקרא buildx.
למרות שקונטיינרים מרגישים כמו משהו שעובד בכל מקום האמת היא קצת יותר מורכבת. הבנה טובה של המנוע וסביבת הריצה שלו יכולה לעזור לכם להתמודד עם באגים מוזרים ולפעמים גם לחסוך בעלויות השרתים.1 419
const countries = Object.keys(db);
const selectedCountry = ref("");
const cities = computed(() => db[selectedCountry.value] || []);
const selectedCity = ref("");
watchEffect(() => {
if (!cities.value.includes(selectedCity.value)) {
selectedCity.value = '';
}
});
</script>
<template>
<div>
<select v-model="selectedCountry">
<option value="" disabled="true">Please select a country</option>
<option v-for="country in countries" :value="country">{{country}}</option>
</select>
<p>Selected country = {{selectedCountry}}</p>
<pre>Cities = {{cities}}</pre>
<select v-model="selectedCity">
<option value="" disabled="true">Please select city</option>
<option v-for="city in cities" :value="city">{{city}}</option>
</select>
<p>You selected country = {{selectedCountry}} and city = {{selectedCity}}</p>
</div>
</template>
<style scoped>
</style>1 419
# חידת ריאקט: פרידה מקומפוננטה
נתון הקוד הבא שכולל שתי קומפוננטות:
import React from 'react';
function Yo() {
React.useEffect(function() {
alert('bye bye');
}, []);
return <p>Yo</p>;
}
export default function App() {
function handleClick() {
}
return (
<div className="App">
<Yo />
<button onClick={handleClick} >Say bye bye</button>
</div>
);
}
עדכנו את קוד הקומפוננטה App כך שלחיצה על הכפתור תגרום להצגת ה alert שמוגדר ב Yo.
אין צורך לשנות את הקוד של Yo ואין לבצע כל שינוי ויזואלי בממשק.1 419
# שיקולי תקציב
אחד הדברים המרכזיים שמבדילים בין אנשי מקצוע לחובבנים הוא הנכונות לעבוד לפי תקציב. ותקציב זה לא רק כסף. התקציב הוא בחירת המגבלות והמסגרת שרק בתוכה אפשר להיות יצירתיים.
תקציב יכול להיות בזמן קלנדרי, ועבודה לפי תקציב היא הבחירה להשיק את המוצר במועד שהתחייבת.
תקציב יכול להיות בשעות עבודה, ועבודה לפי תקציב היא הבחירה לעבוד X שעות ביום ולסיים בזמן.
תקציב יכול להיות בכל פרמטר טכני, לדוגמה צוותים מקצועיים שבונים מערכת ווב מגדירים שזמן הטעינה של האתר עבור 80% מהמשתמשים שלהם יהיה פחות מ X שניות.
בהיבט של איכות קוד אנחנו יכולים להחליט על מגבלת X שורות בקובץ או מספר תנאים והשמות בפונקציה.
הסיפור עם תקציב הוא שבשביל לעמוד בתקציב באופן עקבי חייבים ללמוד לעשות פשרות, ובמיוחד פשרות שנראות בלתי אפשריות בכל סיטואציה אחרת. עבדתי פעם עם מתכנת שבאופן עקבי אף פעם לא פספס דדליין. הרבה מתכנתים אחרים שעבדתי איתם לעולם לא היו מוכנים לעשות את הפשרות שאותו מתכנת עשה בשביל להישאר בתוך התקציב.
כשאנחנו בתור אנשי מקצוע מתיישבים לעבוד כדאי להיות ברורים עם עצמנו ועם הלקוחות שלנו: מה התקציב, מה הפשרות שנצטרך לעשות כדי לעמוד בו ובאיזה מצבים אפשר לאשר הגדלה של התקציב. שיקולי תקציב הם ההזדמנות שלכם להפוך לאנשי מקצוע טובים יותר.
1 419
זה התרגיל הראשון בו יש הבדל מסוים בין פיתרון הפייתון המוצע לפיתרון הקוקנאט שאני חשבתי עליו - בפייתון הם יצרו רשימה חדשה והוסיפו אליה את האלמנטים הכפולים. בקוקנאט השתמשתי ב filter ו map כדי ליצור את הכל באותו Pipeline.
דבר אחד שכבר אנחנו רואים שמטריד לגבי השפה הוא שאין תמיכה ב Destructuring בארגומנטים של פונקציות, ולכן filter מוגדרת לקבל משתנה יחיד x וצריכה לגשת למקום השני בו (במקום לקבל זוג ולבצע השמה). כלומר הייתי רוצה לכתוב את הביטוי:
|> filter$((k, v) -> v > 1)
אבל זה לא היה קוקנאט תקני.
## משולש מספרים
תרגיל אחרון לסיבוב הזה הוא הדפסת משולש מספרים, כלומר כתבו תוכנית שמדפיסה את תבנית המספרים הבאה:
1 1 1 1 1
2 2 2 2
3 3 3
4 4
5
פיתרון בשפת פייתון:
rows = 5
x = 0
# reverse for loop from 5 to 0
for i in range(rows, 0, -1):
x += 1
for j in range(1, i + 1):
print(x, end=' ')
print('\r')
פיתרון שלי בקוקנאט:
def triangle(size)= (
range(size, 0, -1)
|> map$(i -> [i, size-i + 1])
|> map$(x -> f"{x[1]} " * x[0])
|> '\n'.join)
print(triangle(5))
חישובי אינדקסים אף פעם לא היו הצד החזק שלי אבל הפעם נראה שקוד קוקנאט חוסך לי חלק מהחישובים ומאפשר להתמקד ב"מה צריך לעשות" במקום באיך. במקום להסתכל על הבעיה בתור לולאה כפולה אני מסתכל עליה כסידרה של שתי פונקציות map שמייצרות את הערך להדפסה.
יש עוד המון דברים ש coconut יודעת לעשות ולא הופיעו כאן בדוגמאות הקצרות, בפרט יש כמה מנגנונים נחמדים לפיתוח מקבילי ותמיכה מובנית ב Pattern Matching (שעכשיו קיים כבר גם בפייתון). סך הכל התחביר בעיניי יותר נוח מקוד פייתון מקביל.
למידע נוסף על coconut שווה לבקר בדף התיעוד הראשי שלהם בקישור:
https://coconut.readthedocs.io/en/master/DOCS.html.1 419
# חמש תוכניות coconut כדי להכיר שפה חדשה
קוקונאט היא שפה פונקצונאלית שמתקמפלת לפייתון ונותנת גישה מלאה לכל האקוסיסטם של פייתון אבל בכתיב פונקציונאלי עם כל הפינוקים. מתקינים אותה כמו כל ספריית פייתון עם:
$ pip install coconut
ואחרי ההתקנה אפשר ליצור תוכניות קוקונאט בתוך קבצים עם סיומת coco או להפעיל את ה interpreter עם הפקודה coconut ולהריץ ביטויי קוקאנט. ויש גם סביבת הרצה נוחה בענן בקישור: https://cs121-team-panda.github.io/coconut-interpreter/.
בשביל להכיר את השפה לקחתי מספר תרגילי פייתון וניסיתי לממש אותם בקוקנאט. התוצאות לפניכם:
## היפוך כל מילה במחרוזת
נתונה המחרוזת:
text = 'My Name is Jessa'
כיתבו תוכנית שתדפיס את המחרוזת תוך היפוך סדר האותיות בכל מילה, כלומר הפלט הצפוי יהיה:
yM emaN si asseJ
פיתרון בפייתון מתוך אתר התרגול:
def reverse_words(Sentence):
words = Sentence.split(" ")
new_word_list = [word[::-1] for word in words]
res_str = " ".join(new_word_list)
return res_str
# Given String
str1 = "My Name is Jessa"
print(reverse_words(str1))
פיתרון שלי ב coconut:
def reverse_words(sentence) = (
sentence
|> .split(" ")
|> map$(n -> n[::-1])
|> list
|> ' '.join)
reverse_words("My Name is Jessa") |> print
מאוד דומה לקוד פייתון המקורי אבל בזכות השימוש ב Pipeline Operator לא צריך להגדיר משתני ביניים ובעצם התוצאה של כל פונקציה ממשיכה לפונקציה הבאה ב Pipeline.
## החלפת תוי סוף שורה ברווחים
כתבו תוכנית שקוראת את הקובץ sample.txt ומדפיסה את כל השורות בו מופרדות ברווחים (במקום בתו ירידת שורה).
פיתרון פייתון:
with open('sample.txt', 'r') as file:
data = file.read().replace('\n', ' ')
print(data)
פיתרון קוקאנאט שלי:
with open("sample.txt") as file:
(file.read()
|> .replace("\n", " ")
|> print)
שוב מאוד דומה לקוד המקורי - וזו בדיוק המטרה. קוקנט עוזרת למתכנתי פייתון שמכירים תכנות פונקציונאלי לכתוב קוד יותר ידידותי. הדבר היחיד שמעצבן בינתיים הוא שחייבים להקיף כל Pipeline בסוגריים או לכתוב אותו בשורה אחת.
טריק מעניין נוסף כאן הוא הקריאה ל .replace. אנחנו רגילים מפייתון שלפני הפעלת מתודה צריך לבוא אוביקט, ופה בקוקנט קריאה זו היא Partial Application של הפונקציה, כלומר היא מייצרת פונקציה שכשיגיע אליה אוביקט היא פשוט תפעיל את פונקציית replace שלו.
## מיפוי מילון הפוך
בהינתן מילון:
ascii_dict = {'A': 65, 'B': 66, 'C': 67, 'D': 68}
כתבו תוכנית שתהפוך את המפתחות והערכים ותדפיס:
{65: 'A', 66: 'B', 67: 'C', 68: 'D'}
פיתרון בקוד פייתון:
ascii_dict = {'A': 65, 'B': 66, 'C': 67, 'D': 68}
# Reverse mapping
new_dict = {value: key for key, value in ascii_dict.items()}
print(new_dict)
פיתרון בקוד קוקנאט:
({'A': 65, 'B': 66, 'C': 67, 'D': 68}
|> fmap$((k, v) -> [v, k])
|> print)
הפונקציה fmap של קוקנט היא פשוט פונקציית map גנרית שאפשר להפעיל על כל אוביקט והיא תמיד עושה את הדבר הנכון. על מילון היא מפעילה את הפונקציה על ה items של המילון וזה בעצם מחליף את ה Dictionary Comprehension שראינו בפייתון.
## הדפסת כל הערכים ברשימה שמופיעים יותר מפעם אחת
בהינתן רשימה של מספרים:
sample_list = [10, 20, 60, 30, 20, 40, 30, 60, 70, 80]
הציגו את כל הפריטים מהרשימה שמופיעים יותר מפעם אחת. בדוגמה שלנו אלה 20, 60 ו 30.
פיתרון בקוד פייתון:
import collections
sample_list = [10, 20, 60, 30, 20, 40, 30, 60, 70, 80]
duplicates = []
for item, count in collections.Counter(sample_list).items():
if count > 1:
duplicates.append(item)
print(duplicates)
פיתרון בקוד קוקנאט:
import collections
sample_list = [10, 20, 60, 30, 20, 40, 30, 60, 70, 80]
(sample_list
|> collections.Counter
|> .items()
|> filter$(x -> x[1] > 1)
|> map$(x -> x[0])
|> list
|> print)1 419
# איך זה נשבר
יש לי בארון ערימה של צלחות זכוכית שחורות קטנות שלא נראות מאיימות בשום צורה. הן לא יפות במיוחד ומבחינת גודל מקסימום אפשר לשים על אחת כמה צלחות לאורח.
אבל אם במקרה אחת נופלת מהשולחן זה סיפור אחר לגמרי.
כל צלחת כזאת נשברת למיליון חלקיקי זכוכית בגדלים שונים שמתפזרים לכל חור וייקח שעות לנקות את כולם. אחרי הפעם הראשונה שלמדתי את זה אני חושב עשר פעמים לפני שמוציא אחת מהארון. אם היו אומרים לי איך זה הולך להישבר אין מצב שהייתי קונה אותן מההתחלה.
וקצת כמו צלחת, גם אתר שנפרץ מאבד את המידע הפרטי שלנו למיליון חלקים קטנים, ששום חוק לא יצליח לאסוף בחזרה. לא תמיד יש לנו שליטה על דברים ולא תמיד יש ברירה אחרת, אבל כשיש לכם שליטה - שימו לב לחשוב עשר פעמים לפני שנרשמים עם מידע פרטי לאתרים. אף אחד לא יכול לתכנן מראש איך זה יישבר.
Уже доступно! Исследование Telegram 2025 — ключевые инсайты года 
