פרק קודם:
פייתון 11 - קידוד וקבציםכמו משתנה בוליאני שמגדיר True
או False
.
הבסיס למידע בינארי היא יחידה קטנה שנקראת ביט - Bit
.
כל ביט מכיל 0 או 1.
קבצי טקסט מכילים מידע שאנשים יכולים לקרוא - קבצים בינאריים אלו קבצים שמכילים מידע שרק המחשב יודע לקרוא.
או להגדיר בצורה יותר טובה - בשפה שקוד יודע לקרוא.
כשאנחנו סופרים מספרים אנחנו משתמשים בבסיס ספירה מבלי לדעת שאנחנו עושים זאת.
במתמטיקה שמלמדים בתיכון משתמשים בבסיס ספירה 10.
זה מגדיר את כמות הספרות שאנחנו משתמשים.
1 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 |
בעזרת הספרות האלו אנחנו יכולים לבנות קומבינציות ספרות שנותנים לנו מספרים:
וכמובן שזה אניסופי.
נדמיין שאנחנו סופרים:
…7, 8, 9…
אנחנו יודעים שמגיע 10 כי אחרי 9 מקדמים את הספרה שמאלה למספר העשרות.
במתמטיקה המיקום של הספרה מיוצג בעזרת חזקה:
1 | 10^1 * 1 + 0 |
אם למשל לוקחים 99 אז זה:
1 | 10^1 * 9 + 10^0 * 1 + 9 |
מה זה המספר 985?
כתבו את זה מתמטית.
1 | (10^2 * 9) + (10^1 * 8) + 10^0 * 1 + 5 |
החוקיות פה היא לקחת את הבסיס שאנחנו נמצאים בו - בסיס 10 במקרה הזה ולבצע פעולת חזקה על המיקום של הספרה.
מיקום | מספר החזקה |
---|---|
יחידות | 0 |
עשרות | 1 |
מאות | 2 |
אלפים | 3 |
עשרות אלפים | 4 |
… | n |
לעומת בסיס 10 שיש לנו 10 ספרות להשתמש על מנת לספור, בבסיס 2 יש לנו רק 2 ספרות:
0 ו 1.
סופרים בבסיס בינארי באותו אופן שכותבים ספרות רגילות רק עם 0 ו1.
בסיס 10 | בסיס 2 |
---|---|
0 | 0 |
1 | 1 |
2 | 10 |
3 | 11 |
4 | 100 |
5 | 101 |
6 | 110 |
7 | 111 |
8 | 1000 |
בדיחה קומית:
יש לנו 10 אנשים בעולם, כאלו שמבינים בינארית - וכאלו שלא!
ספרו בבינארית עד 50!
בסיס 10 | בסיס 2 |
---|---|
1 | 1 |
2 | 10 |
3 | 11 |
4 | 100 |
5 | 101 |
6 | 110 |
7 | 111 |
8 | 1000 |
9 | 1001 |
10 | 1010 |
11 | 1011 |
12 | 1100 |
13 | 1101 |
14 | 1110 |
15 | 1111 |
16 | 10000 |
17 | 10001 |
18 | 10010 |
19 | 10011 |
20 | 10100 |
21 | 10101 |
22 | 10110 |
23 | 10111 |
24 | 11000 |
25 | 11001 |
26 | 11010 |
27 | 11011 |
28 | 11100 |
29 | 11101 |
30 | 11110 |
31 | 11111 |
32 | 100000 |
33 | 100001 |
34 | 100010 |
35 | 100011 |
36 | 100100 |
37 | 100101 |
38 | 100110 |
39 | 100111 |
40 | 101000 |
41 | 101001 |
42 | 101010 |
43 | 101011 |
44 | 101100 |
45 | 101101 |
46 | 101110 |
47 | 101111 |
48 | 110000 |
49 | 110001 |
49 | 110010 |
תאמת קצת רימיתי כי לא באמת ספרתי לבד - כתבתי קוד בפייתון שעושה את זה!
הקוד הוא פשוט:
1 | def ToBinary(n): |
הפונקציה bin מחזירה את הייצוג הבינארי של מספר.
אך המספר חוזר עם התחילית 0b.
לכן אני מחליף את כולם בתווים ריקים.
המחשב מגדיר את גודל המספרים שהוא יכול לייצג כי בסופו של דבר הוא צריך לייצג את זה באופן חשמלי.
היחידה הכי קטנה במחשב 0 או 1 נקראת ביט-Bit
.
בייט-Byte
מורכב מ-8 ביטים.
מילה-Word
מורכבת מ-2 בייטים.
מילה כפולה-DWord
מורכבת מ-2 מילים.
מילה מרובעת-QWord
מורכבת משתי מילים כפולות או 4 מילים.
גודל היחידות - בייט,מילה, מילה כפולה ומילה מרובעת תלויה במימוש של מערכת ההפעלה.
כיום רוב המחשבים הביתיים מגדירים מילה כ-64 ביטים(או 8 בייטים).
איך מייקרוסופט השאירו את הגדלים שכפי שתיארתי למעלה על מנת לא ליצור בלבול.
בייצוג בינארי מספר מוגדר ע”י שני מצבים בלבד ולכן בייצוג מתמטי כל ספרה מיוצגת בכפל 2 בחזקת המיקום שלו במספר.
למשל 101:
1 | (1 * 2^2) + (0 * 2^1) + (1 * 2^0) = 101 |
בסיס 10 | בסיס 2 |
---|---|
1 | 0000 0001 |
2 | 0000 0010 |
4 | 0000 0100 |
8 | 0000 1000 |
16 | 0001 0000 |
32 | 0010 0000 |
64 | 0100 0000 |
128 | 1000 0000 |
255 | 1111 1111 |
עד ל-255, המספר המקסימלי של בייט.
כל 4 ביטים נקראים ניבל-Nibble
.
יש סיבה למה רושמים ביטים בקבוצות של 4.
מאוד נוח לייצג 4 ביטים בבסיס 16 - האקסאדצימאלי.
האקסה - 6
דצימאלי - 10
אם בסיס 10 זה 10 תווים, בסיס 2 זה 2 תווים, לכן בסיס 16 הוא 16 תווים!
התווים שמשתמשים בהם:
1 | 0 1 2 3 4 5 6 7 8 9 A B C D E F |
בסיס 10 | בסיס 2 | בסיס 16 |
---|---|---|
0 | 0000 0000 | 0 |
1 | 0000 0001 | 1 |
2 | 0000 0010 | 2 |
3 | 0000 0011 | 3 |
4 | 0000 0100 | 4 |
5 | 0000 0101 | 5 |
6 | 0000 0110 | 6 |
7 | 0000 0111 | 7 |
8 | 0000 1000 | 8 |
9 | 0000 1001 | 9 |
10 | 0000 1010 | a |
11 | 0000 1011 | b |
12 | 0000 1100 | c |
13 | 0000 1101 | d |
14 | 0000 1110 | e |
15 | 0000 1111 | f |
16 | 0001 0000 | 10 |
17 | 0001 0001 | 11 |
בייטים גודלם 8 ביטים.
מהסיבה הזו מאוד נוח לייצג בייט אחד עם 2 ספרות הקסאדצימאליות.
לאדם יהיה הרבה יותר גם לקרוא אותיות ומספרים מאשר מספרים ארוכים שכוללים 0 ו-1.
למשל המספר 44,979:
בבינארי זה יהיה:
1 | 1010 1111 1011 0011 |
והקסאדצימאלי זה יהיה:
1 | AFB3 |
הרבה יותר קומפקטי ונוח.
דוגמא נוספת זה ייצוג של צבעים, לצבעים יש קוד הקסאדצימאלי שנוח יותר להעתיק אותו ולכתוב.
1.
אם תפסתם את קונספט הספירה עוד ממקודם אז תנסו לספור לבד מ-17 עד 50!
בסיס 10 | בסיס 16 |
---|---|
37 | |
99 | |
112 | |
960 |
כמו בתרגיל הבינארי קצת רימיתי והשתמשתי בפונקציה - hex
.
1 | def ToBinary(n): |
בסיס 10 | בסיס 16 |
---|---|
37 | 25 |
99 | 63 |
112 | 70 |
960 | 3c0 |
קודם כל - למחשבון של ווינדוס יש מוד למתכנתים שעוזר לכם להתמודד עם כל הספירה הזו:
דוגמא לשימוש במחשבון:
תלמדו להשתמש במחשבון הזה - בתור מישהו שעובד הרבה עם ביטים ובייטים אני משתמש בו הרבה פעמים.
הביטויים מתייחסים למספרים שליליים וחיוביים.unsigned
מתייחס למספרים חיוביים בלבד - 0 ומעלה.signed
מייתחס למספרים שליליים, חיוביים ו0.
אז איך בעצם אנחנו מייצגים מספר שהוא שלילי?
בצורה מאוד פשוטה - משתמשים בביט האחרון או הכי שמאלי!
1 זה מינוס, 0 זה חיובי.
בסיס 10 | בסיס 2 |
---|---|
5 | 0000 0101 |
-6 | 1000 0110 |
10 | 0000 1010 |
-14 | 1000 1110 |
יש ייצוג אחר למספרים שליליים שעוזרים לנו גם לבצע פעולות מתמטיות בצורה פשוטה יותר.
אני לא ארחיב על זה יותר מדי כי זה לא חובה לדעת.
לעוד מידע על זה ניתן לקרוא פה:
משלים ל-2
הביטוי מתייחס לסידור הבייטים.
נניח יש לנו מספר גדול:
1 | 322945 = 0000 0100 1110 1101 1000 0001 |
או שנוכל לכתוב אותו ככה:
1 | 322945 = 0001 1000 1101 1110 0100 0000 |
מה השתנה כאן?
הסדר התהפך!
יש לנו שני מושגים שונים:
Little Endian מתייחס לסידור שהביט הכי קטן הוא קודם.
Big Endian מייתחס לסידור שהביט הכי גדול הוא קודם.
ז”א בדוגמא שכתבנו:
Big Endian:
1 | 322945 = 0000 0100 1110 1101 1000 0001 |
Little Endian:
1 | 322945 = 0001 1000 1101 1110 0100 0000 |
לביט הכי קטן והכי גדול יש מושגים משלהם:
Most Significant Bit - MSB
Least Significant Bit - LSB
תכתבו את המספרים הבאים בבינארית באינדיאניטי קטן וגדול:
בסיס 10 | גדול | קטן |
---|---|---|
5200 | ||
4600 | ||
1288 | ||
16912 | ||
35333 |
בסיס 10 | גדול | קטן |
---|---|---|
5200 | 0001 0100 0101 0000 | 0000 0101 0100 0001 |
4600 | 0001 0001 1111 1000 | 1000 1111 0001 0001 |
1288 | 0101 0000 1000 | 1000 0000 0101 |
16912 | 0100 0010 0001 0000 | 0000 0001 0010 0100 |
35333 | 1000 1010 0000 0101 | 0101 0000 1010 1000 |
אתם כותבים פרוטוקול בינארי, הקוד שלכם מקבל 4 בייטים, מבלי לדעת את הסדר הנכון של הבייטים האלו איך תוכלו לדעת מהו המספר האמיתי?
בצורה אינטואיטיבית נחשוב שדווקא - Big Endian
הוא הלוגי יותר.
כי השכל שלנו רגיל לקרוא מימין לשמאל מספרים.
(שהימין הוא הקטן ביותר).
אך יש מערכות - או יותר נכון היו - שעובדים בעזרת Little endian
.
הסיבות די מורכבות אך אחת הטענות היא שמבחינת זיכרון קל יותר לטעון מספרים שהם Little endian
.
אני לא אכנס פה לפרטים כי צריך לדעת Assembly
.
אחרי שסקרנו בהרחבה,
אנחנו מוכנים לעבוד בקבצים בינאריים בפייתון!
בפרק הקודם הגדרנו פרוטוקול בינארי שנרחיב אותו בפרק הזה.
להלן הקובץ שלנו, חילקנו אותו לשורות כדי שיהיה נוח להסתכל בו:
1 | arr = [0, 25, 19, 1, 2500, 2, 2, 50, 199, 3, 9, 349] |
אנחנו מגדירים כל שורה במערך באופן סדרתי כי ככה קבצים עובדים - הם כתובים כמקשה אחת.
שימו לב לשורה 7 בקוד הקודם.
ה-while
השני נראה מעט מוזר.
הפרמטר :=
מאתחל משתנה במקשה אחת ביחד עם ה-while
.
אם כמות הבייטים תהיה שווה ל-0
תנאי הלולאה לא יתקיים.
זה כמו לכתוב את הקוד בצורה:
1 | with open("binaryFile", "rb") as file: |
רואים כמה זה חוסך לנו?
האופרטור הזה נקרא גם The Walrus Operator
.
על מנת לקרוא או לכתוב קובץ בינארי צריך להוסיף -b
.
1 | writeBinaryFile = open('file.bin','wb') |
יש לנו שני פונקציות ש-int
נותן לנו כדי לעבוד עם בייטים:
1 | to_bytes(num_of_bytes_to_generate, endianity) |
endianity
יכול להיות 'big'
או 'little'
כפי שלמדנו למעלה.
num_of_bytes_to_generate
מחליט כמה בתים לייצר עבור המספר.
אם נביא לו את הערך 1 אך נבקש ממנו לייצר 4 בייטים זה יצור לנו:
1 | num = 1 |
bytes
- זהו משתנה בתים מיוחד.
בפייתון הסוג המשתנה המיוחד שמטפל ברשימה של בייטים הוא - bytearray
.
השימוש בו הוא נורא פשוט, הוא כמו רשימה רגילה שאתם כבר מכירים:
1 | bytes = bytearray([0,0,0,1,2,255]) |
שימו לב: לא ניתן לשים בבייטים מספר שגדול יותר מ255
.
הרשימה bytearray
לא ניתן לשנות.
אם רוצים סוג שניתן לשנות ניתן ליצור רשימה עם bytes
, אך אני לא ממליץ על זה אלה אם כן אין ברירה.
לכן אנחנו נעבוד עם bytearray
.
הדגמה:
פרוטוקול הוא הגדרת חוקים לצורה שבה אנחנו מעבירים מידע או מייצגים מידע.
למשל אם ניקח סדרת מספרים רנדומלית:[1,2,3,4,5]
הם לא יגידו לנו כלום, האם הם אינדקסים למערך אחר? האם זה כמות של מוצרים? האם זה הגדרה של סדרה?
רק פרוטוקול שמוגדר היטב יוכל לומר לנו מהו המידע.
זוכרים שאמרנו שמידע בינארי הוא לא עקבי אלה אם כן אנחנו יודעים בוודאות מה הוא מכיל?
עבור המטרה הזו צריך להגדיר פרוטוקול שיגדיר מה כל מספר מייצג.
מיקום | משמעות | מיקום בייט |
---|---|---|
0 | מזהה | 0 |
1 | כמות במלאי | 4 |
2 | מחיר ליחידה בודדה | 8 |
בתמונה רואים שיש 24 בייטים סך הכל:
מוצר ראשון - בייט 0 עד 11
מוצר שני - בייט 12 עד 23
1 | BytesInNum = 4 |
כדי להיות חכמים ולהגדיר מראש כיצד אנחנו נקרא ונכתוב בפרוטוקול לכן נגדיר פונקציות.
הפונקציות האלו נקראות - Application Programming Interface
.
כמו שיש לנו ממשק משתמש שניתן ללחוץ על כפתורים כדי לבצע פעולות,
יש לנו ממשק מתכנת - פונקציות וכלים אחרים שנותנים למתכנת גישה לפעולות.
הצעד הראשון זה להגדיר עם איזה מידע אנחנו עובדים - מה ה-Input
ומה ה-Output
.
הצעד השני זה להגדיר שמות ברורים
.
והצעד השלישי זה לכתוב את התכנון כיצד להשתמש בפונקציות
.
נתחיל מהמידע שאנחנו עובדים איתו - איזה פרמטרים בעצם נרצה.
תחשבו מעט לפני שאתם פותחים את התשובה.
הפרמטרים שאנחנו רוצים לעבוד איתם הם בייטים ומערך של מערכים.
כל מערך יכיל שלושה מספרים וזה הערך האפליקטיבי שלנו.
ובייטים הם הערכים הבינאריים שלנו.
1 | products = [[0,2,3], [1,20,50],[0,953,5]] |
אם נשמור את זה בקובץ אז למה לא לעבוד עם נתיבי קבצים?
מכיוון שהפרוטוקול שלנו מגדיר צורת מידע ולא צורת תקשורת.
אנחנו רוצים שיהיה ניתן להשתמש בהם בכל מקום - קבצים, לשלוח באינטרנט, לבצע דחיסה וכדו’…
לכן נעבוד מול בייטים ולא שמות קבצים.
הצעד השני אמרנו הוא להגדיר את הפונקציות, חשבו על שמות מתאימים:
הכי פשוט זה לתת שם עם פועל.
למשל:
Read
Write
Create
Modify
Crumble
Zip
ניתן את השמות הבאים:
ValidateProducts
ReadProducts
WriteProducts
וכעת נתכנן את השימוש בפונקציות.
בשלב הזה אנחנו מגדירים את החתימה של הפונקציה - מה היא מקבלת, מה היא מחזירה.
נוכל לזרז תהליכים ופשוט לכתוב בפייתון:
1 | def ValidateProducts(bytes): |
שימו לב שלא מימשנו עדיין כלום - בשלושה שלבים אנחנו הגדרנו מה המידע שלנו, מהם השמות של הפונקציות וכיצד הם נראים.
עכשיו לחלק הפחות כיף - המימוש.
נתכנת את המימוש בעזרת מתודולוגיית פיתוח שנקראת - Test Driven Development
.
יש בתעשייה כל מיני תפקידים בתכנות - חלקם משפיעים יותר על התכנון וחלקם פחות.
מה שבטוח זה שלמתכנתים בדרך כלל יש הרבה השפעה וחופש על כיצד הם מתכננים את העבודה שלהם וכותבים את הקוד.
הסיבה העיקרית לזה שזה התפקיד של מתכנתים בסופו של יום זה לדעת כיצד לתכנן, לכתוב ולהוציא קוד לאור.
מתכנתים טובים שואפים להכיר כמה שיותר בתכנון וכתיבה של קוד
בגישת ה-Test Driven Development אנחנו כותבים קודם כל קוד שבודק את הקוד שאנחנו הולכים לכתוב.
למשל, נתון לנו הקוד הבא:
1 | def Add(a, b): |
שימו לב לפונקציה שאנחנו בודקים - לא מימשנו אותה!
בגישת התכנות הזו - אנחנו קודם מממשים את הקוד שבודק את הקוד שלנו ואחר כך אנחנו מממשים את הקוד שלנו.
1.
תריצו את הקוד מבלי לממש את פונקציית החיבור.
2.
ממשו את פונקציית החיבור ותריצו את הקוד.
בפעם הראשונה:
1 | Fail! a:1 b:2 result: 0 |
אחרי המימוש:
1 | Pass! a:1 b:2 result: 3 |
הטסטים לפרוטוקול הבינארי:
1 | def ReadProducts(bytes): |
אם תריצו את הקוד הזה אז אתם תראו שהכל נכשל.
כעת ניתן לממש את הפונקציות כדי שייעבדו.
ממשו את הפרוטוקול.
1 | def ValidateProducts(bytes): |
ראינו שפרוטוקול בינארי עד כמה שהוא מורכב יכול להיות פשוט כאשר אנחנו מפרקים אותו לגורמים.
למדנו מה זה ספירה בינארית, כיצה לפרש מספרים בעזרת הקסאדצימאל ולכתוב קוד שקורא וכותב מידע בינארי.
בפרק הבא נעבור על פורמט פופולרי - JSON
!