كانت دوالنا تعاني من فقدان الذاكرة: كيف أنقذتنا ‘البرمجة الديناميكية’ من جحيم إعادة الحسابات المتكررة؟

يا أهلاً بالشباب الطيبة والصبايا، حياكم الله. معكم أخوكم أبو عمر.

قبل كم سنة، كنا شغالين على مشروع فيه تحليل لبيانات مالية ضخمة. كان في عنا دالة (function) رئيسية، وظيفتها تحسب أفضل استراتيجية استثمار بناءً على بيانات تاريخية. المشكلة يا جماعة الخير، إنه كل ما كبرت الفترة الزمنية للتحليل، الكود بصير “يصفن” وبياخذ وقت يرفع الضغط. أذكر مرة تركنا الجهاز شغال طول الليل عشان يحسب شغلة بسيطة، وتاني يوم الصبح لقيناه لسا ما خلص!

الشباب في الفريق صاروا يحكوا “شو هالحكي يا أبو عمر؟”، “الكود هاد بدو فرمتة!”. قعدت مع حالي، وفنجان القهوة السادة ما فارق إيدي، وصرت أتبع الكود سطر سطر. لقيت إنه الدالة العودية (Recursive function) اللي كتبناها، مع إنها منطقياً صح، إلا إنها “بتنسى” بسرعة. كانت قاعدة بتحسب نفس القيمة عشرات، بل مئات المرات. زي اللي بروح على الدكان يشتري غرض، وبس يرجع، بنسى شو جاب، وبرجع مرة تانية على الدكان لنفس الغرض! وقتها ضحكت وقلت بصوت عالي: “ولعت! لقينا المشكلة… دوالنا عندها فقدان ذاكرة!”. وهون كانت بداية رحلتنا مع المنقذ: البرمجة الديناميكية.

ما هي المشكلة بالضبط؟ لعنة الحسابات المتكررة

عشان نفهم الموضوع ببساطة، خلينا نأخذ مثال كلنا بنعرفه من أيام الجامعة: متتالية فيبوناتشي (Fibonacci sequence). القاعدة بسيطة: كل رقم هو مجموع الرقمين اللي قبله (…,1, 1, 2, 3, 5, 8). لو بدنا نكتب دالة تحسب الرقم رقم n في المتتالية باستخدام البرمجة العودية، الكود راح يكون هيك (باستخدام JavaScript كمثال):

function fib(n) {
  if (n <= 1) {
    return n;
  }
  return fib(n - 1) + fib(n - 2);
}

console.log(fib(6)); // راح يرجع 8
console.log(fib(40)); // استعد لشرب فنجان قهوة وانت تستنى!

الكود جميل ومختصر، لكنه كارثي من ناحية الأداء. ليش؟ خلينا نشوف شو بصير لما نحاول نحسب fib(6):

  • fib(6) تستدعي fib(5) و fib(4).
  • fib(5) تستدعي fib(4) و fib(3).
  • fib(4) (اللي من fib(6)) تستدعي fib(3) و fib(2).

لاحظت إشي؟ حسبنا fib(4) مرتين! وحسبنا fib(3) ثلاث مرات! وكل ما زاد الرقم n، زادت هاي الحسابات المتكررة بشكل أُسّي. هذا هو ما نسميه “المسائل الفرعية المتداخلة” (Overlapping Subproblems)، وهو جوهر المشكلة. دالتنا المسكينة بتضل تعيد نفس الشغل مرة بعد مرة.

البرمجة الديناميكية: ذاكرة السمكة الذهبية لم تعد مشكلة!

البرمجة الديناميكية (Dynamic Programming أو DP) مش لغة برمجة ولا إطار عمل، يا جماعة. هي طريقة تفكير، استراتيجية لحل المشاكل. فكرتها بسيطة لدرجة بتخليك تحكي “كيف ما خطرتلي من قبل؟”:

إذا حسبت إشي مرة، خزّن النتيجة. المرة الجاية لما تحتاجها، لا تحسبها مرة تانية، بس هاتها من الذاكرة!

ببساطة، بنعطي دالتنا “ذاكرة” عشان ما تضل تنسى. عشان نقدر نستخدم البرمجة الديناميكية، المشكلة لازم يكون فيها خاصيتين:

  1. المسائل الفرعية المتداخلة (Overlapping Subproblems): زي ما شفنا في مثال فيبوناتشي، المشكلة الكبيرة بتتكون من مسائل أصغر بتتكرر.
  2. البنية التحتية المثلى (Optimal Substructure): يعني الحل الأمثل للمشكلة الكبيرة يمكن بناؤه من الحلول المثلى للمسائل الفرعية.

إذا توفروا هدول الشرطين، بنقدر نحكي “بسم الله” ونطبق البرمجة الديناميكية.

كيف نطبق البرمجة الديناميكية؟ طريقتان لا ثالث لهما

عنا طريقتين أساسيتين عشان نعطي دوالنا الذاكرة اللي بتحتاجها. كل طريقة إلها حسناتها وسيئاتها.

الطريقة الأولى: التذكير (Memoization) – النهج التنازلي (Top-Down)

هاي الطريقة هي الأقرب لتفكيرنا الطبيعي لما نحل مشكلة بشكل عودي. الفكرة هي إنه بنحافظ على نفس شكل الدالة العودية، بس بنضيفلها “كاش” أو “ذاكرة مؤقتة” (ممكن تكون مصفوفة أو كائن/object).

قبل ما نحسب أي إشي، بنسأل: “يا ذاكرة، عندك نتيجة هاي العملية من قبل؟”.

  • إذا الجواب “نعم”، بناخذها وبنرجعها فوراً.
  • إذا الجواب “لا”، بنحسبها زي العادة، بس قبل ما نرجعها، بنخزنها في الذاكرة عشان المرات الجاية.

خلينا نعدل دالة فيبوناتشي تبعتنا باستخدام هاي الطريقة:

// الكاش أو الذاكرة، بنمرره للدالة
const memo = {};

function fibWithMemo(n) {
  // إذا كانت القيمة موجودة في الكاش، رجعها فوراً
  if (n in memo) {
    return memo[n];
  }
  
  // القاعدة الأساسية للعودية
  if (n <= 1) {
    return n;
  }
  
  // إذا مش موجودة، احسبها وخزنها في الكاش ثم ارجعها
  memo[n] = fibWithMemo(n - 1) + fibWithMemo(n - 2);
  return memo[n];
}

console.log(fibWithMemo(40)); // الآن النتيجة تظهر في لمح البصر!

شفتوا السحر؟ نفس المنطق العودي، بس مع إضافة بسيطة حولت الكود من بطيء جداً إلى سريع جداً.

نصيحة من أبو عمر: هاي الطريقة “التذكير” هي الحل الأسهل والأسرع للتطبيق إذا كان عندك حل عودي جاهز. زي ما بنحكيها بالبلدي، حل “إسعافي” وفعال جداً. بتحس إنه طبيعي ومنطقي.

الطريقة الثانية: الجدولة (Tabulation) – النهج التصاعدي (Bottom-Up)

هاي الطريقة بتفكر بالمشكلة من تحت لفوق. بدل ما نبدأ من المشكلة الكبيرة (fib(n)) وننزل للصغار، هون بنعمل العكس. بنبدأ من أصغر مشكلة فرعية ممكنة وبنبني عليها الحل خطوة بخطوة لحد ما نوصل للحل النهائي.

في حالة فيبوناتشي، أصغر مشكلتين همه fib(0) و fib(1). بنعرف قيمهم، ومن خلالهم بنحسب fib(2)، بعدين fib(3)، وهكذا، لحد ما نوصل لـ fib(n). ما في أي عودية (recursion) هون، كله بكون من خلال حلقات تكرار (loops).

شوفوا كيف بنحلها بالجدولة:

function fibWithTabulation(n) {
  if (n <= 1) {
    return n;
  }
  
  // إنشاء جدول لتخزين النتائج
  const table = new Array(n + 1);
  
  // تعبئة القيم الأساسية
  table[0] = 0;
  table[1] = 1;
  
  // بناء الحل من تحت لفوق
  for (let i = 2; i <= n; i++) {
    table[i] = table[i - 1] + table[i - 2];
  }
  
  // النتيجة النهائية موجودة في آخر خانة بالجدول
  return table[n];
}

console.log(fibWithTabulation(40)); // سريعة جداً أيضاً!

هاي الطريقة ممكن تكون أسرع بشوي من التذكير لأنه ما فيها الحمل الزائد (overhead) تبع استدعاء الدوال العودية. لكنها بتحتاج تخطيط وتفكير مسبق في كيفية بناء الجدول.

نصيحة من أبو عمر: الجدولة ممتازة لما تكون عارف كل المسائل الفرعية اللي بتحتاجها مسبقاً. بتعطيك تحكم كامل في استخدام الذاكرة، وفي بعض الأحيان ممكن تحسّن الحل أكثر وتكتشف إنك مش بحاجة لكل الجدول، بس لآخر قيمتين أو ثلاث، وهذا بحسن استخدام الذاكرة بشكل كبير (Space Optimization).

الخلاصة: وداعاً للعمل المكرر! 💡

البرمجة الديناميكية، باختصار، هي فن “عدم إعادة اختراع العجلة” داخل الكود تبعك. هي عقلية بتخليك تفكر دائماً: “هل بقدر أستفيد من شغل عملته قبل شوي؟”. سواء استخدمت طريقة التذكير (Top-Down) أو الجدولة (Bottom-Up)، الهدف واحد: حل المشكلة مرة واحدة فقط.

لما واجهنا المشكلة في مشروع التحليل المالي، تطبيقنا لمبدأ التذكير كان زي السحر. الدالة اللي كانت تاخذ ساعات، صارت تخلص شغلها في ثواني معدودة. كل اللي عملناه هو إنه أعطيناها ذاكرة بسيطة تتذكر فيها حساباتها.

نصيحتي الأخيرة إلكم: لا تخافوا من اسم “البرمجة الديناميكية”. هو مجرد اسم فخم لمفهوم بسيط وعبقري. المرة الجاية لما تلاقي الكود تبعك بطيء وفيه دالة عودية، أول إشي يخطر في بالك لازم يكون: “هل دالتي بتعاني من فقدان الذاكرة؟”.

يلا يا جماعة، شدوا حيلكم، وخلي كودكم دايماً “يتذكر” شغله! بالتوفيق. 😉

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

​معمارية البرمجيات

كان كودنا لا يفهم طبيعة عملنا: كيف أنقذنا ‘التصميم الموجه بالمجال’ (DDD) من جحيم ‘ماذا تفعل هذه الدالة؟’

كودنا كان يتحدث لغة لا يفهمها خبراء العمل، مما خلق فوضى عارمة. في هذه المقالة، أشارككم قصة كيف ساعدنا التصميم الموجه بالمجال (DDD) في بناء...

26 مايو، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

كان تطبيقنا جميلاً ولكن أعمى: كيف أنقذتنا ‘إمكانية الوصول’ من جحيم استبعاد 15% من المستخدمين؟

أشارككم قصة حقيقية حول كيف اكتشفنا أن تطبيقنا الذي قضينا شهوراً في تلميعه كان يستبعد شريحة ضخمة من المستخدمين. هذه المقالة ليست مجرد درس تقني...

26 مايو، 2026 قراءة المزيد
الشبكات والـ APIs

كانت تطبيقاتنا تعتمد على التحديث اليدوي: كيف أنقذتنا WebSockets من جحيم ‘الاستقصاء المستمر’ (Polling)؟

مقالة تستعرض تجربة عملية في الانتقال من تقنية الاستقصاء المستمر (Polling) المرهقة إلى استخدام WebSockets لتطبيقات الوقت الحقيقي. اكتشف كيف يمكن لهذا التغيير أن يحسّن...

26 مايو، 2026 قراءة المزيد
الحوسبة السحابية

كانت خوادمنا تلتهم الميزانية وهي خاملة: كيف أنقذتنا الحوسبة بدون خوادم (Serverless) من جحيم الفواتير؟

أنا أبو عمر، مبرمج فلسطيني، وأروي لكم قصتي مع الفواتير السحابية المرتفعة وكيف كانت "الحوسبة بدون خوادم" أو Serverless هي طوق النجاة. اكتشفوا معي كيف...

26 مايو، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

كان ملفي على GitHub مقبرة للمشاريع: كيف أنقذتني المصادر المفتوحة من جحيم “ليس لديك خبرة عملية”؟

هل ملفك على GitHub مليء بالمشاريع غير المكتملة؟ في هذه المقالة، أشارككم تجربتي الشخصية كأبو عمر، وكيف حولتني المساهمة في المصادر المفتوحة من مبرمج يواجه...

26 مايو، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

خدماتنا كانت تنتظر في طابور طويل: كيف أنقذتنا ‘طوابير الرسائل’ من جحيم ‘الرجاء الانتظار’؟

أشارككم قصة حقيقية من تجربتي كمبرمج، وكيف كاد مشروعنا أن يفشل بسبب بطء الاستجابة. اكتشفوا معنا كيف غيّرت "طوابير الرسائل" (Message Queues) طريقة عملنا، وحوّلت...

26 مايو، 2026 قراءة المزيد
البودكاست