ينفّذ HotXLS، مكتبة Excel الأصلية لـ Delphi وC++Builder، إعادة حساب الصيغ تزايديًا عبر TXLSXWorkbook.Recalculate. يبني الاستدعاء الأول رسمًا بيانيًا لتبعيات الصيغ ويقيّم كل خلية صيغة؛ أما كل استدعاء لاحق فلا يعيد تقييم سوى الخلايا المتأثرة بكتابة القيم منذ المرور السابق، بالترتيب الطوبولوجي، في مسحة واحدة تتناسب كلفتها مع عدد الخلايا المتّسخة لا مع حجم المصنف
وهذا القرار التصميمي الواحد هو الفارق بين نموذج مالي يستجيب لافتراض مُعدَّل في أجزاء من الألف من الثانية ونموذج يتجمّد لثوانٍ. فإذا كنت تولّد تقارير تغذّي فيها حفنة من خلايا الإدخال آلاف الصيغ التابعة، فبقية هذا المقال تشرح ما يفعله الرسم البياني، وأي الدوال تنسحب من التزايدية، وكيف يُبلَّغ عن المراجع الدائرية بدلًا من الدوران بلا نهاية
لماذا يعيد تغيير خلية واحدة حساب مئة ألف صيغة؟
لا يحتفظ محرك الصيغ الساذج بأي ذاكرة عمّن يعتمد على من، فتكون خطوته الآمنة الوحيدة بعد أي تعديل هي تقييم كل شيء من جديد. والأسوأ أن الاستراتيجية التعاودية الكلاسيكية — حين تشير الصيغة A إلى الصيغة B، قيّم B في الحال — تعيد تقييم الخلايا المُشار إليها دون شرط، متجاهلةً أي قيمة مخزّنة مؤقتًا. فسلسلة من n صيغة تشير كل واحدة منها إلى سابقتها تكلّف O(n²) عملية تقييم في كل مرور كامل، والمرجع الدائري يدفع التعاود إلى الهاوية. وكل مطوّر جداول بيانات وصل نموذجًا متتاليًا بمُقيِّم تعاودي شهد وقوع نمطَي الفشل كليهما
وقد حلّ Excel نفسه هذه المسألة قبل عقود بسلسلة الحساب لديه: ترتيب لخلايا الصيغ يُصان بحيث يضع التعديل مجموعة صغيرة من الخلايا في حالة اتساخ، فلا يسير المحرك إلا على الذيل المتأثر من السلسلة. ويطبّق HotXLS الفكرة نفسها على هيئة رسم بياني صريح للتبعيات، يُبنى مرة واحدة من أشجار الصيغ المُصرَّفة ويُعاد استخدامه عبر مرورات إعادة الحساب. والمقصود ليس البراعة؛ بل أن تتبع كلفةُ إعادة الحساب حجمَ تعديلك لا حجم مصنفك
كيف يحوّل رسم التبعيات البياني تعديلًا إلى مرور واحد
يمنح رسم التبعيات البياني في HotXLS كل خلية صيغة عقدة واحدة، وتمتد الحواف من السابق إلى التابع. فحين يكتب كودك قيمة خلية، يسجّل المصنف تلك الخلية بوصفها متّسخة؛ وحين تعمل Recalculate، ينتشر الاتساخ على امتداد الحواف إلى كل صيغة تابعة، ويُقيَّم الرسم الجزئي المتّسخ مرة واحدة بالضبط بالترتيب الطوبولوجي باستخدام خوارزمية Kahn. ولأن الصيغة لا تُزار قبل سوابقها أبدًا، تحتاج كل عقدة إلى تقييم واحد — وهذا ما يجعل كلفة المرور O(dirty)
ويعالج الترتيب الطوبولوجي كذلك مشكلة التعاود من جذرها. فأثناء مرور إعادة الحساب ينتقل المحرك إلى وضع مخصّص تقرأ فيه أي إشارة إلى خلية صيغة أخرى القيمةَ المخزّنة مؤقتًا لتلك الخلية مباشرةً بدل إعادة تقييمها — فالترتيب يضمن أن المخزون المؤقت حديث سلفًا. وتعني الآلية نفسها أن دورة المراجع لا تستطيع إطلاق تعاود بلا حدود: إذ لا شيء داخل المرور يعاود دخول المُقيِّم من أجل خلية مجاورة
var
Book: TXLSXWorkbook;
Inputs, Model: TXLSXWorksheet;
begin
Book := TXLSXWorkbook.Create;
try
Inputs := Book.Sheets.Add('Inputs');
Model := Book.Sheets.Add('Model');
Inputs.Cells[2, 2].Value := 0.05; // افتراض النمو
Model.Cells[2, 2].Formula := 'Inputs!B2*1000'; // صيغ XLSX لا تبدأ بعلامة '='
Model.Cells[3, 2].Formula := 'B2*(1+Inputs!B2)';
// ... آلاف الصفوف الأخرى تتتالى عن الافتراض نفسه ...
Book.Recalculate; // الاستدعاء الأول: يبني الرسم البياني ويقيّم كل شيء
Inputs.Cells[2, 2].Value := 0.07; // تعديل واحد يجعل خلية واحدة متّسخة
Book.Recalculate; // الاستدعاء الثاني: لا تعمل إلا السلسلة التابعة
finally
Book.Free;
end;
end;
تستقر كل نتيجة في Value المخزّنة مؤقتًا للخلية، فبعد أن تعود Recalculate تقرأ المخرجات بالطريقة نفسها التي تقرأ بها أي خلية أخرى. وفي حلقة توليد التقارير يكون النمط هو الكود أعلاه بالضبط: حمّل النموذج أو ابنِه مرة واحدة، ثم ناوب بين كتابة بضع خلايا إدخال واستدعاء Recalculate، فلا تدفع إلا ثمن الصيغ التي تعتمد فعلًا على ما تغيّر
أي دوال Excel تفرض إعادة الحساب في كل مرور؟
يعامل HotXLS الدوال NOW وTODAY وRAND وOFFSET وINDIRECT بوصفها متقلبة: فأي صيغة تحتوي إحداها يُعاد تقييمها في كل مرور من Recalculate، سواء تغيّر شيء في أعلى السلسلة أم لم يتغيّر. والثلاث الأولى متقلبة للسبب نفسه الذي يجعلها متقلبة في Excel — فنتيجتها تعتمد على لحظة التقييم لا على خلايا أخرى. أما OFFSET وINDIRECT فمتقلبتان لسبب أدقّ: الخلايا التي تقرآنها تُحسب وقت التشغيل، فلا يستطيع الرسم البياني أن يعرف بشكل ساكن أي الحواف يرسم لهما
وتمتد القاعدة المحافظة نفسها إلى المراجع التي لا يستطيع بانِي الرسم البياني تثبيتها في مستطيل واحد. فالصيغة التي تمرّ عبر نطاق مسمّى متعدد المناطق، أو التي تشير إلى مصنف خارجي، تُخفَّض كذلك إلى متقلبة ويُعاد تقييمها في كل مرور. والسياسة مقصودة: التقييم الزائد يكلّف قليلًا من الوقت، أما حافة تبعية مفقودة فتعني قيمة قديمة تمرّ بصمت في تقرير مُسلَّم، وذلك هو الفشل الأسوأ بكثير. وإذا كان نموذجك يتّكئ على أسماء بنطاق المصنف، فالمقال المرافق عن الأسماء المعرَّفة والصيغ بين الأوراق يشرح كيفية حلّ الأسماء أحادية المنطقة — وهي تشارك في الرسم البياني على نحو طبيعي
والإرشاد العملي يتبع ذلك مباشرة. أبقِ المسارات الساخنة في النموذج الكبير على مراجع الخلايا والنطاقات العادية حيث يستطيع الرسم البياني أداء عمله، واحصر OFFSET وINDIRECT في المواضع القليلة التي تحتاج فعلًا إلى العنونة الديناميكية. فالنموذج الذي يضم ألف صيغة متقلبة يعيد تشغيل تلك الألف في كل مرور مهما كان التعديل صغيرًا — وهو بالضبط السلوك الذي يعرفه مستخدمو Excel من المصنفات التي «تعيد الحساب مع كل ضغطة مفتاح»
كيف يُبلّغ HotXLS عن المراجع الدائرية؟
تُعيد TXLSXWorkbook.Recalculate القيمة lxOk عند المرور النظيف، وlxErrorRef حين تكتشف دورة مراجع. وتُحدَّد أعضاء الدورة أثناء الفرز الطوبولوجي — فهي العقد التي لا تستطيع خوارزمية Kahn تحريرها أبدًا — ويجري تخطّيها بدل الدوران فيها: تبقى قيمها المخزّنة مؤقتًا على ما كانت عليه، بينما تظل كل صيغة خارج الدورة تُقيَّم بشكل طبيعي وبالترتيب. فيحصل موضع الاستدعاء لديك على رمز خطأ محدَّد بدل التعليق
case Book.Recalculate of
lxOk:
SaveReport(Book);
lxErrorRef:
// توجد دورة مراجع؛ احتفظ أعضاء الدورة بقيمهم المخزّنة
// مؤقتًا السابقة، وكل ما هو خارج الدورة محدَّث
LogWarning('Circular reference detected - review model inputs');
end;
ومعرفة أي الخلايا تشكّل الدورة مهمة تصحيح أخطاء، ومتتبّع تقييم الصيغ هو الأداة المناسبة لها: تتبّع الصيغة المشبوهة فتظهر خطوةً خطوة سلسلة المراجع التي تنطوي على نفسها. والدورات في النماذج الواقعية تكاد تكون دائمًا خطأ تأليف — صف ملخّص أُدرج سهوًا داخل نطاق SUM الخاص به — ولذا فإن ظهور رمز خطأ صريح وقت إعادة الحساب هو تحديدًا ما تريده
صيغ المصفوفات وتتبّع الاتساخ ومتى يُعاد بناء الرسم البياني
تحصل صيغ المصفوفات من نمط CSE على عقدة واحدة للمستطيل المثبَّت كله، لا عقدة لكل خلية. فالصيغة الجذر تُقيَّم مرة واحدة في كل مرور؛ وتُكتب المصفوفة الناتجة مباشرةً في كل خلية عضو، والصيغة التي تشير إلى أي خلية داخل النطاق المثبَّت — لا إلى المرساة العلوية اليسرى وحدها — تلتقط حافة تبعية من تلك العقدة الجذر. أما النتائج القياسية فتُبثّ عبر المستطيل بالطريقة التي تفرضها دلالات المصفوفات القديمة في Excel
ويتشبّث تتبّع الاتساخ بمُعيِّنات الخصائص العادية، فلا يتغيّر شيء في كودك. فكتابة Value على خلية تُخطر المصنف وتجعل تابعيها متّسخين؛ أما إسناد Formula جديدة فتغيير بنيوي، ولذا يجعل الرسم البياني كله قديمًا، ويعيد Recalculate التالي بناءه قبل التقييم. وإضافة الأوراق أو حذفها أو نقلها يُبطل الرسم البياني أيضًا، لأن هوية العقدة تشفّر فهرس الورقة. وحين لا يوجد رسم بياني نشط — أي مصنف لا تستدعي عليه Recalculate أبدًا — تكلّف هذه الخطّافات فحص nil واحدًا لكل إسناد، فتبقى أحمال القراءة والكتابة البسيطة بلا تأثر
وثمة حدّ يستحق قوله بصراحة: يتتبّع الرسم البياني التبعيات بين الخلايا، ولذا فإن الدالة المعرَّفة من المستخدم والمسجَّلة عبر OnUserFunction يُعاد تقييمها حين تتغيّر الخلايا التي تغذّي وسائطها، شأنها شأن أي صيغة أخرى. وإذا كنت توسّع المحرك بهذه الطريقة، فالمقال عن الدوال المخصصة في محرك صيغ HotXLS يمرّ على عقد الاستدعاء الخلفي وكيفية وصول قيم الوسائط
إعادة الحساب التزايدية جزء من محرك XLSX القياسي في مكوّن HotXLS لـ Excel في Delphi، إلى جانب حاسب الصيغ والأسماء المعرَّفة وخط الاستيراد والتصدير الذي يسرّعه. وإذا كان تطبيقك بـ Delphi أو C++Builder يصون نماذج حيّة — أوراق تسعير، ومصنفات توحيد، وسلاسل تقارير — فإن Recalculate هو الفارق بين إعادة حساب مصنف وإعادة حساب تعديل