التفريغ الكامل للمحاضرة 10: القوائم وقابلية التغيير
التفريغ الكامل للمحاضرة 10: القوائم وقابلية التغيير
المصدر الأصلي: التفريغ الإنجليزي الرسمي بصيغة PDF، المرتبط بـصفحة المحاضرة وصفحة الفيديو.
النسبة والترخيص: آنا بيل (Ana Bell)، مقرر 6.100L: مقدمة في علوم الحاسوب والبرمجة باستخدام Python، خريف 2022، معهد ماساتشوستس للتكنولوجيا، MIT OpenCourseWare. هذه ترجمة عربية غير رسمية ولا تعني تأييد MIT أو اعتماده لها. المواد الأصلية المشمولة والترجمة تحت رخصة CC BY-NC-SA 4.0، مع النسبة إلى أصحابها، والاستخدام غير التجاري، ومشاركة التكييفات بالرخصة نفسها.
منهج المطابقة: تقابل عناوين صفحات المصدر فواصل الصفحات في الاستخراج الرسمي 1thJR8x0QK8HpwQ3OqOE-MVtYg_P0QZ_T_transcript.txt، وعددها 18 صفحة غير فارغة. تُترجم الفقرات بالترتيب مع التكرار والتصحيحات الشفهية وأقوال الطلاب والإشارات الصوتية. تُحفظ أسماء Python والرموز دون ترجمة، وتُوسم الإضافات التوضيحية بملاحظة المترجم. عنوان التسجيل في المصدر: MITOCW | 6.100L Lecture 10 Version 3_1.mp4.
صفحة المصدر 1
[صرير]
[حفيف]
[نقر]
آنا بيل: حسنًا، أهلًا بالجميع. لنبدأ. هذه المحاضرة 10. في المحاضرة الماضية قدمنا نوعين جديدين من البيانات (Data types): تحدثنا عن نوع يسمى الصف (Tuple)، ونوع يسمى القائمة (List).
اليوم لن نتحدث عن الصفوف بعد الآن، لأنها كانت مباشرة جدًا. كثير من العمليات التي تستطيعون إجراءها على السلاسل النصية (Strings) تستطيعون إجراءها على الصفوف. كانت كائنات غير قابلة للتغيير (Immutable objects). هذا يعني أنه بمجرد إنشائها في الذاكرة، لا تستطيعون فعل شيء لتغييرها. لذلك كانت مملة جدًا على ما أظن، باستثناء أنكم تستطيعون ملء الصفوف بكائنات من أي نوع. يمكنكم ملء صف بأعداد صحيحة وأعداد فاصلة عائمة وقيم منطقية وصفوف أخرى، كلها في الوقت نفسه.
وقدمنا القوائم في المرة الماضية أيضًا كشيء مشابه جدًا للصفوف والسلاسل من حيث التعامل معها. كانت القوائم جميلة أيضًا لأنكم تستطيعون، مرة أخرى، ملأها بأي نوع من كائنات البيانات، كما تستطيعون مع الصفوف.
لكن ما سنركز عليه اليوم هو فكرة قابلية التغيير (Mutability) حين نتحدث عن القوائم، وهو شيء جديد. لم نتحدث عن هذه الفكرة من قبل أبدًا. ستكون المحاضرة مكثفة جدًا في هذه الفكرة، ومكثفة قليلًا في الصياغة وما شابه لتذكيركم بكيفية التعامل مع أنواع البيانات المركبة (Compound data types). من فضلكم، إن كان هناك أي سؤال فلا تترددوا في إيقافي، وأستطيع مراجعة ما تحدثت عنه للتو إذا كان شيء مربكًا.
هذه الشريحة أساسًا نسخة من شريحة القوائم في المحاضرة الماضية. تعرض مجموعة مختلفة— عفوًا، مجموعة عمليات مختلفة تستطيعون إجراءها على القوائم، مشابهة جدًا لعمليات السلاسل. هنا أنشئ قائمة فارغة فقط. وأنشئ قائمة فيها مجموعة عناصر. نستطيع رؤية أن هذه القائمة تحتوي أربعة عناصر، كلها من أنواع مختلفة. هذا عدد صحيح، وهذه سلسلة، وهذا عدد صحيح، وهذه قائمة أخرى. وهذا مقبول تمامًا مع أنواع البيانات هذه.
إجراء كل هذه العمليات، والحصول على الطول، والفهرسة (Indexing)، والاقتطاع (Slicing)، والضم (Concatenation)، والحصول على أكبر قيمة، كل ذلك ينبغي أن يكون مراجعة، وكذلك تكرار حلقة for على عناصر القائمة مباشرة. كما كررنا حلقة for على محارف سلسلة، تجعل هذه الحلقة أساسًا e، متغير حلقتنا، تأخذ قيمة كل عنصر في قائمتنا L.
الجديد، الشيء المكتوب بالخط العريض هنا، شيء لم نستطع فعله من قبل. يدخل أساسًا إلى الذاكرة ويغير العنصر عند الفهرس 3 في L. هكذا نقرأه نوعًا ما، ويغيّر ذلك العنصر لتكون له القيمة في الطرف الأيمن. هذا نوعًا ما، أعني أننا نقرأه بالطريقة نفسها التي نقرأ بها عبارات الإسناد الأخرى. ننظر إلى الطرف الأيمن ونقيّمه، وهو في هذه الحالة 10 فقط.
لكن الطرف الأيسر يبدو مختلفًا. ليس اسم متغير كما رأينا من قبل، بل هو هذا: يشير إلى العنصر في القائمة المسماة L عند الفهرس 3. فتكون الفهارس 0 ثم 1 ثم 2 ثم 3. هذا السطر في الأسفل، L[3] = 10، يستبدل أساسًا هذا العنصر كله هنا بالعدد 10.
صفحة المصدر 2
في الشرائح التالية سنتحدث عما يعنيه هذا بالضبط داخل الذاكرة، لأنه مختلف عما كنا نفعله من قبل. ماذا يحدث بالضبط حين ندخل كائنًا قابلًا للتغيير مثل قائمة، ونغيّر عنصرًا باستخدام هذه الصياغة نفسها؟
حسنًا، لنرسم مخططات الذاكرة كما فعلنا سابقًا. هذه سحابتنا الصغيرة التي تمثل الذاكرة. L = [2, 4, 3] تنشئ لي هذه القائمة في الذاكرة، كائن القائمة هذا. وL الاسم الذي أشير به إلى كائن القائمة. أربط أساسًا الاسم L بذلك الكائن في الذاكرة.
تخبر L[1] = 5 Python أن تتبع الاسم L إلى الكائن في الذاكرة، ثم تبحث عن الفهرس في الأقواس المربعة، وهو 1 في هذه الحالة. هذا 0، وهذا 1، هذه 4. وتأخذ العنصر في هذا الموضع وتكتب فوقه ما يقوله الطرف الأيمن. الطرف الأيمن يقول 5. فندخل أساسًا إلى الذاكرة ونغيّر ذلك العنصر الأوسط.
هذا مختلف عن السلاسل والصفوف. لم يكن مسموحًا لنا فعل شيء كهذا مع السلاسل والصفوف. لننظر إلى مثال في الشريحة التالية عن معنى هذا. لكن الفكرة التي أحاول الوصول إليها أن هذا الكائن الذي غيّرنا أحد عناصره في الداخل— الذي غيّرنا أحد عناصره، غيّرنا الكائن نفسه. لم نصنع نسخة جديدة، ولم نصنع إصدارًا من ذلك الكائن نوعًا ما. لقد غيّرنا الكائن نفسه.
لنرَ ربما شيفرة مشابهة قد تظنون أنها تفعل الشيء نفسه، لكن مع الصفوف. حسنًا، سيكون سطرا الشيفرة الأولان كما هما. لدينا L في الذاكرة الكائن 2, 4, 3، أو L اسم مرتبط بالكائن 2, 4, 3. L[1]، أي L عند الفهرس 1، تساوي 5، تغيّر العنصر الأوسط ليصبح 5. كما في الشريحة السابقة.
ماذا لو كانت لدينا هذه الأسطر الاثنين؟ t ستكون اسم متغير مرتبطًا بالصف 2, 4, 3. لاحظوا أن هذا الآن صف تدل عليه الأقواس المستديرة. إذا قلت t تساوي 2, 5, 3، فما الذي يحدث؟ بهذا السطر أنشئ أساسًا كائنًا جديدًا في الذاكرة. فهذا كائني الجديد. وآخذ الاسم t وأربطه بهذا الكائن الجديد.
الكائن القديم، 2, 4, 3 كصف، يبقى في الذاكرة. لم أعدل ذلك الكائن إطلاقًا. ما زال هناك، لكنني فقدت الارتباط به فقط. فالاسم t منفصل عن الكائن الفعلي في الذاكرة. وما يعنيه ذلك لنا مع الصفوف أننا لا نستطيع أبدًا تغيير كائن الصف في الذاكرة بعد إنشائه. لكن مع القوائم، باستخدام هذه العملية المحددة، هذه هنا، L[1] = 5، نستطيع الدخول إلى الذاكرة وتغيير الكائن المرتبط بالاسم L حرفيًا.
هل الجميع بخير مع هذه الشريحة؟ هل هذا مفهوم؟ تُظهر هذه الفرق. نحتاج إلى التفكير في اسم الكائن مقابل الكائن نفسه في الذاكرة.
تُظهر لكم هذه كيفية إنشاء قائمة ثم تغيير العناصر إلى قيم مختلفة داخلها. لكن بعدما صار لدينا كائن قائمة نستطيع تغييره (Mutation)، من العمليات الأخرى التي نستطيع إجراءها مثلًا إضافة عناصر أخرى إلى نهاية القائمة. فنستطيع جعل القائمة أكبر، وتغيير الكائن بفعل ذلك باستخدام دالة append هذه.
صفحة المصدر 3
سأتحدث عن صياغة دالة append بعد قليل. لكن أساسًا، إذا أردت تغيير L لإضافة عنصر إلى نهايتها، فعلي استخدام هذه الصياغة. لا يوجد شكل مختلف أو دالة مختلفة لفعل هذا. يجب استخدام هذه الصياغة المحددة. append أساسًا اسم الدالة، والعنصر هو المعامل، الشيء الذي أريد إضافته إلى نهاية قائمتي.
وL، الشيء قبل النقطة، هو الكائن الذي أريد إضافة العنصر إلى نهاية قائمته. أستخدم L في هذه الحالة استخدامًا عامًا، لكن تخيلوا إنشاء قائمة بالموظفين في شركتكم. ربما تسمّونها employees. عندئذ نقول employees.append مع Ana أو أي شيء. فالـL عامة نوعًا ما الآن، لكنها تُستبدل بأي اسم متغير لقائمتكم.
هذه العملية تغيّر القائمة أساسًا، فتجعلها أطول بعنصر إضافي واحد. والعنصر الذي تضيفونه إلى نهاية القائمة، إلى طرفها الأيمن، هو ما يوجد داخل أقواس append.
لننظر إلى مثال. سننشئ L تساوي 2, 1, 3 في الذاكرة. ثم لنفترض أننا نفّذنا L.append(5). يقول هذا السطر: ابحث عن L، وهي هذا الكائن في الذاكرة هنا، 2, 1, 3، وأضف الكائن 5 إلى نهايته. سأضيف 5 إلى نهاية القائمة. لم يعد طولها ثلاثة عناصر، بل أربعة. ومرة أخرى لم أصنع نسخة، ولم أحافظ على القائمة الأصلية التي فيها 2 و3 فقط. غيّرت حرفيًا هذه القائمة في الذاكرة، التي تشير إليها L.
تُستخدم دالة append هذه أساسًا من أجل أثرها الجانبي (Side effect)، والأثر الجانبي هنا تغيير القائمة. بعدما تضيف الدالة 5 في هذه الحالة إلى نهاية القائمة، لا تحتاج إلى إعادة شيء. أنجزت أساسًا عملها في التغيير. لذلك دوال مثل append، وسنرى دوال أخرى لاحقًا، لا توجد لها قيمة معادة.
من الأخطاء الشائعة جدًا ونحن نتعلم الكائنات القابلة، عفوًا، للتغيير ونستخدم دوال التغيير أن نقول: سأفعل L.append(5) وأحفظ هذا، نتيجة الدالة، من جديد في المتغير المسمى L. وهذا غير صحيح. لنرَ بالضبط ما سيحدث إذا نفّذنا هذا السطر.
إنه إسناد، فأول ما نفعله النظر إلى الطرف الأيمن وتقييمه. يقول الطرف الأيمن أساسًا L.append(5)، وهو الشيء نفسه تمامًا كالسطر السابق. سنضع 5 أخرى في نهاية قائمتنا المتغيرة حاليًا. نمضي في هذه العمليات بالترتيب نوعًا ما.
ثم قلت إن هذه الدالة، append، أنجزت عملها في تغيير القائمة بإضافة 5 إلى نهايتها. فلا تعيد شيئًا. لا شيء ذا قيمة تستطيع إعادته، لأنها أنجزت بالفعل عمل التغيير. تعيد في الواقع None. فيقول الإسناد، علامة المساواة، أساسًا: خذ الاسم L واربطه بالقيمة المعادة من دالة L.append(5) هذه.
والقيمة المعادة من الدالة هي None. نفقد الآن أساسًا الارتباط بـ2, 1, 3, 5, 5، التي كانت قائمتنا المتغيرة، ونعيد الربط بالقيمة المعادة None. هذه طريقة غير صحيحة لتغيير القائمة بإضافة عنصر إلى نهايتها. هل الجميع بخير حتى الآن؟ نعم. حسنًا، ممتاز.
ماذا كان ينبغي أن نفعل بدلًا من ذلك؟ آسفة، نعم، انتبهوا إلى عملية append: تُجرون تغييرًا وتعيدون None نتيجة لذلك، فلا تريدون حفظ هذا من جديد في أي متغير. بدلًا من ذلك، ننفّذ العملية فقط. ليس هناك شيء نحفظه، ولا شيء نحفظه في أي متغير للقيمة المعادة. إذا أردتم إضافة خمسَتين إلى نهاية تلك القائمة، تقولون L.append(5) مرة أخرى فقط، وعندئذ تكون L قد تغيرت إلى 2, 1, 3, 5, 5.
صفحة المصدر 4
وفي شيفرتكم، إذا طبعتم L بين عمليتي الإلحاق، فإذا طبعتموها بعد L.append(5) الأولى، ستطبع 2, 1, 3, 5. ثم إذا طبعتم L بعد إلحاق 5 الثانية، ستطبع 2, 1, 3, 5, 5. لأنها عملية مستمرة، تغيّر هذه القائمة، وتُجرون الآن العمليات على القائمة التي تغيرت للتو. الجميع— نعم.
طالب: بالنسبة إلى العنصر، هل تستطيعين استخدام عدد صحيح واحد فقط، أم تستطيعين فعل [غير مسموع]؟
آنا بيل: بالنسبة إلى العنصر، هل يجب أن يكون عددًا صحيحًا واحدًا، أم تستطيع استخدام 5، فاصلة، 5؟ تعمل append مع شيء واحد فقط. إذا أردت إلحاق صف، تستطيع إلحاق كائن صف واحد فيه أشياء كثيرة، لكنها ستلحق ذلك الصف الواحد فقط. سنرى قرب نهاية المحاضرة عملية تتيح توسيع القائمة بمجموعة عناصر. توجد طريقة، لكن ليست باستخدام append. نعم؟
طالب: لماذا أعاد الشيء الآخر None مرة أخرى؟
آنا بيل: الشيء الآخر، هذه العملية تعيد None دائمًا. L.append(5) أو أي شيء، append تعيد None دائمًا. لكنها هنا موجودة على سطر وحدها فقط، ولا نحفظها في شيء. في السابقة أخذنا القيمة المعادة وحفظناها من جديد في L، ولهذا فقدنا الارتباط بالقائمة الفعلية.
ما نقوله عادة أننا نستخدم append ومجموعة دوال التغيير الأخرى من أجل آثارها الجانبية. والأثر الجانبي في هذه الحالة تغيير الكائن الذي أستدعي append عليه، القائمة المسماة L في هذه الحالة.
لنعطكم هذه المسألة للتفكير فيها. وأثناء ذلك— ثم نستطيع كتابتها على السبورة معًا. حين نمر بأسطر الشيفرة هذه واحدًا واحدًا، ما القيم التي ستصبح عليها القوائم؟ L1 هي السلسلة re، وL2 هي mi، وL3 هي do. ماذا ستكون L4 بالسطر L4 = L1 + L2؟ هل يعرف أحد؟ ما النوع؟ إنه ضم. الضم مع القوائم يشبه الضم مع السلاسل؟ نعم؟
طالب: [غير مسموع]
آنا بيل: نعم. ما العناصر فيها؟
طالب: [غير مسموع]
آنا بيل: نعم، بالضبط. لن أكتب السلاسل، لكنكم تعرفون ما أقصد. حسنًا، L4 بهذا السطر هي هذان العنصران فقط في قائمة جديدة. ماذا يحدث بالسطر التالي، L3.append(L4)؟ أيهما تتغير، L3 أم L4؟ تتغير L3، بالضبط. وإلى ماذا تتغير؟ L3 فيها أصلًا do. ماذا أضيف إلى نهاية L3؟
طالب: [غير مسموع]
آنا بيل: بالضبط. نعم، أضيف عنصرًا واحدًا، وهذا مرتبط بالسؤال الذي كان هنا. ماذا ألحق؟ ألحق عنصرًا واحدًا، أيًا تكن L4. وL4 هذه القائمة. سأضيف re وmi داخل قائمتي هنا، وعلي إغلاق هذه القائمة هنا. هذا عنصر واحد، كائن واحد، عنصر واحد، وهذا عنصر آخر هنا. يحدث فقط أنه قائمة.
وماذا عن السطر التالي؟ L تساوي L1.append مع 3. ماذا سيعطيني الطرف الأيمن؟ هل أغيّر L1 أم L3؟
ملاحظة المترجم: يرد وصف هذا السطر في التفريغ الرسمي بذكر
3، بينما يوضح الحوار المتصل في الصفحة التالية أن العنصر الملحق هوL3. كما تُوصف القوائم شفهيًا بأسماء سلاسلها؛ السياق هنا قوائم تضم هذه السلاسل، لا سلاسل منفردة.
صفحة المصدر 5
طالب: L1.
آنا بيل: نعم. وإلى ماذا أغيّر L1؟ L1 هي re أصلًا. وماذا أضيف إلى نهايتها؟
طالب: [غير مسموع]
آنا بيل: نعم، بالضبط. هذه L3، هذا الشيء الكبير هنا. قائمة فيها عنصران، الأول سلسلة والثاني قائمة أخرى كهذه. هذا الطرف الأيمن. ثم ماذا سيكون الطرف الأيسر؟ ماذا ستكون L؟ نعم، بالضبط. بالضبط.
حسنًا، بعدما قدمنا الكائنات القابلة للتغيير، علينا الانتباه إلى الدوال التي نستخدمها. بعضها يغيّر القائمة ولا يعيد شيئًا. append واحدة منها، وسنرى بضع دوال أخرى في محاضرة اليوم.
تُستخدم هذه الدوال من أجل أثرها الجانبي فقط. تغيّر الشيء الذي تستدعون الدالة عليه، وهذا كل شيء. لا تحتاج إلى الإعادة. لا تعيد شيئًا. لا تحتاج إلى إعادة شيء. أنجزت عملها من خلال جانب التغيير فقط.
أريد أن أستطرد سريعًا بشأن صياغة النقطة (Dot notation) هذه التي قدمناها مع دالة append. هذا شيء لم نره من قبل فعلًا، لكنه شيء سنتعلمه مستقبلًا حين ننشئ أنواع كائناتنا الخاصة. نستخدم الآن أنواع كائنات كتبها شخص آخر، كقائمة أو صف أو نحو ذلك.
لكن في حصة لاحقة سنتعلم كيفية إنشاء أنواع كائناتنا الخاصة، وعندها سنستخدم صياغة النقطة كثيرًا. حاليًا عليكم أساسًا تذكر أي الدوال تستخدم صياغة النقطة وأيها لا تستخدمها. لكن سأعطيكم قليلًا من الحدس عن معناها الفعلي.
حين تكون لدينا— كل شيء في Python كائن. وحين تكون لدينا كائنات في Python، فالفكرة أن الكائنات التي لديكم ترتبط بها بيانات، ما يكوّن الكائن، ولها سلوكيات معينة. تناولنا هذا ربما في المحاضرة الأولى حين قلنا إن ما تستطيعون فعله بالأعداد الصحيحة مختلف عما تستطيعون فعله بالسلاسل. هذا واضح جدًا، ومختلف عما تستطيعون فعله بالقوائم.
أنواع الأشياء التي تستطيعون فعلها بكل نوع كائن تختلف بحسب النوع. وفي الجوهر يمكن حقًا كتابة كل شيء بصياغة النقطة هذه. لكن بعض العمليات الأكثر شيوعًا، كالحصول على طول شيء أو جمع عددين، هي في الواقع— نجريها بصياغة مختصرة كهذه، باستخدام عامل الجمع أو len. لكن في جوهرها نستطيع حقًا أخذ كل تلك العمليات وتحويلها إلى صياغة نقطة. لا نفعل هذا اليوم، لكنه ما نستطيع فعله.
حين نرى صياغة النقطة، نقرأها عادة بالقول: ما الذي إلى يسار النقطة؟ سيكون كائننا، الشيء الذي نريد إجراء عملية عليه. في هذه الحالة قائمة اسمها L، لكن يمكن أن تكون قائمة اسمها employees أو words أو أي شيء، book أو أي شيء، أيًا يكن اسم القائمة. ثم تأتي النقطة لصياغة النقطة، والشيء في الطرف الأيمن هو العملية التي تريد إجراءها على الكائن إلى يسار النقطة.
صفحة المصدر 6
العملية، إذا غطيتم أساسًا L.، تبدو مثل دالة تمامًا: append وأقواس وبعض المعاملات. فالعملية أساسًا دالة تريدون تشغيلها على كائن من نوع قائمة، هذا الكائن المحدد المسمى L. وترون أن لها اسمًا، append، ولها معاملات أو وسائط، وهي في هذه الحالة الشيء الذي تريدون إضافته إلى نهاية القائمة.
مرة أخرى، للأسف في هذه المرحلة من المقرر عليكم تذكر أي الدوال تستخدم صياغة النقطة وأيها لا يستخدمها. لكن سيتضح معنى صياغة النقطة الفعلي قرب نهاية المقرر.
لنعطكم هذه الشيفرة الصغيرة للعمل عليها. ستستخدم append بالطبع، وستطلب منكم إنشاء قائمة. اسم الدالة التي ينبغي أن تصنعوها هنا هو make_ordered_list. تأخذ معاملًا واحدًا، عددًا صحيحًا n موجبًا. وأريد منكم إنشاء قائمة فيها كل الأعداد الصحيحة من 0 حتى n مع تضمينها، داخل تلك القائمة بالترتيب.
مثلًا، هنا بالأسفل قرب 34: إذا استدعينا make_ordered_list مع 6، ستنشئ لنا هذه القائمة داخل الدالة وتعيد القائمة. لديكم دقيقتان للعمل على ذلك، ثم نستطيع كتابتها معًا. حسنًا، ما أول ما ينبغي فعله هنا؟ أو كيف ستقاربون المسألة؟ نعم. تريد إنشاء قائمة فارغة. ماذا تريد تسميتها؟ سميتها list. list اسم مقبول، لكن لاحظ أن list أيضًا اسم نوع الكائن. سأتجنب تسمية أي شيء بأسماء يتغير لونها. نستطيع استخدام L أو my_list أو أي شيء، شيء آخر. my_list قائمة فارغة. حسنًا، هي فارغة أصلًا، والآن نحتاج إلى ملئها ببعض الأشياء. هل تريد المتابعة؟
طالب: إنشاء حلقة for.
آنا بيل: إنشاء حلقة for. تمر على ماذا؟
طالب: [غير مسموع]
آنا بيل: نعم، من 0 إلى n + 1، بالضبط، لأننا نحتاج إلى الحد حتى n مع تضمينها. ممتاز. الآن لدي i تتغير لتكون 0 ثم 1 ثم 2 ثم 3، ماذا أحتاج إلى فعله بقائمتي؟ نعم، بالضبط، إلحاق i. فـmy_list اسم القائمة التي أنشأتها، نقطة، append مع i. آخر شيء إعادة القائمة: return my_list. نشغّلها. ممتاز. إذا غيّرنا هذه إلى 2، فما زالت تعمل. نختبرها فقط بمدخلين مختلفين للتأكد من عملها. هل من أسئلة عن الشيفرة؟ نعم.
طالب: [غير مسموع]
آنا بيل: الـ0 ليست ضرورية في range. تبدأ افتراضيًا من 0، بالضبط. لم ننته بعد، لديكم مزيد من الكتابة. لنكتب دالة مختلفة قليلًا الآن تسمى remove_elem. تأخذ معاملين: الأول قائمة، والثاني متغير ما فقط. يمكن أن يكون عددًا صحيحًا أو سلسلة أو أي شيء. وما ينبغي أن تفعله الدالة هو إنشاء قائمة جديدة، وملؤها بعناصر L نفسها بالترتيب نفسه، لكن مع استبعاد ما يساوي e. لا تريدون تضمين العناصر التي تساوي e. بخلاف ذلك أبقوا كل ما في القائمة الأصلية L بالترتيب نفسه.
كمثال هنا، إذا كانت قائمة الإدخال 1, 2, 2, 2, 2 واستدعيت الدالة مع L و2، فينبغي ألا تحتوي القائمة التي تعيدها الدالة إلا عنصرًا واحدًا، الـ1 فقط. جرّبوا شيفرتكم في الدقيقتين التاليتين قرب السطر 50، ثم نستطيع كتابتها معًا. حسنًا، كيف نبدأ؟ نعم.
صفحة المصدر 7
طالب: أنشأت قائمة جديدة.
آنا بيل: نعم. ماذا سميتها؟
طالب: [غير مسموع]
آنا بيل: جميل. ماذا جعلتها— قائمة فارغة. حسنًا.
طالب: ثم قلت [غير مسموع].
آنا بيل: نعم، إذن for i in L. عند هذه النقطة سأكتب ملاحظة لنفسي، لأنك استخدمت i، وهي تعني الفهرس في ذهني. لكن سأدوّن أن i ربما 1 ثم 2 ثم 2 ثم 2، وفقًا لهذا المثال الأول. إذا قرأت الشيفرة فسـ— سأتذكر أنها ليست الفهرس، لكن تابع. إذن for i in L مباشرة.
طالب: [غير مسموع]
آنا بيل: هكذا؟ حسنًا.
طالب: [غير مسموع]
آنا بيل: نعم. حسنًا، هذا جيد. أعد قائمتك. لنجرّبها.
طالب: [غير مسموع]
آنا بيل: نعم، e ليست قائمة. ستكون e عنصرًا. هذا خطئي، كان ينبغي أن أضع هذا هنا: e كائن أو شيء. يمكن أن تكون قائمة، لكن عندها سأبحث عن تلك القائمة الفرعية نفسها بالضبط، تلك القائمة نفسها كعنصر فرعي. فربما نفكر في e ككائن مثل 5 أو نحو ذلك.
طالب: [غير مسموع]
آنا بيل: نعم، لا يساوي e. إذا كنت أبحث عن ذلك العنصر مباشرة فقط، أريد ألا تساوي i القيمة e، وفي هذه الحالة أحتفظ بالعنصر في قائمتي الجديدة. إذا شغّلنا ذلك، يعطيني 1 وفقًا لهذا. يبدو صحيحًا. ثم نستطيع تشغيلها بهاتين الحالتين الأخريين. هنا أحذف العنصر 1، فسأبقي 2, 2, 2 كقائمتي المعادة. وهنا أحذف 0، التي لا توجد في قائمتي إطلاقًا. فينبغي أن يُبقي فقط، وهو يفعل، قائمتي الأصلية دون تغيير. هل من أسئلة عن المثال؟ هل جرّبه أحد بطريقة أخرى؟ حسنًا.
حسنًا، عمليات أخرى مفيدة على القوائم. نستطيع تحويل السلاسل إلى قوائم، ثم القوائم من جديد إلى سلاسل. وهذا مفيد جدًا حين تقرؤون نصًا أو نحو ذلك إلى دالة. سيكون مفيدًا لمجموعة المسائل الثالثة، وهكذا. لنرَ أولًا كيف نأخذ سلسلة s ونحوّلها إلى قائمة.
إذا حوّلنا نوع (Casting) s إلى قائمة فقط، كما كنا نحوّل العدد 5 إلى عدد فاصلة عائمة بقول float(5)، نستطيع أخذ قائمة وتحويلها إلى قائمة بقول list(s). وإذا حوّلناها هكذا، تأخذ Python كل محرف منفرد في s وتجعله عنصرًا منفصلًا في قائمة. ترون هنا أن لدي السلسلة «أنا أحب علوم الحاسوب وأنت»، الممثلة بـI<3 cs &u؛ تصنع لي قائمة يصبح فيها كل محرف، بما فيه المسافة وكل المحارف الخاصة، مدخلًا منفصلًا في قائمتي.
ملاحظة المترجم: عبارة «أخذ قائمة» منقولة كما وردت شفهيًا؛ المدخل
sفي هذا المثال سلسلة نصية. وتمثيل المثالI<3 cs &uيوضح معنى «I heart cs &u» المنطوق، وتتناول الفقرة التالية التقسيم عند<.
صفحة المصدر 8
هذا ليس مفيدًا جدًا. أعني أنه قد يكون، لكنه ليس مفيدًا جدًا. الأكثر فائدة أخذ سلسلة إدخال وتقسيمها (Split) عند محرف معين. من المحارف الشائعة جدًا التي نقسم عندها المسافة. إذا فعلنا شيئًا كهذا، فإنه يستخرج لنا أساسًا من سلسلتنا كل الكلمات المفردة، وهو مفيد جدًا. هنا لدي s.split، وداخل الأقواس المحرف الذي أريد التقسيم عنده، وهو مسافة في هذه الحالة.
إذا أخذت s وقسمتها عند المسافة، ستمضي Python من بداية القائمة إلى المسافة الأولى، وتجعل ذلك عنصرًا واحدًا في القائمة. ثم تمضي من المسافة الأولى إلى التالية في سلسلتي، وتجعل ذلك العنصر التالي في القائمة، وهكذا وهكذا، حتى تصل إلى نهاية القائمة وتجعل الجزء الأخير العنصر الأخير في قائمتي. هنا، بعدما قسمت عند المسافة، لدي ثلاث كلمات أساسية، «كلمات» بين علامتي اقتباس. I<3 واحدة، وها هي كمدخلي الأول. وcs بين هاتين المسافتين، وهي مدخلي التالي. و&u؟ مدخلي الأخير هنا.
هذه دالة مفيدة جدًا. نستطيع بالطبع التقسيم عند أي محرف نريده. هنا أقسم عند محرف أصغر من <. يوجد واحد فقط. إذا قسمت عنده، يكون أحد عناصر القائمة الناتجة I الكبيرة فقط. والعنصر المتبقي في القائمة الناتجة هو 3 cs &u. وها هو هناك.
حسنًا، بعد حصولنا على قائمة، نستطيع العودة أيضًا. نأخذ القائمة ونحوّلها من جديد إلى سلاسل. نستخدم دالة join هذه. والشيء قبل النقطة هو المحرف الذي تريدون وصل عناصر القائمة به. وهذه القائمة التي تريدون وصلها من جديد لتصبح سلسلة. لننظر إلى مثال.
لنفترض أن لدي القائمة L وفيها ثلاثة مداخل، A وB وC. إذا وصلت باستخدام سلسلة فارغة، فهنا علامتا اقتباس متجاورتان، لا توجد مسافة أو شيء داخلهما، سيأخذ ذلك كل العناصر في L ويوصلها كشيء واحد، دون شيء بين A وB وC. وهذه العملية ستصنع لي أساسًا A وB، السلسلة ABC.
إذا وصلت باستخدام شرطة سفلية، فربما خمنتم أنه سيوصل A وB وC بشرطة سفلية بين كل محرف. وها هي A_B_C. يمكنكم الوصل بأي محرف تريدونه. لا أعرف هل يمكن الوصل باستخدام محارف متعددة، لكن لا أرى سببًا يمنع ذلك. يمكنكم تجربة هذا بأنفسكم.
تعمل join فقط مع قوائم لا تحتوي إلا عناصر سلاسل نصية. إذا حاولنا وصل قائمة فيها أعداد صحيحة أو أعداد فاصلة عائمة أو قيم منطقية، أي شيء لا يحتوي سلسلة، فستحصلون على خطأ، لأنها تحاول أساسًا وضع هذه كلها في سلسلة كبيرة. إذا أردتم وصل عناصر ليست سلاسل، فستحتاجون أساسًا إلى التكرار وتحويل كل منها إلى سلسلة أولًا، ثم وصلها معًا. إذا أردتم وصل 1, 2, 3، فعليكم تحويلها إلى سلاسل، ثم يمكن وصلها لتصنع السلسلة 123.
حسنًا، لنعطكم هذا المثال للعمل عليه. سنحاول تقسيم المدخل. هذه دالة اسمها count_words، تأخذ مدخلًا واحدًا sen اختصارًا للجملة (Sentence). أردت استخدام شيء غير s لتوضيح أن الشيء قبل النقطة ليس دائمًا s. إنه أي كائن تريدون تقسيمه أو وصله أو أي شيء. ستعيد هذه الدالة عدد الكلمات في s.
«كلمات» بين علامتي اقتباس في هذه الحالة، لأنني مهتمة فقط بالعناصر أو المحارف بين المسافات وبين بداية الكلمة ونهايتها. فإذا كان عددًا سأعدّه كلمة أيضًا. وإذا كان محرفًا خاصًا، نقطة أو علامة تعجب، فسأعدّه كلمة كذلك. ينبغي أن يكون هذا سطرين فقط قرب 99. سأعطيكم نحو دقيقة للعمل عليه، ثم نستطيع كتابته معًا.
صفحة المصدر 9
ما أفكاركم عن كيفية فعل هذا؟ L1 = sen.split. نعم. آسفة، أقواس، مسافة. نعم.
طالب: [غير مسموع]
آنا بيل: نعم، ثم نستطيع إعادة طول L1. ممتاز. لنشغّلها على هذين المثالين. ينبغي أن تطبع 3 و12، وهي تفعل. لاحظوا كم كان هذا سهلًا مع القوائم، لأنها بنية بيانات (Data structure) تكرارية بطبيعتها نوعًا ما. تشغيل len على هذه القائمة المقسمة أو السلسلة المقسمة التي أعطتنا قائمة سهل جدًا. قطعة شيفرة من سطرين.
من دون القوائم، تخيلوا إنشاء متغيرات تتتبع موضع المسافة الأولى، ثم التكرار محرفًا محرفًا. وإذا كان مسافة، تتبع حقيقة أنك رأيت مسافة، ثم البحث عن التالية، ثم إعادة ضبط الأشياء كلما رأيت مسافة. سيكون ذلك مرهقًا جدًا جدًا. سيكون سؤالًا جيدًا جدًا للاختبار القصير الأول، لكن ليس بعدما قدمنا القوائم، لأنه يصبح سهلًا جدًا جدًا بها.
حسنًا، بعدما لدينا قوائم، نستطيع إجراء عمليات أخرى مثيرة للاهتمام ومفيدة جدًا لتغيير القائمة. رأينا صياغة النقطة على قائمة لإجراء append، لإضافة عنصر إلى نهايتها أساسًا، وكان ذلك مفيدًا. من الأشياء الأخرى التي نستطيع فعلها لتغيير القائمة فرزها (Sort) وعكسها (Reverse). وهاتان عمليتان مفيدتان جدًا أيضًا. الأوليان هنا، sort وreverse، صياغة كيفية فرز القائمة وعكسها. وستغيّران القائمة التي تستدعون الدالتين عليها.
إذا كانت لدي القائمة 4, 2, 7 هنا واستدعيت L.sort()، وطبعت L في السطر التالي، ستكون L قد تغيرت في الذاكرة إلى 2, 4, 7 بهذا الترتيب. لم تصنع نسخة لي ولم تحافظ على الترتيب الأصلي، بل غيّرت القائمة لتكون الآن مرتبة.
وبالمثل reverse: إذا نفّذنا L.reverse() على 4, 2, 7، ستعكس كل العناصر. الذي في النهاية يصبح في البداية، وقبل الأخير يصبح الثاني، والثالث من النهاية يصبح العنصر الثالث، وهكذا. ومرة أخرى يغيّر هذا قائمتي، فأكون قد فقدت ترتيبي الأصلي بأمر L.reverse() وبـL.sort() بالطبع.
توجد حالات كثيرة تريدون فيها الحفاظ على الترتيب الأصلي. لا أعرف، ربما ترتيب انضمام الناس إلى شركة، أو دخولهم طابور متجر بقالة، أشياء كهذه. قد تريدون الحفاظ على الترتيب الأصلي، لكن ربما أيضًا الحصول على أسماء الأشخاص مرتبة لدالة تفعل شيئًا بتلك الأسماء المرتبة.
في تلك الحالة لا تريدون استدعاء sort على القائمة الأصلية لأنكم ستفقدون الترتيب الأصلي. تستطيعون بالطبع صنع نسخة، أو استدعاء دالة sorted هذه. ستُبقي sorted قائمتي الأصلية L سليمة بالترتيب نفسه الذي أنشأتها به، لكنها ستعيد لي، فهذه الدالة ستصنع نسخة بالفعل وتعيد النسخة المرتبة من L. وتبقى L دون تغيير. لا تُجري هذه الدالة تغييرًا، وعلينا أخذ القيمة المعادة وحفظها في متغير جديد، سميته L_new في هذه الحالة.
قد يصعب قليلًا إبقاء الأمر واضحًا في أذهانكم، هل تستخدمون sort أم sorted. تستطيعون بالطبع تجربته دائمًا في وحدة التحكم لمعرفة أيهما تفعل ماذا. الطريقة التي أتذكر بها وأفكر أن sort تبدو لي أمرًا: افرز هذه القائمة، غيّرها وافرزها. أما sorted فأشبه بطلب: هل يمكنك إعطائي النسخة المرتبة من L من فضلك؟ هكذا أحتفظ بالأمر في ذهني، هل أستدعي sort لإجراء التغيير أم أطلب الحصول على النسخة المرتبة من القائمة. نعم؟
صفحة المصدر 10
طالب: وفق ماذا يفرزها؟
آنا بيل: وفق الفرز المدمج لتلك الأنواع المحددة من الكائنات. مع الأعداد الصحيحة هو ترتيب تصاعدي فقط، ومع السلاسل سيكون أبجديًا. تستطيع اختيار دوال فرز مختلفة، لكننا لا ندخل في ذلك. نعم.
طالب: [غير مسموع]
آنا بيل: سؤال جيد. أظن أنها يجب أن تكون كذلك كي يعمل. نستطيع تجربة L تساوي 1، ثم إعطاءها صفًا أو شيء. ثم نطلب فرز L. نعم. في هذه الحالة لا يعرف كيف يحسم الأمر. يحاول إجراء مقارنة «أصغر من» خلف الكواليس ليحدد أيهما أكبر من الآخر.
وفي هذه الحالة لا يعرف كيف يحسمها. كيف تختار هل الصف أكبر من عدد صحيح؟ لكن تخيلوا، مرة أخرى كما ذكرت، هذا ليس شيئًا نفعله، إلا أنه يمكنكم كتابة دالة فرز خاصة بكم تقررون فيها أيهما أكبر بحسب النوع. نعم. سؤال؟
طالب: [غير مسموع]
آنا بيل: ستكتب L.sort فقط دون أقواس، لكن يجب أن تكون L قائمة تحتوي أشياء لا يمكن فرزها. كلها أعداد صحيحة، أو كلها سلاسل، أو نحو ذلك.
ملاحظة المترجم: وردت «دون أقواس» و«لا يمكن فرزها» هكذا في التفريغ الرسمي. تنفيذ الفرز يتطلب
L.sort()، وعناصر يمكن مقارنتها؛ وتؤكد المحاضِرة ضرورة الأقواس في الصفحة التالية. يُحفظ الكلام المنقول ولا يُقدَّم بوصفه شيفرة صحيحة.
لننظر إلى مخطط الذاكرة لهذا، لتأكيد الفكرة عن الكائنات التي تُغيّر. L الأصلية هي 9, 6, 0, 3. في الذاكرة لدي الاسم L مرتبطًا بـ9, 6, 0, 3. مرة أخرى، لنجرِ إلحاقًا للتسلية فقط. ستضيف L.append(5) العدد 5 إلى نهاية القائمة. وستُستخدم append وsort وreverse كلها من أجل أثر جانبي. يعني أنها ستغيّر الكائن، أما sorted فلن تُجري تغييرًا.
لنجرِ إلحاقًا إلى النهاية. سيضع ذلك 5 في نهاية القائمة، وهو شيء نعرف بالفعل كيف يعمل. الآن لننفّذ A = sorted(L). مرة أخرى، إنها مساواة. الشيء في الطرف الأيمن هو الدالة التي تعيد لي النسخة المرتبة من L، فستنشىء كائنًا جديدًا.
أيًا تكن طريقة فرزها، ستنشئ لي قائمة جديدة تحوي ذلك الترتيب المرتب. لاحظوا أن L الأصلية في الذاكرة تبقى دون تغيير. إذا أردت الإشارة إلى L في برنامجي من الآن فصاعدًا، سيستخدم هذه L غير المتغيرة. والآن القيمة المعادة من sorted هذه القائمة، وأربطها بـA. صار الاسم A يشير إلى النسخة المرتبة من القائمة.
حسنًا، ماذا إذا نفّذت هذا السطر، B = L.sort()؟ مرة أخرى، ننظر إلى الطرف الأيمن. ستغيّر L.sort() القائمة L. ستذهب الدالة نفسها وتغيّر L لتكون— كائن L، الكائن الذي تشير إليه L، ليكون القائمة المرتبة. لكنها لم تنته. تُستخدم الدالة من أجل أثر جانبي. فما القيمة المعادة؟ None، صحيح؟ تشبه append. سيجعل هذا المثال B تشير إلى القيمة المعادة من تلك الدالة، وهي None فقط.
من فضلكم لا تفعلوا هذا أبدًا. كل ما يلزمكم لفرز L هو قول L.sort() في سطر وحدها. فعلت هذا فقط لأريكم مجددًا أنه إذا نفّذتم L = L.sort() ستحدث أشياء سيئة، ستعيدون إسناد L لتصبح None. حفظتها في هذه الحالة باسم متغير مختلف، لكنه خطأ سهل الوقوع.
صفحة المصدر 11
وماذا عن الأخيرة هنا، L.reverse()؟ مرة أخرى سأذهب وآخذ الكائن الذي تشير إليه L وأعكس كل العناصر. إجراء L.sort() ثم L.reverse() مباشرة بعدها يجعل قائمتي مرتبة ترتيبًا عكسيًا، من أكبر عدد إلى أصغر عدد. بذلك الأمر لدي 9, 6, 5, 3, 0 بدلًا من 0, 3, 5, 6, 9. ومرة أخرى غيّرت sort وreverse قائمتي L مباشرة، ففقدت الترتيب الأولي 9, 6, 0, 3 الذي كان لدي هنا في الأعلى.
نقطة أخيرة أريد ذكرها. أعرف أننا رأينا عادة دوال تأخذ معاملات. ما زالت sort وreverse دالتين، ويحدث فقط أنهما لا تحتاجان إلى معاملات. تستدعونهما على الكائن L باستخدام صياغة النقطة. فكأن لهما فعليًا «معاملًا»، بين علامتي اقتباس، الشيء قبل النقطة، لكنهما لا تأخذان شيئًا آخر داخل أقواسهما الخاصة. ومع ذلك لا بد من وجود الأقواس لأنهما دالتان، عمليتان ستفعلان شيئًا لنا. هل من أسئلة؟ هل الأمر جيد؟
حسنًا، جيد جدًا، لأنكم الآن تحصلون على فرصة لتجربته. لنعطكم شيئًا مشابهًا لما فعلناه في المرة الماضية. خذوا معاملًا sen، سلسلة تمثل جملة. أريد تحديد كل الكلمات، بين علامتي اقتباس، بالطريقة نفسها التي استخدمناها من قبل في المثال السابق، لكن أعيدوا لي الآن قائمة بهذه الكلمات مرتبة. إذا كان المدخل look at this photograph كجملتي، فسأعيد قائمة فيها at وlook وphotograph وthis كعناصري الأربعة بهذا الترتيب. ابدأوا الكتابة هنا في السطر 134.
حسنًا، ما الحل؟ ماذا لديكم حتى الآن؟ نعم.
طالب: L تساوي sen.split مع الـ[غير مسموع].
آنا بيل: L تساوي sen.split ونقسم عند مسافة. فهمت. حسنًا.
طالب: ثم L.sort أقواس دون شيء حولها.
آنا بيل: فهمت. return L. ممتاز. لنرَ هل عمل ذلك مع مثالينا. نعم، هذا الأول، وهذا الثاني. هل فعلها أحد بطريقة أخرى؟ هل استخدم أحد sorted؟ نعم.
طالب: قلت فقط return sorted أقواس L.
آنا بيل: return sorted أقواس L. نعم. هل هكذا أنت—
طالب: [غير مسموع]
آنا بيل: نعم، نستطيع فعلها كلها في واحدة. ممتاز. نعم، يمكن بالتأكيد أن تكون سطرًا واحدًا. تعمل هذه لأن هذا الشيء هنا ينشئ لي كائنًا جديدًا. كان يمكنني حفظه في متغير مختلف ثم إعادة ذلك المتغير، لكنها تفعل كل شيء مرة واحدة. وللاستكمال فقط، إذا حوّلنا الحل الآخر إلى تعليق، ما زالت هذه الطريقة تعمل. هل من أسئلة حتى الآن؟ حسنًا.
ما رأيناه حتى الآن مجموعة من هذه الدوال، الدوال المدمجة (Built-in functions)، التي لها آثار جانبية. تغيّر قائمة الإدخال. نستطيع بالفعل كتابة دوالنا الخاصة ذات أثر جانبي، فإذا مررنا معاملًا هو قائمة، نجعل دوالنا تغيّر تلك القائمة بأي طريقة نريدها.
صفحة المصدر 12
لنمرّ على هذا المثال. لنفترض أن لدينا مهمة كتابة دالة تأخذ قائمة إدخال L وتغيّر القائمة L بحيث يتحول كل عنصر فيها إلى مربع ذلك العنصر. فالقائمة 2, 3, 4 عند الإدخال تصبح 4, 9, 16. وأنا أغيّر تلك القائمة. لا أنشئ قائمة جديدة وأعيدها. أريد فعلًا تغيير قائمة الإدخال L نفسها.
فإذا واجهتنا هذه المهمة، فالطريقة التي سنتناولها بها، ربما بناءً على ما تعلمناه حتى الآن، هي أن نقول: سأمرّ على كل عنصر في L لأن هذه طريقة بايثونية (Pythonic) جدًا لفعل ذلك. فأنا آخذ العنصر من القائمة L. لكنني سأعلق حينها. لأن الصيغة لتغيير عنصر في موضع معين هي L[i] = أيًّا كان الشيء المُغيَّر. لكن متغير الحلقة (Loop variable) يمرّ على العنصر مباشرة. فما الفهرس (Index) في هذه الحالة؟ ليس بين يدي. لدي العنصر، لكن ليس لدي الفهرس.
فما الحلول؟ حسنًا، أول حل ممكن هو أن ننشئ قبل الحلقة مباشرة متغيرًا جديدًا يتتبع الفهرس. فتجعل i = 0 قبل حلقة for مباشرة. وداخل حلقة for تزيد i في كل مرة. وهكذا صرت تتتبع الفهرس بنفسك.
الخيار الثاني هو تغيير ما نمرّ عليه. فبدل المرور على كل عنصر في L مباشرة، لنمرّ على الفهرس. أي نمرّ على range(len(L)). في هذه الحالة يصبح range(len(L)) في الأساس range(5) أو range(20) أو أيًّا كان طول قائمتي.
والخيار الأخير هو محاولة استخدام ما يسمى enumerate، وهو كلمة محجوزة في Python، أظن أنه دالة في Python. والصيغة لذلك هي for i, e in enumerate(L) أي مجموعة مرتبة (Tuple) i, e. فأنا في الأساس أغلّف L داخل دالة enumerate. وPython في كل مرة عبر الحلقة تجعل هذه المجموعة المرتبة الصغيرة i, e هي الفهرس والعنصر عند كل موضع في كل مرة عبر الحلقة. فتعطيك صفقة «اثنان بسعر واحد» باستخدام دالة enumerate هذه.
لن أشرح الخيار الأول ولا الخيار الثالث. أشجعكم على البحث عنهما أو محاولة تنفيذهما بأنفسكم. لكنني سأشرح الخيار الثاني في هذه الشرائح. فإذا مررت على الفهرس مباشرة، فالطريقة أن أغيّر متغير الحلقة: for i in. والشيء الذي أريد المرور عليه هو كل الفهارس. أريد الحصول على الأعداد 0, 1, 2, 3, 4 وصولًا إلى طول L دون تضمينه.
فبمجرد أن يكون هذا الفهرس بين يدي، أستطيع فعل شيء كهذا بسهولة تامة، لأن هذا i هنا سيكون فهرسي. والشيء في الطرف الأيمن مجرد مسألة أخذ العنصر عند ذلك الفهرس وتربيعه. فهنا L[i] في الطرف الأيمن تأخذ لي العنصر عند الفهرس i.
فما قيمة ذلك العنصر في ذلك الموضع تحديدًا؟ 23 أيًّا كانت. ربّعها. فـ**2 تربّعها. ثم الشيء في الطرف الأيسر هو صيغة تغيير عنصر في موضع معين. رأينا هذا منذ زمن في الشريحة الثانية. فبهذا السطر من الشيفرة، تمرّ Python على كل عنصر في القائمة وتربّعه وتحفظه مرة أخرى في القائمة نفسها. لا تُنشأ قائمة جديدة. إنه يغيّر القائمة الأصلية. لا يوجد return. لا شيء يُعاد. ستعيد هذه الدالة None لأنها تؤدي مهمتها وهي التغيير.
فإذا مررنا على مثال، لنفترض أن L هي 2, 3, 4. ماذا ستفعل هذه الحلقة؟ ستكون i في المرة الأولى عبر الحلقة 0. وفي تلك المرة الأولى عبر الحلقة ستغيّر القائمة عند ذلك الفهرس، فتقول L[0] = أيًّا كان العنصر عند 0، أي 2 تربيعًا. فسيتغير L[0] إلى 4. إذن غيّرنا العنصر عند الفهرس 0 ليصبح 4، وكل شيء آخر كما هو.
صفحة المصدر 13
في المرة التالية عبر الحلقة، أغيّر القائمة التي غيّرتها للتو. فالعنصر الأول ما زال القيمة المُغيَّرة 4. لكنني الآن سأغيّر عنصري عند الفهرس 1 ليصبح 3 تربيعًا أي 9. وفي المرة الأخيرة عبر الحلقة، تكون كل العناصر حتى الفهرس 2 عناصر مُغيَّرة. أي 4 و9. ثم في المرة الأخيرة عبر الحلقة بتغيير 4 ليصبح 16، أي النسخة المربّعة منها.
وللتحقق من أننا أجرينا التغيير على الوجه الصحيح، ما سنفعله هو أن ننشئ قائمة إدخال. سميتها L_in. وضبطتها على 2, 3, 4، مثالي. وإذا طبعت قيمة L_in قبل استدعاء الدالة، فهي 2 فاصلة 3 فاصلة 4، كما هو متوقع. لا ينبغي أن تكون شيئًا مختلفًا عن ذلك. ثم أستدعي الدالة التي كتبناها للتو. لاحظوا أنني لا أعيد شيئًا هنا. فلا أحفظ استدعاء الدالة في أي متغير.
وإذا طبعت L_in بعد استدعاء الدالة، فستطبع القائمة المُغيَّرة. فهذا L_in هنا وهنا وهنا هو الكائن نفسه. لم يُعَد شيء. هذه الدالة هنا ليس لديها ما تسند إليه قيمة إعادتها. ولو أسندناها إلى شيء ما، لكان ذلك المتغير None. تمامًا مثل append ومثل sort ومثل reverse. حسنًا. فعندما تكتبون— آه، نعم، سؤال.
طالب: إذن أنشأنا دالة، لكن ليس فيها return ولا تقول None.
آنا بيل: أنشأنا دالة. ليس فيها return. ولا تقول None لأننا لم نحفظ استدعاء الدالة في أي متغير. لو قلنا A = استدعاء هذه الدالة، ثم طبعنا A، فسيظهر None. نعم.
حسنًا، فعندما نكتب دوالًا تغيّر قوائم الإدخال، فالأمران المرجح أن تحتاجوا إلى فعلهما، ويعتمد ذلك على ما تفعله دالتكم فعلًا، لكن على الأرجح ستحتاجون إلى المرور على طول القائمة. أي for i in range(len(L)) لأخذ الفهرس وكذلك العنصر. لتتمكنوا من أخذ الفهرس وكذلك العنصر. وهذه الدوال، أعني، قد تفعل أشياء أخرى. لكن إن استخدمتموها للتغيير ولأمور من هذا القبيل، فسوف تعيد None. فعندما تستدعونها، فعلى الأرجح ستكون تلك الاستدعاءات في سطر وحدها دون حفظ القيمة المعادة في أي متغير.
تحدثنا إذن عن الكائنات القابلة للتغيير (Mutable objects). وهي مفيدة جدًا جدًا. الأماكن التي تفيد فيها، أو سبب فائدتها، هو أنها تتيح لكم في الأساس قواعد بيانات كبيرة من الكائنات، مثل الموظفين في شركة، أو قائمة بكل الطلاب في MIT، وأشياء من هذا القبيل. وإذا أردتم إجراء تغيير على شيء في تلك القائمة، كأن يغيّر طالب اسمه أو عنوانه أو شيئًا من هذا القبيل، ففي حالة المجموعات المرتبة (Tuples) ستضطرون إلى عمل نسخة جديدة من تلك القائمة بأكملها. فقد يكون ذلك غير كفء في المساحة (Space inefficient)، لأنه في كل مرة يغيّر فيها طالب عنوانه أو اسمه أو شيئًا عن نفسه أو درجاته أو شيئًا كهذا، تصنعون نسخة جديدة من بنية بيانات قد يبلغ طولها آلافًا.
القوائم ليس لديها هذه المشكلة. مع القوائم، تغيّرون الكائن في مكانه وانتهى الأمر. لا تُصنع نسخ إضافية، فهي بنية بيانات كفؤة جدًا. لكن مع القوائم تأتي بعض التحديات غير المتوقعة. وسنمرّ اليوم على ثلاثة أمثلة مخادعة. وفي المحاضرة القادمة سنرى المثال المخادع رقم أربعة. وهذه الأمثلة الثلاثة المخادعة تتضمن المرور على القائمة بطريقة أو بأخرى. على مدى طول القائمة، أو عبر القائمة مباشرة.
فلننظر إلى المثال الأول. في هذه الشيفرة هنا في الأسفل، سنمرّ على range(len(L)). ثم ما سنفعله هو أن نضيف متغير الحلقة i إلى نهاية قائمتي. والآن، ماذا يفعل range(len(L))؟ تذكروا أن ما تمرّ عليه حلقة for لدينا هو متتالية من القيم (Sequence).
صفحة المصدر 14
الآن، range مع عدد ما تنشئ لنا شيئًا يشبه المجموعة المرتبة. ليست مجموعة مرتبة تحديدًا، لكن يمكنكم التفكير فيها كأنها مجموعة مرتبة. فـrange(4)، وهو طول هذه القائمة بعينها، ينشئ لنا في الذاكرة شيئًا يشبه المجموعة المرتبة. المتتالية 0, 1, 2, 3. وهذه هي المتتالية التي سيمرّ عليها متغير الحلقة i. أولًا ستكون 0، ثم 1، ثم 2، ثم 3.
فعندما نمرّ عبر المتتالية، تقول Python: حسنًا، في أول مرة صادفت فيها حلقة for هذه، سأحفظ هذه المتتالية التي أحتاج إلى المرور عليها ككائن في الذاكرة. ثم سأجعل متغير الحلقة يمرّ على كل عنصر من هذه العناصر. والشيء الذي أفعله هو إضافة i إلى نهاية قائمة. ففي أول مرة عبر القائمة سأضيف 0 إلى النهاية. أي أن 0 هو متغير الحلقة هنا. وفي المرة التالية عبر القائمة، أضيف 1 إلى القائمة التي غيّرتها للتو. وفي المرة التالية، أضيف 2 إلى القائمة التي غيّرتها للتو. وفي المرة الأخيرة عبر القائمة، أضيف 3 إلى القائمة التي غيّرتها للتو. وننتهي. مررنا أربع مرات. أضفنا أربعة عناصر إلى نهاية القائمة. 0 و1 و2 و3، عناصر متتاليتي التي أمرّ عليها.
لننظر إلى مخطط الذاكرة. في الأصل L هي 1, 2, 3, 4. ماذا يحدث بالضبط عندما نصادف range(len(L)) أول مرة؟ يوضع ذلك كمتغير، هذا الشيء الشبيه بالمجموعة المرتبة. جعلته مجموعة مرتبة، لكنه ليس تمامًا مجموعة مرتبة في الذاكرة. وهذا i سيمرّ على كل واحدة من هذه القيم في متتاليتي. هذه هي متتالية القيم التي سأمرّ عليها.
فعند أول مرة عبر الحلقة، تجعل Python i تشير إلى 0 هنا. فماذا تفعل داخل الحلقة؟ ستضيف 0 إلى نهاية L. وفي المرة التالية، ثم ستطبع L. عذرًا. ثم في المرة التالية عبر الحلقة، يزداد متغير الحلقة بمقدار 1. فقد نظرنا بالفعل إلى 0. الآن سنفعل 1. فمتغير الحلقة i هو الآن 1. فسنضيف متغير الحلقة 1 إلى نهاية L. ونطبع L. يصبح متغير الحلقة 2. نضيف متغير الحلقة إلى نهاية L. فصار فيها الآن 2. نطبع L. ثم في المرة الأخيرة نضيف متغير الحلقة 3 إلى نهاية القائمة L ونطبع L.
واضح تمامًا. تنتهي الشيفرة لأننا أنشأنا هذا الكائن الأصلي الشبيه بالمجموعة المرتبة هنا، الذي يخبر Python بالقيم التي تحتاج إلى المرور عليها. هذه هي متتالية القيم التي نمرّ عليها. فهذا في الأساس ما قلته.
لننظر إلى مثال مختلف قليلًا. في هذه الحالة، بدل المرور على range(len(L))، لنمرّ على العناصر في L مباشرة. أي for e in L. والآن، للحفاظ على التوازي مع ما فعلناه من قبل، لننشئ متغير حلقة i = 0 قبل حلقة for ولنزده بمقدار 1 في كل مرة عبر الحلقة. فسنظل نضيف 0 ثم 1 ثم 2 ثم 3 إلى نهاية قائمتنا.
فنبدأ في هذه الحالة بمخطط الذاكرة هكذا. لدينا L تشير إلى 1, 2, 3, 4. متغير الحلقة i سيكون 0 في الأصل. وسيشير e أولًا إلى العنصر الأول في القائمة. هذا ما تفعله حلقة for على عناصر القائمة.
فبالدخول إلى القائمة، نقول L.append(i). فعند نهاية L، سأغيّرها لتحتوي على 0. جيد؟ أزيد i بمقدار 1. جيد. أطبع L حسنًا، جيد. ثم في المرة التالية عبر الحلقة، تقول Python: حسنًا، ما العنصر التالي في متتاليتي؟ حسنًا، نظرت إلى 1 أولًا. الآن لأنظر إلى 2.
صفحة المصدر 15
حسنًا. الآن أنظر إلى 2 بوصفه عنصري التالي في المتتالية. سأضيف i إلى نهاية القائمة. فسأضيف 1 إلى نهاية L. أزيد i بمقدار 1 لتصبح 2. أطبع L. حسنًا، القيمة التالية في متتاليتي. يتقدم e إلى العنصر التالي في المتتالية، وهو 3. نضيف i إلى نهاية القائمة. فنضيف 2 إلى نهاية L. نزيد i بمقدار 1. نطبع L.
ماذا تلاحظون؟ هل ستنتهي هذه الشيفرة؟ لا، لأن متغير الحلقة سيظل دائمًا على بعد أربعة عناصر من القائمة، أي من نهايتها. فبينما أضيف عنصرًا إلى نهاية قائمتي، يتقدم متغير الحلقة إلى العنصر التالي. ثم أضيف عنصرًا آخر إلى نهاية قائمتي، ويتقدم متغير الحلقة. وهكذا سنظل دائمًا متأخرين بأربعة عناصر عن نهاية القائمة. لذا فهذه الشيفرة لن تتوقف أبدًا في الواقع.
حسنًا. فالفرق هنا هو ما أمرّ عليه. في المثال السابق، بمجرد أن رأت Python range وlen وما إلى ذلك، أنشأت متتالية القيم المحددة سلفًا التي تحتاج إلى المرور عليها. أما هنا فلا تفعل ذلك، لأنها تمرّ على كائني L. لا توجد متتالية محددة سلفًا لتُنشأ. المفروض أن تمرّ على L مباشرة. فهذا هو الفرق بين الاثنين.
حسنًا. الآن سأريكم— قبل أن أنتقل إلى المثال المخادع الأخير الذي يتضمن الضم (Concatenation)، أردت أن أذكر شيئًا واحدًا، وهو أنه كان هناك سؤال قبل قليل: كيف نضيف فعلًا أكثر من عنصر واحد إلى نهاية قائمتنا؟ ونفعل ذلك باستخدام عملية extend هذه. وعملية extend هذه تشبه append إلى حد ما، لكننا سنضيف كل عناصر قائمة ما، وهي المعامل، إلى نهاية قائمتنا L. فعمليًا نغيّر L لتتمدد بكل العناصر الموجودة في some_list.
وهذا مثال. أولًا، لنفعل الضم لنذكّر أنفسنا بما يفعله. L1 هي 2, 1, 3 في الذاكرة. وL2 هي 4, 5, 6 في الذاكرة. وL3 ستكون L1 مضمومة إلى L2. واضح تمامًا. إنه ضم. فتنشئ Python لي كائنًا جديدًا، هو كل الكائنات الموجودة في L1 وL2 مجموعة معًا ككائن جديد تمامًا مرتبط بالاسم L3. فتبقى L1 وL2 دون تغيير. لا مشكلات هناك.
لكن extend سيغيّر. لاحظوا صيغة التدوين بالنقطة (Dot notation) في extend. سيغيّر L1 لتتمدد بكل العناصر في هذه القائمة. فسيضيف 0 و6 إلى نهاية L1. ها هي. صارت L1 مُغيَّرة إلى 2, 1, 3 ثم 0 ثم 6.
ولتأكيد الفكرة، فإن ما نمدّد به هو كل عناصر هذه القائمة الموجودة في المعامل. ففي هذه الحالة تحديدًا، L2.extend سيتمدد بكم عنصرًا؟ اثنين أم أربعة؟ نعم، أرى اثنين. بالضبط. سيتمدد بعنصرين. على المستوى الأعلى، في هذه القائمة عنصران، قائمة ثم قائمة أخرى. فهذا الأمر هنا سيمدد L2 بهذين العنصرين تحديدًا، 1, 2 كقائمة و3, 4 كقائمة. لكن هذه كائنات فردية. هي كائنات مفردة هي قوائم. وصادف أن فيها مجموعة من العناصر بوصفها جزءًا من القائمة، لكنها كائنان اثنان. نعم.
طالب: [غير مسموع]
آنا بيل: عندما مددنا بـ0, 6، لا توجد أقواس لأننا نمدد بعناصر هذه القائمة ذات المستوى الأعلى. فهما عددان صحيحان. وهنا نمدد بعناصر هذه القائمة ذات المستوى الأعلى. فبمعنى ما، الأقواس الخارجية، وهي قوائم. نعم.
صفحة المصدر 16
فهذا يقدّم extend. لن نستخدم extend فعليًا في هذا المثال بالذات، لكنني أردت ذكره. في هذا المثال، سنستخدم عامل الضم (Concatenation operator) لينشئ لنا هذا الكائن الجديد ونربطه بـL مرة أخرى.
فلنرَ ماذا سيفعل هذا. أولًا، سأخبركم بالإجابة فعلًا، ثم سنفعل مخطط الذاكرة مرة أخرى لتأكيد الفكرة. فهذه الحلقة ستمرّ مرة أخرى على كل العناصر في L. في الأصل هي 1, 2, 3, 4 في قائمة.
فماذا ستفعل الحلقة فعلًا؟ ستأخذ أيًّا كان ما في L وتضاعفه. ففي الأصل L هي 1, 2, 3, 4. المرة الأولى عبر الحلقة ستنشئ كائنًا جديدًا، هو ببساطة L أي 1, 2, 3, 4 مضاعفة. ضممت L إلى نفسها. ثم سأحفظها ككائن جديد بالاسم L مرة أخرى. هذه المرة الأولى عبر الحلقة.
في المرة الثانية عبر الحلقة، سآخذ أيًّا كانت L بعد التغيير— أو أيًّا كانت L من قبل. عذرًا، ليس بعد التغيير، بل أيًّا كانت L من قبل. فهي 1, 2, 3, 4, 1, 2, 3, 4. أضاعف ذلك وأحفظه تحت الاسم L. المرة الثانية عبر الحلقة. الآن في المرة الثالثة عبر الحلقة، سآخذ أيًّا كانت L الآن. أي هذين الصفين من 1, 2, 3, 4, 1, 2, 3, 4. أضاعف ذلك وأحفظه تحت الاسم L.
ثم في المرة الأخيرة عبر الحلقة، سآخذ أيًّا كانت L من قبل. أي هذه الصفوف الأربعة من 1, 2, 3, 4, 1, 2, 3, 4، أضاعفها، وأحفظها بوصفها L الجديدة. وهذا كل شيء. هذه الشيفرة لا تذهب إلى ما لا نهاية.
والآن، لنرَ لماذا بالضبط. فهذا سيساعد. في الأصل، L هي 1, 2, 3, 4. وهذا واضح. متغير الحلقة e يمرّ على كل عنصر في هذا الكائن. فأولًا سيشير إلى 1. حتى الآن الأمر نفسه. L = L + L. لننظر إلى الطرف الأيمن أولًا. هذا ينشئ لي كائنًا جديدًا. تذكروا أن الضم ينشئ لي كائنًا جديدًا. لا يغيّر أي شيء. فسأحصل في الذاكرة على 1, 2, 3, 4, 1, 2, 3, 4. ضاعفت L بنفسها.
ماذا سيفعل L =؟ هل تذكرون أننا عملنا شريحة كهذه، شبيهة جدًا بهذه. إنه مثل عندما أعدنا إسناد المجموعة المرتبة. فـL = سيأخذ الربط (Binding) من كائني الأصلي ويضعه على الكائن الجديد الذي أنشأته للتو. هذا بالضبط مخطط الذاكرة نفسه مع المجموعة المرتبة. صادف أن لدي الاسم نفسه، لكنه يشير إلى كائن جديد. وكذلك هنا. صادف أن لدي الاسم نفسه L، لكنه الآن يشير إلى هذا الكائن الجديد. أما الكائن القديم، هذا الشيء الذي أمرّ عليه، فقد فقدت الربط به. وفعليًا الطريقة الوحيدة التي أستطيع بها حتى الإشارة إلى ذلك الكائن القديم هي عبر هذا e، لأن e ما زال سيمرّ على عناصر هذا الكائن القديم. فهذه هي المرة الأولى. نعم، سؤال.
طالب: لماذا لا يـ[غير مسموع] e؟ لأنك عرّفته قبل أن [غير مسموع]؟
آنا بيل: نعم، أنت تعرّفه ليكون الكائن في الذاكرة، لا الاسم L. فـe مرتبط بالكائن في الذاكرة. وهو هذا الشيء هنا. ولذلك كان مهمًا جدًا أن نفصل أنفسنا عن الكائن في الذاكرة مقابل الاسم الذي نعطيه لكائن، لأن ذلك الاسم قد يتغير إلى أي شيء، إلى مجموعة أشياء أخرى. لكن الكائن نفسه يبقى في الذاكرة.
فيصبح هذا واضحًا تمامًا إن فهمتم هذه النقطة. المرة الأولى عبر الحلقة، أصبحت L مسندة إلى هذا الكائن الجديد هنا. وفقدت الربط بقائمتي الأصلية التي أمرّ عليها. فعندما أطبع L، أطبع هذا. في المرة التالية يتقدم e إلى العنصر التالي في متتاليتي. وستضاعف L ما هي عليه حاليًا. وهي حاليًا هذا الشيء هنا. وستتضاعف إلى ذاك.
صفحة المصدر 17
وسأفقد الربط من الكائن الأصلي، أو ليس الأصلي، بل هذا الشيء الذي كنت قد ربطت به للتو، لأربطها بالكائن التالي الذي أنشأته للتو. وهذه هي المرة الثانية عبر الحلقة. تبدو هكذا. المرة الثالثة عبر الحلقة. يتقدم e بمقدار واحد، إلى القيمة التالية في متتاليتي. سآخذ L + L، أي أضاعف كائن البيانات السابق ذاك. آخذ الربط من الكائن السابق إلى الجديد. أزيد e مرة أخرى بمقدار واحد.
وستكون هذه آخر مرة سيتغير فيها e، لأنه بعد هذا، سيكون e قد مرّ على كل العناصر في متتاليته. فقد بلغ نهاية تلك القائمة. وفي المرة الأخيرة عبر الحلقة، ضاعفت تلك L، وفقدت الربط بالكائن السابق، ونقلته إلى الجديد، ثم هذا كل شيء. انتهى الأمر. أسئلة؟ هل هو واضح؟ هل تساعد الصورة في ذلك كله؟ حسنًا.
هناك شيء آخر أريد ذكره، وهو نوع من معاينة لما سنفعله في المرة القادمة. إحدى العمليات المفيدة جدًا التي قد ترغبون في فعلها هي أخذ قائمة وإزالة كل عناصرها. نعم، تفضل.
طالب: عذرًا. [غير مسموع] لأنه عادة يتوقف قبل الأخير.
آنا بيل: آه، هذا في range. أما هنا فهو يمرّ ببساطة على كل العناصر في المتتالية ويذهب إلى كل واحد منها.
فإحدى العمليات المفيدة التي قد نرغب في فعلها هي إزالة كل العناصر في قائمة، لكن لا— عذرًا، لكن بتغيير القائمة. أي نريد الاحتفاظ بكائن قائمتنا الأصلي. نريد ببساطة تفريغه من كل عناصره. والأمر لذلك له اسم لطيف جدًا. يسمى clear. فإذا أردتم أخذ قائمة L وتفريغها، أي إزالة كل العناصر داخلها، فتقولون L.clear(). وهذا يغيّر قائمتي الأصلية L لتصبح فارغة.
وأحد الأشياء التي قد تساعد في هذه المحاضرة عن التغيير ومعرفة أي كائن هو أي، وهل أنشأتم كائنًا جديدًا أم لا، هو أن تعرفوا— أن تسألوا: كيف أعرف أن هذا الكائن هو الكائن الذي أغيّره؟ ولفعل ذلك، سنستخدم فعلًا هذه الدالة المسماة id. وتتيح لنا id الحصول على موقع الذاكرة، أو كائن الذاكرة، أو هوية الكائن نفسه في الذاكرة.
فالشيفرة على اليسار هي شيفرة تأخذ قائمة L. نحصل على id الخاصة بها لنرى ما هذا الكائن في الذاكرة. ما رقمه؟ ثم نضيف 8 إليها. وسنرى أن id لهذه ستكون نفسها id لهذه. لأننا نغيّر L. لا نضيفها. لا نبدّلها. لا ننشئ كائنًا جديدًا.
وأخيرًا، سنفرّغها ونتحقق من id مرة أخرى. وسترون أن id هي نفسها تمامًا في كل هذه الحالات المختلفة. فأفعل هذا في وحدة التحكم (Console) فقط لأريكم سريعًا. فهنا لدي 4, 5, 6 بوصفها L. ها هي L أي 4, 5, 6. id الخاصة بها هي هذا الرقم هنا. يمكننا النظر إلى آخر ثلاثة أرقام أو ما شابه. 8, 0, 8.
لنضف عنصرًا إلى نهاية قائمتنا. تغيّرت L لتحتوي ذلك العنصر. وهوية L تبقى نفسها. تنتهي بـ8, 0, 8. إنه الكائن نفسه تمامًا في الذاكرة. لقد غيّرناه. L.clear(). L قائمة فارغة. أزلت كل عناصر L. وستظهر لي id الخاصة بـL أنه الكائن نفسه تمامًا. أنا فقط أغيّر الكائن نفسه في الذاكرة.
لنفعل ذلك مرة أخرى، لكن في النسخة الجديدة بدل استخدام L.clear()، سأقول L تساوي القائمة الفارغة. وهذا أيضًا خطأ شائع جدًا. فهنا لدي L هي 4, 5, 6. مرة أخرى، هذه هي L خاصتي. لنحصل على هوية L. ستكون جديدة، لأنني أعدت إسناد L إلى هذه القائمة الجديدة. فهذه تنتهي بـ3, 1, 2.
صفحة المصدر 18
مرة أخرى، لنفعل append للمتعة فقط. id أو id(L) ستكون، مرة أخرى، 3, 1, 2. لكن الآن إذا قلت L تساوي القائمة الفارغة، فهذا هو بالضبط الوضع نفسه الذي رأيناه من قبل مع المجموعة المرتبة ومع ذلك المثال المخادع رقم 3. عندما أقول L تساوي القائمة الفارغة، تأخذ Python اسمي L وتسنده إلى هذا الكائن الذي هو القائمة الفارغة. وكائني الأصلي 4, 5, 6, 8 ما زال في الذاكرة. لقد فقدت الربط به فحسب.
فها هي L. إنها قائمة فارغة. لكن id الخاصة بـL ستكون الآن مختلفة. في الأصل كنت أعمل مع هذه القائمة عند الهوية 312. لكن بعد أن قلت L تساوي القائمة الفارغة، فقدت الربط من تلك القائمة القديمة وأعدت ربط اسمي L بهذه القائمة الفارغة الجديدة. ويمكنكم رؤية ذلك باستخدام id هذه، وهذا رائع جدًا.
حسنًا، ملخص سريع. رأينا القوائم والمجموعات المرتبة بوصفها طريقة لإنشاء بنى البيانات المركّبة هذه، التي يمكن أن تحتوي أي نوع من الكائنات عناصر لها. المجموعات المرتبة غير قابلة للتغيير (Immutable). فهي مفيدة جدًا للأشياء التي لا تتغير، مثل البلد وخط العرض وخط الطول. فهذه الأشياء لن تتغير. أو الكلمة التي تظهر في رقم صفحة ورقم سطر، شيء من هذا القبيل.
القوائم كائنات قابلة للتغيير، فتستخدمونها في المواقف التي تحتاجون فيها إلى ذلك الجانب الديناميكي. فإذا أردتم الاحتفاظ بقائمة موظفين، أو قائمة طلاب، أو قائمة مشتريات بقالة، أو أشياء في ثلاجتكم، فهذه مواقف جيدة جدًا تريدون فيها قائمة، لأن الأشياء تتغير باستمرار. لا تريدون عمل نسخ من كل شيء طوال الوقت لأن ذلك يصبح غير كفء جدًا.
وفي المحاضرة القادمة سنواصل الأمثلة المخادعة. وسيكون لدينا أيضًا اختبار قصير (Quiz). تذكروا، الاختبارات القصيرة الآن أيام الاثنين.