اذهب إلى المحتوى

لوحة المتصدرين

  1. Adnane Kadri

    Adnane Kadri

    الأعضاء


    • نقاط

      3

    • المساهمات

      5196


  2. سامح أشرف

    سامح أشرف

    الأعضاء


    • نقاط

      3

    • المساهمات

      2934


  3. علي عبد محسن

    علي عبد محسن

    الأعضاء


    • نقاط

      2

    • المساهمات

      651


  4. Mohamed Eid

    Mohamed Eid

    الأعضاء


    • نقاط

      2

    • المساهمات

      71


المحتوى الأكثر حصولًا على سمعة جيدة

المحتوى الأعلى تقييمًا في 10/04/22 في كل الموقع

  1. السلام عليكم اولاً شكراَ على المرور ثانياً سؤالي هو كيف ابحث عن كلمة كاملة وليس بجزء منها؟؟!!!!! لم تفهم السؤال بعد، اليس كذلك: اليك الشرح : مثلاً لدي عمود باسم col1 يحتوي على الآتي : مجد الأمة عاودني مجدداً مجدي سعيد فكرة مجدية عند تنفيذ الكود الاتي SELECT col1 FROM tab1 WHERE col1 LIKE %مجد% سيكون الناتج هو كل الصفوف السابقة لان هذا الكود يبحث عن جزء من الكلمة ، وانا اريد ان يكون الناتج هو الصف الاول فقط "مجد الأسلام" لأن كلمة مجد موجودة فيه بالكامل، اي لا اريده ان يحضر الكلمات التي تحتوي على جزء من الكلمة التي ابحث عنها فقط اريد ان تكون النتائج التي يحضرها تكون بالكلمة كاملة فقط وليس بالكلمات التي تحتوي على جزء من كلمة البحث. قمت بتجربه هذا الكود SELECT col1 FROM tab1 WHERE col1 LIKE % مجد % OR col2 LIKE مجد % OR col2 LIKE % مجد اولا استخدمت مسافة قبل وبعد الكلمة وايضا احتمال ان تكون الكلمة في بدايه الجملة ونهايتها ، كان جيدا ويعمل 100% ، لكن ظهرت مشكلة علامات الترقيم ، هناك علامات كثيرة تستخدم في الجمل وستخرج احتمالات كثيرة ، هل الحل هو باستخدام هذه الطريقه واستخدام جميع احتمالات علامات الترقيم؟ ام ان هناك حل اخر؟! وشكرا لكم
    2 نقاط
  2. مرحبا انا عندي 14 سنه و اخدت الشهاده الخاصة بدورة تطوير واجهات المستخدم وانا الان في دورة تطوير باستخدام js و تعلمت فيها react, nodejs, js ولكن مينفعش اشتغل فماذا ترشحو لي التعلم حتي اوصل لسن 18 سنه ؟
    1 نقطة
  3. عند مراجعتي لمشاريع الاكادمية الموجودة على githup الخاصة ب laravel وجدت انه تم تطويرها ب laravel 7 ومع العلم انه تم اضافة تحديثات جوهرية في laravel 8,9 هل تم تحديث المشاريع ام مازالت على نفس الاصدار
    1 نقطة
  4. السلام عليكم... تحية طيبة للجميع. وبعد: عندي صفحة مثلا على موقع اسفله: mysite1.com/video.php وانا اريد ان يظهر محتوى هذه الصفحة على موقع واحد مثلا : mysite2.com/iframe.html ومنع الظهور على باقي المواقع الاخرى . ملاحظة جربت الكثير من اكواد php و htaccess تقوم بالغرض ولكن هناك ثغرة تزيف الدومين المسموح به بواسطة php curl referer فهل من حل وشكرا.....
    1 نقطة
  5. سلام عليكم انا عملت slider بالجافاسكريبت ومش عارف ايه الغلط اللى انا عامله ومش عارف يتصحح ازاى اتمنى حد يساعدنى دى ملفات المشروع HafsQuran.rar
    1 نقطة
  6. الملاحظ لديك في الصور أنها بأحجام مختلفة بما أنك تقوم بعرضهم دون أخفاء الشريحة السابقة تظهر لك وكأنها صورة فوق الأخرى واحدة أطول من الأخرى إعتماداً على حجم كل صورة. يمكنك حل خذه المشكلة من خلال css فقط من خلال إضافة سطرين للصنف img-slider وكما يلي .img-slider { ... height: 15em; /* نعطي طول ثابت للصور مثلاً هذا الطول أو حسب ما تجده مناسب للتصميم*/ object-fit: cover; /* أيضاً تحتاج وضع هذا الخيار الذي يعدل وضع الصورة آلياً في الشريحة دون تشويه*/ ... }
    1 نقطة
  7. تحيه طيبه للجميع ما هيا الأنواع التي يمكن استعمالها لتحقق من شخصية صاحب الحساب او من يحاول التسجيل في تطبيقي او ما يسمى (Human verification) ما هيا الطرق المتوفرة للعمل مع flutter? وهل طريقة slide_to_confirm فعالة لمثل هذا الامر ؟ ياليت لو فيه طريقة بسيطة بدون استعمال مكتبة
    1 نقطة
  8. يمكنك من هذا السلوك فهم أن الحاوية الخاصة بالصورة والخاصة بالنص لا يتم اخفاءهما وإنما يتم حقن الصورة والفقرة فوقهما بشكل مباشر. بشكل يجعلها تظهر وكأنها فوق بعضها. ولتلافي هاته المشكلة، لنبحث عن السطر الذي نقوم فيه بحقن محتوى الى الحاوية بهما ونتأكد من تفريغها من اي محتوى سابق قبل حقنها بالمحتوى الجديد. من ملف main.js نبحث عن: sliderContainer.innerHTML += arrimg[i]; sliderContainer.innerHTML += `<p id="p-${i}">${i + 1} / ${arrimg.length}</p>`; ونكتب قبلهما مباشرة السطر: sliderContainer.innerHTML = ''; // ++++++++++++++++ sliderContainer.innerHTML += arrimg[i]; sliderContainer.innerHTML += `<p id="p-${i}">${i + 1} / ${arrimg.length}</p>`; ستلاحظ اختفاء المشكلة عند الضغط على زر next ولكن نفس الشيء سيبقى مستمرا عند الضغط على زر pre. نتفحص الشيفرات الخاصة بهما ونرى أننا لا نحقن اصلا اي محتوى في الحاوية، أرى أنك تقوم بتعليق الأسطر الخاصة بها لسبب ما: // sliderContainer.innerHTML += arrimg[i]; // sliderContainer.innerHTML += ` <p id="p-${i}"> ${i + 1} / ${ // arrimg.length // }</p> `; الغ تعليق هاته الأسطر، ولا تنسى اضافة السطر: sliderContainer.innerHTML = ''; قبلهما مباشرة ايضا. يفترض ان يحل هذا المشكل لديك.
    1 نقطة
  9. مع الضغط اكتر من مرة على next & previous بيحصل ان الصور بتظهر فوق بعضها وكمان ترقيم الصور بيتراكم فوق بعضه مع الضغط اكتر من مرة على next & previous بيحصل ان الصور بتظهر فوق بعضها وكمان ترقيم الصور بيتراكم فوق بعضه
    1 نقطة
  10. ما هي المشكلة التي تحدث تحديدا؟ تفحصت عارض الشرائح الخاص بك ولا يبدوا به اي مشكلة.
    1 نقطة
  11. وايضا بالنسبة للمشاريع العملية هناك بعض المشاريع التي تعتبر في مثل هذا الوقت احد المتطلبات مثل reltimeApp هل ستقوم الاكادمية باضافته او ستقوم باضافة مشاريع اضافية اخرى .
    1 نقطة
  12. للأسف لا يمكن ذلك إلا بحزمة أو serive تقدمها أحد الشركات مثل google recaptcha ، وأنا أنصحك ب f_grecaptcha يسمح هذا المكون الإضافي flutter البسيط باستخدام SafetyNet API على أجهزة Android للتحقق من أن المستخدم إنسان. كيفية إستعمالها في تطبيقك قم بتضمين المكون الإضافي في تبعيات مشروعك عن طريق تضمينه في القسم ذي الصلة من pubspec.yaml الخاص بك: dependencies: f_grecaptcha: ^1.0.0 بعد ذلك ، ستحتاج إلى تسجيل تطبيقك في وحدة تحكم مسؤول reCAPTCHA. انتقل إلى https://www.google.com/recaptcha/admin#list وقم بالتسجيل عن طريق ملء النموذج. تأكد من تحديد "reCAPTCHA-Android" كنوع. بعد تسجيل تطبيقك في reCAPTCHA API ، يمكنك استدعاء الطريقة التالية في أي مكان في رمز dart الخاص بك ، والأكثر شيوعًا بعد الضغط على الزر. استبدل SITE_KEY بمفتاح الموقع الذي تعرضه واجهة المسؤول بعد تسجيل تطبيقك. FGrecaptcha.verifyWithRecaptcha(SITE_KEY).then((result) { // يمكنك إرسال رمز النتيجة ، مع بعض حقول النموذج ، إلى ملف // reCAPTCHA API for servers, see https://developers.google.com/recaptcha/docs/verify }, onError: (e, s) { // لا يعني الخطأ أن المستخدم ليس بشريًا. أخطاء // يمكن أن يحدث أيضًا عندما يكون مفتاح الموقع غير صالح أو لا يتطابق مع // التطبيق ، عندما يكون الجهاز غير مدعوم أو عندما تكون الشبكة // يحدث خطأ. // يجب عليك إبلاغ المستخدم بالأخطاء ، وشرح سبب عدم تمكنه من ذلك // تخطي خطوة reCAPTCHA عندما يكون FGrecaptcha.isAvailable خاطئًا. print("لم نتمكن من التحقق:\n $e at $s"); } );
    1 نقطة
  13. يتم الإشارة بشكل دائم الى التحديثات الجوهرية خلال مسارات التطبيق العملي بجانب تحديث المستودعات الخاصة بها على github مثل مسار أساسيات لارافيل ومسار تطوير متجر لبيع الكتب واللذان يستعملان النسخة 8 منه. أما بالنسبة لتلك التي لا تؤثر في طريق سير المشروع او طريقة عمله فلا يتم الإشارة إلى تحديثها لأنها تستعمل مكونات ومعمارية لارافيل الأساسية وهاته الأخيرة ثابتة نسبيا منذ زمن ولم تطرأ عليها أي تغييرات. يتم ايضا خلال المسارات العملية تصويب بعض المشاكل التي تظهر بسبب استعمال نسخة غير تلك التي يستعملها المدرب من قبل فريق كامل من المدربين متواجد بشكل دائم للمتابعة مع الطلبة.
    1 نقطة
  14. كيفية عمل مصادقة مخصصة ب django rest framework
    1 نقطة
  15. لعمل custom permission بواسطة django rest framework يرجى إتباع هذه الخطوات إنشاء ملف نسميه permission.py ثم نستدي المكتبات التي تتيح لنا عمل هذه الخاصية from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): """ مثال على مصادقة أو إذن حيث يسمح فقط لمالك الحساب او بما يسمى owner """ def has_object_permission(self, request, view, obj): # يُسمح بأذونات القراءة لأي طلب مسموح if request.method in permissions.SAFE_METHODS: return True # يجب أن يكون الكائن هو نفسه return obj.owner == request.user بحيث يُسمح فقط لمالك الحساب على التعديل مثلا على البروفيل الخاص به ولا يُسمح لأي أحد بفعل ذلك .
    1 نقطة
  16. ليس لدي الخبرة الكافية في مجال الـ UI/UX ولكن يمكنك العثور على الكثير من الدورات في هذا المجال. ولكن هنالك مجموعة مقالات مفصلة في هذه المجالات على موقع الأكاديمية : مقالات عن تصميم الـ UX مقالات تصميم الـ UI ويمكنك البدء من هذا المقالة :
    1 نقطة
  17. هذا الملف قمت بانشاءه و محتواه الاكواد البرمجية الخاصة بتاثير الانعكاس للصور و انعكاس لكتابة الجملة , حيث يمكن ببساطة انشاء لونين احدهما اسود يكتب فيه النص والاخر فضي للظل والنتيجة الاكواد التي كتبتها تقول تعذر عرض الصورة لا اعرف ما المشكلة بها ؟ افيدوني وجزاكم الله خيرا create_name.php
    1 نقطة
  18. أعتقد لو تعاملت بين الموقعين باستخدام api سيكون أفضل بكثير ، بحيث الموقع الأول يقوم بإرسال ما يريده للموقع الثاني من خلال API و الموقع الثاني يقوم بالتعامل مع البيانات المرجعة من api و عرضها كما يريد، هذا برأيي أفضل شيء لمنع استخدام بيانات الموقع الأول إلا في الموقع الثاني
    1 نقطة
  19. يمكنك العمل على مشاريع خاصة بك مثل بناء مواقع وبيعها على بيكاليكا، أو البحث عن عمل عند الأقارب والأشخاص المحيطين بك وقم بعرض خدماتك عليهم، وكذلك يمكنك البحث على عمل حر على الانترنت في مواقع التواصل الخاصة بالعمل مثل linked in. اما بالنسبة لموضوع التعلم فيمكنك النظر في مخططات الطرق التالية واكمال تعلم التفاصيل التي لم تتعلمها بعد : بالنسبة للـ frontend : بالنسبة للـ backend : وكذلك سيفيدك الاطلاع على هذه المقالة في إكمال طريقك في تعلم برمجة المواقع :
    1 نقطة
  20. تُعدّ القائمة المترابطة linked list نوعًا خاصًا من بنى البيانات data structure، حيث تتكوَّن من مجموعة كائناتٍ مربوطةٍ مع بعضها بعضًا باستخدام مؤشرات pointers. استخدمنا في المقال السابق قائمةً مترابطةً لتخزين قائمةٍ مرتّبةٍ من السلاسل النصية من النوع String، كما نفَّذنا عمليات الإضافة والحذف والبحث على تلك القائمة. ومع ذلك، كان بإمكاننا بسهولة تخزين قائمة السلاسل النصية بمصفوفةٍ أو كائنٍ من النوع ArrayList بدلًا من استخدام قائمةٍ مترابطة، وكنا سنتمكَّن من إجراء نفس العمليات على القائمة. على الرغم من أن تنفيذ تلك العمليات سيكون مختلفًا بالتأكيد، إلا أن الواجهات interfaces المُستخدمة والسلوك المنطقي لتلك العمليات سيكون نفسه. يُشير مصطلح نوع بيانات مجرد abstract data type -أو اختصارًا ADT- إلى مجموعة قيمٍ محتملة ومجموعة من العمليات المسموح بتطبيقها على تلك القيم دون أي تخصيصٍ لطريقة تمثيل تلك القيم أو طريقة تنفيذ تلك العمليات، حيث يُمكِننا مثلًا تعريف قائمةٍ مُرتَّبةٍ من السلاسل النصية باستخدام نوع بياناتٍ مجرد، على أنها أي متتاليةٍ من السلاسل النصية من النوع String شرط أن تكون مُرتَّبةً ترتيبًا تصاعديًا، وبحيث يُمكِننا إجراء عملياتٍ عليها، مثل إضافة سلسلةٍ نصيةٍ جديدة، أو حذف سلسلةٍ نصية، أو البحث عن سلسلةٍ نصيةٍ ضمن القائمة. يُمكِنك بالتأكيد تنفيذ نفس نوع البيانات المُجرَّد ADT باستخدام طرقٍ مختلفة، فمثلًا قد تُنفِّذ قائمة السلاسل النصية المُرتَّبة باستخدام قائمة مترابطة أو مصفوفة array. في الواقع، تعتمد البرامج المُستخدِمة لهذا النوع من البيانات على التعريف المجرد للنوع، وبالتالي يُمكِنها استخدام أيٍ من تنفيذاتها implementation المُتوفِّرة، كما يُمكِنها التبديل بينها بسهولة؛ وهذا يعني أنه من الممكن تبديل التنفيذ الخاص بنوع بياناتٍ مجردٍ معين دون التأثير على البرنامج، ويُسهِّل ذلك من عملية تنقيح أخطاء debug البرامج. تُعدّ أنواع البيانات المجردة ADTs عمومًا واحدةً من الأدوات الهامة بهندسة البرمجيات software engineering. سنناقش في هذا المقال نوعين من أنواع البيانات المجردة، هما المكدس stack والرتل queue، حيث تُستخدَم عادةً القوائم المترابطة لتنفيذ كليهما، ولكنها بالتأكيد ليست التنفيذ الأوحد. يُمكِنك النظر للجزء المتبقي من هذا القسم على أساس كونه مناقشةً عن الأكداس والأرتال من ناحية؛ وعلى أساس كونه دراسة حالة case study لأنواع البيانات المجردة من الناحية الأخرى. 9.3.1: المكدسات Stacks يتكوَّن المكدس stack من متتاليةٍ من العناصر التي يُمكِننا النظر إليها على أنها مجموعةٌ من العناصر المتراكمة فوق بعضها بعضًا، حيث يُمكننا الوصول مباشرةً وفي أي لحظة إلى العنصر الموجود بالأعلى، كما يُمكِننا حذفه من المكدس باستخدام عمليةٍ تُعرَف باسم السحب pop، ويُمكِننا في المقابل حذف عنصرٍ آخر أسفل المكدس فقط بعد سحب pop جميع العناصر الواقعة أعلى ذلك العنصر من المكدس. من الممكن أيضًا إضافة عنصرٍ جديدٍ أعلى المكدس باستخدام عمليةٍ تُعرَف باسم الدفع push. يُمكِن لعناصر المكدس أن تكون من أي نوع، فإذا كانت العناصر قيمٌ من النوع int مثلًا، فيمكن تنفيذ عمليتي السحب pop والدفع push كما في توابع النسخ instance methods التالية: void push(int newItem)‎: لإضافة عنصرٍ جديد newItem أعلى المكدس. int pop()‎: لحذف العنصر الموجود أعلى المكدس وإعادته. لاحِظ أنه من الخطأ أن تحاول سحب pop عنصرٍ من مكدسٍ فارغ، ولذلك عليك أن تختبر أولًَا فيما إذا كان المكدس فارغًا أم لا، وسنحتاج بالتالي إلى عمليةٍ أخرى لإجراء ذلك الاختبار، والتي يُمكِننا تنفيذها باستخدام تابع النسخة التالي: boolean isEmpty()‎: يعيد القيمة true إذا كان المكدس فارغًا. نكون الآن قد عرَّفنا مكدسًا من الأعداد الصحيحة على أنه نوع بيانات مجرد abstract data type - ADT، ويُمكِننا تنفيذه بعدة طرقٍ شرط أن يَكُون سلوكه العام مكافئًا للتصور المُجرَّد للمكدس. لدى تنفيذ المكدس باستخدام القوائم المترابطة linked list، ستكون عقدة رأس القائمة head أعلى المكدس. تُعدّ إضافة العقد إلى مقدمة قائمةٍ مترابطة أو حذفها من المقدمة أسهل بكثير بالموازنة مع الإضافة أو الحذف من منتصف قائمة مترابطة. يَستخدِم الصنف بالشيفرة التالية قائمةً مترابطةً لتنفيذ النوع المجرد ADT مكدس أعداد صحيحة. لاحِظ تعريف الصنف المُوضّح بالأسفل لصنفٍ متداخلٍ nested ساكن static لتمثيل عقد القائمة المترابطة، وهو ما يُعدّ جزءًا من التنفيذ الخاص private implementation للنوع المجرد. public class StackOfInts { private static class Node { int item; Node next; } // مؤشر إلى العقدة الموجودة أعلى المكدس // ‫إذا كان top يُساوِي القيمة الفارغة، فإن المكدس يكون فارغًا private Node top; // ‫أضف N إلى أعلى المكدس public void push( int N ) { Node newTop; // العقدة التي ستحمل العنصر الجديد newTop = new Node(); newTop.item = N; // ‫خزِّن N بالعقدة الجديدة newTop.next = top; // تشير العقدة الجديدة إلى العقدة التي كانت تحتل موضع أعلى المكدس مسبقًا top = newTop; // تُصبح العقدة الجديدة أعلى المكدس } // 1 public int pop() { if ( top == null ) throw new IllegalStateException("Can't pop from an empty stack."); int topItem = top.item; // العنصر المسحوب من المكدس top = top.next; // العنصر الموجود أعلى المكدس حاليًا return topItem; } // 2 public boolean isEmpty() { return (top == null); } } // end class StackOfInts [1] احذف العنصر الموجود في أعلى المكدس، ثم أعده. إذا كان المكدس فارغًا، بلِّغ عن حدوث اعتراض من النوع IllegalStateException. [2] أعد القيمة المنطقية true إذا كان المكدس فارغًا؛ أما إذا كان يحتوي على عنصرٍ واحد أو أكثر، أعد القيمة المنطقية false. عليك التأكُّد من فهم طريقة عمل عمليتي السحب pop والدفع push بالقائمة المترابطة، وقد يُساعدك رسم بعض الصور على ذلك. تُمثّل القائمة المترابطة بالأعلى جزءًا من التنفيذ الخاص للصنف StackOfInts، أي لا يحتاج البرنامج الذي يَستخدِم ذلك الصنف إلى معرفة حقيقة كونه يستخدم قائمةً مترابطة. يُمكننا الآن أيضًا تنفيذ المكدس باستخدام مصفوفةٍ بدلًا من قائمةٍ مترابطة، حيث سنحتاج إلى عدّاد counter لمعرفة عدد الخانات المُستخدَمة فعليًا بالمصفوفة، لأن عدد عناصر المكدس يختلف من وقتٍ لآخر. بفرض أن اسم العداد هو top، فستكون عناصر المكدس مُخزَّنةً بمواضع المصفوفة 0، و1، … حتى top-1، ويتواجد العنصر بالموضِع 0 أسفل المكدس بينما يتواجد العنصر بالموضِع top-1 أعلى المكدس. من السهل دفع push عنصرٍ إلى المكدس على النحو التالي: ضع العنصر بالموضِع top وزِد قيمة top بمقدار الواحد. وإذا لم نكن نريد وضع حدٍ أقصى لعدد العناصر التي يستطيع المكدس أن يحملها، يُمكِننا استخدام مصفوفةٍ ديناميكية dynamic array، التي كنا قد تعرَّضنا لها بالقسم الفرعي 7.2.4. يعرض التصور النموذجي للمصفوفة المكدس مقلوبًا رأسًا على عقب؛ أي سيظهر أسفل المكدس بأعلى المصفوفة، وهذا في الواقع غير مهم؛ فالمصفوفة هي مجردُ تنفيذٍ للفكرة المجرَّدة للمكدس، وطالما تجري العمليات على المكدس كما ينبغي لها، فليس هناك أي داعٍ للقلق. تعرض الشيفرة التالية تنفيذًا آخرًا للصنف StackOfInts باستخدام مصفوفةٍ ديناميكية. import java.util.Arrays; // Arrays.copyOf() من أجل تابع public class StackOfInts { // (إصدار بديل، استعمال مصفوفة) private int[] items = new int[10]; // مصفوفة لحمل عناصر المكدس private int top = 0; // عدد العناصر الموجودة بالمكدس حاليًا // ‫أضف N إلى أعلى المكدس public void push( int N ) { if (top == items.length) { // إذا كانت المصفوفة ممتلئة، أنشِئ واحدة جديدة أكبر حجمًا // وانسخ العناصر الموجودة بالمكدس حاليًا إليها items = Arrays.copyOf( items, 2*items.length ); } items[top] = N; // ‫ضع N بالموضِع المتاح التالي top++; // أزد عدد العناصر بمقدار الواحد } // 1 public int pop() { if ( top == 0 ) throw new IllegalStateException("Can't pop from an empty stack."); int topItem = items[top - 1]; // العنصر الموجود أعلى المكدس top--; // انقص عدد العناصر الموجودة بالمكدس بمقدار الواحد return topItem; } // 2 public boolean isEmpty() { return (top == 0); } } // end class StackOfInts [1] احذف العنصر الموجود أعلى المكدس، ثم أعده مثل قيمةٍ للدالة، وإذا كان المكدس فارغًا، بلِّغ عن حدوث اعتراض من النوع IllegalStateException. [2] أعد القيمة المنطقية true إذا كان المكدس فارغًا؛ أما إذا كان يحتوي على عنصرٍ واحدٍ أو أكثر، أعد القيمة المنطقية false. نُعيد التأكيد على أن تنفيذ المكدس على هيئة مصفوفة شأنٌ خاصٌ بالصنف؛ وهذا يعني أنه من الممكن استبدال نسختي الصنف StackOfInts المُعرفتين بالأعلى بعضهما ببعض دون أي مشكلة، لأن واجهتهما العامة public interface متطابقة. يُمكِننا الآن تحليل زمن تشغيل run time العمليات على المكدس (ألقِ نظرةً على القسم 8.5)، حيث يمكننا قياس حجم المشكلة من خلال عدد العناصر الموجودة بالمكدس؛ فبالنسبة للتنفيذ الذي يَستخدِم قائمةً مترابطة، فإن زمن تشغيل الحالة الأسوأ worst case لعمليتي الدفع push والسحب pop يُساوِي Θ(1)‎، وهذا يعني أن زمن التشغيل أقل من قيمةٍ ثابتةٍ معينة لا تعتمد على عدد عناصر المكدس. يُمكِنك رؤية ذلك بوضوحٍ من خلال الشيفرة ذاتها؛ فتنفيذ العمليتين مُكوَّنٌ من عدة تعليمات إسناد assignment قليلة وبسيطة، وعدد عناصر المكدس ليس له أي دور. أما بالنسبة للتنفيذ المُعتمِد على مصفوفة، فهناك حالةٌ خاصة تَحدُث أحيانًا أثناء عملية الدفع push، تحديدًا عندما تكون المصفوفة ممتلئة، حيث يُنشِئ الصنف في تلك الحالة مصفوفةً جديدةً ويَنسَخ جميع عناصر المكدس إلى تلك المصفوفة الجديدة. يستغرق ذلك وقتًا يتناسب طرديًا مع عدد عناصر المكدس. لذلك، على الرغم من أن زمن تشغيل عملية الدفع push يُساوِي عادةً Θ(1)‎، فإنه في الحالة الأسوأ worst case يُساوِي Θ(n)‎، حيث تمثّل n عدد العناصر الموجودة بالمكدس. نظرًا لندرة حدوث الحالة الأسوأ، يُمكِننا أن نقول أن زمن تشغيل الحالة المتوسطة average case هو Θ(1)‎. 9.3.2: الأرتال Queues على نحوٍ مشابه من المكدس، يتكوَّن الرتل queue من متتاليةٍ من العناصر كما يوجد عددٌ من القيود بخصوص الكيفية التي نُضيف بها العناصر إلى الرتل أو نحذفها منه؛ وبخلاف المكدس، فإن الرتل لديه طرفين هما الطرف الأمامي والخلفي، حيث تُضاف العناصر دائمًا إلى الطرف الخلفي من الرتل، بينما تُحذَف من طرفه الأمامي. سنُطلِق على عملية إضافة العناصر إلى الرتل اسم إدراج enqueue، بينما سنُطلِق على عملية حذف العناصر منه اسم "سحب dequeue*، مع الملاحظة بأن هذه التسميات ليست قياسيةً بخلاف عمليتي *الدفع push* والسحب pop. عند إضافة عنصر إلى الطرف الخلفي من الرتل، فإنه يبقى بالرتل إلى أن تُحذَف جميع العناصر التي تسبقه، ويُعدّ ذلك أمرًا بديهيًا؛ فالرتل queue بالنهاية يُشبه خطًا أو صفًا من العملاء المنتظرين لخدمةٍ معينة، حيث تُقدَم الخدمة للعملاء بنفس ترتيب وصولهم إلى الصف. يُمكِن لعناصر الرتل أن تكون من أي نوع، فمثلًا قد يكون لدينا رتلًا من النوع العددي الصحيح int، كما يُمكِن تنفيذ عمليتي الإدراج enqueue والسحب dequeue كأنها توابع نسخ instance methods داخل تعريف الصنف QueueOfInts. علاوةً على ذلك، سنحتاج أيضًا إلى تابع نسخة آخر لاختبار فيما إذا كان الرتل queue فارغًا أم لا: void enqueue(int N)‎: يضيف عنصرًا جديدًا N إلى الطرف الخلفي من الرتل. int dequeue()‎: يَحذِف عنصرًا من الطرف الأمامي من الرتل ويعيده. boolean isEmpty()‎: يُعيد القيمة true إذا كان الرتل فارغًا. يمكننا تنفيذ الرتل باستخدام قائمةٍ مترابطة أو مصفوفة، ومن الممكن أن يكون استخدام مصفوفةٍ لتنفيذ الرتل أعقد قليلًا من استخدامها لتنفيذ المكدس stack، ولهذا لن نتناوله هنا، وسنكتفي بالتنفيذ المبني على قائمةٍ مترابطة. يُمثِل العنصر الأول بقائمةٍ مترابطة الطرف الأمامي من الرتل، بينما يُمثِل العنصر الأخير بالقائمة الطرف الخلفي من الرتل، وتشبه عملية سحب dequeue عنصرٍ من الطرف الأمامي للرتل عملية سحب pop عنصرٍ من المكدس. في المقابل، تتضمَّن عملية إدراج enqueue عنصرٍ جديدٍ إلى الرتل ضبط المؤشر pointer الموجود بآخر عقدةٍ ضمن القائمة؛ لكي يُشير إلى عقدةٍ جديدةٍ تحتوي على العنصر المطلوب إضافته. يُمكِننا إجراء ذلك بتنفيذ الأمر tail.next = newNode;‎، حيث تمثّل tail مؤشرًا لآخر عقدةٍ ضمن القائمة المترابطة. بفرض أن head مؤشرٌ لأول عقدةٍ ضمن قائمةٍ مترابطة، يُمكننا استخدام الشيفرة التالية للحصول على مؤشرٍ لآخر عقدةٍ ضمن تلك القائمة: Node tail; // سيشير إلى العنصر الأخير ضمن القائمة tail = head; // ابدأ من العقدة الأولى while (tail.next != null) { tail = tail.next; // تحرَك إلى العقدة التالية } // 1 [1] بوصولنا إلى تلك النقطة من البرنامج، فإن tail.next يكون فارغًا مما يَعنِي أن tail يُشير إلى آخر عقدةٍ بالقائمة. ومع ذلك، سيكون من غير المناسب فعل ذلك في كل مرةٍ نرغب فيها بإضافة عنصرٍ جديدٍ إلى الرتل. لزيادة كفاءة الشيفرة بالأعلى، سنُعرِّف متغير نسخة instance variable يحتوي على مؤشرٍ لآخر عقدةٍ ضمن القائمة المترابطة، وهذا سيُعقِّد الصنف QueueOfInts بعض الشيء؛ وبالتالي علينا الانتباه إلى ضرورة تحديث قيمة ذلك المتغير أينما أضفنا عقدةً جديدةً إلى نهاية القائمة. يُمكِننا إذًا إعادة كتابة الصنف QueueOfInts على النحو التالي: public class QueueOfInts { /** * ‫كائنٌ من النوع Node * يحمل أحد العناصر الموجودة ضمن القائمة المترابطة الممثلة للرتل */ private static class Node { int item; Node next; } private Node head = null; // يشير إلى العنصر الأول ضمن القائمة private Node tail = null; // يُشير إلى العنصر الأخير ضمن القائمة // أضف‫ N إلى الطرف الخلفي من الرتل public void enqueue( int N ) { Node newTail = new Node(); // العقدة التي تحمل العنصر الجديد newTail.item = N; if (head == null) { // 1 head = newTail; tail = newTail; } else { // تُصبِح العقدة الجديدة هي ذيل القائمة بينما لا يتأثر رأسها tail.next = newTail; tail = newTail; } } // 2 public int dequeue() { if ( head == null) throw new IllegalStateException("Can't dequeue from an empty queue."); int firstItem = head.item; head = head.next; // أصبح العنصر الثاني مسبقًا العنصر الأول بالرتل if (head == null) { // 3 tail = null; } return firstItem; } // ‫أعد القيمة المنطقية `true` إذا كان الرتل فارغًا boolean isEmpty() { return (head == null); } } // end class QueueOfInts [1] بما أن الرتل كان فارغًا، ستصبح العقدة الجديدة العقدة الوحيدة الموجودة بالقائمة؛ ونظرًا لكونها العقدة الأولى والأخيرة، سيشير head وtail إليها. [2] احذِف العنصر الموجود بمقدمة الرتل وأعده مثل قيمةٍ للتابع. في حال كان الرتل فارغًا، بلِّغ عن اعتراضٍ من النوع IllegalStateException. [3] أصبح الرتل فارغًا، وبما أن العقدة التي حذفناها كانت بمثابة عقدة الرأس head والذيل tail، فلم يَعُدّ هناك ذيلٌ للقائمة في الوقت الحالي. لفهم الدور الذي يلعبه المؤشر tail بالأعلى، يمكنك التفكير وفقًا لقاعدة الأصناف اللامتغايرة class invariant (أو الصنف اللامتغاير)، التي تعرَّضنا لها بالقسم الفرعي 8.2.3 والتي تنص على: "إذا لم يكن الرتل queue فارغًا، فإن tail يُشير إلى آخر عقدةٍ بالرتل". لا بُدّ أن يكون اللامتغاير صحيحًا ببداية ونهاية كل عملية استدعاءٍ للتابع. إذا طبَقنا تلك القاعدة على التابع enqueue في حالة القوائم غير الفارغة، يُخبرنا اللامتغاير بأننا نستطيع إضافة عقدةٍ جديدةٍ إلى نهاية القائمة بتنفيذ تعليمة مثل tail.next = newNode، كما يُخبرنا بكيفية ضبط قيمة tail قبل العودة من التابع، وجعله يُشير إلى العقدة الجديدة التي أُضيفَت للتو إلى الرتل. يستخدم الحاسوب الأرتال queues عندما يرغب بمعالجة عنصرٍ واحدٍ فقط بكل مرة في حين تنتظر عناصرٌ أخرى دورها للمعالجة. نستعرض فيما يلي بعض الأمثلة على ذلك: برامج جافا المُستخدِمة لعدِّة خيوط threads، حيث يحتفظ الحاسوب بالخيوط التي تطمع بزمنٍ للمعالجة من قِبَل وحدة المعالجة المركزية CPU داخل رتل. عند إنشاء خيطٍ جديد، سيُضيفه الحاسوب إلى الطرف الخلفي من ذلك الرتل. تسير الأمور على النحو التالي: يَحذِف الحاسوب خيطًا من الطرف الأمامي للرتل ثم يُعطِيه بعضًا من زمن المعالجة، وإذا لم ينتهي بعدها، يُرسِله الحاسوب مُجددًا إلى الطرف الخلفي من الرتل لينتظر دوره مرةً أخرى. تُخزَّن الأحداث events، مثل نقرات الفأرة وضغطات لوحة المفاتيح داخل رتلٍ اسمه "رتل الأحداث event queue"، ويَحذِف برنامجٌ معينٌ الأحداث الموجودة بذلك الرتل ويُعالِجها واحدًا تلو الآخر. يُمكِن وقوع أحداثٍ كثيرة أخرى بينما يُعالِج البرنامج حدثًا معينًا، ولكن نظرًا لأنها تُخزَّن تلقائيًا داخل ذلك الرتل، فإن البرنامج يُعالِجها دائمًا بنفس ترتيب حدوثها. يستقبل خادم الويب web server طلباتٍ من المتصفحات للوصول إلى بعض الصفحات. بطبيعة الحال، تَصِل طلبات جديدة بينما يُعالِج خادم الويب طلبًا سابقًا؛ لذلك تُوضَع الطلبات الجديدة برتلٍ وتنتظر إلى أن يحين دورها للمعالجة، حيث يَضمَن الرتل معالجة الطلبات بنفس ترتيب وصولها. تُنفِّذ الأرتال سياسة الداخل أولًا، يخرج أولًا FIFO؛ بينما تُنفِّذ الأكداس stacks سياسة الداخل آخرًا، يخرج أولًا LIFO. على نحوٍ مشابهٍ من الأرتال، يُمكِن اِستخدَام الأكداس لحمل العناصر التي تنتظر أن يحين دورها للمعالجة، على الرغم من أنها قد لا تكون عادلة ببعض الأحيان. لتوضيح الفرق بين المكدس stack والرتل queue، سنناقش البرنامج التوضيحي DepthBreadth.java. حاول تشغيل البرنامج، فهو يَعرِض شبكة مربعاتٍ مُلوَّنةٍ جميعها باللون الأبيض على نحوٍ مبدئي، وعندما يَنقُر المُستخدِم على مربعٍ أبيض، سيَضِع البرنامج علامةً على ذلك المربع، ثم يُحوِّله إلى اللون الأحمر. بعد ذلك، سيبدأ البرنامج بوضع علاماتٍ على أي مربعٍ مُتصِلٍ أفقيًا أو رأسيًا مع أيٍ من المربعات التي سَبَقَ للبرنامج وضع علامةٍ عليها. بالنهاية، ستُطبَق تلك العملية على جميع مربعات الشبكة. لنتمكَّن من فهم طريقة عمل البرنامج، سنحاول أن نضع أنفسنا مكان البرنامج: عندما يَنقُر المستخدم على مربع، لنتخيل أننا سنحصل على بطاقةٍ مكتوب عليها موضع ذلك المربع أي رقمي الصف والعمود. سنَضَع بعدها تلك البطاقة في كومة pile، والتي ستَتَضمَّن الآن بطاقةً واحدةً فقط. سنُكرِّر بعد ذلك ما يلي: إذا كانت الكومة فارغةً، نكون قد انتهينا؛ أما إذا لم تكن فارغة، فسنسحب إحدى البطاقات التي تُقابِل إحدى مربعات الشبكة. سنَفْحَص جميع المربعات المجاورة أفقيًا ورأسيًا لذلك المربع؛ فإذا لم نَكن قد مررنا بأيٍ من المربعات المجاورة من قبل، سنُدوِن موضعه على بطاقةٍ جديدة ونضعها بالكومة، ونكون قد انتهينا عندما لا يكون هناك أي بطاقات أخرى بالكومة تنتظر المعالجة. بهذا البرنامج: عندما يكون هناك مربعٌ بالكومة بانتظار المعالجة، يكون ملوَّنًا باللون الأحمر؛ وبالتالي تُمثِل المربعات الملونة بالأحمر تلك المربعات التي مررنا عبرها ولم نعالجها بعد. يتبدل لونه إلى اللون الرمادي بعد أن نأخذ المربع من الكومة ونعالجه، وبمجرد حدوث ذلك، فإن البرنامج يتخطاه دائمًا ولن يحاول معالجته مرة أخرى؛ لأن جميع المربعات المجاورة له أخذت بالحسبان بالفعل. بالنهاية، سيُعالِج البرنامج جميع المربعات، أي سيتحول لونها جميعًا إلى اللون الرمادي، وسينتهي البرنامج. يمكنك تشغيل البرنامج باستخدام إحدى الخيارات الثلاثة التالية: المكدس stack أو الرتل queue أو عشوائيًا، وسيتبّع البرنامج نفس النهج العام أيًا كان الخيار، حيث أن الفرق الوحيد بينها هو أسلوب إدارة كومة البطاقات؛ فبالنسبة للمكدس، ستُضاف البطاقات وتُحذَف بأعلى الكومة؛ أما بالنسبة للرتل، ستُضاف البطاقات إلى أسفل الكومة وتُحذَف من أعلى الكومة؛ وبالنسبة للحالة العشوائية، سيختار البرنامج إحدى البطاقات الموجودة بالكومة عشوائيًا. سيختلف بالتأكيد ترتيب معالجة مربعات الشبكة تمامًا باختلاف نوع الكومة، وهو ما يظهر بوضوحٍ من خلال الصور الثلاثة التالية المأخوذة من البرنامج، حيث سيبدأ البرنامج باختيار مربعٍ بالقرب من منتصف الشبكة مهما كان نوع الكومة المُستخدَمة. يَستخدِم البرنامج على اليسار مكدسًا، أما البرنامج بالمنتصف فيَستخدِم رتلًا، أما البرنامج على اليمين فيَستخدِم الاختيار العشوائي random selection. تختلف الأنماط الناتجة اختلافًا كبيرً، فعندما يَستخدِم البرنامج مكدسًا stack، فإنه يُحاوِل أن يستكشف أقصى قدرٍ ممكنٍ قبل البدء بإجراء تتبعٍ خلفي backtracking لفحص المربعات التي واجهها مُسبقًا؛ وعندما يستخدم البرنامج رتلًا queue، فإنه يعالج المربعات بنفس ترتيب بُعدها عن النقطة المبدئية تقريبًا؛ أما عندما يستخدم البرنامج الاختيار العشوائي random selection، فإننا نحصل على كائنٍ blob غير منتظم، ولكنه يَكون متصلًا بالتأكيد؛ حيث يمكن للبرنامج أن يواجه مربعًا معينًا فقط في حالة كان ذلك المربع مجاورًا لمربعٍ سَبَقَ أن واجهه البرنامج. يُمكنك تجريب البرنامج لترى طريقة عمله، وحاول أيضًا فهم طريقة استخدام المكدس والرتل ضمن ذلك البرنامج،وابدأ بمربعٍ واقعٍ بإحدى الزوايا المربعة. بينما ما يزال البرنامج قيد المعالجة، تستطيع أن تنقر على مربعاتٍ بيضاء أخرى، وستُضاف عندها إلى قائمة المربعات التي واجهها البرنامج. في تلك الحالة، إذا كان البرنامج يَستخدِم مكدسًا، ستلاحظِ أن البرنامج يُعالِج المربع الذي نقرت عليه فورًا بينما ستضطّر بقية المربعات الحمراء التي كانت بالفعل تنتظر دورها إلى الانتظار أكثر؛ وإذا كان البرنامج يستخدم رتلًا، فإنه لن يُعالِج المربع الذي نقرت عليه إلا بعد الانتهاء من معالجة جميع المربعات التي كانت موجودة بالفعل ضمن الكومة. يُمكِنك الإطلاع على الشيفرة المصدرية للبرنامج من الملف DepthBreadth.java. يبدو الرتل أكثر بداهةً لأنه يحدث بصورةٍ أكبر بالحياة الواقعية؛ ولكن في بعض الأحيان، يكون المكدس مناسبًا أكثر أو حتى أساسيًا. فماذا سيحدث مثلًا عندما يستدعي برنامج routine برنامجًا فرعيًا subroutine؟ يُعلَّق البرنامج الأول أثناء تنفيذ البرنامج الفرعي، ولا يكتمل تنفيذه إلى بعد انتهاء البرنامج الفرعي. لنفترض الآن أن ذلك البرنامج الفرعي استدعى بدوره برنامجًا فرعيًا ثانيًا، وأن ذلك البرنامج الفرعي الثاني قد استدعى بدوره برنامجًا ثالثًا، وهكذا، لذلك لا بُدّ من تعليق تنفيذ كل برنامجٍ فرعي بينما تُنفَّذ البرامج الفرعية اللاحقة؛ وهذا يعني أن الحاسوب لا بُدّ أن يحتفظ بحالة جميع البرامج الفرعية المُعلَّقة، وهو ما يفعله باستخدام مكدس. عند استدعاء برنامجٍ فرعي، يُنشَأ سجلٌ نشطٌ activation record له؛ حيث يحتوي على المعلومات المُتعلِّقة بتنفيذ البرنامج الفرعي، مثل متغيراته المحلية local variables ومعاملاته parameters، ويُوضَع ذلك السجل بالمكدس، ويُحذَف فقط عندما ينتهي البرنامج الفرعي من عمله ويعيد قيمة. إذا استدعى البرنامج الفرعي برنامجًا فرعيًا آخرًا، يُنشَئ سجلًا نشطًا جديدًا للبرنامج الفرعي الآخر ويُدفَع push إلى المكدس أعلى السجل الخاص بالبرنامج الفرعي الأول. يستمر المكدس بالنمو مع كل استدعاءٍ لبرنامجٍ فرعي، ويتقلص حجمه عند انتهاء تلك البرامج الفرعية من عملها. قد يحتوي المكدس على عدة سجلات لنفس البرنامج الفرعي في حالة البرامج الفرعية التعاودية recursive subroutine؛ أي تلك التي تعاود استدعاء ذاتها تعاوديًا. في الواقع، هذه هي ببساطة الطريقة التي يتمكَّن بها الحاسوب من تذكُّر مجموعة من الاستدعاءات التعاودية بنفس الوقت. 9.3.3: تعبيرات الإلحاق Postfix Expressions يمكن استخدام المكدس لتحصيل قيم تعبيرات الإلحاق postfix expressions. يُطلَق اسم "تعبير تدوين داخلي infix expression" على أي تعبيرٍ حسابيٍ عادي، مثل "‎2+(15-12)17‎"، حيث يُوضَع العامل operator في هذا النوع من التعبيرات بين معاملين operands، مثل "2 + 2"؛ أما بالنسبة لتعبيرات الإلحاق، يأتي العامل بعد معامليه مثل "‎2 2 ‎+‎". يُمكِن كتابة التعبير "‎2+(15-12)17" بصياغة تعبيرات الإلحاق على النحو التالي "‎2 15 12 - 17 * +‎"، حيث يُطبَق العامل "-" بهذا التعبير على المعاملين اللذين يَسبِقانه أي "15" و"12". يُطبّق العامل "*" بالمثل على المعاملين اللذين يسبقانه أي "‎15 12 -‎" و"17". أخيرًا، يُطبّق "+" على "2" و"‎15 12 - 17 *‎". في الواقع، هذه هي نفس العمليات الحسابية التي يُنفِّذها تعبير التدوين الداخلي الأصلي. لنفترض الآن أننا نريد معالجة التعبير "‎2 15 12 - 17 * +‎" من اليسار إلى اليمين، وحساب قيمته. سيكون العنصر الأول الذي نواجهه هو 2، ولكن ما الذي يُمكِننا أن نفعله به؟ لا نعرف بهذه النقطة من البرنامج أي عاملٍ ينبغي تطبيقه على العدد 2، كما أننا لا نعرف قيمة المعامل الآخر. ينبغي إذًا أن نتذكر العدد 2 حيث سنحتاج إلى مُعالِجته لاحقًا بدفعه push إلى المكدس. سننتقل بعدها إلى العنصر التالي، أي 15، والذي سندفعه أيضًا إلى المكدس أعلى العدد 2، ثم ندفع العدد 12. الآن، سنواجه العامل "-"، والذي ينبغي أن نطبّقه على المعاملين اللذين يسبقانه بالتعبير، وبما أننا خزَّنا قيمة هذين المعاملين بالمكدس، يُمكِننا معالجة العامل "-" بسحب pull عددين من المكدس أي 12 و15، ثم نحسب قيمة التعبير "‎15 - 12" لنحصل على الإجابة 3. لا بُدّ من أن نتذكر تلك الإجابة لأننا سنحتاج إلى مُعالِجتها لاحقًا، ولهذا سندفعها إلى المكدس أعلى العدد 2 الذي ما يزال منتظرًا. سنفحص الآن العنصر التالي بالتعبير، أي العدد 17، والذي سندفعه إلى المكدس أعلى العدد 3. ولنتمكَّن من معالجة العنصر التالي أي "*"، يجب أن نسحب pop عددين من المكدس أي 17 و3، حيث يُمثّل العدد 3 قيمة "‎15 12 -‎". سنحسب الآن حاصل ضرب العددين، وسنحصل على الناتج 51، الذي سندفعه إلى المكدس. سنجد بعد ذلك أن العنصر التالي بالتعبير هو العامل "+"، والذي يُمكِننا مُعالجته بسحب pop العددين 51 و2 من المكدس، ثم حساب مجموعهما، ودفع الناتج 53 إلى المكدس. أخيرًا، وصلنا إلى نهاية التعبير، ويكون العدد الموجود بالمكدس هو القيمة الإجمالية للتعبير؛ أي أن كل ما علينا فعله هو أن نسحبه من المكدس، ونكون قد انتهينا. على الرغم من أنك قد ترى أن تعبيرات التدوين الداخلي infix expressions أكثر سهولةً، لكن تتمتع تعبيرات الإلحاق postfix expression ببعض المميزات؛ فهي لا تتطلَّب أي أقواسٍ أو قواعد أولوية precedence rules، حيث يعتمد الترتيب الذي تُطبّق على أساسه العوامل operators على ترتيب حدوثها ضمن التعبير، ويسمح ذلك بكتابة خوارزمياتٍ algorithms بسيطةٍ ومباشرة لتحصيل قيم تعبيرات الإلحاق. ألقِ نظرةً على ما يلي: // ابدأ بمكدس فارغ Start with an empty stack // لكل عنصر ضمن التعبير، نفذ ما يلي for each item in the expression: // إذا كان العنصر عددًا if the item is a number: // ادفع العدد إلى داخل المكدس Push the number onto the stack // إذا كان العنصر عاملًا else if the item is an operator: // اسحب معاملين من المكدس Pop the operands from the stack // Can generate an error // طبّق العامل على المعاملين Apply the operator to the operands // ادفع الناتج إلى المكدس Push the result onto the stack else // هناك خطأ بالتعبير There is an error in the expression // اسحب عددًا من المكدس Pop a number from the stack // Can generate an error // إذا كان المكدس فارغًا if the stack is not empty: // هناك خطأ بالتعبير There is an error in the expression else: // القيمة الأخيرة المسحوبة من المكدس هي قيمة التعبير The last number that was popped is the value of the expression علاوةً على ذلك، يمكن الكشف عن الأخطاء الموجودة ضمن أي تعبيرٍ بسهولة. لنفترض مثلًا أنه لدينا التعبير التالي "‎2 3 + ‎"، حيث يُمكِننا بسهولة أن نرى أنه لا توجد معاملاٌت operands كافيةٌ للعامل ""، وستكشف الخوارزمية الموضحة أعلاه عن ذلك الخطأ عندما تحاول سحب pop معاملٍ ثانٍ للعامل "*" من المكدس الذي سيكون فارغًا. قد تحدث مشكلةٌ أخرى عندما لا يكون هناك عددٌ كافٍ من العوامل operators لجميع الأعداد ضمن التعبير مثل "‎2 3 4 +‎". ستكشف الخوارزمية عن ذلك الخطأ عندما يبقى العدد 2 بالمكدس بعد انتهاء الخوارزمية. يستخدم البرنامج التوضيحي PostfixEval.java تلك الخوارزمية، حيث يسمح للمُستخدم بكتابة تعبيرات إلحاقٍ posftix expression مكوّنةٍ من أعدادٍ حقيقيةٍ موجبة إلى جانب العوامل التالية "+" و"-" و"*" و"/" و"^"، حيث يُمثِل العامل "^" الأس؛ أي يُقيّّم التعبير "‎2 3 ^‎" على النحو التالي 23؛ كما يطبع البرنامج رسالةً نصيةً أثناء معالجته لكل عنصرٍ ضمن التعبير، ويستخدِم الصنف StackOfDouble المُعرَّف بالملف StackOfDouble.java، والذي يتطابق مع الصنف الأول StackOfInts المُبيَّن أعلاه باستثناء أنه يُخزِّن قيمًا من النوع double بدلًا من النوع int. الجانب الوحيد الشيّق في هذا البرنامج هو التابع method المُنفِّذ لخوارزمية تحصيل قيمة تعبيرات الإلحاق postfix evaluation. تعرض الشيفرة التالية تنفيذًا لتلك الخوارزمية، والذي هو في الواقع مجرد تحويلٍ مباشرٍ للشيفرة الوهمية المُوضحة بالأعلى إلى لغة جافا. private static void readAndEvaluate() { StackOfDouble stack; // المكدس المستخدم لتحصيل قيمة التعبير stack = new StackOfDouble(); // أنشِئ مكدسًا فارغًا System.out.println(); while (TextIO.peek() != '\n') { if ( Character.isDigit(TextIO.peek()) ) { // العنصر التالي المُدْخَل عبارة عن عدد // اِقرأه وخزنه بالمكدس double num = TextIO.getDouble(); stack.push(num); System.out.println(" Pushed constant " + num); } else { // نظرًا لأن العنصر التالي ليس عددًا، فإنه ينبغي أن يكون عاملًا // اقرأ العامل ونفذ العملية char op; // ‫العامل الذي ينبغي أن تكون قيمته + أو - أو / أو * double x,y; // المعاملين اللذين سنسحبهما من المكدس double answer; // ناتج العملية op = TextIO.getChar(); if (op != '+' && op != '-' && op != '*' && op != '/' && op != '^') { // لا يُمثِل المحرف أي من العمليات المتاحة System.out.println("\nIllegal operator found in input: " + op); return; } if (stack.isEmpty()) { System.out.println(" Stack is empty while trying to evaluate " + op); System.out.println("\nNot enough numbers in expression!"); return; } y = stack.pop(); if (stack.isEmpty()) { System.out.println(" Stack is empty while trying to evaluate " + op); System.out.println("\nNot enough numbers in expression!"); return; } x = stack.pop(); switch (op) { case '+': answer = x + y; break; case '-': answer = x - y; break; case '*': answer = x * y; break; case '/': answer = x / y; break; default: answer = Math.pow(x,y); // (op must be '^'.) } stack.push(answer); System.out.println(" Evaluated " + op + " and pushed " + answer); } TextIO.skipBlanks(); } // end while // 1 if (stack.isEmpty()) { // يستحيل إذا كان المدخل غير فارغ فعليًا System.out.println("No expression provided."); return; } double value = stack.pop(); // قيمة التعبير System.out.println(" Popped " + value + " at end of expression."); if (stack.isEmpty() == false) { System.out.println(" Stack is not empty."); System.out.println("\nNot enough operators for all the numbers!"); return; } System.out.println("\nValue = " + value); } // end readAndEvaluate() [1] إذا وصلنا إلى تلك النقطة من البرنامج، سنكون قد قرأنا المُدْخلات بصورة سليمة. إذا كان التعبير صالحًا، فإن قيمة التعبير ستكون الشيء الوحيد الموجود بالمكدس. يلجأ الحاسوب عادةً إلى الاعتماد على تعبيرات الإلحاق postfix expressions. في الواقع، تُعدّ آلة جافا الافتراضية Java virtual machine آلة مكدس stack machine، بمعنى أنها تَستخدِم أسلوبًا يعتمد على المكدس لتحصيل قيم التعبيرات expressions. يُمكِن تمديد الخوارزمية بسهولةٍ بحيث تتمكَّن من معالجة المتغيرات variables والثوابت constants. عندما تواجه الخوارزمية متغيرًا ضمن تعبير، فإن قيمة ذلك المتغير ينبغي أن تُدفَع إلى المكدس، ويُمكن أيضًا تطبيقها على العوامل operators التي تَملُك أكثر أو أقل من مجرد معاملين اثنين operands، حيث يُمكِننا ببساطةٍ سحب pop العدد المطلوب من المعاملات من المكدس، ثم دفع الناتج إليه؛ فيُستخدَم على سبيل المثال عامل الطرح الأحادي "-" ضمن تعبيرٍ مثل "‎-x" له معاملٌ واحد. سنستمر بمناقشة التعبيرات وتحصيل قيمة التعبيرات expression evaluation بالقسمين التاليين. ترجمة -بتصرّف- للقسم Section 3: Stacks, Queues, and ADTs من فصل Chapter 9: Linked Data Structures and Recursion من كتاب Introduction to Programming Using Java. اقرأ أيضًا مقدمة إلى الاستثناءت exceptions ومعالجتها في جافا التوكيد assertion والتوصيف annotation في لغة جافا مقدمة إلى صحة البرامج ومتانتها في جافا التعاود recursion والمكدس stack في جافاسكربت
    1 نقطة
  21. يوفر جانغو Django طريقة سهلة للحصول على سجل عشوائي بسطر واحد فقط، كالتالي: Post.objects.order_by('?').first() لكن هذه الطريقة تستهلك الكثير من موارد الخادم وتسبب بطئ في التشغيل، لذلك يمكنك أن تستخدم طريقة أخرى وهي إستخدام التابع choice من الحزمة random، كالتالي: random.choice(Post.objects.all()) هذه الطريقة جيدة في قواعد البيانات الصغير (التي تحتوي على أقل من 100,000 من الصفوف في الجدول). أيضًا يمكنك أن تقوم بعمل نفس الأمر لكن من خلال حساب عدد الكائنات في قاعدة البيانات، على النحو التالي: from random import randint count = Post.objects.all().count() random_index = randint(0, count - 1) random_post = Post.objects.all()[random_index]
    1 نقطة
  22. لإنشاء عمليات ترحيل أولية لأحد التطبيقات بما في ذلك المجلد migrations، قم بتشغيل الأمر "makemigrations" وحدد اسم التطبيق. سيتم إنشاء مجلد migrations بشكل تلقائي. ./manage.py makemigrations <myapp> كما يجب تضمين تطبيقك في INSTALLED_APPS أولاً داخل الملف settings.py وإلا سوف يؤدي إلى ظهور خطأ بعدم وجود التطبيق من الأساس. تحتاج إلى تحديد اسم التطبيق إذا كان التطبيق لا يحتوي على مجلد migrations. وقد يحدث هذا إذا قمت بإنشاء التطبيق يدويًا، أو قمت بالترقية من إصدار قديم من جانغو Django لا يحتوي على أي ملفات تهجير migrations. إيضًا إذا لم تكن تستخدم الملف models.py وتستخدم مجلد models مخصص للنماذج فيجب أن تقوم بإنشاء الملف init، فعلى سبيل المثال، إن كنت تستخدم الملف my_model.py في المجلد models، فيجب أن تقوم بإنشاء الملف التالي: my_app/models/__init__.py أيضًا يجب أن تستدعي في داخله الملف my_model.py، على النحو التالي: from .my_model import MyModel
    1 نقطة
  23. لاحظ أن لديك خطأ في كتابة كود JavaScript في عملية تحديد العنصر من خلال الصنف open، حيث أن الكود يجب أن يكون كالتالي: <script> let ahref = document.querySelectorAll('.open'); console.log(ahref[1].classList) </script> لاحظ إستخدام التابع querySelectorAll وليس quer أيضًا يجب إضافة نقطة . قبل كلمة open حيث أننا نقوم بتحديد العناصر من خلال الصنف class ولذلك يجب إضافة . قبل اسم الصنف. ملاحظة: يتم إضافة العلامة # قبل المعرف في حالة إستخدام المعرفات IDs بدلًا من الأصناف. أيضًا كما وضح وائل أن كود جافاسكريبت يتم تنفيذه قبل تحميل عناصر HTML، لذا حتى في حالة كان كود جافاسكريبت يعمل بشكل سليم، فلن يتم إيجاد العنصر .open وذلك لأنه لم يتم تحميله بعد (في وقت تنفيذ كود جافاسكريبت)، ولحل هذه المشكلة يجب نقل الكود إلى أسفل المستند (قبل نهاية جسم الصفحة body)، على النحو التالي: <html> <head> <meta charset="UTF-8" /> <title>Learn JavaScript</title> </head> <body> <a class="open" href="https://google.com">Google</a> <a class="open" href="https://wikipedia.org">wikipedia</a> <a class="not" href="https://facebook.com">Facebook</a> <a class="linked" href="https://wikipedia.org">wikipedia</a> <script> let ahref = document.querySelectorAll('.open'); console.log(ahref[1].classList) </script> </body> </html> أو يمكنك أن تستخدم الحدث onload ليتم تنفيذ الكود عندما يكتمل تحميل الصفحة فقط، كالتالي: <html> <head> <meta charset="UTF-8" /> <title>Learn JavaScript</title> <script> document.onload = function(e){ let ahref = document.querySelectorAll('.open'); console.log(ahref[1].classList) } </script> </head> <body> <a class="open" href="https://google.com">Google</a> <a class="open" href="https://wikipedia.org">wikipedia</a> <a class="not" href="https://facebook.com">Facebook</a> <a class="linked" href="https://wikipedia.org">wikipedia</a> </body> </html> ويمكنك أن تستعين بهذه المقالة لفهم المزيد عن الأحداث وكيف تعمل (فهم الأحداث في جافاسكربت - أكاديمية حسوب)
    1 نقطة
×
×
  • أضف...