التفريغ الكامل للمحاضرة 4: الحلقات على السلاسل النصية والتخمين والتحقق والنظام الثنائي
التفريغ الكامل للمحاضرة 4: الحلقات على السلاسل النصية والتخمين والتحقق والنظام الثنائي
المصدر الأصلي: التفريغ الإنجليزي الرسمي بصيغة PDF، المرتبط بـصفحة المحاضرة والفيديو الرسمي.
النسبة والترخيص: آنا بيل (Ana Bell)، مقرر 6.100L: مقدمة في علوم الحاسوب والبرمجة باستخدام Python، خريف 2022، معهد ماساتشوستس للتكنولوجيا، MIT OpenCourseWare. هذه ترجمة وتكييف عربي غير رسمي، ولا تعني تأييد MIT أو اعتماده. الأصل المشمول والترجمة تحت رخصة CC BY-NC-SA 4.0: الاستخدام غير التجاري، مع النسبة ومشاركة التكييفات بالمثل. لا تشمل الرخصة تلقائيًا المواد الخارجية أو المستثناة من ترخيص المصدر، ولا تُعاد هنا صور الروبوتات الخارجية المذكورة في الكلام.
منهج المطابقة: ترجمة التفريغ الرسمي المستخرج من PDF، لا الملاحظات ولا كلام مستنتج من الشرائح. عناوين صفحات المصدر تطابق الصفحات التسع عشرة غير الفارغة المفصولة بمحرف فصل الصفحة في الاستخراج. حُفظ ترتيب الفقرات وأدوار المتحدثين والتكرار والتصحيحات والإشارات الصوتية. تبقى المعرّفات والتعبيرات البرمجية، وأي توضيح تحريري موسوم بأنه ملاحظة مترجم.
صفحة المصدر 1
MITOCW | 6.100L Lecture 4 Multi.mp4
[صرير] [حفيف] [نقر]
آنا بيل: لنبدأ. سنواصل اليوم الحديث قليلًا عن الحلقات (Loops) ونرى بعض دقائقها الأخرى، ثم نبدأ أول خوارزمية لنا، التخمين والتحقق (Guess and Check). وقرب النهاية نبدأ الأعداد الثنائية (Binary Numbers)، تمهيدًا للخوارزمية التالية.
لنراجع سريعًا ما تعلمناه، ثم مثال برمجي صغير، ثم نتابع. رأينا في المحاضرة الماضية آليات تكرار: while وfor. كلتاهما طريقة للتحكم بما يحدث في الشيفرة، وبالتحديد لتكرار أسطر معينة تلقائيًا نوعًا ما.
في while تتكرر الأسطر ما دام شرط قائمًا، وفي for تتكرر لتسلسل قيم. كنا نحن نقرر التسلسل، وكان عدديًا؛ هذا ما رأيناه. اليوم سنرى أنه قد يكون غير عددي أيضًا.
كان نوعا الحلقات ينتهيان في أوقات معينة: while حين يصير الشرط خاطئًا، وfor حين نستنفد التسلسل. لكن كثيرًا ما نريد الخروج مبكرًا، دون أن يصبح شرط while خاطئًا ودون استنفاد مجموعة قيم for كلها.
للخروج قبل النهاية الطبيعية نستخدم عبارة break. تتيح الخروج من الحلقة التي تحيط بها مباشرة. هذا مثال لحلقتي while متداخلتين (Nested Loops): حلقة وأخرى داخلها.
تعمل الخارجية ما دام الشرط الأول قائمًا، والداخلية ما دام الثاني قائمًا. يُقيَّم التعبير a عندما يصح الشرطان؛ ندخل الأولى ثم الثانية، فيجب أن يكون كلاهما صحيحًا.
لكن بمجرد رؤية break يخرج Python فورًا من الحلقة المحيطة بها، وهي ذات الشرط الثاني. تستمر ذات الشرط الأول.
بمجرد رؤية break يتوقف فورًا عن تشغيل while، ولا يعود حتى لفحص الشرط الثاني. لذا لن يُقيَّم التعبير B أبدًا. هذه شيفرة سيئة جدًا، لا نريد مثلها لأن B لن يعمل أبدًا. لكنها لتوضيح أثر break فقط.
ثم يُقيَّم c كلما صح الشرط الأول. قد يكون الثاني صحيحًا أو لا أثناء الطريق، لكن c يُقيَّم فقط عندما يصح الأول. يكون الثاني قد توقف عن كونه صحيحًا، ونعود لمستوى إزاحة الحلقة الداخلية نفسه.
أحد الحضور: يُقيَّم مرة واحدة فقط؟ يعطي a مرة واحدة بسبب وجود break؟
صفحة المصدر 2
آنا بيل: بالضبط، نقطة رائعة. يقيّم a مرة واحدة فقط لأنه يرى break مباشرة بعدها، فنخرج فورًا من while وننتهي. ملاحظة رائعة، نعم.
وهذا ما ستعرضه الشيفرة. هنا نستخدم break بالبنية نفسها في الشريحة السابقة. سنشغّل Python Tutor فقط لتتدربوا أكثر على استخدامه.
الشيفرة نفسها. لدي for على تسلسل قيم. من يخبرني ما القيم؟ 5 و7 و9 ثم نتوقف، صحيح؟ لأن 11 هي النهاية لكننا نصل دون تضمين n - 1. إذن هذه القيم الثلاث وحدها التي قد نمرّ بها.
نضغط Next. نهيّئ mysum إلى صفر. نتخيل أننا سنجعل متغير الحلقة 5 و7 و9. في أول مرة يكون i هو 5. نضيفه، وقيمته الحالية 5، إلى mysum؛ 5 زائد 0 تجعلها 5.
ثم نرى break فورًا؛ شرط مساواة mysum بـ5 صحيح، فندخل ونرى العبارة، وهذا السطر لن يُنفَّذ أبدًا. لن نزيد mysum بمقدار 1 أبدًا.
تخرج break فورًا من الحلقة. if ليست حلقة، بل شرط. إذن نخرج من for. لا توجد حلقة أخرى تحيط بها، فينتهي البرنامج أساسًا ونطبع 5.
مجددًا هذه شيفرة سيئة لا نكتب مثلها، لكنها لإظهار ما يحدث بالضبط. هذه كتلة for وهذه كتلة if، وbreak تخرج من الحلقة ذات البنفسجي الأفتح، لا من if.
لنكتب شيفرة صغيرة للتدرب على الحلقات عمومًا مما رأيناه، دون break فعلًا في هذا البرنامج، مجرد تدريب.
أريد حلقة for تمرّ بهذا النطاق: for i in ثم اختاروا واحدًا منها. اكتبوا داخلها ما يعدّ الأعداد الزوجية في النطاق، مع تضمين 0.
في range(5) يلتقط العدّاد أن 0 زوجي و2 زوجي و4 زوجي، فقط، فيطبع 3. التمرين في ملف اليوم وقد بدأت لكم بـfor i in range(5). أعطيكم دقيقتين للكتابة.
هل يود أحد البدء ببعض الشيفرة أو إعطائي — نعم؟
أحد الحضور: [غير مسموع].
آنا بيل: نعم. يأخذ هذا السطر i. يمكن لتذكيرنا بقيمه، وهذا مفيد في الاختبارات أيضًا، كتابة تعليق: i هو 0 و1 و2 و3 و4، حتى لا نحتاج تذكر ذلك؛ ننظر هنا ونعرف قيمه.
صفحة المصدر 3
آنا بيل: وهذا السطر، صحيح تمامًا، يأخذ i وباقي قسمته على 2. إن كان الباقي 0 فالعدد زوجي. ماذا نفعل داخله؟
عندما يصح، عندما يكون زوجيًا، كيف نتتبع هل — أو كم مرة يتحقق الشرط؟ نعم؟
أحد الحضور: [غير مسموع].
آنا بيل: بالضبط. ننشئ متغيرًا؟ نعم، لنسمّه even_nums. ونزيده 1 لأن هذا عدد زوجي آخر: even_nums += 1. ونتذكر تهيئته قبل الحلقة مباشرة.
قبل بدء الحلقة لدينا 0 أعداد زوجية. ثم في كل مرور نرى عددًا يقبل القسمة على 2 تمامًا، فنزيد العدّاد الصغير واحدًا.
أحد الحضور: ليس المتغير نفسه.
آنا بيل: آه نعم، شكرًا، even_nums. النموذج الذهني الآن، أو في البداية، أن هذه الأسطر الثلاثة تحل المشكلة. تعدّ تلقائيًا لأن i تأخذ 0 ثم 1 ثم 2 ثم 3 ثم 4 أثناء المرور بالتسلسل. وفي النهاية، عند مستوى إزاحة for نفسه، نطبع عددها فقط.
إن شغّلناه يطبع 3، وإن غيّرنا هذا إلى 10 فسيطبع ربما 6 لأنه يعدّ 0. أسئلة — نعم، تفضل.
أحد الحضور: هل هناك شيء أنتبه إليه إذا طبع 2 بدل 3؟
آنا بيل: إذا كان العد ناقصًا، ربما هيّأت — هل هيّأت even_nums إلى شيء آخر؟ أو لا تُزاد صحيحًا، أو النطاق مختلف؟
أحد الحضور: سأحاول قراءتها لك.
آنا بيل: نعم، عملت. رائع. استخدام for للمرور بتسلسل قيم مفيد جدًا. لننظر لهذا البرنامج. هذه الشيفرة وهذه وهذه تفعل الشيء نفسه في النهاية، لكن ننظر للعليا الآن.
يأخذ هذا البرنامج سلسلة s كمدخل نوعًا ما، ويمرّ بالأعداد من 0 لطولها. for index in range(len(s)) تعني نطاق 13 أو عدد أحرفها، D-E-M-O ثم مسافة وكل الأحرف؛ 13 أو نحو ذلك.
يجعل السطر index يأخذ 0 إلى 13 ممثّلًا الفهرس في s. d الصغيرة عند 0 وe عند 1 وهكذا. وبالفهرس في اليد يفحص الجزء التالي، if وقوس مربع index ثم علامتا المساواة i، هل المحرف i أو هل هو u.
ملاحظة مترجم: قول «ربما 6» عند
range(10)محفوظ كما في التفريغ؛ الأعداد الزوجية فيه هي 0 و2 و4 و6 و8، وعددها 5. كذلك لا تُضمَّن قيمة نهايةrange.
صفحة المصدر 4
آنا بيل: كلما حدث ذلك أطبع على الشاشة «هناك i أو u». هذه الشيفرة الأولى هنا. عند التشغيل تطبع الجملة مرتين لأن هناك اثنتين فقط من i أو u. وإذا تكرر، تطبع مرتين؛ هناك u واحدة وi واحدة.
لكن يمكن كتابتها ببساطة أكبر بكثير. لاحظوا أن شرحها استغرق وقتًا، وغالبًا ستحتاجون وقتًا لمعرفة ما تفعل أول نظرة. لأننا نعتمد على الفهرس كوسيط.
نمرّ بـfor عبر الفهرس ثم نفهرس بذلك المتغير لجلب الحرف. قلت إن for تستطيع المرور على أي تسلسل، لا الأعداد فقط. وتذكروا أن السلاسل تسلسل محارف، حساسة لحالة الأحرف.
يمكن كتابة هذه الشيفرة، المربع الأوسط. تمرّ مباشرة بكل محرف في السلسلة، لا بالفهرس من 0 إلى 12. متغير الحلقة الذي سمّيته char، ويمكن أي اسم، يأخذ الحروف نفسها واحدًا واحدًا.
في المرة الأولى تكون char هي d الصغيرة، ثم e الصغيرة، ثم m، وهكذا. أصبح تسلسل القيم هو الحروف مباشرة.
عند التحقق هل الحرف i أو u، أفحص فقط هل char تكافئ i أو u. الشيفرة نفسها تمامًا. هذه القديمة وهذه التي شرحتها، وتطبع الجملة مرتين أيضًا لأن البداية السلسلة نفسها.
تسلسلنا الآن المحارف مباشرة، الحروف لا الفهرس. وهناك طريقة أكثر موافقة لأسلوب Python (Pythonic)، هذا المربع في الأسفل. الجزء الوحيد الذي غيّرته هو if.
أستخدم الكلمة المفتاحية in لفحص هل المحرف الموجود، d ثم e ثم m وهكذا، موجود في تسلسل المحارف i أو u.
ليس هذا مهمًا جدًا هنا، فالمربع الأوسط يمكن أن يقول char == "i" or char == "u"، وهو جيد. لكن إن أردنا فحص هل هو أحد الأرقام من 0 إلى 9، سيصبح سطر if وor وor وor طويلًا جدًا.
كل ما نفعله إذن if char in ثم تسلسل معين، ويحوّل Python ذلك تلقائيًا إلى الفحص الأطول: هل هذا أو هذا أو هذا أو هذا، وهكذا.
الفكرة الكبرى: نعم، تمرّ for بتسلسل قيم، لكننا لسنا محدودين بالأعداد. الرائع إمكان المرور بالمحارف مباشرة. وسنرى لاحقًا قوائم أعداد وقوائم سلاسل وأشياء كثيرة أخرى.
صفحة المصدر 5
آنا بيل: لنكتب برنامجًا أعقد قليلًا. هذه نسخة 0.01 من روبوتات التشجيع. في الزاوية كتبت أن الروبوتات ليست لي، لكن الشيفرة لي. تجمع المرور بالسلاسل مباشرة وبالأعداد مباشرة.
سأعرض ما يفعله البرنامج ثم الشيفرة. أعطوني اسمًا لشيء أنتم متحمسون له جدًا. ما هذا؟
أحد الحضور: [غير مسموع].
آنا بيل: ماذا؟ لا بأس، شيء آخر أعرفه. ماذا؟ الأناناس، حسنًا، pineapples. سيشجع لنا الأناناس، ولنقل مستوى الحماس 8.
هذا برنامج التشجيع. كتبت الكلمة ومستوى حماس الأناناس. يفعل فقط — لاحظوا التكرار الذي تجيده الحواسيب — أعطني P، P، أعطني I، I، أعطني N، N، وهكذا. ماذا تهجّي هذه؟ ثم يطبع pineapples بثلاث علامات تعجب ثماني مرات، لشدة حماسي.
لننظر للشيفرة. جزآن: التهجئة، وتكرار الكلمة عددًا من المرات. حلقتان منفصلتان؛ التهجئة هنا بهذه for، والتكرار هنا.
تمرّ حلقة التهجئة بالحروف مباشرة. لا أفعل شيئًا خاصًا بها، فأستطيع ذلك: for w in word، حيث word إدخال المستخدم، وw متغير الحلقة الذي يكون p ثم i ثم n ثم e ثم a وهكذا.
ثم if/else. إن دققتم، فالفرق الوحيد بين داخليهما هو كتابة an ثم الحرف أو a ثم الحرف. بعض الأحرف يصح معها بالإنجليزية give me an a بدل give me a a؛ الأخيرة لا تبدو صحيحة.
الحروف المناسبة لـan محددة هنا كسلسلة طويلة. يستخدم شرط if كلمة in التي رأيناها: إن كان w أحد هذه المحارف، موجودًا في هذا التسلسل، اطبع give me an ثم المحرف. وإلا فليس من الحروف التي تناسب an، فاطبع give me a ثم الحرف.
أعدت كتابة عبارتي الطباعة باستخدام f-strings التي ناقشناها في المحاضرة 2، لأريكم الطريقة، لكن كلا الأسلوبين ممكن.
في نهاية ذلك أكملنا التهجئة. ثم طباعة «ماذا تهجّي هذه؟»، والجزء الأخير تكرار الكلمة عدد n من المرات بحسب المستخدم. أحفظ ذلك العدد في متغير times.
صفحة المصدر 6
آنا بيل: كل ما أفعله حلقة for صغيرة تمرّ بعدد المرات المحدد. range(times) تعني من 0 إلى times - 1 شاملًا، أي ثمانية مرورات هنا.
ثم نطبع الكلمة بثلاث علامات تعجب. لاحظوا أن الطباعة داخل for السفلى لا تستخدم متغير الحلقة أصلًا. إنه i ولا نفعل به شيئًا، وهذا جائز تمامًا.
نستخدم مرات الحلقة فقط لإجراء هذا الفعل عددًا من المرات. لا يلزم دائمًا استخدام متغيرها. أسئلة عن الشيفرة؟ نعم.
أحد الحضور: هل كان يمكن استخدام عبارات if للطباعة أيضًا؟
آنا بيل: أي طباعات، هذه؟ وكيف؟
أحد الحضور: لتقييم قيم w بدل الوصل. أعني ليس if، بل f.
آنا بيل: آه، f-strings. نعم، هكذا: f ثم المحارف داخل الأقواس المعقوفة. لا بأس، لا بأس. نعم هناك سؤال.
أحد الحضور: هل تشرحين عمل الجزء الأخير بما أننا لا نفعل ذلك فعليًا؟
آنا بيل: الأخيرة ما زالت تمرّ times مرة. ومتغيرها في كل مرور 0 ثم 1 ثم 2 ثم 3. لا نفعل شيئًا بـi؛ تُنفَّذ الشيفرة المزاحة، لكن لا نستخدم كونه 0 أو 1 أو 2 إطلاقًا. يزيد نفسه تلقائيًا ونحن لا نستخدمه، بالضبط.
هذا ما قلته. لنكتب قليلًا. نفترض سلسلة أحرف صغيرة، فلا نهتم بالكبيرة والصغيرة؛ نفترضها صغيرة، مخزنة في s. مثل s = "abca".
أريد شيفرة تعدّ الأحرف الفريدة (Unique Letters). لاحظوا a مرتين، لكن العد في هذه السلسلة يجب أن يكون 3، لا نعدّ a مرتين. الأحرف الفريدة في abca هي a وb وc.
تلميح: نستخدم متغيرًا إضافيًا كالمعتاد. فكروا فيه كسلسلة تحتوي كل ما رأيتم حتى الآن. بمجرد رؤية حرف لم تروه، أضيفوه إليها لتسجل أنكم رأيتموه، ثم استخدموها لكتابة البقية. أثناء المرور بالأحرف تحقّقوا هل رأيتم الحرف قبل إعادة عدّه.
كالعادة هنا قرب السطر 76. هذه الشيفرة للتمرين. أعطيكم دقيقتين ثم نكتب معًا.
سأتتبّعه. أظن هذا مفيد في الاختبارات: كتابة الأفكار على الورق أولًا. كون الصف للبرمجة وعلوم الحاسوب لا يعني البدء بالبرمجة فورًا. وضع أفكار على الورق مفيد جدًا.
صفحة المصدر 7
آنا بيل: طريقتي أنني أحتاج بوضوح للمس كل محرف في s، فأحتاج حلقة. وعند النظر لكل محرف أتتبّعه. إن لم يكن شيئًا لم أره — أي إن لم أره من قبل — أقول: رأيت a الآن، فأضيفه لمتغير seen.
ثم أزيد العدّاد: رأيته مرة، ربما count هي 1 الآن. التالي b: هل رأيته؟ لا، أضيفه لـseen وأزيد count. ثم c: هل رأيته؟ لا، أضيفه وأزيد العدّاد.
وفي المرة الأخيرة a: هل هو في seen أصلًا؟ نعم، فلا أفعل شيئًا به. حين أرى حرفًا سبق أن أضفته، لا أفعل شيئًا في الشيفرة.
معظم العمل عند لقاء شيء لم أره. هل لدى أحد بداية؟ لا يلزم كتابة كاملة مثالية من الأعلى للأسفل؛ يمكن أجزاء هنا وهناك. نعم؟
أحد الحضور: [غير مسموع] كتبت for char in s. ولا أظن أن هذه الصياغة المطلوبة، لكن قلت if char in s in seen للقيمة false؛ أريد أن أقول إن لم يكن في seen.
آنا بيل: بداية رائعة. نقول ببساطة if char not in seen، سواء تنطقونه char أو car. يأخذ عكس الصحيح والخاطئ؛ لأن in seen إما صحيح أو خاطئ، وnot تعطي العكس. ممتاز. نعم؟
أحد الحضور: [غير مسموع] لكن حين نستخدم كلمة not، هل نستخدم علامة التعجب [غير مسموع]؟
آنا بيل: نستخدم not مع القيم المنطقية، أي تعبير يُقيَّم لصحيح أو خاطئ. أما عدم المساواة، علامة التعجب والمساواة، فمع تعبيرات أخرى لفحص المساواة، مثل 3 != 2 أو a لا تساوي b. أشياء قد تكون عددية، لا صحيح وخاطئ فقط.
إذا كان char ليس في seen، ولم أره، ماذا أفعل؟ نعم؟
أحد الحضور: [غير مسموع].
آنا بيل: نلحق المحرف الذي نظرنا إليه بقائمة seen، كما فعلنا تدريجيًا. هذا يتكفل بإضافة الحرف واحدًا واحدًا إن لم نره. جيد. شيء آخر؟ بل نستطيع الاختبار هكذا.
نطبع seen في كل مرور.
أولًا a، ثم ab، ثم abc. وفي الأخيرة يفترض أن تبقى abc، وهي كذلك. والخطوة الأخيرة تنفيذ المطلوب: طباعة عدد المحارف — المحارف الفريدة — في هذه القائمة أو السلسلة. نعم؟
أحد الحضور: [غير مسموع].
صفحة المصدر 8
آنا بيل: يمكن عدّاد يبدأ بصفر قبل الحلقة، وكلما أضفنا شيئًا جديدًا لـseen نزيده. وهذا معظم العمل: العد كله وإضافة الفريد الذي رأيناه.
في نهاية الحلقة العدد في يدنا، فنطبعه. هنا هو 3. لو أضفنا مزيدًا من a في مواضع عشوائية يبقى 3. نعم؟
أحد الحضور: [غير مسموع].
آنا بيل: الآن بعدما لدينا شيفرة تعمل جيدًا يمكن تحسينها. اقتراح: بدل العدّاد نلاحظ أن طول seen هو كل المحارف الفريدة التي رأيناها.
عند التكرار لا نعيد الإضافة. لذلك لطباعة عدد الفريد يكفي طباعة طول seen، دون زيادة أي عدّاد. وما زلنا نحصل على 3.
أسئلة؟ مفهوم؟ لاحظوا عدم وجود else: if بسيطة فقط، لأنه لا عمل عند رؤية حرف مكرر. يمكن else: pass، وpass كلمة مفتاحية في Python، أصبحت زرقاء لأنها كذلك؛ تعني عدم فعل شيء. طبعًا لن نكتبها هنا.
لو كانت حالة else، فهذا ما سنفعله: لا شيء. أسئلة أخرى؟ واضح؟
أحد الحضور: [غير مسموع]
آنا بيل: عفوًا، مرة أخرى.
أحد الحضور: [غير مسموع].
آنا بيل: لماذا نطبع طول seen؟ لأننا حين نضيف محرفًا فريدًا إلى المتغير لم نكن قد رأيناه. لا أضيف إليه إلا الجديد. حتى حين مررت يدويًا قلت رأيت a ورأيت b ورأيت c، فأضفتها واحدًا واحدًا.
ثم حين رأيت a المكررة لم أضفها هنا. إذن seen تحتوي أصلًا كل المحارف الفريدة في قائمتي — سلسلتي الأصلية.
ملخص سريع قبل أول خوارزمية: رأينا الكائنات، وهي طريقة كتابة برامج Python. نتعامل معها بحفظها في متغيرات لتسهيل الوصول لقيمها. ولدينا تعبيرات تُقيَّم لأعداد صحيحة وأعداد فاصلة عائمة وقيم منطقية ونحوها.
أضفنا التفرّع (Branching) آليةً لتدفّق التحكم (Control Flow): يا Python، قيّم هذه المجموعة من العبارات أو الأخرى بحسب صحة الشرط. ثم الآلية الأخيرة، الحلقات: كرّر الشيفرة ما دام شرط صحيحًا، أو كرّرها لهذا التسلسل من القيم.
صفحة المصدر 9
آنا بيل: بهذه الأدوات لدينا صندوق جميل لكتابة برامج مثيرة. هذا تقريبًا كل ما نحتاجه، لكنه ليس نهاية الصف. سنرى أشياء تجعل الشيفرة أرتب وأسهل قراءة، ونكتب المزيد بكفاءة أكبر، ونحو ذلك. لكن لبدء خوارزميات صغيرة، هذا كل ما نحتاج من صياغة Python.
أول تطبيق لهذه المعرفة خوارزمية التخمين والتحقق، وتسمى أيضًا التعداد الشامل (Exhaustive Enumeration). الفكرة أن لدينا مسألة، فنخمّن قيمة لحلها، مجرد تخمين.
ثم نختبر صحته: هل يحل المسألة؟ إن فعل انتهينا ووجدنا حلًا. وإلا نواصل التخمين حتى نستنفد مجموعة التخمينات الممكنة. إما نجد الحل، أو نقول لم نستطع إيجاده.
لا يعني هذا أنه غير موجود، بل إن التخمين والتحقق والتعداد الشامل لهذه القيم لم يجداه. في مخطط انسياب (Flow Chart)، نبدأ بتخمين أولي ونسأل عن صحته، إن صح انتهينا، وإلا نختار التالي.
لننظر لإيجاد جذر مربع كامل (Perfect Square). هذه مسألتنا: إما نجد الجذر أو نقول إن العدد ليس مربعًا كاملًا.
ماذا لو أردنا معرفة هل 7 مربع كامل، وإن كان فما جذره، وإلا نقول إنه ليس كذلك؟
نخمّن أولًا 6، ليس حلًا. ثم 9، ليس حلًا. ثم 2، ليس حلًا. ثم 0، ليس حلًا طبعًا. يمكن التخمين العشوائي هكذا، لكنه غير كفء.
نريد استخدام قوة الحواسيب، وهي تعمل بهذه الأنماط. تذكروا range تبدأ من 0 وتتبع نمطًا إلى عدد ما.
الفكرة أن نكون منهجيين لنستغل البرمجة وقدرة الحواسيب على فعل الأشياء سريعًا جدًا. في المسألة نفسها، معرفة هل x مربع كامل، لنبدأ منهجيًا بتخمين 0.
حالتان: العدد مربع كامل، مثل 4. نبدأ بـ0: هل 0**2 يحل المشكلة؟ لا، نزيد. هل 1**2 يحلها؟ لا، نزيد. هل 2**2 يحلها؟ نعم، انتهينا.
وماذا إن لم تكن x مربعًا كاملًا، مثل 10؟ نستخدم المنهج نفسه، لكن نضيف بعض الجبر (Algebra)، وإلا فقد نقع في حلقة لا نهائية.
صفحة المصدر 10
آنا بيل: الجبر المطلوب: إذا لم يكن العدد مربعًا كاملًا، نحتاج طريقة للتوقف. لا نريد التخمين إلى ما لا نهاية. هذا تخمين وتحقق، فنحتاج مجموعة شاملة من الحلول المحتملة.
نستخدم الجبر ونقول نتوقف حين يصبح مربع التخمين أكبر من x. نبدأ 0 ثم 1 ثم 2 ثم 3 ثم 4، وفي لحظة يتجاوز مربع التخمين x. نعرف أننا نتوقف لأن الأعداد الأكبر من ذلك ستكون بالتأكيد أكبر من x.
أول تخمين 0**2 أصغر من 10، و1**2 أصغر، و2**2 أصغر، و3**2 أصغر لأنه 9. أما 4**2 فيصبح 16 فنتوقف. لم نجد جذرًا تربيعيًا لـ10، فليست مربعًا كاملًا. مفهوم؟ واضح؟
مجموعة الحلول المحتملة الشاملة من 0 إلى 4؛ اقتربنا من 10 وتجاوزناه عند 4. لا حاجة لفحص 5 و6 و7؛ مربعاتها بالتأكيد أكبر من 10.
هذه الشيفرة تحل المسألة. نأخذ إدخال المستخدم: ما العدد الذي تريد معرفة هل هو مربع كامل، وما جذره إن كان كذلك؟ لدينا while تفحص شرطًا واحدًا، شرط التوقف هنا.
نمرّ بالحَلقة حين يكون مربع guess أصغر من x. على خط الأعداد نزيد 1 ما دام المربع أصغر، هذه while.
داخلها نزيد التخمين، guess = guess + 1. وفي لحظة ما، سواء وجدنا مربعًا كاملًا أو لا، يصير الشرط خاطئًا: عند تحقق عكس علامة أصغر من.
يصبح مربع التخمين أكبر من أو يساوي x، وهما حالتان مختلفتان جدًا. الأكبر يعني لم نجد المربع الكامل، والمساوي يعني وجدناه. وكلتاهما تخرجاننا من while.
بعد الحلقة نحتاج if/else لفحص أي الحالتين. if guess**2 == x يعني خرجنا لأن العدد مربع كامل، مثل 4: حين نصل إلى 2 يصير شرط الحلقة خاطئًا ونخرج لأن 4 مربع كامل.
أما 10 فتدخل else، لأننا خرجنا حين كان مربع التخمين 4، أي 16، أكبر من 10. فنطبع أن x ليست مربعًا كاملًا.
يعمل هذا لكثير من القيم، كبيرة كما تشاؤون، لكن لا يعمل للسالبة لأننا لا ندخل الحلقة أصلًا. مثلًا عند فحص سالب 2، نبدأ التخمين 0، فهذه طريقة تنفيذنا. في الشريحة السابقة guess = 0 في الأعلى.
صفحة المصدر 11
آنا بيل: نسأل: هل مربع 0 أصغر من x؟ لا، 0 ليس أصغر من سالب 2. فلا تدخل الحلقة إطلاقًا، وهذا جيد لأن سالب 2 — أو سالب 4 — الأعداد السالبة ليست مربعات كاملة إلا إن كنا نتحدث عن أعداد تخيلية (Imaginary Numbers)، ولسنا كذلك هنا.
لكن ربما نريد معالجة إعطاء المستخدم عددًا سالبًا؛ ربما كتب الإشارة بالخطأ. نستطيع إضافة قليل من الشيفرة حول ما كتبناه. المحاط بالأحمر هو الإضافي، وكل شيء آخر كما كان قبل شريحتين.
عند عدد سالب نريد فقط تأشير ذلك بمتغير جديد إما صحيح أو خاطئ. وفي النهاية نتعامل مع الراية (Flag): إن كانت صحيحة نفعل شيئًا، وإلا شيئًا آخر.
هنا راية السالب تبدأ False، أي نفترض أولًا قيمة موجبة من المستخدم. إذن neg_flag = False.
نأخذ الإدخال ونفحص هل أعطى عددًا سالبًا. إن كانت x < 0، نغيّر الراية إلى True، من خاطئ إلى صحيح.
والبقية نفسها كما قبل شريحتين، إلا أننا في النهاية نفحص هل أعطى فعلًا عددًا سالبًا، ونسأله: هل قصدت نسخته الموجبة، أو نحو ذلك؟
هكذا تبدو الشيفرة. نعطيها 4، فتخبرنا أنه مربع كامل وجذره. 9 يعمل، و10 ليست مربعًا كاملًا. ومع عدد سالب، مربعًا أو غيره، تقول سالب 4 ليس مربعًا كاملًا، ثم «للتأكد فقط، هل قصدت 4؟». هذه طباعة إضافية عندما يكون العدد سالبًا. نعم، سؤال؟
أحد الحضور: لم أفهم تمامًا [غير مسموع].
آنا بيل: أشرح مجددًا. neg_flag = False متغير فقط، سمّيته neg_flag وهيّأته للخاطئ لأقول إنني أفترض العدد غير سالب. ولا نؤشّره — لا نغيّر قيمته إلى صحيح — إلا إذا كان سالبًا.
كان يمكن ببساطة if/else هنا دون السطر في الأعلى. لدينا x تساوي int، ثم نقول إن كانت x < 0 نجعل neg_flag صحيحًا وإلا خاطئًا. يمكن ذلك أيضًا.
الفكرة الكبرى في التخمين والتحقق أننا لا نختبر عددًا لا نهائيًا من القيم. علينا التوقف في وقت ما. عملنا بشيفرة كاليسار باستخدام while.
لكن رأينا إمكان كتابة شيفرة فعالة بـfor أيضًا. وبالفعل قد تكون طريقة التخمين والتحقق أنسب حدسيًا لـfor من while لأننا نمرّ بمجموعة شاملة من القيم.
صفحة المصدر 12
آنا بيل: الأعداد من 0 إلى عدد ما. ربما for أفضل لكتابة خوارزمية كهذه، وسنرى إعادة كتابتها بعد قليل. في مخطط الانسياب، تمرّ for تباعًا بكل القيم الممكنة. إذا استنفدناها نقول لم نجد حلًا، وإلا تجلب الحلقة تلقائيًا القيمة التالية.
اعملوا على هذا قليلًا. ثبّتوا عددًا سريًا في الشيفرة، كما فعلنا في المحاضرة الماضية: secret تساوي 7 أو 5 أو ما تشاؤون. ثم اكتبوا شيفرة تمرّ بالأعداد من 1 إلى 10 شاملًا، لنقل، وتطبع أنها وجدت السر.
إذا كان السر ضمن 0 إلى 10 اطبعوا أنكم وجدتموه، وإلا لا تطبعوا شيئًا. أثناء العمل، وإذا انتهيتم، فكروا بتغيير واحد: إذا لم تجدوه، اطبعوا أنكم لم تجدوه.
في النسخة الأولى، إن لم تجدوه لا شيء، وفي الثانية أخبروني بذلك. الشيفرة في ملف Python؛ الأسهل قرب السطر 129. وبعد إكمالها يمكن نسخها إلى قرب 144 وتعديلها للمواصفات الجديدة: إن لم تجدوه اطبعوا ذلك.
أعطوني شيفرة الأولى: إن وجدناه اطبعوا وإلا لا شيء. الأفضل while أم for؟ for، نعم. for i in range، كيف نحصل على 1 إلى 10 شاملًا؟ 1 و11، بالضبط. ويمكن كتابة تذكير لنفسي: i هي 1 و2 و3 و4 ... 11. كيف أفحص هل i هي السر؟
أحد الحضور: [غير مسموع].
آنا بيل: if i == secret، لنقل اطبع found. نشغّل: 4 في النطاق طبعًا، و100 ليس فيه. مع 4 طبع وجد، ومع 100 لا شيء.
أنسخ الشيفرة وألصقها هنا. لنجرّب النسخة ذات التغيير الصغير: إن لم تجد الرقم ضمن النطاق، اطبع لم يجد. ماذا نجرب؟
else، حسنًا، اطبع not found. وجد 4 طبعًا، لكن طبعنا أيضًا كل «لم يجد» هذه. لماذا؟ نعم.
أحد الحضور: [غير مسموع] تمرّ بالنطاق كله، فيمكن محاولة الخروج من [غير مسموع].
آنا بيل: نعم، طبعنا ذلك لأننا نمرّ بالنطاق كله. في كل فحص لـi نطبع وجد أو لم يجد. يمكن الخروج بـbreak عند إيجاده. نشغّل: نطبع لم يجد حتى نجده، ثم نخرج.
نقترب، ويبدو أفضل قليلًا. ماذا أيضًا؟ break أخرى، نجربها بعد لم يجد، لكن عندئذ لا نجد 4. نعم؟
أحد الحضور: [غير مسموع].
ملاحظة مترجم: يتبدل حد البداية في الكلام بين 0 و1، ويذكر 11 في التذكير؛ حُفظت الأقوال.
range(1, 11)تضم 1 إلى 10 فقط.
صفحة المصدر 13
آنا بيل: تعجبني الفكرة. يمكن راية منطقية (Boolean Flag). هل كان هذا اقتراحك أيضًا؟ لنجرّبها. نحذف عبارات break ونعود لما كان.
الفكرة أننا لا نريد طباعة لم يجد إلا بعد المرور بكل أعداد النطاق. مثل هذا: أريدها مرة واحدة فقط في نهاية الحلقة.
لكن هذه الشيفرة لا تعمل أيضًا، لأنني أطبع لم يجد دائمًا، مهما طبعت شيئًا إضافيًا هنا، لأن الطباعة الأخيرة عند مستوى إزاحة for نفسه. لذا اقترح بعضكم ضبط راية.
نضبط found مبدئيًا إلى False. قبل بدء الحلقة أفترض الخاطئ، وأستخدم الراية لتفعيل — أو أغيّرها كلما وجدت العدد.
تبدأ found خاطئة، وأعرف هنا أنني وجدت العدد عندما تكافئ i السر، فأجعل الراية صحيحة. أسميها راية لأنها تؤشر وقوع حدث أو عدمه، حدث منطقي. لكنها متغير فقط؛ لا شيء خاص بالكلمة.
بعد ضبطها حسب ما حدث، نقول if found، أو هنا if not found، عكس القيمة، ونطبع لم يجد.
لا else لأن تلك الحالة عولجت عند إيجاد السر داخل الشيفرة. الآن نطبع وجد لـ4، ولم يجد لعدد خارج النطاق طبعًا مثل 100.
يمكن تعديل صغير: لا نحتاج طباعة وجد بالداخل. للاتساق يمكن else ثم طباعة وجد هنا، وأظنه يعمل أيضًا. 100 لم يُجد و4 وُجد.
نعمل الآن باتساق: الطباعة عن الإيجاد هنا، وداخل for منطق الإيجاد أو عدمه فقط. أسئلة؟ تبدو صحيحة؟ مفهومة؟
أعرض الرايات المنطقية لأنها مفيدة جدًا لتأشير ما حدث في الشيفرة. إذا سألتم: كيف أفعل، كيف أعرف أن هذا الشيء حدث أو نحو ذلك؟ فالجواب راية منطقية. اجعلوها صحيحًا أو خاطئًا، 0 أو 1، a أو b، كما تريدون، ثم افحصوا المتغير لاحقًا لمعرفة وقوع الحدث.
هذه الشيفرتان جنبًا لجنب لتوضيح الفرق. هنا إذا لم نجد العدد لا نطبع شيئًا: for مع if ونقول وجدناه.
صفحة المصدر 14
آنا بيل: واليمنى شيفرة وجدناه — شيفرة نطبع فيها إن لم نجد. الإضافات إلى اليسرى بالخط العريض: راية أبدأها خاطئة.
أجعلها صحيحة عند وقوع الحدث، أي إيجاد العدد، ثم أفحص في النهاية لطباعة وجد أو عدم طباعتها.
أحد الحضور: هل تشرحين لماذا لا نستخدم else عند [غير مسموع]؟
آنا بيل: في if أم هنا أسفل؟ في if؟ لا نستخدم else داخل if i == secret لأن if/else تنفّذ كل مرة بالحَلقة، وأريد طباعة لم نجد مرة واحدة في النهاية فقط.
إذا وضعت else داخل for، أسأل هل i هي السر: 0 ليست السر فندخل else، و1 ليست السر فندخلها، و2 ليست السر فندخلها. فقط عند 7 هنا تكون السر فندخل if وهكذا. لا أريد هذا في كل مرور، بل أضعه في النهاية مرة واحدة. مفهوم؟
المتغيرات المنطقية في واحدة من حالتين. استخدمت صحيحًا وخاطئًا، لكن كما ذكرت يمكن 0 أو 1، a أو b، ما دمتم تتذكرون بصفتكم المبرمجين القيم المتوقع أن يأخذها المتغير.
يمكن استخدامها إشارات لوقوع شيء في الشيفرة؛ مفيدة في اختبار. نسميها رايات منطقية، لكنه اسم فقط. متغير يغيّر حالته بحسب وقوع حدث.
أعود لفكرة while وfor. رأينا حالات كثيرة تكون for أسهل فيها لأنها تمرّ بتسلسل قيم. خوارزمية التخمين والتحقق تلائم for أكثر قليلًا.
مثال لإيجاد الجذر التكعيبي (Cube Root) هذه المرة، لا التربيعي. نسأل فقط هل x، أو هنا cube، مكعب كامل (Perfect Cube). تعمل for بالمرور بكل القيم الممكنة.
لدينا for guess in range وعدد، ونفحص القيم من 0 إلى cube + 1. أضفنا 1 لأننا إذا أخذنا العدد 1 نريد فحصه نفسه؛ دون الزيادة نتوقف خطأً عند 0، مع أن 1 مكعب كامل.
داخل for، إذا كان مكعب guess يساوي cube وجدنا مكعبنا الكامل. للقيم السالبة نضيف شيفرة صغيرة، لكنها ليست غريبة كالجذر التربيعي: الجذر التكعيبي لسالب هو جذر نسخته الموجبة مع إشارة سالب أمامه.
صفحة المصدر 15
آنا بيل: كل ما نفعله بإدخال سالب هو المرور بالقيم من 0 لنسخته الموجبة: نأخذ القيمة المطلقة (Absolute Value) لما أعطانا المستخدم ونضيف 1. مثل الشريحة السابقة لكن بالقيمة المطلقة.
نفحص هل مكعب التخمين يكافئ القيمة المطلقة لـcube؛ كما سبق إلا القيمة المطلقة. ثم جزء إضافي يفحص هل أعطى المستخدم سالبًا؛ هل نحتاج إشارة سالب أمام التخمين؟
إن أعطى سالبًا، نأخذ سالب القيمة التي وجدناها، ثم نطبع جذر المكعب الكامل. الشيفرة نفسها؛ الفرق القيمة المطلقة وفحص السالب.
يمكن تسريعها قليلًا. عند فحص الجذر التكعيبي لـ27 نتحقق من 0 و1 و2 و3 و4 و5 و6 إلى 27. لكن يمكن ملاحظة أنه عند 27 — لنقل 26 — عند 3 يكون مكعب التخمين 27.
لا معنى للاستمرار بفحص 4 و5 و6 و7 لمعرفة إن كانت جذورًا. هذا ما تفعله هذه الشيفرة. شرط if صغير، والبقية كما كانت.
يقول إن كان مكعب التخمين أكبر من أو يساوي — لا مساويًا فقط بل أكبر أو يساوي — فلنخرج من الحلقة.
حين يكون الشرط خاطئًا — عفوًا، صحيحًا، مكعب التخمين أكبر أو يساوي — نخرج. لكن كحال الجذر التربيعي بـwhile علينا معرفة لماذا خرجنا مبكرًا.
إما لأن المكعب يساوي cube، أو أكبر منها. نضع if/else: إن خرجنا لعدم المساواة، أي الأكبر، فليس مكعبًا كاملًا. وإلا خرجنا للمساواة، ونتبع الشيفرة السابقة: نفحص الموجب والسالب، ونضيف الإشارة للتخمين، ونطبع الجذر.
كلها تنويعات للشيفرة الأولية نفسها، نضيف وظائف صغيرة ونحسن الكفاءة هنا وهناك. لدي مثال آخر، ربما لحظة تقولون فيها: آه، هذا معنى التفكير الحوسبي (Computational Thinking).
تذكروا المسائل اللفظية من الطفولة: مسألة رياضيات هي منظومة معادلات (System of Equations)، يمكن حلها جبريًا في دقيقة تقريبًا. نستطيع تطبيق الحوسبة على أمثالها؛ لا نحتاج الحل الجبري، بل نخبر الحاسوب: هذه قيم جرّبها لترى هل تطابق منظومة المعادلات، ثم اطبع الجواب.
مثال: أليسا (Alyssa) وبن (Ben) وسيندي (Cindy) يبيعون تذاكر لجمع التبرعات. يبيع بن أقل من أليسا باثنتين، وسيندي ضعف ما تبيع. المجموع 10 تذاكر. كم باعت أليسا؟
صفحة المصدر 16
آنا بيل: هذه شيفرة تحلها. أحسب كل التركيبات الممكنة للتذاكر التي يبيعونها. ثلاث حلقات متداخلة؛ قد تبيع أليسا 0 أو 1 أو 2، وهكذا.
لكل قيمة من أليسا، يمكن أن يبيع بن 0 أو 1 أو 2. أليسا 0، بن 0، سيندي 0؛ أليسا 0، بن 1، سيندي 0، وهكذا. تعطينا الحلقات الثلاث كل التركيبات الممكنة.
هنا يحاول الثلاثة بيع التذاكر، وهذه منظومة معادلاتي: المجموع 10. المتغيرات total وtwo_less وtwice كلها منطقية. a + b + c == 10 أول شرط أحتاجه.
وb == a - 2 الثاني، وc == 2*a الأخير، وهي شروط الشرائح السابقة. عندما تكون القيم الثلاث صحيحة، total وtwo_less وtwice، أكون وجدت الحل.
في الشيفرة هنا هم يبيعون التذاكر، وتخبرني تلقائيًا كم باع كل واحد. الرائع أنني أستطيع تغيير شيء وتشغيلها مجددًا، فتخبرني فورًا بالحل الجديد، دون حله جبريًا من البداية.
المشكلة في هذه الشيفرة بالذات أنها بطيئة جدًا للأعداد الكبيرة. إن غيّرت إلى 1000 تذكرة وثلاثة أشخاص وبعض التعديلات هنا، فمجرد المرور لأليسا من 0 إلى 1000، وبن كذلك، وسيندي كذلك، يستغرق وقتًا طويلًا جدًا.
لن أشغّل هذه الشيفرة؛ ستستغرق وقتًا طويلًا إذا غيّرت القيم إلى 1020 والضعف. بدل ذلك نمزج الجبر بالحوسبة. نلاحظ أننا نحتاج حلقة واحدة فقط؛ ربما أفحص تذاكر أليسا من 0 إلى 1000.
ثم أستخدم المعادلتين الأخريين، بن وسيندي، لمعرفة مبيعاتهما نسبة لأليسا. هنا المعادلتان تخبرانني بمبيعاتهما بالنسبة لحلقة أليسا.
والمعادلة الأخيرة أن بن وسيندي وميليسا معًا باعوا 1000. بهذه الشيفرة أجد كم باعوا. رائع مجددًا أن تغييرات صغيرة في الأعداد تحل المسألة فورًا، دون العودة لحلها جبريًا كما في الرياضيات.
يمكن تطبيق الحوسبة على مسائل مختلفة كثيرة. آمل أن هذه المسألة اللفظية عرض جيد لمعنى التفكير الحوسبي وما نريد أن تخرجوا به من الصف.
ملاحظة مترجم: يظهر في الاستخراج
1020 and twice؛ أُبقي1020كما هو لعدم وضوح المقصود، دون افتراض أعداد منفصلة أو صيغة رياضية. واسم «ميليسا» بدل «أليسا» محفوظ كما في الكلام.
صفحة المصدر 17
آنا بيل: آخر ما أردت مناقشته سريعًا حدسيًا هو الأعداد الثنائية، تمهيدًا لخوارزمية المحاضرة المقبلة، خوارزمية التقريب (Approximation Algorithm)، تحسين للتخمين والتحقق.
رأينا في Python أعدادًا صحيحة (Integers)، وهي كاملة، وأعداد فاصلة عائمة (Floats)، وهي أعداد حقيقية. لكن تحدث أشياء مثيرة عند التعامل مع floats في البرمجة. هذا دافعنا لمناقشة الثنائي ثم الكسور ثم floats في هذه المحاضرة والتالية. إليكم شيفرة.
هذه نفسها في الشرائح. لدي عدد صحيح x، وحلقة range(10) تمرّ عشر مرات. أضيف 0.1 عشر مرات: 0.1 زائد 0.1 زائد 0.1، عشرًا.
وأطبع هل المجموع x يساوي 1. ولكي تروا أنني لا أمزح، أشغّل وأطبع هل مجموع 0.1 عشر مرات يكافئ 1. تطبع الشيفرة False. غير حدسي، أليس كذلك؟ ينبغي أن أحصل على 1 لكن لا في البرمجة.
لإظهار الإجابة الفعلية، نطبع قيمة x ونسأل هل هي مثل ضرب 0.1 في 10 مباشرة. الحلقة تعطيني فعلًا 0.9999999، بينما الضرب المباشر يعطيني 1 كما أتوقع.
هذا الدافع — أو التمهيد — للخوارزمية التالية. لدينا خطأ فاصلة عائمة (Floating Point Error). لماذا يحدث؟ ولأنه يحدث لا نستطيع التكافؤ، أي استخدام == بين floats، لأن نتائج كهذه تفسد البرنامج حين نتوقع من شيء بسيط كجمع 0.1 عشر مرات أن يكون 1 وليس كذلك.
الفكرة الكبرى: طريقة تخزين floats في الذاكرة تجعل العمليات عليها تدخل خطأ صغيرًا جدًا جدًا في كل مرة. لكن كلما كررنا عملية ذات خطأ صغير تضخّم، فنرى نتائج مفاجئة. هذا من طريقة تخزينها في الحاسوب.
الحاسوب يعمل بالثنائي، الأصفار والآحاد، والبشر بالأساس 10، من 0 إلى 9. الحاسوب بالأساس 2، إما 0 أو 1، بسبب بناء عتاده.
يسهل على العتاد القول إن اللف المغناطيسي أعلى أو أسفل، 0 أو 1، أو إن الجهد منخفض أو مرتفع. الأصعب أن يقول: لدي جهد 0 منخفض، و1 مرتفع، أو عُشر المرتفع أو عُشران؛ ستدخل أخطاء كثيرة جدًا.
صفحة المصدر 18
آنا بيل: لذلك الأسهل جدًا للعتاد أن يكون في إحدى حالتين، 0 أو 1، ومن هنا الثنائي. مع الأعداد الصحيحة ليست مشكلة؛ يمكن تحويل الكامل من الأساس 10 إلى الأساس 2 بسهولة. المشكلة مع floats.
لا تحتاجون معرفة التحويل، لكنه يعطي حدسًا لما سيحدث. العدد 1507 في الأساس 10، هنا، هو 1000 زائد 500 زائد 0 في 10 زائد 7. في الأساس 2 نمط مشابه: عدد كامل مضروب في قوة من 2.
هنا كان العدد من 0 إلى 9 مضروبًا بقوة من 10. في الأساس 2 هو 0 أو 1 مضروبًا بقوة من 2. لتحويل 1507 نفكر بشريًا: ما أكبر قوة 2 تقترب دون تجاوز 1507؟ 2**10، أي 1024، لأن 2**11 ألفان وشيء، كبير جدًا.
ثم ما أكبر قوة تالية يمكن إضافتها إلى 1024 دون تجاوز 1507؟ 256. تجاوزنا 2**9 لأن إضافة 512 إلى 1024 تتجاوز 1507. ونكرر العملية لنعرف أكبر قوى 2 بالترتيب التي تكوّن 1507.
هي 2**10 + 2**8 + 2**7 + 2**6 + 2**5، والقوى 4 و3 و2 كلها أصفار، ثم 2**1 و2**0. والبتّات (Bits)، 1 في 2**10 و1 في 2**8، ما يمثل هنا، الأجزاء الصحيحة التي نضرب بها قوى 10.
هكذا نحوّل من عشري إلى ثنائي، لكنها طريقة بشرية. يمكن فعل ذلك بمنهج أكثر انتظامًا، وأكثر — ليس أكثر أمرية (Imperative)، أليس كذلك؟ طريقة وصفة يستطيع الحاسوب استخدامها للتحويل.
لن يُطلَب منكم ابتكار الطريقة، لكن إن أُعطيتموها ينبغي أن تبرمجوها. نأخذ العدد وننظر لباقي قسمته على 2.
إن كان فرديًا فالباقي 1، وإن زوجيًا فـ0. والباقي يعطينا آخر بتّ في أقصى اليمين. ثم نقسم العدد على 2 قسمة كاملة، فتعطينا الخانات الأربع الباقية؛ ترون كل شيء آخر يُزاح.
الشيفرة قسمة متتابعة وحساب للبواقي. سأنظر سريعًا في Python Tutor ثم نتوقف. لتحويل 1507 بهذه الوصفة، ننظر فقط لباقي قسمته على 2.
العدد فردي، فالباقي 1. نضيف 1 لتمثيلنا الثنائي، ونواصل قسمة الأعداد المتبقية وإضافة الباقي لمقدمة هذه السلسلة.
صفحة المصدر 19
آنا بيل: قسمة 1507 على 2 تعطينا 753. هل فردي أم زوجي؟ فردي، فنضيف 1 أخرى لمقدمة سلسلة النتيجة. قسمة 753 على 2 تعطي 376، زوجي، فنضيف 0 في المقدمة. لاحظوا ما يحدث للسلسلة خطوة خطوة.
376 على 2 تساوي 188، زوجي، فنضيف 0 في المقدمة. 188 على 2 تساوي 94، زوجي أيضًا، فنضيف 0. و94 على 2 تساوي 47، فردي، فنضيف 1.
47 على 2 تساوي 23، فردي، فنضيف 1. 23 على 2 تساوي 11، فنضيف فرديًا — نضيف 1. 11 على 2 تساوي 5، فردي، فنضيف 1. ثم 5 على 2 زوجي، فنضيف 0. وأخيرًا 1 آخر عدد، فنضيف 1.
لاحظوا أنه العدد نفسه الذي حصلنا عليه بالطريقة البشرية المتأنية، حين بحثنا عن أكبر قوى 2 تقترب من 1507 دون تجاوزه. لكن استخدمنا شيفرة تكرارية جميلة بحلقة. ولعدد سالب نضيف هذين المربعين فقط، أي نضع إشارة سالب أمامه. حسنًا، نعم.