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

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

  1. منتصر احمد

    منتصر احمد

    الأعضاء


    • نقاط

      2

    • المساهمات

      760


  2. Ahmed.Sayed

    Ahmed.Sayed

    الأعضاء


    • نقاط

      2

    • المساهمات

      23


  3. Anas Badawy

    Anas Badawy

    الأعضاء


    • نقاط

      1

    • المساهمات

      148


  4. Muhammed Hacibrahim

    Muhammed Hacibrahim

    الأعضاء


    • نقاط

      1

    • المساهمات

      151


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

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

  1. الإصدار 1.0.0

    62943 تنزيل

    لا يخفى على أي متعلم لمجال علوم الحاسوب كثرة الاهتمام بمجال الذكاء الاصطناعي وتعلم الآلة، وكذلك الأمر بالنسبة لمستخدم التقنية العادي الذي بات يرى تطورًا كبيرًا في الآلات والتقنيات التي تحيط به بدءًا من المساعد الصوتي الآلي في جواله وحتى سيارته وبقية الأشياء الذكية المحيطة به. تتوالى الاختراعات والاكتشافات يومًا بعد يوم وتتنافس كبرى الشركات حول من يحرز أكبر تقدم ليخطف الأضواء من غيره. ونظرًا لهذا الاهتمام، ولضعف المحتوى العربي وسطحيته في هذا المجال أيضًا، قررنا توفير مصدر عربي دسم لشرح مجال الذكاء الاصطناعي وتعلم الآلة نظريًا وعمليًا لذا وضعنا فهرس المحتوى آنذاك وبدأنا العمل. هذا الكتاب هو الجزء الأول النظري التأسيسي من أصل جزآن عن الذكاء الاصطناعي وتعلم الآلة، ويبدأ بعرض أهمية الذكاء الاصطناعي وتعلم الآلة عبر الإشارة إلى المشاريع والإنجازات التي قدَّمها هذا المجال إلى البشرية حتى يومنا هذا وكيف أثرت على كل مجالات حياتنا اليومية. ينتقل بعدها إلى لمحة تاريخية عن المجال وكيفية ولادته ومراحل حياته حتى يومنا الحالي. ستجد بعدئذٍ المعلومات الدسمة في الفصل الثالث الذي يشرح المصطلحات المتعلقة بمجال تعلم الآلة ويشرح أساليب تعليم الإنسان للآلة والأسس التي بنيت عليها عمليات تعليم الآلة (منها شرح طرائق تعلم الآلة التقليدية ثم التجميع والتعلم المعزز وحتى الشبكات العصبية والتعلم العميق). يعرض الفصل الأخير تحديات عملية تعليم الآلة وما علاقة البيانات فيها، ثم أخيرًا عرض خارطة طريق لأهم المفاهيم التي يجب أن تتقنها في حال أردت التوسع في المجال وإتقانه. بعد الانتهاء من الجزء الأول في هذا الكتاب وتأسيس المفاهيم والمصطلحات التي يقوم عليها مجال الذكاء الاصطناعي وتعلم الآلة، يمكنك الانتقال إلى الجزء الثاني وهو كتاب عشرة مشاريع عملية عن الذكاء الاصطناعي لبدء تطبيق مشاريع عملية تطبيقية مبنية على بيانات واقعية وتنفيذ أفكار مشاريع من الحياة العملية باستخدام الذكاء الاصطناعي. ساهم بالعمل على هذا الكتاب، محمد لحلح تأليفًا، وجميل بيلوني تحريرًا وإشرافًا، وأخرجه فنيًا فرج الشامي. أرجو أن نكون قد وُفقنَا في هذا العمل لسد ثغرةً كبيرةً في المحتوى العربي -كما خططنا لذلك- الذي يفتقر أشد الافتقار إلى محتوى جيد ورصين في مجال الذكاء الاصطناعي وتعلم الآلة. هذا الكتاب مرخص بموجب رخصة المشاع الإبداعي Creative Commons «نسب المُصنَّف - غير تجاري - الترخيص بالمثل 4.0». يمكنك قراءة فصول الكتاب على شكل مقالات من هذه الصفحة، «الذكاء الاصطناعي: أهم الإنجازات والاختراعات وكيف أثرت في حياتنا اليومية»، أو من مباشرةً من الآتي: الفصل الأول: الذكاء الاصطناعي: أهم الإنجازات والاختراعات وكيف أثرت في حياتنا اليومية الفصل الثاني: الذكاء الاصطناعي: مراحل البدء والتطور والأسس التي نشأ عليها الفصل الثالث: المفاهيم الأساسية لتعلم الآلة الفصل الرابع: تعلم الآلة: التحديات الرئيسية وكيفية التوسع في المجال
    1 نقطة
  2. لماذا عندما ادخل وسم frameset في صفحةالhtml يظهر باللون الاحمر و لا يتم اظهاره في صفحة المعاينة ؟؟
    1 نقطة
  3. انا خلصت كورس تطوير واجهات المستخدم في اكاديمية حسوب واخذت الشهاده هل في اي حاجه زياده ممكن اتعلمها + ازاي اطور من مهارت البحث والتفكير المنطقي
    1 نقطة
  4. ازاي اطور من مهارت البحث والتفكير المنطقي
    1 نقطة
  5. تهانيا لك لإنهاء دورة تطوير واجهات المستخدم ومع مزيد من التفوق ، لزيادة مهارات وصقلها يجب أن تستمر في ممارسة ما تعلمته خلال الدورة في ابتكار تصميم من خيالك أو تقليد تصاميم مع إضافة لمسة خاصة بك حتى لا تنسى المهارات التي تعلمتها ، يمكنك من تطوير نفسك من خلال تعلم مهارات في Front End غير موجودة مثل إطارات عمل أخرى ـ أو البدء في تطوير نفسك في مجال Back End حتى تصبح مبرمج كامل تستطيع برمجة مواقع تفاعلية مع المستخدم .
    1 نقطة
  6. نعم ، يوجد بالفعل حزمة متوفرة على المستودع التالي في جيت هب. laravel-subscriptions كذلك أرشح لك خيار أفضل وهو حزمة مقدمة من laravel https://laravel.com/docs/9.x/billing والحزمة كذلك خالية من نظام المدفوعات ، بمعنى أنها مناسبة تمامًا لإحتياجاتك المكتوبة. يمكنك كذلك عمل النظام يدويًا ، لكن سيكون هناك مجهود أكبر في إنشاءها ، بمعنى آخر ستحتاج لإعادة إنشاء العجلة ، على كلً يمكنك جعل الإشتراك ينتهي عبر جدولة المهام الموجودة في لارافيل يمكنك الإطلاع على الشرح الخاص بها على الويكي الخاص بحسوب. جدولة المهام (Task scheduling) في Laravel
    1 نقطة
  7. كيف يمكنني اضافة ثلاث عناصر داخل الcarousel في شاشات الكبيرة و عنصرين في الصغيرة
    1 نقطة
  8. لو سمحت انا كنت مشترك في دورة تطوير واجهات المستخدم وامامي باذن الله شهرين وانتهي من هذه الدورة كنت اريد ان اشترك في دورة من دورات تطوير الواجهات الخلفية وانا متردد بين هذه اللغات ( Python , js , php , roby ) ولا اعرف ايها اختار هي هذا الوقت ولا اعرف شئ عن هذه اللغات بشكل خاص ايها افضل ومطلوبه في سوق العمل ؟
    1 نقطة
  9. نعم يمكنك ، وهناك عدة طرق لتستطيع الولوج إلى موقع مُستضاف محليًا على جهاز آخر وأسهل طريقة هي الإتصال عبر نفس الشبكة يجب على الجهاز الذي تستخدمه أن يكون متصل بنفس الشبكة المُستضاف عليها الجهاز ، ثم ستستخدم الأيبي للولوج للموقع ، ويمكنك الحصول على الأيبي عبر الدخول لإعدادات الشبكة وإيجاد IPv4 Address من "مشاهدة حالة الإتصال" كذلك يمكنك إستعمال الـhostname بدلًا من الأيبي للولوج إلى التطبيقات المستضافة محليًا. والطريقة الأخرى هي بتحويل الجهاز الخاص بك إلى خادم لإستضافة الموقع (ينطوي هذا الخيار على بعض من المخاطرة على ملفاتك) وهي بالخطوات التالية:- فتح البورت الخاص المطلوب من الراوتر الخاص بك ، أو التواصل مع الشركة التي تقوم بتزويدك بالإنترنت لفتحه. ضبط إعدادات الـ NAT & DHPC gateway لتعمل مع الأجهزة الخارجية. ربط الدومين الخاص بك على جهازك الذي أصبح بمثابة مستضيف الآن للموقع.
    1 نقطة
  10. هل يمكن ان اتعلم برمجة الواجهات و الشبكات في نفس الوقت لكن مع التقسيم اي متلا بمعدل 3 ساعات لكل واحدة منهما وهدا متال فقط ف3 ساعات كتيرة لكن فهمتم مااقصد
    1 نقطة
  11. لكل حاسوب متصل على شبكة الانترنت 2 من IP address الأول محلي local IP على مستوى شبكة المنزل مثلاً 192.168.1.7 الثاني عام على مستوى الانترنت global / public مثلا 145.149.95.90 حيث أن كلاً من هذه العناوين قابل للتغير من فترة لأخرى. أما عند حجز استضافة عامة سيكون لها static IP وهو عنوان ثابت لا يتغير، من النوع public IP. يمكنك التواصل مع شركة الاتصالات لديك للاشتراك بخدمة static IP وبهذا يصبح للراوتر المنزلي الخاص بك عنوان ثابت على الانترنت و يصبح بقدرتك مشاركة مشاريعك على حاسوبك الذي سيعمل ك Server ولكن لا أنصح بذلك. حيث أن أي خدمة مجانية للاستضافة أسهل و أوفر وتكون متوفرة طيلة الوقت
    1 نقطة
  12. لا تُكتب البرامج بصورةٍ صحيحةٍ من قبيل المصادفة، وإنما تتطلّب تخطيطًا وانتباهًا للتفاصيل لتجنُّب أي أخطاءٍ محتمَلةٍ، ولحسن الحظ، تتوفّر بعض التقنيات التي عادةً ما يستعين بها المبرمجون لزيادة فرصة صحة برامجهم. برامج يمكن إثبات صحتها تستطيع في بعض الحالات القليلة أن تُثبت صحة برنامجٍ، بمعنى أن توضِّح بصورةٍ رياضيةٍ أن متتالية الحسابات التي يمثّلها البرنامج ستعطي دائمًا النتيجة الصحيحة، وغالبًا ما يصعُب توفير هذا النوع من الإثباتات الصارمة، فغالبًا ما يَقتصر تطبيقها بصورةٍ عمليةٍ على البرامج الصغيرة. وإلى جانب ذلك، يجب أن يوصَّف البرنامج على نحوٍ سليمٍ وكاملٍ ليعطي النتيجة الصحيحةً دائمًا؛ فليس هناك أي فائدةٍ من برنامج ينفّذ توصيفًا specification خاطئًا حتى إذا نفّذه بصورةٍ سليمةٍ، حيث تتوفّر في الواقع بعض الأفكار والأساليب التي تُستخدم لإثبات صحة البرامج. يُقصد بالأفكار الأساسية كلًا من الحالة state والعملية process؛ وتتكون الحالة من كلّ المعلومات المتعلّقة بتنفيذ البرنامج خلال لحظةٍ معينةٍ، فقد تتضمن قيم المتغيّرات مثلًا، الخرْج الناتج (أي دخْلٍ يُنتظر قراءته) من سطْر البرنامج المنفَّذ حاليًا؛ بينما تتكون العملية من متتالية الحالات التي يمر بها البرنامج أثناء تنفيذه، ومن وجهة النظر تلك، يمكننا أن نفسّر معنى أي تعليمةٍ statement ضِمن برنامجٍ بالكيفية التي تؤثّر بها على حالته، فمثلًا، تعني التعليمة x = 7 أن قيمة المتغيّر x ستساوي 7، في الواقع نحن على يقينٍ من تلك النتيجة تمامًا، ولهذا يمكننا أن نبني على أساسها إثباتًا رياضيًا. قد ندرك أحيانًا أن حقيقةً معينةً هي تصِح خلال لحظةٍ معينةٍ في البرنامج بمجرد أن ننظر إليه؛ انظر إلى حلقة التكرار loop التالية: do { System.out.print("Enter a positive integer: "); N = TextIO.getlnInt(); } while (N <= 0); ندرك أن قيمة المتغيّر N ستكون أكبر من الصفر بعد تنفيذ الحلقة السابقة؛ لأنها لن تنتهي حتى يَصِح ذلك الشرط condition، حيث تُعَد تلك الحقيقة جزءًا من معنى حلقة التكرار while؛ وعمومًا إذا تضمّنت حلقة التكرار while اختبارًا while (condition)‎، ولم تكن تحتوي على أي تعليماتٍ من نوع break، فسنعلم يقينًا أن ذلك الشرط لن يكون متحقِّقًا بعد انتهائها، وقد نبني على تلك الحقيقة استنتاجاتٍ أخرى ضِمن لحظاتٍ أخرىٍ في نفْس البرنامج، ويجب أن نتأكد أيضًا من إمكانية انتهاء حلقة التكرار خلال وقتٍ ما. الشروط اللاحقة Postconditions والشروط المسبقة Pretconditions يمكننا أن نُثبِت صحة الشروط اللاحقة ضِمن برنامجٍ متى انتهينا من تنفيذه، بمعنى أنها ستكون بمثابة حقائق معروفةٍ قد نبني عليها استنتاجاتٍ عن سلوك البرنامج. وبتعبيرٍ آخرٍ، يُعَد أي شرْطٍ لاحقٍ ضِمن برنامجٍ حقيقةً يمكن إثباتها بمجرد انتهاء تنفيذ البرنامج، وعليه يمكننا أن نُثبِت صحة برنامجٍ معينٍ عن طريق بأن نوضّح أن شروطه اللاحقة مُستوفيةٌ لتوصيفه. انظر شيفرة المثال التالي حيث كل المتغيّرات من النوع double: disc = B*B - 4*A*C; x = (-B + Math.sqrt(disc)) / (2*A); نفرض أن قيمة disc أكبر من أو تساوي الصفر وأن قيمة A غير صفريةٍ، إذ تؤكد لنا المعادلة التربيعية في الشيفرة السابقة أن القيمة المسنَدة للمتغيّر x هي حلٌ للمعادلة A*x2 + B*x + C = 0، وإذا كنا سنضمن صحة كلّ من الشرطين B*B-4*A*C >= 0 وA != 0، ستكون عندها حقيقةً "لأن x يمثّل حلًا للمعادلة" شَرطًا لاحقًا لذلك الجزء من البرنامج، كما سيكون عندها الشرْط B*B-4*A*C >= 0 شرطًا مُسبقًا، بالإضافة إلى الشرْط A != 0 لذلك الجزء من البرنامج، ويمكننا تعريف الشرْط المُسبق بأنه شرطٌ يجب أن يتحقّق ضِمن نقطةٍ معينةٍ في البرنامج ليستمر على نحوٍ صحيحٍ. إذا ما أردت لبرنامجك أن يكون صحيحًا، tيجب أن تفْحص الشرْط المُسبق إذا ما كان متحقِّقًا أم لا، أو أن تُجبره على ذلك. لقد تعرّضنا لمفهوميْ الشروط اللاحقة والشروط المسبَقة من قَبل في القِسم 4.7.1 لكونهما طريقةٌ لتخصيص المواصفة الاصطلاحية contract لبرنامجٍ فرعيٍ subroutine، حيث عرّفنا الشرْط المسبَق لبرنامجٍ فرعيٍ بأنه شرْطٌ مُسبَقٌ لشيفرة ذلك البرنامج الفرعي، بينما عرّفنا الشرْط اللاحق بأنه شرطٌ يتحقّق بتنفيذ تلك الشيفرة؛ وبهذا القِسم، عمّمنا المقصود بهذين المصطلحين ليكونا أكثر فائدةً بينما نتحدث عن صحة البرامج عمومًا. افحص المثال التالي: do { System.out.println("Enter A, B, and C."); System.out.println("A must be non-zero and B*B-4*A*C must be >= 0."); System.out.print("A = "); A = TextIO.getlnDouble(); System.out.print("B = "); B = TextIO.getlnDouble(); System.out.print("C = "); C = TextIO.getlnDouble(); if (A == 0 || B*B - 4*A*C < 0) System.out.println("Your input is illegal. Try again."); } while (A == 0 || B*B - 4*A*C < 0); disc = B*B - 4*A*C; x = (-B + Math.sqrt(disc)) / (2*A); بعدما تنتهي الحلقة، نتأكد من تحقُّق الشرطين B*B-4*A*C >= 0 وA != 0، ونظرًا لتحقُّق الشروط المسبقَة للسطرين الأخيرين، فإن الشرْط اللاحق (أي كَوْن x حلًا للمعادلة A*x2 + B*x + C = 0) يكون صالحٌ كذلك، إذًا تَحسِب الشيفرة السابقة حلّ المعادلة بصورةٍ صحيحةٍ ومُثبَتةٍ. الآن سنفحص مثالًا آخر، تختبِر تعليمة if في الشيفرة التالية شرطًا مسبَقًا، إذ يتحقّق بكلّ تأكيدٍ أثناء حساب قيمة الحل وطباعته ضِمن الجزء الأول من تعليمة if، بينما لا يتحقّق الشرط ضِمن الأجزاء الأخرى من التعليمة، إلا أن البرنامج صحيحٌ في جميع الأحوال. System.out.println("Enter your values for A, B, and C."); System.out.print("A = "); A = TextIO.getlnDouble(); System.out.print("B = "); B = TextIO.getlnDouble(); System.out.print("C = "); C = TextIO.getlnDouble(); if (A != 0 && B*B - 4*A*C >= 0) { disc = B*B - 4*A*C; x = (-B + Math.sqrt(disc)) / (2*A); System.out.println("A solution of A*X*X + B*X + C = 0 is " + x); } else if (A == 0) { System.out.println("The value of A cannot be zero."); } else { System.out.println("Since B*B - 4*A*C is less than zero, the"); System.out.println("equation A*X*X + B*X + C = 0 has no solution."); } قبل تكتب برنامجًا، فكِّر بشروطه المسبَقة وبالكيفية التي سيتعامل بها برنامجك معها؛ فغالبًا ما سيعطيك ذلك الشرْط تلميحًا لما ينبغي أن تفعله. فمثلًا، يملِك كل عنصر مصفوفة مثل A[‎i] شرطًا مسبَقًا بضرورة أن يقع الموضع المُشار إليه ضِمن مجال تلك المصفوفة؛ إذًا سيفحص الحاسوب الشرْط المسبَق للعنصر A[‎i]< وهو 0 <= i < A.length عندما يُحصّل قيمة A[‎i]، فإذا لم يتحقّق الشرْط فسينتهي البرنامج، وينبغي عليك أن تتجنّب وقوع ذلك. تبحث الشيفرة التالية عن عدد x ضِمن مصفوفة A، حيث تُضبَط قيمة i إلى موضع عنصر المصفوفة الذي يحتوي على ذلك العدد إذا وُجد: i = 0; while (A[i] != x) { i++; } عندما نُشغّل الشيفرة السابقة، يكون لدينا شرطٌ مسبَقٌ بأن العدد يوجد فعليًا ضِمن نطاق المصفوفة، فإذا تحقّق ذلك الشرط، فستنتهي الحلقة عندما تكون A[‎i] == x، وذلك يعني أن قيمة i ستحمل موضع العدد x في المصفوفة، بينما إذا لم يكن x ضِمن المصفوفة، فإن قيمة i ستزداد إلى أن تصل قيمتها إلى A.length، بحيث سيشير A[‎i] إلى عنصرٍ غير صالحٍ، ثم بعد ذلك سينتهي البرنامج؛ ولذلك يَلزم عليك أن تضيف اختبارًا يتأكد من تحقُّق الشرْط المسبَق، كالتالي: i = 0; while (i < A.length && A[i] != x) { i++; } هنا ستنتهي الحلقة، ونستطيع أن نحدّد أي الشرطين i == A.length أوA[‎i] == x قد تسبّب في انتهاء الحلقة، وهو ما تختبره تعليمة if في نهاية الشيفرة التالية: i = 0; while (i < A.length && A[i] != x) { i++; } if (i == A.length) System.out.println("x is not in the array"); else System.out.println("x is in position " + i); اللامتغايرات Invariants سنفحص الآن طريقة عمَل حلقات التكرار من قُربٍ، حيث يحسِب البرنامج الفرعي التالي حاصل مجموع عناصر مصفوفةٍ من الأعداد الصحيحة: static int arraySum( int[] A ) { int total = 0; int i = 0; while ( i < A.length ) { total = total + A[i]; i = i + 1; } return total; } سيفترِض البرنامج الفرعي السابق شرطًا مسبَقًا بأن A لا تحتوي على القيمة الفارغة، فإذا لم يتحقّق ذلك الشرْط، سيبلِّغ البرنامج الفرعي عن اعتراضٍ من النوع NullPointerException. إذًا كيف نتأكد من أن البرنامج في السابق يعمل بطريقةٍ سليمةٍ؟ ينبغي أن نُثبِت أن قيمة total تُساوي حاصل مجموع العناصر ضِمن المصفوفة A قبل تنفيذ تعليمة return؛ ولهذا نستخدم حلقات تكرار لامتغايرة loop invariants. تُمثّل لامتغاير حلقة التكرار تلك التعليمات التي تَبقى صحيحةً أثناء تنفيذ الحلقة، وبتعبيرٍ آخرٍ، نتحقّق من أن تعليمة معينةً تُعبّر عن حالة صحيحة ثابتة لحلقة التكرار إذا حافظت هذه التعليمة على تحقّقها وصحّتها قبل تنفيذ الشيفرة، وبعد انتهائها، ويعني ذلك أن اللامتغاير invariant يعبّر عن شرطٍ مسبَقٍ ولاحقٍ في نفس الوقت، وهو ما يجعل الحلقة أكثر متانةً. ملاحظة: اخترنا المصطلح العربي "لامتغاير" لترجمة المصطلح الأجنبي invariant، إذ معنى مُتغَاير variant بالعربية مختلف ومتباين ومتبدِّل وحتى تكون مختلفة عن "مُتغيِّر" التي هي ترجمة variable. فمثلًا، تُعَد تعليمة total (حاصل مجموع أول عددٍ i من عناصر المصفوفة A) لامتغاير لحلقة التكرار السابقة، وسنفترض أن ذلك كان متحققًا في بداية حلقة التكرار while، أي قبل تنفيذ التعليمة total = total + A[‎i]؛ لذا فبعْد إضافة A[‎i] إلى total، فستساوي total حاصل مجموع أول عددٍ i+1 من عناصر تلك المصفوفة، في هذه اللحظة تحديدًا لا يَصِح لامتغاير الحلقة، بينما يعود لامتغاير الحلقة ليُصبِح صحيحًا مرةً أخرى بعد أن ننفّذ التعليمة التالية i = i + 1، أي زيادة i بمقدار الواحد؛ وبذلك نكون قد تأكدنا من صحة لامتغاير الحلقة أثناء بداية الحلقة ونهايتها. هل نكون بذلك قد أثبتنا أن البرنامج الفرعي arraySum()‎ صحيحٌ تمامًا؟ لا بكلّ تأكيد، فما تزال هناك بعض الأشياء التي يجب أن تُفحَص، حيث يجب أولًا أن نتأكد من صحة لامتغاير حلقة التكرار قبل أول تنفيذٍ للحلقة، ففي تلك اللحظة، كانت قيمة كلّ من i وtotal تساوي 0، وهو حاصل المجموع الصحيح لمصفوفةٍ فارغةٍ، ويعني ذلك أن لامتغاير الحلقة كان صحيحًا قبل بدايتها، كما أنه سيبقى صحيحًا بعد كلّ تنفيذٍ لها وكذلك حتى بعد انتهائها. تبقّى لنا أن نتأكد من قابلية حلقة التكرار للانتهاء، إذ تزداد قيمة i بمقدار الواحد مع كلّ تنفيذٍ للحلقة مما يعني أنها ستصل في النهاية إلى A.length، وهنا لن يتحقَّق شرْط حلقة while وستنتهي الحلقة. بعد انتهاء الحلقة، ستتساوي قيمة كلّ من i وA.length، كما سيتحقّق لامتغاير الحلقة، ونظرًا لتساوي قيمة كلّ من i وA.length، فسنجد أن total تعبّر عن حاصل مجموع أول عددٍ مقداره A.length من عناصر المصفوفة A، كما تعبّر عن حاصل جمع جميع عناصر المصفوفة A، وبهذا سيعطينا لامتغاير الحلقة ما نريده تمامًا؛ وعندما يعيد البرنامج الفرعي قيمة total، فإنها ستساوي حاصل مجموع عناصر المصفوفة. قد يُعَد هذا جهدًا كبيرًا لإثبات صحة شيءٍ واضحٍ، ولكنك إذا حاولت أن تشرح لماذا يعمل arraySum()‎ على نحوٍ صحيحٍ، فغالبًا ستستخدِم نفس المنطق الذي يُبنى عليه لامتغاير حلقة التكرار حتى إذا لم تستخدِم نفس المصطلح. مثالٌ آخر، يبحث البرنامج الفرعي التالي عن أكبر قيمةٍ ضِمن مصفوفةٍ من الأعداد الصحيحة على فرْض أنها تحتوي على عنصرٍ واحدٍ على الأقل، اُنظر الشيفرة التالية: static int maxInArray( int[] A ) { int max = A[0]; int i = 1; while ( i < A.length ) { if ( A[i] > max ) max = A[i]; i = i + 1; } return max; } يُعَد لامتغاير الحلقة في تلك الحالة أن max هو أكبر قيمةٍ ضِمن أول عناصر i من المصفوفة A، وسنفترض صحة ذلك قبل تعليمة if،إذًا بعد انتهاء الحلقة، سيكون max أكبر من أو يساوي A[‎i] لأنه شرْطٌ لاحقٌ لتعليمة if، كما سيكون أكبر من أو يساوي القيم التي تتراوح بين A[0]‎ وA[i-1]‎ نتيجةً للامتغاير الحلقة. وعندما نضع تلك الحقيقتين معًا، فنستطيع أن تُثبِت أن max هو أكبر قيمةٍ بين أول عددٍ i+1من عناصر المصفوفة A؛ وعندما نستبدل i+1 بـ i في التعليمة التالية، فسيتحقّق لامتغاير الحلقة مرةً أخرى، كما سنجد بعد انتهاء الحلقة أن i يساوي A.length، وسيُثبِت لامتغاير الحلقة أن max هو أكبر عنصرٍ في المصفوفة. مثالٌ آخر، افحَص خوارزمية الترتيب بالإدراج insertion sort التالية التي سبق وأن ناقشناها في القِسم الفرعي 7.4.3، وسنفترِض أننا سنرتّب مصفوفة A، ولذا ينبغي أن يتحقّق ما يلي في نهاية الخوارزمية: A[0] <= A[1] <= ... <= A[A.length-1] سيَطرح السؤال التالي نفْسه، ما هو الأسلوب الذي نتبعه لنتأكّد من صحة ذلك خطوةٍ بخطوةٍ؟ وهل يمكننا أن نوجد لامتغاير حلقةٍ يُمثّل العِبارة التي نريدها أن تتحقَّق في النهاية؟ فمثلًا، إذا أردنا لجميع عناصر مصفوفة أن تكون مرتبة في النهاية، فماذا عن لامتغاير حلقةٍ يشير إلى أن بعض عناصر المصفوفة مرتّبةٌ، وبصورةٍ أكثر تحديدًا يشير إلى أن العناصر الأولى من المصفوفة وحتى الفهرس i مرتبةٌ؟! ويقودنا ذلك إلى الخوارزمية التالية: i = 0; while (i < A.length ) { // ‫لامتغاير الحلقة [A[0] <= A[1] <= ... <= A[i-1 . . // شيفرة إضافة عنصر إلى الجزء المرتب من المصفوفة . i = i + 1; } // بهذه النقطة، تكون i = A.length, وA[0] <= A[1] <= ... <= A[A.length-1] تُعَد لامتغايرات الصنف class invariants (أو يطلق عليها أصناف لامتغايرة في بعض المواضع) نوعًا آخرًا من اللامتغايرات التي تفيد أثناء كتابة البرامج، حيث يمثّل لامتغاير الصنف عبارةً صحيحةً دائمًا عن حالة الصنف أو حالة الكائنات التي أُنشئت من ذلك الصنف؛ سنفترض أن لدينا صنف PairOfDice يُخزّن القيم الظاهرة على حجَريْ نرْدٍ في متغيّرين هما die1 وdie2 لأننا نرغب بوجود لامتغاير صنف يُخبِر أن قيَم حجَريْ النرْد تتراوح بين 1 و6؛ وهذه العِبارة صحيحةٌ دائمًا لأي حَجريْ نْرد. ينبغي أن تَصِح العبارة السابقة في جميع الأحوال إذا أردنا تمثيلها بأحد لامتغايرات الأصناف، وإذا كان die1 وdie2 عبارة عن متغيّرات نُسخٍ عامةٍ instance variables، فلا توجد أي ضمانةٍ ممكنةٍ لتحقيق ذلك؛ لأننا لا نتحكم بالقيم التي يُسنِدها أي برنامج (يستخدم هذا الصنف) إلى تلك المتغيّرات، ولذلك نَعُدهما مثل المتغيرات الخاصة private variables، وبعد ذلك، نتأكد من تطبيق الشيفرة الموجودة داخل الصنف لقاعدة لامتغاير الصنف. بدايةً، عندما نُنشِئ كائنًا من الصنف PairOfDice، فإن قيم die1 وdie2 ينبغي أن تُهيّئ إلى قيم تتراوح بين 1 و6، كما يجب أن يحافِظ كل تابعٍ method معرَّف داخل الصنف على تلك الحقيقة، بمعني أن يتأكد أيّ تابعٍ يُسنِد قيمةً إلى die1 أو die2 من وقوع تلك القيمة في نطاقٍ يتراوح من 1 إلى 6؛ فمثلًا، قد يَفحص تابع ضبْطٍ setter method ما إذا كانت القيمة المسنَدة صالحةٌ أم لا. عمومًا يُمثّل لامتغاير الصنف شرطًا لاحقًا للمُنشئ constructor، كما أنه يُمثّل شرطًا لاحقًا ومسبقًا لأيّ تابعٍ آخرٍ ضِمن الصنف، فعندما تكتب صنفًا، فيجب أن تتأكد من صحة لامتغاير الصنف في كل الأوقات؛ بينما عندما تكتب تابعًا، فيجب أن تتأكد من أن شيفرة ذلك التابع تطبّق قواعد لامتغاير الصنف. سنفترض مثلًا تحقُّق لامتغاير الصنف عند استدعاء تابع، إذًا ينبغي أن نتأكد من صحته حتى بعد انتهاء تنفيذ شيفرة ذلك التابع، وستساعدك طريقة التفكير السابقة بقوةٍ أثناء تصميم الأصناف. مثالٌ آخرٌ، سبَق وأن عرّفنا صنفًا ممثِّلًا لمصفوفةٍ ديناميكيةٍ في القِسم الفرعي 7.2.4، حيث يخزّن ذلك الصنف القيم في مصفوفةٍ عاديةٍ، بالإضافة إلى عدّادٍ لمعرفة عدد العناصر المخزَّنة فعليًا ضِمن المصفوفة: private int[] items = new int[8]; private int itemCount = 0; تحتوي لامتغايرات الصنف على عباراتٍ مِثل "تمثيلٍ itemCount لعدد العناصر" و"تراوح itemCount بين صفر وعدد عناصر المصفوفة" و"وجود قيم العناصر بمواضع المصفوفة من items[0‎]‎ إلى items[itemCount-1]‎"؛ ولذلك يجب أن تتذكر دائمًا لامتغايرات الصنف أثناء تعريفه، فعندما تكتب تابعًا ليضيف عنصرًا مثلًا، فسيذّكرك اللامتغاير الأول بضرورة زيادة itemCount بمقدار الواحد ليتحقّق اللامتغاير دائمًا، بينما سيُخبرك اللامتغاير الثاني بالمكان الذي ينبغي أن تخزِّن فيه العنصر الجديد، كما سيخبرك اللامتغاير الأخير أنه في حالة زيادة itemCount إلى items.length، فستحتاج إلى فعل شئٍ لتتجنّب مخالفة ما تنص عليه قواعد اللامتغاير. سنشير أحيانًا خلال الفصول القادمة إلى فائدة التفكير بالشروط اللاحقة والمسبَقة واللامتغايرات. يتعقّد تحليل البرامج المتوازية parallel programs باستخدام اللامتغايرات، إذ تنفِّذ هذه البرامج عِدّة سلاسل threads من التعليمات تتعامل مع نفْس البيانات في نفْس الوقت. وسنناقش تلك المشكلة أثناء حديثنا عن السلاسل بالفصل 12. معالجة المدخلات تُعَد معالجة البيانات المُدخَلة من المستخدِم أو المقروءة من ملفٍ أو المستقبَلة عبْر الشبكة واحدةٌ من أهم الأشياء التي نتأكد من خلالها من صحة البرنامج ومتانته، وسنتناول الملفات والشبكات بالفصل 11 بصورةٍ تعتمد على ما سنُناقشه بالقِسم التالي، سنفحص الآن مثالًا على معالجة مدخَلات المستخدِم. سنستخدِم الصنف TextIO الدّاعم لمعالجة أخطاء قراءة مُدخَلات المستخدِم، فمثلًا تعيد الدالة TextIO.getDouble()‎ قيمةً صالحةً من النوع double، فإذا أَدخل المستخدِم قيمةً غير صالحةً، فإن الصنف TextIO سيَطلب منه أن يعيد إدخال قيمةٍ صالحةٍ، بمعنى أن هذا الصنف لن يمرِّر قيمةً غير صالحةٍ للبرنامج؛ ومع ذلك، قد لا تتلاءم تلك الطريقة مع بعض الحالات، لاسيّما إذا أدخل المستخدِم بياناتٍ معقدّةٍ، وسنفحص تلك الأخطاء في المثال التالي. قد يفيد أحيانًا أن تطلّع على ما سيُقرَأ لاحقًا مثل مُدخَلٍ دون أن تقرأه بصورةٍ فعليةٍ، فقد يحتاج برنامجٍ معينٍ مثلًا إلى معرفة إذا ما كانت قيمة المُدخَل التالي عبارةٌ عن عددٍ أم كلمةٍ، ولهذا يتضمّن الصنف TextIO على الدالة TextIO.peek()‎، حيث تعيد تلك الدالة قيمةً من النوع char، وتعرّف المحرّف التالي بمُدخَلات المستخدِم، رغم أنها لا تقرؤها بصورةٍ فعلية، فإذا كان المُدخَل التالي عبارةٌ عن محرّف نهاية السطر، فإن الدالة TextIO.peek()‎ ستعيد المحرّف إلى بداية السطر '‎\n'. نحتاج عادةً إلى معرفة المحرّف التالي غير الفارغ بمُدخَلات المستخدِم، ولكننا نحتاج إلى تخطّي أيّ مسافاتٍ أو فراغاتٍ قبل أن نختبر ذلك؛ إذ تستخدِم الشيفرة المعرَّفة بالأسفل الدالة TextIO.peek()‎ لتتطلّع على الأحرف التالية إلى أن تُقابِل محرّف نهاية السطر أو محرّفًا غير فارغٍ، كما تقرأ الدالة TextIO.getAnyChar()‎ المحرّف التالي وتعيده ضِمن مُدخَلات المستخدِم حتى إذا كان مجرّد مسافةٍ فارغةٍ، في المقابل، تتخطّى الدالة TextIO.getChar()‎ الأكثر شيوعًا أي مسافاتٍ فارغةٍ ثم تعيد المحرّف التالي غير الفارغ بعد قراءته؛ ولا يمكننا أن نستخدم TextIO.getChar()‎ هنا، لأن الغرض هو تخطّي المسافات الفارغة دون قراءة المحرّف غير الفارغ التالي. //[1] static void skipBlanks() { char ch; ch = TextIO.peek(); while (ch == ' ' || ch == '\t') { // الحرف التالي عبارة عن مسافة فارغة، اقرأه // وافحص الحرف الذي يليه ch = TextIO.getAnyChar(); ch = TextIO.peek(); } } // end skipBlanks() [1] في الشيفرة السابقة تعني "اقرأ أي مسافاتٍ فارغةٍ بمُدخَلات المستخدم وسيكون الشرط المسبق، أما الحرف التالي بمُدخَلات المستخدم الذي هو عبارةٌ عن محرّف نهاية السطر أو حرفٍ غير فارغٍ". تَشيع هذه العملية جدًا لدرجة أننا أضفناها إلى الصنف TextIO، ونستخدمها باستدعاء TextIO.skipBlanks()‎ المُماثِل تمامًا للتابع skipBlanks()‎. يَسمح المثال بالقِسم الفرعي 3.5.3 للمستخدِم بأن يدخِل قياسات الطول مثل "‎3 miles" أو "‎1 ft"، ثم يحوِّلها إلى وِحداتٍ مثل البوصة inches والقدم feet والياردة yards والميل miles؛ وعادةً ما يدخِل المستخدِم عدّة قياساتٍ بأكثر من وحدةٍ مثل "‎3 feet 7 inches"، وسنحسِّن البرنامج ليسمَح بمُدخَلاتٍ من هذه النوعية. بصورةٍ أكثر تحديدًا، سيدخِل المستخدِم أسطرًا تحتوي على واحدةٍ أو أكثر من قياسات الطول مثل "‎1 foot" أو "‎3 miles 20 yards 2 feet" بحيث تكون وِحدات القياس المسموح بها هي inch وfoot وyard وmile، وكذلك يجب أن يُميّز البرنامج صيغ الجمع منها أي inches وfeet وyards وmiles، واختصاراتها مثل in وft وyd وmi. سنكتب الآن برنامجًا فرعيًا يقرأ سطرًا واحدًا بتلك الصيغة ثم يحسِب عدد البوصات المكافِئة، ثم سيَستخدم البرنامج ذلك العدد لحساب القيمة المكافئة بالوحدات الأخرى feet وyards وmiles، وإذا احتوت مُدخَلات المستخدِم على خطأٍ، فسيطبَع البرنامج الفرعي رسالة خطأٍ ثم يُعيد القيمة -1؛ حيث سيَفترض البرنامج الفرعي أن السطر المُدخَل غير فارغٍ، ولذلك سيَفحص البرنامج main ذلك قبل استدعائه للبرنامج الفرعي، حيث سيَستخدم سطرًا فارغًا ليُشير إلى انتهاء البرنامج، وبفرض تجاهُل احتمالية المُدخَلات غير الصالحة، سنكتب خوارزمية الشيفرة الوهمية pseudocode للبرنامج الفرعي كالتالي: inches = 0 // This will be the total number of inches // طالما ما يزال هناك مُدخَلات بالسطر while there is more input on the line: // اقرأ قيمة القياس الغددية read the numerical measurement // اقرأ وحدة القياس read the units of measure // ‫أضف القيمة المقروءة إلى inches add the measurement to inches return inches الآن، سنختبر إذا ما كانت هناك أي مُدخَلاتٍ أخرى ضِمن نفْس السطر، وسنفحَص إذا ما كان المحرّف التالي غير الفارغ هو محرّف نهاية السطر أم لا، إلا أن هذا الاختبار سيتطلّب هنا شرطًا مُسبَقًا للتأكد من أن المحرّف التالي إما محرّف نهاية السطر أو محرّفٌ غير فارغٍ، ولهذا يجب أن نتخطّى أولًا أي محارف فارغةٍ، وسنعيد كتابة الخوارزمية كالتالي: inches = 0 skipBlanks() while TextIO.peek() is not '\n': // اقرأ قيمة القياس الغددية read the numerical measurement // اقرأ وحدة القياس read the unit of measure // ‫أضف القيمة المقروءة إلى inches add the measurement to inches skipBlanks() return inches يتأكد التابع skipBlanks()‎ في نهاية حلقة التكرار while من تحقُّق الشرط المسبَق، وعمومًا إذا كان هناك شرْطٌ مسبَقٌ لحلقة تكرارٍ معينةٍ، فيجب أن نتأكد من تحقُّقه في نهاية الحلقة قبل أن يحاول الحاسوب أن يحصّل قيمته مرةً أخرى. ماذا عن فحْص الأخطاء؟ ينبغي أن نتأكد من وجود قيمةٍ عدديةٍ قبل أن نقرأ قيمة القياس، كما يجب أن نتأكد من وجود رمزٍ أو كلمةٍ قبل أن نقرأ وحدة القياس، إلا أن وجود العدد آخر شيءٍ ضِمن السطر مثل "3" بدون وحدة قياسٍ يُعَد أمرًا غير مقبولٍ؛ في النهاية يجب أن نفحص إذا ما كانت وِحدة القياس المُدخَلة هي إحدى القيم التالية: inches و feet وyards و miles . ستكون الخوارزمية التي تتضمن شيفرة فحْص الأخطاء كالتالي: inches = 0 skipBlanks() while TextIO.peek() is not '\n': // إذا لم يكن الحرف التالي عبارة عن رقم if the next character is not a digit: // بلغ عن خطأ وأعد -1 report an error and return -1 Let measurement = TextIO.getDouble(); skipBlanks() // Precondition for the next test!! if the next character is end-of-line: report an error and return -1 Let units = TextIO.getWord() if the units are inches: // ‫إذا كانت وحدة لقياس inches // ‫أضف قيمة القياس إلى inches add measurement to inches else if the units are feet: // ‫إذا كانت قدم feet // ‫أضف حاصل ضرب 12*قيمة القياس إلى inches add 12*measurement to inches else if the units are yards: // ‫إذا كانت ياردة // ‫أضف حاصل ضرب 36*قيمة القياس إلى inches add 36*measurement to inches else if the units are miles: // ‫أضف حاصل ضرب 5280*قيمة القياس إلى inches add 12*5280*measurement to inches else // بلغ عن الخطأ وأعد -1 report an error and return -1 skipBlanks() return inches كما ترى، عقّد اختبار الأخطاء الكثير من الخوارزمية بصورةٍ كبيرةٍ مع أنها مجرّد مثالٍ بسيطٍ؛ رغم أننا لم نعالِج جميع الأخطاء الممكِنة مثل إدخال قيمة قياسٍ من خارج النطاق المسموح به للنوع double مثل "1e400"، ولذلك سيعود البرنامج في تلك الحالة للاعتماد على الصنف TextIO ليعالِج الأخطاء، باإضافة إلى ذلك، قد يُدخِل المستخدِم قيمةً مثل "1e308 miles"، وفي حين أن العدد 1e308 يصلح للإدخال، إلا أن قيمته المكافِئة بوحدة البوصة تقع خارج النطاق المسموح به للنوع double، وعندها سيَحصل الحاسوب (كما ذكرنا في القِسم السابق) على القيمة Double.POSITIVE_INFINITY، وبإمكانك أن تُشغّل البرنامج وتجرّبه بنفْسك. سنكتب الخوارزمية بلغة جافا كالتالي: // [2] static double readMeasurement() { double inches; // حاصل مجموع البوصات الكلية double measurement; // قيمة القياس المُدخَلة String units; // وحدة القياس المُدخَلة char ch; // لفحْص الحرف التالي من مُدخَل المستخدم inches = 0; // No inches have yet been read. skipBlanks(); ch = TextIO.peek(); // [1] while (ch != '\n') { // اقرأ قيمة القياس التالية ووحدة القياس المستخدمة if ( ! Character.isDigit(ch) ) { System.out.println( "Error: Expected to find a number, but found " + ch); return -1; } measurement = TextIO.getDouble(); skipBlanks(); if (TextIO.peek() == '\n') { System.out.println( "Error: Missing unit of measure at end of line."); return -1; } units = TextIO.getWord(); units = units.toLowerCase(); /* ‫حوّل قيمة القياس إلى وحدة البوصة وأضفها إلى inches */ if (units.equals("inch") || units.equals("inches") || units.equals("in")) { inches += measurement; } else if (units.equals("foot") || units.equals("feet") || units.equals("ft")) { inches += measurement * 12; } else if (units.equals("yard") || units.equals("yards") || units.equals("yd")) { inches += measurement * 36; } else if (units.equals("mile") || units.equals("miles") || units.equals("mi")) { inches += measurement * 12 * 5280; } else { System.out.println("Error: \"" + units + "\" is not a legal unit of measure."); return -1; } // اختبر إذا ما كان المحرّف التالي هو محرّف نهاية السطر skipBlanks(); ch = TextIO.peek(); } // end while return inches; } // end readMeasurement() حيث تعني [1] أنه طالما ما تزال هناك مُدخَلات أخرى بالسطر، اقرأ قيمة القياس وأضف مكافئها من البوصات إلى المتغيّر inches، وإذا وقع خطأٌ أثناء تنفيذ الحلقة، انهي البرنامج الفرعي فورًا وأعِد القيمة -1. في حين [2] تعني "اقرأ سطرًا واحدًا من مُدخَلات المستخدم، بحيث يكون الشرط المسبَق هو السطر المُدخَل غير الفارغ، ويكون الشرط اللاحق هو تحويل قيمة القياس إلى وحدة البوصة ثم إعادتها مثل قيمةٍ للدالة إذا كان المُدخَل صالحًا، بينما إذا كانت قيمة المُدخَل غير صالحةٍ، تعيد الدالة قيمة "-1". توجد شيفرة البرنامج بالكامل في الملف LengthConverter2.java. ترجمة -بتصرّف- للقسم Section 2: Writing Correct Programs من فصل Chapter 8: Correctness, Robustness, Efficiency من كتاب Introduction to Programming Using Java. اقرأ أيضًا المقال السابق: مقدمة إلى صحة البرامج ومتانتها في جافا تصميم البرامج في جافا البرامج الفرعية والمتغيرات الساكنة في جافا أسلوب كتابة الشيفرات البرمجية وتحقيق سهولة قراءتها كيفية تصريف وبناء البرامج المكتوبة بلغة Cpp
    1 نقطة
×
×
  • أضف...