الدرس 25 · الوحدة 12: حلول تطبيقية لمشكلات واقعية
دراسة حالة: نظام توصيل
كل ما تعلمته في نظام واحد: من شكاوى المدير إلى حل مقيس. ⏱ نحو 45 دقيقة.
بعد هذا الدرس ستستطيع أن:
- تحلل مشكلة واقعية إلى متطلبات وعمليات متكررة.
- تختار لكل متطلب التركيب أو الخوارزمية الأنسب.
- تدمج قاموسًا وكومة ودايكسترا في نظام واحد.
- تقيس الأداء وتكتب تقرير دراسة حالة بتوصيات.
١المشكلة
متجر في المدينة المنورة يوصّل الطلبات من مستودع واحد إلى الأحياء. مدير العمليات يشتكي:
- «البحث عن طلب برقمه بطيء حين يتصل العميل».
- «الطلبات المستعجلة تتأخر خلف العادية».
- «السائقون يختارون طرقًا طويلة».
- «لا نعرف بسرعة أكثر الأحياء طلبًا».
هذه أربع مشكلات، ولكل منها تركيب أو خوارزمية مما تعلمته في المقرر.
٢التحليل والقرارات
| المتطلب | العملية المتكررة | القرار | التعقيد |
|---|---|---|---|
| البحث عن طلب برقمه | بحث بمفتاح آلاف المرات | قاموس: رقم الطلب ← البيانات | O(1) |
| المستعجل أولًا، ثم بترتيب الوصول | إضافة وإخراج الأهم باستمرار | كومة: (الأولوية، رقم الوصول، الطلب) | O(log n) |
| أقصر طريق من المستودع | مسافات موزونة موجبة | دايكسترا | O((V + E) log V) |
| أكثر الأحياء طلبًا | عدّ | Counter | O(n) |
٣التنفيذ
import heapqfrom collections import deque # ---------- البيانات ----------roads = [("المستودع", "العزيزية", 6), ("المستودع", "قباء", 4), ("قباء", "العوالي", 3), ("العزيزية", "العوالي", 5), ("العزيزية", "الحرة", 7), ("العوالي", "الحرة", 2), ("الحرة", "سلطانة", 4), ("قباء", "سلطانة", 9)]graph = {}for a, b, km in roads: graph.setdefault(a, []).append((b, km)) graph.setdefault(b, []).append((a, km)) orders = {} # رقم الطلب ← بياناته: بحث O(1)queue = [] # كومة: (الأولوية، رقم الوصول، رقم الطلب)arrival = 0 def add_order(oid, customer, district, express=False): global arrival orders[oid] = {"customer": customer, "district": district, "status": "جديد"} heapq.heappush(queue, (0 if express else 1, arrival, oid)) arrival += 1 def shortest(start): # دايكسترا dist, heap = {start: 0}, [(0, start)] while heap: d, u = heapq.heappop(heap) if d > dist[u]: continue for v, w in graph[u]: if d + w < dist.get(v, float("inf")): dist[v] = d + w heapq.heappush(heap, (d + w, v)) return dist # ---------- يوم عمل ----------add_order(101, "سعود", "العوالي")add_order(102, "نورة", "سلطانة", express=True)add_order(103, "خالد", "الحرة")add_order(104, "ريم", "العوالي", express=True) print("بحث عن 103:", orders[103]) dist = shortest("المستودع")print("المسافات:", dict(sorted(dist.items(), key=lambda x: x[1]))) print("ترتيب التوصيل:")while queue: pr, _, oid = heapq.heappop(queue) o = orders[oid] o["status"] = "مُسلَّم" kind = "مستعجل" if pr == 0 else "عادي" print(f" {oid} | {kind} | {o['customer']} | {o['district']} | {dist[o['district']]} كم")
>>> %Run main.py
بحث عن 103: {'customer': 'خالد', 'district': 'الحرة', 'status': 'جديد'}
المسافات: {'المستودع': 0, 'قباء': 4, 'العزيزية': 6, 'العوالي': 7, 'الحرة': 9, 'سلطانة': 13}
ترتيب التوصيل:
102 | مستعجل | نورة | سلطانة | 13 كم
104 | مستعجل | ريم | العوالي | 7 كم
101 | عادي | سعود | العوالي | 7 كم
103 | عادي | خالد | الحرة | 9 كم
>>>
٤قِس قبل أن تقول «أسرع»
نقارن البحث عن طلب في قائمة بالبحث في القاموس، مع 20 ألف طلب، ونبحث عن آخر 100 طلب:
import time n = 20_000order_list = [(i, "عميل") for i in range(n)]order_dict = {i: "عميل" for i in range(n)}targets = list(range(n - 100, n)) # الحالة الأسوأ: في آخر القائمة t = time.perf_counter()for x in targets: for oid, c in order_list: if oid == x: breakt_list = time.perf_counter() - t t = time.perf_counter()for x in targets: c = order_dict[x]t_dict = time.perf_counter() - t print("القائمة:", round(t_list * 1000), "ms | القاموس:", round(t_dict * 1000, 3), "ms")
>>> %Run main.py
القائمة: 103 ms | القاموس: 0.011 ms
>>>
٥تقرير الحالة
هكذا يُكتب تقرير دراسة الحالة كما تطلب الخطة: شرح الحل، وتحليل أدائه، وتوصيات:
| البند | المحتوى |
|---|---|
| المشكلة | أربع شكاوى: بحث بطيء، وأولوية مفقودة، وطرق طويلة، وتقارير غائبة. |
| الحل | قاموس للطلبات، وكومة للأولوية، ودايكسترا للطرق، و Counter للتقارير. |
| التحليل | البحث من O(n) إلى O(1)، والإخراج بالأولوية O(log n)، والطرق O((V + E) log V). |
| القياس | البحث في القاموس أسرع بآلاف المرات على 20 ألف طلب (القياس في القسم 4). |
| التوصيات | مع مئات السائقين: توزيع الطلبات على أقرب سائق (مشكلة أصعب قريبة من البائع المتجول؛ تُحل تقريبيًّا). |
توقّع وتحقّق
البحث عن طلب برقمه آلاف المرات:
O(1).المستعجل أولًا ثم بترتيب الوصول:
heapq.أقصر طريق بالكيلومترات:
أوزان موجبة.
أكثر الأحياء طلبًا:
عدّ.
الخلاصة
- كل متطلب ← عمليته المتكررة ← تركيبه.
- قاموس للبحث، وكومة للأولوية، ودايكسترا للطرق، و
Counterللتقارير. - قِس لتثبت التحسين، ولا تكتفِ بالكلام.
- التقرير: المشكلة، والحل، والتحليل، والقياس، والتوصيات.
تحدٍّ برمجي: تقرير الأحياء والمسافات
متقدمأضف إلى النظام تقريرًا: عدد الطلبات في كل حي، ومجموع الكيلومترات إن وُصّل كل طلب في رحلة مستقلة ذهابًا وإيابًا.
المطلوب:
- أضف الطلبات الخمسة المعطاة.
- Counter لأحياء الطلبات: اطبع كل حي وعدده من الأكثر.
- المسافة الكلية = مجموع 2 × المسافة لكل طلب: المجموع: 80 كم
ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:
import heapqfrom collections import deque # ---------- البيانات ----------roads = [("المستودع", "العزيزية", 6), ("المستودع", "قباء", 4), ("قباء", "العوالي", 3), ("العزيزية", "العوالي", 5), ("العزيزية", "الحرة", 7), ("العوالي", "الحرة", 2), ("الحرة", "سلطانة", 4), ("قباء", "سلطانة", 9)]graph = {}for a, b, km in roads: graph.setdefault(a, []).append((b, km)) graph.setdefault(b, []).append((a, km)) orders = {} # رقم الطلب ← بياناته: بحث O(1)queue = [] # كومة: (الأولوية، رقم الوصول، رقم الطلب)arrival = 0 def add_order(oid, customer, district, express=False): global arrival orders[oid] = {"customer": customer, "district": district, "status": "جديد"} heapq.heappush(queue, (0 if express else 1, arrival, oid)) arrival += 1 def shortest(start): # دايكسترا dist, heap = {start: 0}, [(0, start)] while heap: d, u = heapq.heappop(heap) if d > dist[u]: continue for v, w in graph[u]: if d + w < dist.get(v, float("inf")): dist[v] = d + w heapq.heappush(heap, (d + w, v)) return dist from collections import Counter for oid, name, d in [(1, "أ", "العوالي"), (2, "ب", "الحرة"), (3, "ج", "العوالي"), (4, "د", "سلطانة"), (5, "هـ", "قباء")]: add_order(oid, name, d)
العوالي 2
الحرة 1
سلطانة 1
قباء 1
المجموع: 80 كم
💡 تلميح 1
Counter(o["district"] for o in orders.values())💡 تلميح 2
المسافات: العوالي 7، الحرة 9، سلطانة 13، قباء 4.
حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
import heapqfrom collections import deque # ---------- البيانات ----------roads = [("المستودع", "العزيزية", 6), ("المستودع", "قباء", 4), ("قباء", "العوالي", 3), ("العزيزية", "العوالي", 5), ("العزيزية", "الحرة", 7), ("العوالي", "الحرة", 2), ("الحرة", "سلطانة", 4), ("قباء", "سلطانة", 9)]graph = {}for a, b, km in roads: graph.setdefault(a, []).append((b, km)) graph.setdefault(b, []).append((a, km)) orders = {} # رقم الطلب ← بياناته: بحث O(1)queue = [] # كومة: (الأولوية، رقم الوصول، رقم الطلب)arrival = 0 def add_order(oid, customer, district, express=False): global arrival orders[oid] = {"customer": customer, "district": district, "status": "جديد"} heapq.heappush(queue, (0 if express else 1, arrival, oid)) arrival += 1 def shortest(start): # دايكسترا dist, heap = {start: 0}, [(0, start)] while heap: d, u = heapq.heappop(heap) if d > dist[u]: continue for v, w in graph[u]: if d + w < dist.get(v, float("inf")): dist[v] = d + w heapq.heappush(heap, (d + w, v)) return dist from collections import Counter for oid, name, d in [(1, "أ", "العوالي"), (2, "ب", "الحرة"), (3, "ج", "العوالي"), (4, "د", "سلطانة"), (5, "هـ", "قباء")]: add_order(oid, name, d) for district, n in Counter(o["district"] for o in orders.values()).most_common(): print(district, n) dist = shortest("المستودع")total = sum(2 * dist[o["district"]] for o in orders.values())print("المجموع:", total, "كم")