مقدمة في علوم الحاسوب والبرمجة باستخدام Python › المحاضرة 26: الوصول إلى عناصر القائمة، والتجزئة، والمحاكاة، والختام 28 / 30 السابق التالي

المحاضرة 26: الوصول إلى عناصر القائمة، والتجزئة (Hashing)، والمحاكاة، والختام

المحاضرة 26: الوصول إلى عناصر القائمة، والتجزئة (Hashing)، والمحاكاة، والختام

المصادر والنسبة والترخيص

Ana Bell. 6.100L Introduction to CS and Programming using Python, Fall 2022. Massachusetts Institute of Technology: MIT OpenCourseWare, https://ocw.mit.edu/courses/6-100l-introduction-to-cs-and-programming-using-python-fall-2022/. License: CC BY-NC-SA 4.0.

  • صفحة المحاضرة على MIT OpenCourseWare: https://ocw.mit.edu/courses/6-100l-introduction-to-cs-and-programming-using-python-fall-2022/pages/lecture-26-list-access-hashing-simulations-and-wrap-up/
  • ملف الشرائح (صفحة الوصف): https://ocw.mit.edu/courses/6-100l-introduction-to-cs-and-programming-using-python-fall-2022/resources/mit6_100l_f22_lec26_pdf/
  • ملف الشرائح (PDF مباشر): https://ocw.mit.edu/courses/6-100l-introduction-to-cs-and-programming-using-python-fall-2022/mit6_100l_f22_lec26.pdf
  • ملف الشيفرة: https://ocw.mit.edu/courses/6-100l-introduction-to-cs-and-programming-using-python-fall-2022/resources/mit6_100l_f22_lec26_code_py/
  • تفريغ المحاضرة على MIT OpenCourseWare (بالإنجليزية): https://ocw.mit.edu/courses/6-100l-introduction-to-cs-and-programming-using-python-fall-2022/resources/mit6_100l_f22_lec26/
  • الترخيص: CC BY-NC-SA 4.0 — https://creativecommons.org/licenses/by-nc-sa/4.0/
  • شروط الاستخدام في MIT OpenCourseWare: https://ocw.mit.edu/terms/

منهج الترجمة: نُقلت هذه المحاضرة ترجمةً عربية كاملة أمينة لمحتوى ملف الشرائح الأصلي في MIT OpenCourseWare، تحت رخصة CC BY-NC-SA 4.0 التي تسمح بإعادة التوزيع مع الإبقاء على الإسناد والتأهيل والإشارة إلى التغيير، وبشرط أن يكون الاستخدام غير تجاري وأن تُوزَّع كل ترجمة مشتقّة تحت الرخصة نفسها. أُبقيت الشيفرة وأسماء دوال random و plt كما هي بالإنجليزية. ولكل شريحة من الشرائح الأربعين عنوانها الخاص. في الشرائح 13 و15 و17 و18 رسم مخطّطات الذاكرة، ونُقلت أرقام الحسابات كما هي في النصّ المستخرَج مع جداول توضّح مواقع كل مفتاح.

الشريحة 1: الوصول إلى عناصر القائمة، والتجزئة، والمحاكاة، والختام!

يُرفق ملف الشرائح وملفات .py للمتابعة الجانبيًا.

الشريحة 2: اليوم

  • القوائم (Lists) بعض الشيء.
  • التجزئة (Hashing).
  • المحاكاة (Simulations).

الشريحة 3: القوائم

شريحة فاصلة.

الشريحة 4: تعقيد بعض عمليات Python

القوائم: حيث n هو len(L)

العملية التعقيد
access θ(1)\theta(1)
store θ(1)\theta(1)
length θ(1)\theta(1)
append θ(1)\theta(1)
== θ(n)\theta(n)
delete θ(n)\theta(n)
copy θ(n)\theta(n)
reverse θ(n)\theta(n)
iteration θ(n)\theta(n)
in list θ(n)\theta(n)

الشريحة 5: الوصول إلى عنصر القائمة في زمن ثابت

رسم يوضّح قائمة [1234, …, 5295] في الذاكرة: مواقع ذاكرة متتالية، واسم القائمة يشير إلى موقع الذاكرة الأول، والقيمة الفعلية (actual value) هي 5295 في الموقع الأخير.

  • إذا كانت القائمة كلها أعداد صحيحة، وطولها len(L):
    • خصّص 4*len(L) بايت.
    • خزّن القيم مباشرة.
    • مجموعة متتالية من مواقع الذاكرة.
  • يشير اسم القائمة إلى موقع الذاكرة الأول.
  • للوصول إلى العنصر رقم i:
    • أضف 32*i إلى الموقع الأول.
    • استدعِ ذلك الموقع في الذاكرة.
  • تعقيد زمن ثابت (constant time complexity).

ملاحظة المترجم: الرسم في الشريحة يبيّن القيمة 5295 في آخر موقع والمربّعات المتتالية قبلها؛ وخطوط الربط بين المواقع لا تنجو من طبقة النصّ المستخرَجة.

الشريحة 6: الوصول إلى عنصر القائمة في زمن ثابت (قائمة غير متجانسة)

  • إذا كانت القائمة غير متجانسة (heterogeneous):
    • لا يمكن تخزين القيم مباشرة (فلا تتّسع كلها في 32 بت).
    • استخدمالإشارة غير المباشرة (indirection) للإشارة إلى كائنات أخرى.
    • خزّن مؤشرات (pointers) إلى القيم (لا القيمة نفسها).
    • ما زلنا نستخدم مجموعة متتالية من مواقع الذاكرة.
    • ما زلنا نخصّص 4*len(L) بايت.
    • ما زلنا نضيف 32*i إلى الموقع الأول، و +1 للوصول إلى ذلك الموقع في الذاكرة.
    • وما زال التعقيد ثابت الزمن.

الرسم يوضّح: مؤشر إلى قائمة (pointer to a list)، والقيمة المخزَّنة هي مؤشر إلى الكائن الفعلي في الذاكرة (value stored is pointer to actual object in memory)، والقيمة 5295.

الشريحة 7: تنفيذ ساذج (naive) للقاموس

  • استخدم قائمة من الأزواج: مفتاح، قيمة:
[['Ana', True], ['John', False], ['Eric', False], ['Sam', False]]
  • ما التعقيد الزمني للفهرسة في هذا القاموس الساذج؟
    • لا نعرف ترتيب المدخلات.
    • علينا إجراء بحث خطّي للعثور على المدخل.

الشريحة 8: تعقيد بعض عمليات Python (القوائم والقواميس)

القوائم: حيث n هو len(L)

العملية التعقيد
access θ(1)\theta(1)
store θ(1)\theta(1)
length θ(1)\theta(1)
append θ(1)\theta(1)
== θ(n)\theta(n)
delete θ(n)\theta(n)
copy θ(n)\theta(n)
reverse θ(n)\theta(n)
iteration θ(n)\theta(n)
in list θ(n)\theta(n)

القواميس: حيث n هو len(d)

الحالة العملية التعقيد
أسوأ حالة (نادرة جدًّا) length θ(n)\theta(n)
access θ(n)\theta(n)
store θ(n)\theta(n)
delete θ(n)\theta(n)
iteration θ(n)\theta(n)
الحالة المتوسطة access θ(1)\theta(1)
store θ(1)\theta(1)
delete θ(1)\theta(1)
in θ(1)\theta(1)
iteration θ(n)\theta(n)

الشريحة 9: التجزئة (Hashing)

شريحة فاصلة.

الشريحة 10: تنفيذ القواميس

  • يستخدم جدول تجزئة (hash table).
  • كيف يفعل ذلك:
    • تحويل المفتاح إلى عدد صحيح، باستخدام دالة تجزئة (hash function).
    • استخدام ذلك العدد الصحيح كفهرس في قائمة.
    • هذا يتم في زمن ثابت.
    • العثور على القيمة المرتبطة بالمفتاح يتم في زمن ثابت.
  • البحث في القاموس له تعقيد زمن ثابت، إذا:
    • كانت دالة التجزئة سريعة بما يكفي.
    • وكان الفهرسة في القائمة ثابتة الزمن.

الشريحة 11: الاستعلام عن دالة التجزئة

لمجرد كشف ما يجري تحت الكبوت، إليك الدالة hash().

الشريحة 12: جدول التجزئة

  • ما الحجم المناسب لجدول التجزئة؟
  • لتفادي أن تتجزأ مفاتيح كثيرة إلى القيمة نفسها، اجعل كل مفتاح يتجزأ إلى قيمة منفصلة.
  • إذا كنّا نجزّئ نصوصًا (strings):
    • مثّل كل محرف بشيفرة ثنائية (binary code).
    • ألصق البتات معًا، وحوّلها إلى عدد صحيح.

الشريحة 13: الأسماء إلى الفهارس

مثال: 'Ana Bell'

= 01000001 01101110 01100001 00100000 01000010 01100101 01101100 01101100
= 4,714,812,651,084,278,892
  • الميزة: أسماء فريدة تُقابل فهارس فريدة.
  • العيب: غير كفّئ للمساحة إلى حدّ بعيد (VERY space inefficient).
  • فكّر في جدول يحتوي على نحو 4000 طالب جامعي في MIT:
    • نفترض أن أطول اسم 20 محرفًا.
    • كل محرف 8 بتات، أي 160 بت لكل اسم.
    • كم مدخلًا سيحتويه الجدول؟
2160=1,461,501,637,330,902,918,203,684,832,716,283,019,655,932,542,9762^{160} = 1,461,501,637,330,902,918,203,684,832,716,283,019,655,932,542,976

الشريحة 14: فكرة أفضل: نسمح بالتصادم (Collisions)

شريحة فاصلة.

الشريحة 15: دالة التجزئة

دالة التجزئة:

  1. اجمع قيم الحروف.
  2. خذ الباقي عند القسمة على 16 (لتملأ جدول تجزئة بـ 16 مدخلًا).
الاسم جمع قيم الحروف النتيجة
Ana 1+14+1=161 + 14 + 1 = 16 16%16=016 \% 16 = 0
Eric 5+18+9+3=355 + 18 + 9 + 3 = 35 35%16=335 \% 16 = 3
John 10+15+8+14=4710 + 15 + 8 + 14 = 47 47%16=1547 \% 16 = 15
Eve 5+22+5=325 + 22 + 5 = 32 32%16=032 \% 16 = 0

جدول التجزئة (مثل القائمة):

الفهرس القيمة
0 C
1
2
3 A
4
5
6
7
8
9
10
11
12
13
14
15 B

أماكن المفاتيح: Ana: C، Eric: A، John: B، Eve: B

ملاحظة المترجم: القيمة الأولى في العمود الأيسر من جدول الشريحة تُلخّص بالحرف C في الموقع صفر، والجدول أعلاه مبنيّ على الأرقام المحسوبة (0، 3، 15، 0) وهو مطابق لما يظهر في الشريحة.

الشريحة 16: خصائص دالة التجزئة الجيّدة

  • تُسقط مجال اهتمامك (domain of interest) على أعداد صحيحة بين 0 وحجم جدول التجزئة.
  • قيمة التجزئة تحدّدها القيمة المُجزَّأة تمامًا (لا شيء عشوائي).
  • دالة التجزئة تستخدم كامل المدخل المُجزَّأ.
  • تصادمات أقلّ (Fewer collisions).
  • توزيع القيم منتظم (uniform)، أي احتمال landing على أيّ مدخل في جدول التجزئة متساوٍ.
  • تذكير جانبي: يجب أن تكون مفاتيح القاموس قابلة للتجزئة (hashable)، أي غير قابلة للتغيير (immutable)، وأن تتجزّأ دائمًا إلى القيمة نفسها.
  • ماذا يحدث إن لم تكن قابلة للتجزئة؟

الشريحة 17: دالة التجزئة (مع اسم إضافي)

دالة التجزئة:

  1. اجمع قيم الحروف.
  2. خذ الباقي عند القسمة على 16 (لتملأ كتلة ذاكرة بـ 16 مدخلًا).
الاسم جمع قيم الحروف النتيجة
Ana 1+14+1=161 + 14 + 1 = 16 16%16=016 \% 16 = 0
Eric 5+18+9+3=355 + 18 + 9 + 3 = 35 35%16=335 \% 16 = 3
John 10+15+8+14=4710 + 15 + 8 + 14 = 47 47%16=1547 \% 16 = 15
Eve 5+22+5=325 + 22 + 5 = 32 32%16=032 \% 16 = 0
[K, a, t, e] 11+1+20+5=3711 + 1 + 20 + 5 = 37 37%16=537 \% 16 = 5

جدول التجزئة:

الفهرس القيمة
0 C
1
2
3 A
4
5 B
6
7
8
9
10
11
12
13
14
15 B

أماكن المفاتيح: Ana: C، Eric: A، [K,a,t,e]: B، John: B، Eve: B

ملاحظة المترجم: أرقام الجدول أعلاه مشتقّة من عمود «النتيجة» أعلاه (0، 3، 15، 0، 5)، وهي مطابقة لمواقع الحروف كما في الشريحة.

الشريحة 18: دالة التجزئة (تغيّر الاسم)

تغيّرت Kate اسمها إلى Cate. الشخص نفسه، والاسم مختلف. هل تبحث عن درجتها؟

الاسم جمع قيم الحروف النتيجة
[C, a, t, e] 3+1+20+5=293 + 1 + 20 + 5 = 29 29%16=1329 \% 16 = 13

جدول التجزئة:

الفهرس القيمة
0 C
1
2
3 A
4
5 B
6
7
8
9
10
11
12
13
14
15 B

أماكن المفاتيح: Ana: C، Eric: A، [K,a,t,e]: B، ثم سهم يشير إلى «؟؟؟ ليس هنا!» عند [C,a,t,e] لأن الفهرس 13 فارغ، و John: B، Eve: B.

ملاحظة المترجم: هذا هو مثال «التصادم» في الخاتمة: شخص واحد له اسمان مختلفان، في situación-site مختلفين في الجدول. لم يُضَف أيّ استنتاج من عندي خارج ما صُرّح به في الشريحة.

الشريحة 19: تعقيد بعض عمليات Python (القواميس)

القواميس: حيث n هو len(d)

الحالة العملية التعقيد
أسوأ حالة (نادرة جدًّا) length θ(n)\theta(n)
access θ(n)\theta(n)
store θ(n)\theta(n)
delete θ(n)\theta(n)
iteration θ(n)\theta(n)
الحالة المتوسطة access θ(1)\theta(1)
store θ(1)\theta(1)
delete θ(1)\theta(1)
in θ(1)\theta(1)
iteration θ(n)\theta(n)

الشريحة 20: المحاكاة (Simulations)

شريحة فاصلة.

الشريحة 21: موضوع مفيد للميادين الكثيرة

  • صف العالم حسابيًا باستخدام العشوائية.
  • موضوع مهمّ جدًّا يتّصل بالميادين الدراسية الكثيرة:
    • نمذجة المخاطر وتحليلها (risk modeling and analysis).
    • تبسيط النماذج المعقّدة.
  • الفكرة:
    • لاحظ حدثًا وتريد حساب شيء عنه.
    • باستخدام الحوسبة، صمِّم تجربة (experiment) لذلك الحدث.
    • كرِّر التجربة K مرّات كثيرة (أوْجِد محاكاة = simulation).
    • تتبَّع نتيجة الحدث.
    • بعد K تكرار، اذكر القيمة محلّ الاهتمام.

الشريحة 22: رمي حجر النرد

  • لاحظ حدثًا وتريد حساب شيء عنه: ارمِ نردًا، فما احتمال الحصول على ::؟ وكيف على .؟
  • باستخدام الحوسبة، صمِّم تجربة لذلك الحدث:
    • أنشئ قائمة تمثّل أوجه النرد واختر واحدة عشوائيًا:
random.choice(['.',':',':.','::','::.',':::'])
  • كرِّر التجربة K مرّات كثيرة (حاكِمها!):
    • اختر وجه نرد عشوائيًا من قائمة، تكرارًا 10000 مرّة.
    • كيف؟ لفّ المحاكاة في حلقة!
for i in range(10000):
    roll=random.choice(['.',':',':.','::','::.',':::'])
  • تتبَّع نتيجة الحدث:
    • عُدّ كم مرّة من أصل 10000 كانت الرمية يساوي ::.
  • بعد K تكرار، اذكر القيمة محلّ الاهتمام: اقسم العدد على 10000.

الشريحة 23: شيفرة المحاكاة

def prob_dice(side):
    dice = ['.',':',':.','::','::.',':::']
    Nsims = 10000
    count = 0
    for i in range(Nsims):
        roll = random.choice(dice)
        if roll == side:
            count += 1
    print(count/Nsims)

prob_dice('.')
prob_dice('::')

الشريحة 24: تلك محاكاة سهلة

  • يمكننا حساب احتمال رمية النرد حسابيًا (mathematically).
  • فلمَ نكتب الشيفرة؟ لأننا نستطيع الإجابة عن تنويعات السؤال الأصلي، وطرح أسئلة أصعب!
  • تعديلات صغيرة في الشيفرة.
  • سهل تغيير الشيفرة.
  • سريع في التشغيل.

ملاحظة المترجم: ظهرت حروف دخيلة عند استخراج جملة «فلمَ… الشيفرة؟»، وأصلحتُها إلى: «فلمَ نكتب الشيفرة؟». المعنى واضح من السياق.

الشريحة 25: سؤال جديد ليس سهلًا حسابيًا

  • لاحظ حدثًا وتريد حساب شيء عنه: ارمِ نردًا 7 مرّات، فما احتمال الحصول على :: ثلاث مرّات على الأقل من أصل 7 رميات؟
  • باستخدام الحوسبة، صمِّم تجربة لذلك الحدث:
    • أنشئ قائمة تمثّل أوجه النرد واختر واحدة عشوائيًا 7 مرّات متتالية.
    • عدّاد الأوجه يزداد عندما تختار :: (تتبَّع هذا العدد).
  • كرِّر التجربة K مرّات كثيرة:
    • كرِّر الخطوة السابقة 10000 مرّة. كيف؟ لفّ المحاكاة في حلقة!
  • تتبَّع نتيجة الحدث: عُدّ كم مرّة من أصل 10000 كان عدّاد :: يساوي 3 أو أكثر.
  • بعد K تكرار، اذكر القيمة محلّ الاهتمام: اقسم عدد النتائج على 10000.

الشريحة 26: تعديل سهل على شيفرة موجودة

def prob_dice_atleast(Nrolls, n_at_least):
    dice = ['.',':',':.','::','::.',':::']
    Nsims = 10000
    how_many_matched = []
    for i in range(Nsims):
        matched = 0
        for i in range(Nrolls):
            roll = random.choice(dice)
            if roll == '::':
                matched += 1
        how_many_matched.append(matched)
    count = 0
    for i in how_many_matched:
        if i >= n_at_least:
            count += 1
    print(count/len(how_many_matched))

prob_dice_atleast(7, 3)
prob_dice_atleast(1, 1)

الشريحة 27: سؤال من العالم الحقيقي

مثال شائع جدًّا على مدى نفع المحاكاة:

  • الماء يجري عبر حنفية بمعدل يتراوح بين 1 غالون في الدقيقة و3 غالونات في الدقيقة.
  • كم من الوقت يلزم لملء حوض سعة 600 غالون؟
  • حدسك؟
    • ليس 300 دقيقة (600/2).
    • وليس 400 دقيقة ((600/1 + 600/3)/2).
  • في الشيفرة:
    • خُذ حزمة من القيم العشوائية بين 1 و3.
    • حاكِ الزمن اللازم لملء حوض 600 غالون، مستخدمًا كل قيمة عشوائية مختارة.
    • اطبع الزمن المتوسّط اللازم لملء الحوض عبر كل هذه القيم العشوائية.

ملاحظة المترجم: جملة «بمعدل… بين …» كانت تالفة في نصّ الاستخراج (ظهرت فيها كلمة إنجليزية زائدة)، وأصلحتُها إلى: «بمعدل يتراوح بين 1 غالون في الدقيقة و3 غالونات في الدقيقة». المعنى واضح من السياق.

الشريحة 28: شيفرة ملء الحوض

def fill_pool(size):
    flow_rate = []
    fill_time = []
    Npoints = 10000
    for i in range(Npoints):
        r = 1+2*random.random()
        flow_rate.append(r)
        fill_time.append(size/r)
    print('avg flow_rate:', sum(flow_rate)/len(flow_rate))
    print('avg fill_time', sum(fill_time)/len(fill_time))

plt.figure()
plt.scatter(range(Npoints),flow_rate,s=1)
plt.figure()
plt.scatter(range(Npoints),fill_time,s=1)

fill_pool(600)

الشريحة 29: رسم معدلات الملء العشوائية والزمن المقابل لملء الحوض

  • قيم عشوائية لمعدل الجريان.
  • الزمن اللازم للملء بالمعادلة pool_size/rate.

الشريحة 30: رسم معدلات الملء العشوائية والزمن المقابل لملء الحوض (مرتّبة)

  • قيم عشوائية لمعدل الجريان (مرتّبة sort).
  • زمن الملء (مرتّب) بالمعادلة pool_size/rate.

الشريحة 31: النتائج

  • avg flow_rate: = 1.992586945871106 ≈ 2 غالون/دقيقة (المتوسّط للقيم العشوائية بين 1 و3).

  • avg fill_time = 330.6879477596955 ≈ 331 دقيقة (ليس ما توقّعناه!).

  • ليس 300 وليس 400.

  • هناك علاقة عكسية (inverse relationship) بين زمن الملء ومعدل الجريان.

  • حسابيًا كان عليك أن تعمل تكاملًا (integral).

  • حاسوبيًا تكتب بضعة أسطر من الشيفرة فقط!

الشريحة 32: ختام 6.100L

شكرًا لأنكم كنتم في هذا الصف!

الشريحة 33: ماذا تعلّمت؟

المحور الموضوعات
صياغة Python (Python syntax)
سير التنفيذ (Flow of control) الحلقات، التفريع (branching)، الاستثناءات
هياكل البيانات (Data structures) الصفوف (Tuples)، القوائم (Lists)، القواميس (Dictionaries)
التنظيم والتفكيك والتجريد الدوال (Functions)، الأصناف (Classes)
الخوارزميات (Algorithms) الثنائي / البحث بالتنصيف، التعقيد الحاسوبي، رمز ثيتا الكبير (Big Theta notation)، البحث والترتيب

الشريحة 34: تجربتك

  • هل كنت «طبيعيًا الموهبة» (a "natural")؟
  • هل انضممت إلى الصف متأخّرًا؟
  • هل اجتهدت؟

انظر إلى أول مجموعة تمارين (pset) — ستبدو سهلة جدًّا الآن! لقد تعلّمت الكثير في كل الأحوال!

الشريحة 35: ما التالي

  • 6.100B — لمحة عامة عن مواضيع مثيرة في علوم الحاسوب وعلوم البيانات (Python):
    • مسائل التحسين (optimization problems).
    • المحاكاة (simulations).
    • البيانات التجريبية (experimental data).
    • تعلّم الآلة (machine learning).

الشريحة 36: ما التالي

  • 6.101 — أساسيات البرمجة (Python):
    • تنفيذ خوارزميات فعّالة.
    • تصحيح الأخطاء (debugging).

الشريحة 37: ما التالي

  • 6.102 — هندسة البرمجيات (TypeScript):
    • كتابة شيفرة آمنة من الأخطاء، سهلة الفهم، جاهزة للتغيير.

الشريحة 38: ما التالي

  • مقرّرات أخرى (تعلّم الآلة، الخوارزميات، إلخ).

الشريحة 39: من السهل أن تنسى دون ممارسة!

برمجة سعيدة!

الشريحة 40: MIT OpenCourseWare

MITOpenCourseWare
https://ocw.mit.edu

6.100L Introduction to Computer Science and Programming Using Python
Fall 2022

For information about citing these materials or our Terms of Use, visit: https://ocw.mit.edu/terms.