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

تعليمة حلقة التكرار for في جافا


رضوى العربي

سنتناول في هذا القسم نوعًا آخر من الحَلْقات، هو تَعْليمَة الحَلْقة for. ينبغي أن تُدرك أنه يُمكن لأيّ حَلْقة تَكْرارية (loop) عامةً أن تُكتَب بأيّ من التَعْليمَتين for و while، وهو ما يَعني أن لغة الجافا لم تتَحَصَّل على أيّ مِيزَة وظيفية إضافية بتَدْعِيمها لتَعْليمَة for. لا يَعني ذلك أن تَعْليمَة for غَيْر مُهمة، على العكس تمامًا، ففي الواقع، قد يَتجاوز عَدَد حَلْقات for المُستخدَمة ببعض البرامج عَدَد حَلْقات while. (كما أن الكاتب على معرفة بأحد المبرمجين والذي لا يَستخدِم سوى حَلْقات for). الفكرة ببساطة أن تَعْليمَة for تكون أكثر ملائمة لبعض النوعيات من المسائل؛ حيث تُسهِل من كتابة الحَلْقات وقرائتها بالموازنة مع حَلْقة while.

حلقة التكرار For

عادةً ما تُستخدَم حَلْقة التَكْرار while بالصياغة التالية:

<initialization>
while ( <continuation-condition> ) {
    <statements>
    <update>
}

مثلًا، اُنظر لهذا المثال من القسم ٣.٢:

years = 0;  // هيئ المتغير
while ( years < 5 ) {   // شرط حلقة التكرار
    // نفذ التعليمات الثلاثة التالية
    interest = principal * rate;    
    principal += interest;          
    System.out.println(principal);  

    // حدث قيمة المتغير
    years++;   
}

ولهذا أضيفت تَعْليمَة for للتسهيل من كتابة هذا النوع من الحَلْقات، حيث يُمكِن إِعادة كتابة حَلْقة التَكْرار بالأعلى باستخدام تَعْليمَة for، كالتالي:

for ( years = 0;  years < 5;  years++ ) {
    interest = principal * rate;
    principal += interest;
    System.out.println(principal);
}

لاحظ كيف دُمجَت كُُلًا من تعليمات التهيئة ، والشَّرْط الاستمراري لحَلْقة التَكْرار ، والتَحْدِيث جميعًا بسَطْر واحد هو السَطْر الأول من حَلْقة التَكْرار for. يُسهِل ذلك من قراءة حَلْقة التَكْرار وفهمها؛ لأن جميع تَعْليمَات التحكُّم بالحَلْقة (loop control) قد ضُمِّنت بمكان واحد بشكل منفصل عن مَتْن الحَلْقة الفعليّ المطلوب تَكْرار تَّنْفيذه.

تُنفَّذ حَلْقة التَكْرار for بالأعلى بنفس الطريقة التي تُنفَّذ بها الشيفرة الأصلية، أي تُنفَّذ أولًا تعليمة التهيئة مرة وحيدة قبل بدء تَّنْفيذ الحَلْقة، ثم يُفحَص الشَّرْط الاستمراري للحَلْقة قَبْل كل تَكْرار (iteration/execution) لمَتْن الحَلْقة، بما في ذلك التَكْرار الأوَّليّ (first iteration)، بحيث تَتوقَف الحَلْقة عندما يؤول هذا الشَّرْط إلى القيمة false. وأخيرًا، تُنفَّذ تعليمة التَحْدِيث بنهاية كل تَكْرار (iteration/execution) قَبْل العودة لفَحْص الشَّرْط من جديد.

تُكتَب تَعْليمَة حَلْقة التَكْرار for بالصياغة التالية:

for ( <initialization>; <continuation-condition>; <update> )
    <statement>

أو كالتالي إذا كانت التعليمة كُتليّة البِنْية (block statement):

for ( <initialization>; <continuation-condition>; <update> ) {
    <statements>
}

يُمكِن لأيّ تعبير منطقي (boolean-valued expression) أن يُستخدَم محل الشَّرْط الاستمراري . يُمكِن لأيّ تعبير (expression) -طالما كان صالحًا كتَعْليمَة برمجية- أن يُستخدَم محل تعليمة التهيئة ، وفي الواقع غالبًا ما تُستخدَم تَعْليمَة تَّصْريح (declaration) أو إِسْناد (assignment). يُمكِن لأي تَعْليمَة بسيطة (simple statement) أن تُستخدَم محل تعليمة التَحْدِيث ، وعادةً ما تكون تَعْليمَة زيادة/نقصان (increment/decrement) أو إِسْناد (assignment). وأخيرًا، يُمكِن لأي من تلك التعليمات الثلاثة بالأعلى أن تكون فارغة. لاحظ أنه إذا كان الشَّرْط الاستمراري فارغًا، فإنه يُعامَل وكأنه يُعيد القيمة المنطقية true، أي يُعدّ الشَّرْط مُتحقِّقًا، مما يعني تَّنْفيذ مَتْن حَلْقة التَكْرار (loop body) بشكل لا نهائي (infinite loop) إلى أن يتم إيقافها لسبب ما، مثل اِستخدَام تَعْليمَة break. يُفضِّل بعض المبرمجين في الواقع تَّنْفيذ الحَلْقة اللا نهائية (infinite loop) باِستخدَام الصياغة for (;;)‎ بدلًا من while (true)‎.

يُوضح المخطط (diagram) التالي مَسار التحكُّم (flow control) أثناء تَّنْفيذ حَلْقة التَكْرار for:

001For_Loop_Flow_Control.png

عادةً ما تُسْنِد تعليمة التهيئة قيمة ما إلى مُتَغيِّر معين، ثم تُعدِّل تعليمة التَحْدِيث قيمة هذا المُتَغيِّر إِمّا بواسطة تَعْليمَة إِسْناد (assignment) وإِمّا بعملية زيادة/نُقصان (increment/decrement)، وتُفْحَص تلك القيمة من خلال الشَّرْط الاستمراري لحَلْقة التَكْرار (continuation condition)، فتَتوقَف الحَلْقة عندما يؤول الشَّرْط إلى القيمة المنطقية false. يُطلق عادة على المُتَغيِّر المُستخدَم بهذه الطريقة اسم المُتحكِّم بالحَلْقة (loop control variable). في المثال بالأعلى، كان المُتحكِّم بالحَلْقة هو المُتَغيِّر years.

تُعدّ حَلْقة العَدّ (counting loop) هي النوع الأكثر شيوعًا من حَلْقات التَكْرار for، والتي يأخذ فيها المُتَغيِّر المُتحكِّم بالحَلْقة (loop control variable) قيم جميع الأعداد الصحيحة (integer) الواقعة بين قيمتين إحداهما صغرى (minimum) والآخرى عظمى (maximum). تُكتَب حَلْقة العَدّ كالتالي:

for ( <variable> = <min>;  <variable> <= <max>; <variable>++ ) {
    <statements>
}

يُمكِن لأيّ تعبير يُعيد عددًا صحيحًا (integer-valued expressions) أن يُستخدَم محل و ، ولكن تُستخدَم عادةً قيم ثابتة (constants). يأخذ المُتَغيِّر -المشار إليه باستخدام بالأعلى والمعروف باسم المُتحكِّم بالحَلْقة (loop control variable)- القيم المتراوحة بين و ، أي القيم ‎<min>+1‎‎ و ‎<min>+2‎‎ ..وحتى . وغالبًا ما تُستخدَم قيمة هذا المُتَغيِّر داخل المَتْن (body). مثلًا، حَلْقة التَكْرار for بالأعلى هي حَلْقة عَدّ يأخذ فيها المُتَغيِّر المُتحكِّم بالحَلْقة years القيم ١ و ٢ و ٣ و ٤ و ٥.

تطبع الشيفرة التالية قيم الأعداد من ١ إلى ١٠ إلى الخَرْج القياسي (standard output):

for ( N = 1 ;  N <= 10 ;  N++ )
    System.out.println( N );

مع ذلك، يُفضِّل مبرمجي لغة الجافا بدء العَدّ (counting) من ٠ بدلًا من ١، كما أنهم يميلون إلى اِستخدَام العَامِل > للموازنة بدلًا من ‎<=‎‎. تطبع الشيفرة التالية قيم الأعداد العشرة ٠، ١، ٢، …، ٩، كالتالي:

for ( N = 0 ;  N < 10 ;  N++ )
   System.out.println( N );

يُعدّ اِستخدَام عَامِل الموازنة > بدلًا من ‎<=‎‎ أو العكس مصدرًا شائعًا لحُدوث الأخطاء بفارق الواحد (off-by-one errors) بالبرامج. حاول دائمًا أن تأخذ وقتًا للتفكير إذا ما كنت تَرغَب بمعالجة القيمة النهائية أم لا.

يُمكنك أيضًا إجراء العَدّ التنازلي، وهو ما قد يكون أسهل قليلًا من العَدّ التصاعدي. فمثلًا، لإجراء عَدّ تنازلي من ١٠ إلى ١. ابدأ فقط بالقيمة ١٠، ثم اِنقص المُتَغيِّر المُتحكِّم بالحَلْقة (loop control variable) بدلًا من زيادته، واستمر طالما كانت قيمة المُتَغيِّر أكبر من أو تُساوِي ١:

for ( N = 10 ;  N >= 1 ;  N-- )
    System.out.println( N );

في الواقع، تَسمَح صيغة (syntax) تَعْليمَة for بأن تَشتمِل كُلًا من تعليمتي التهيئة والتَحْدِيث على أكثر من تعبير (expression) مربوطين بفاصلة (comma). يَعنِي ذلك أنه من الممكن الإبقاء على أكثر من عَدَّاد بنفس الوقت، فمثلًا قد يكون لدينا عَدَّاد تصاعدي من ١ إلى ١٠، وآخر تنازلي من ١٠ إلى ١، كالتالي:

for ( i=1, j=10;  i <= 10;  i++, j-- ) {
    // ‫اطبع قيمة i بخمس خانات
    System.out.printf("%5d", i); 
    // ‫اطبع قيمة j بخمس خانات
    System.out.printf("%5d", j); 
    System.out.println();     
}

كمثال أخير، نريد اِستخدَام حَلْقة التَكْرار for لطباعة الأعداد الزوجية (even numbers) الواقعة بين العددين ٢ و ٢٠، أي بالتحديد طباعة الأعداد ٢، ٤، ٦، ٨، ١٠، ١٢، ١٤، ١٦، ١٨، ٢٠. تتوفَّر أكثر من طريقة للقيام بذلك، نسْتَعْرِض منها أربعة حلول ممكنة (ثلاث منها هي حلول نموذجية تمامًا)؛ وذلك لبيان كيف لمسألة بسيطة مثل تلك المسألة أن تُحلّ بطرائق مختلفة:

// (1)   
// المتغير المتحكم بالحلقة سيأخذ القيم من واحد إلى عشرة
// وبالتالي سنطبع القيم 2*1 و 2*2 و ... إلى 2*10
for (N = 1; N <= 10; N++) {             
    System.out.println( 2*N );         
}

// (2)   
// المتغير المتحكم بالحلقة سيأخذ القيم المطلوب طباعتها مباشرة
// عن طريق إضافة 2 بعبارة التحديث بدلًا من واحد
for (N = 2; N <= 20; N = N + 2) {
    System.out.println( N );
}

// (3)   
// مر على جميع الأرقام من اثنين إلى عشرين 
// ولكن اطبع فقط الأعداد الزوجية
for (N = 2; N <= 20; N++) {             
    if ( N % 2 == 0 ) // is N even?     
        System.out.println( N );       
} 

// (4)   
// فقط اطبع الأعداد المطلوبة مباشرة
// غالبًا سيغضب منك الأستاذ في حالة إطلاعه على مثل هذا الحل
for (N = 1; N <= 1; N++) {
    System.out.println("2 4 6 8 10 12 14 16 18 20");
}

من المهم أن نُعيد التأكيد على أنه -باستثناء تَعْليمَة التَّصْريح عن المُتَغيِّرات (variable declaration)- ليس مُمكنًا بأي حال من الأحوال تَّنْفيذ أي تَعْليمَة برمجية، بما في ذلك تَعْليمَة for، بشكل مستقل، وإنما ينبغي أن تُنفَّذ إما داخل البرنامج (routine) الرئيسي main أو داخل إحدى البرامج الفرعية (subroutine)، والمُعرَّفة ضِمْن صَنْف معين (class).

لابُدّ أيضًا من التَّصْريح (declaration) عن أي مُتَغيِّر قبل إمكانية اِستخدَامه، بما في ذلك المُتَغيِّر المُتحكِّم بالحَلْقة (loop control variable) المُستخدَم ضِمْن حَلْقة التَكْرار for. صَرَّحنا عن هذا المُتَغيِّر بكونه من النوع العددي الصحيح (int) بالأمثلة التي فحصناها حتى الآن بهذا القسم. مع ذلك، فإنه ليس أمرًا حتميًا، فقد يكون من نوع آخر. فمثلًا، تَستخدِم حَلْقة التَكْرار for -بالمثال التالي- مُتَغيِّرًا من النوع char، وتعتمد على إمكانية تطبيق عَامِل الزيادة ++ على كُلًا من الحروف والأرقام:

char ch;  // المتغير المتحكم بالحلقة; 
for ( ch = 'A';  ch <= 'Z';  ch++ )
    // اطبع حرف الأبجدية الحالي
    System.out.print(ch); 
System.out.println();

مسألة عد القواسم (divisors)

سنُلقِي الآن نظرة على مسألة أكثر جدية، والتي يُمكِن حلّها باِستخدَام حَلْقة التَكْرار for. بفَرْض أن لدينا عددين صحيحين موجبين (positive integers)‏ N و D. إذا كان باقي قسمة (remainder) العدد N على العدد D مُساوٍ للصفر، يُقال عندها أن الثاني قَاسِمًا (divisor) للأول أو أن الأول مُضاعَفًا (even multiple) للثاني. بالمثل، يُقال -بتعبير لغة الجافا- أن العدد D قَاسِمًا (divisor) للعدد N إذا تَحقَّق الشَّرْط N % D == 0، حيث % هو عَامِل باقي القسمة.

يَسمَح البرنامج التالي للمُستخدِم بإِدْخَال عدد صحيح موجب (positive integer)، ثُمَّ يَحسِب عَدَد القواسم (divisors) المختلفة لذلك العَدَد. لحِساب عَدَد قواسم (divisors) عَدَد معين N، يُمكننا ببساطة فَحْص جميع الأَعْدَاد التي يُحتمَل أن تكون قَاسِمًا للعَدَد N، أيّ جميع الأَعْدَاد الواقعة بدايةً من الواحد ووصولًا للعَدَد N (١، ٢، ٣، … ،N). ثم نَعدّ منها فقط تلكم التي أَمكنها التقسيم الفعليّ للعَدَد N تقسيمًا مُتعادلًا (evenly). على الرغم من أن هذه الطريقة ستُؤدي إلى نتائج صحيحة، فلربما هي ليست الطريقة الأكثر كفاءة لحلّ هذه المسألة. تَسْتَعْرِض الشيفرة التالية الخوارزمية بالشيفرة الوهمية (pseudocode):

// ‫اقرأ قيمة عددية موجبة من المستخدم N
Get a positive integer, N, from the user
// هيئ عداد القواسم
Let divisorCount = 0
// ‫لكل عدد من القيمة واحد وحتى القيمة العددية المدخلة testDivisor
for each number, testDivisor, in the range from 1 to N:
    // إذا كان العدد الحالي قاسم للعدد المدخل
    if testDivisor is a divisor of N:
        // أزد قيمة العداد بمقدار الواحد
        Count it by adding 1 to divisorCount
// اطبع قيمة العداد       
Output the count

تَسْتَعْرِض الخوارزمية السابقة واحدة من الأنماط البرمجية (programming pattern) الشائعة، والتي تُستخدَم عندما يكون لديك مُتتالية (sequence) من العناصر، وتَرغَب بمعالجة بعضًا من تلك العناصر فقط، وليس كلها. يُمكِن تَعْمِيم هذا النمط للصيغة التالية:

// لكل عنصر بالمتتالية
for each item in the sequence:
   // إذا نجح العنصر الحالي بالاختبار
   if the item passes the test:
       // عالج العنصر الحالي
       process it

يُمكننا تَحْوِيل حَلْقة التَكْرار for الموجودة ضِمْن خوارزمية عَدّ القواسم بالأعلى (divisor-counting algorithm) إلى لغة الجافا كالتالي:

for (testDivisor = 1; testDivisor <= N; testDivisor++) {
   if ( N % testDivisor == 0 )
      divisorCount++;
}

بإمكان الحواسيب الحديثة تَّنْفيذ حَلْقة التَكْرار (loop) بالأعلى بسرعة، بل لا يَسْتَحِيل حتى تَّنْفيذها على أكبر عَدَد يُمكن أن يَحمله النوع int، والذي يَصِل إلى ٢١٤٧٤٨٣٦٤٧، ربما حتى قد تَستخدِم النوع long للسماح بأعداد أكبر، ولكن بالطبع سيستغرق تَّنْفيذ الخوارزمية وقتًا أطول مع الأعداد الكبيرة جدًا، ولذلك تَقَرَّر إِجراء تعديل على الخوارزمية بهدف طباعة نقطة (dot) -تَعمَل كمؤشر- بَعْد كل مرة ينتهي فيها الحاسوب من اختبار عشرة مليون قَاسِم (divisor) مُحتمَل جديد. سنضطر في النسخة المُعدَّلة من الخوارزمية إلى الإبقاء على عَدَّادين (counters) منفصلين: الأول منهما لعَدّ القواسم (divisors) الفعليّة التي تحصَّلَنا عليها، والآخر لعَدّ جميع الأعداد التي اُختبرت حتى الآن. عندما يَصل العَدَّاد الثاني إلى قيمة عشرة ملايين، سيَطبع البرنامج نقطة .، ثُمَّ يُعيد ضَبْط قيمة ذلك العَدَّاد إلى صفر؛ ليبدأ العَدّ من جديد. تُصبِح الخوارزمية باِستخدَام الشيفرة الوهمية كالتالي:

// اقرأ عدد صحيح موجب من المستخدم
Get a positive integer, N, from the user
Let divisorCount = 0  // عدد القواسم التي تم العثور عليها
Let numberTested = 0  // عدد القواسم المحتملة والتي تم اختبارها

// ‫اقرأ رد المستخدم إلى المتغير str
// ‫لكل عدد يتراوح من القيمة واحد وحتى قيمة العدد المدخل
for each number, testDivisor, in the range from 1 to N:
    // إذا كان العدد الحالي قاسم للعدد المدخل 
    if testDivisor is a divisor of N:
        // أزد عدد القواسم التي تم العثور عليها بمقدار الواحد
        Count it by adding 1 to divisorCount
    // أزد عدد الأعداد المحتملة التي تم اختبارها بمقدار الواحد
    Add 1 to numberTested
    // إذا كان عدد الأعداد المحتملة المختبر يساوي عشرة ملايين
    if numberTested is 10000000:
    // اطبع نقطة
        print out a '.'
    // أعد ضبط عدد الأعداد المختبرة إلى القيمة صفر
        Reset numberTested to 0
// اطبع عدد القواسم
Output the count

وأخيرًا، يُمكننا تَحْوِيل الخوارزمية إلى برنامج كامل بلغة الجافا، كالتالي:

import textio.TextIO;

public class CountDivisors {

    public static void main(String[] args) {

        int N;  // القيمة العددية المدخلة من قبل المستخدم 
        int testDivisor;  // ‫عدد يتراوح من القيمة واحد وحتى N
        int divisorCount; // عدد قواسم‫ N التي عثر عليها حتى الآن
        int numberTested;  // عدد القواسم المحتملة التي تم اختبارها 

        // اقرأ قيمة عدد صحيح موجبة من المستخدم
        while (true) {
            System.out.print("Enter a positive integer: ");
            N = TextIO.getlnInt();
            if (N > 0)
                break;
            System.out.println("That number is not positive.  Please try again.");
        }

        // عِدّ القواسم واطبع نقطة بعد كل عشرة ملايين اختبار
        divisorCount = 0;
        numberTested = 0;

        for (testDivisor = 1; testDivisor <= N; testDivisor++) {
            if ( N % testDivisor == 0 )
                divisorCount++;
            numberTested++;
            if (numberTested == 10000000) {
                System.out.print('.');
                numberTested = 0;
            }
        }

        // اعرض النتائج
        System.out.println();
        System.out.println("The number of divisors of " + N
                           + " is " + divisorCount);

    } // ‫نهاية البرنامج main

} // ‫نهاية الصنف CountDivisors

حلقات for المتداخلة

كما ذَكَرنا مُسْبَّقًا، فإن بُنَى التحكُّم (control structures) بلغة الجافا هي ببساطة تَعْليمَات مُركَّبة، أي تَتضمَّن مجموعة تَعْليمَات. في الحقيقة، يُمكن أيضًا لبِنْية تحكُّم (control structure) أن تَشتمِل على بِنْية تحكُّم أخرى أو أكثر، سواء كانت من نفس النوع أو من أيّ نوع آخر، ويُطلَق عليها في تلك الحالة اسم بُنَى التحكُّم المُتداخِلة (nested). لقد مررنا بالفعل على عدة أمثلة تَتضمَّن هذا النوع من البُنَى، فمثلًا رأينا تَعْليمَات if ضِمْن حَلْقات تَكْرارية (loops)، كما رأينا حَلْقة while داخلية (inner) مُضمَّنة بداخل حَلْقة while آخرى خارجية. لا يَقتصر الأمر على هذه الأمثلة؛ حيث يُسمَح عامةً بدمج بُنَى التحكُّم بأي طريقة ممكنة، وتستطيع حتى القيام بذلك على عدة مستويات من التَدَاخُل (levels of nesting)، فمثلًا يُمكن لحَلْقة while أن تَحتوِي على تَعْليمَة if، والتي بدورها قد تَحتوِي على تَعْليمَة while آخرى؛ فلغة الجافا Java لا تَضع عامةً أي قيود على عدد مستويات التَدَاخُل المسموح بها، ومع ذلك يَصعُب عمليًا فهم الشيفرة إذا اِحْتَوت على أكثر من عدد قليل من مستويات التَدَاخُل (levels of nesting).

تَستخدِم كثير من الخوارزميات حَلْقات for المُتداخِلة (nested)، لذا من المهم أن تفهم طريقة عملها. دعنا نَفحْص عدة أمثلة، مثلًا، مسألة طباعة جدول الضرب (multiplication table) على الصورة التالية:

 1   2   3   4   5   6   7   8   9  10  11  12
 2   4   6   8  10  12  14  16  18  20  22  24
 3   6   9  12  15  18  21  24  27  30  33  36
 4   8  12  16  20  24  28  32  36  40  44  48
 5  10  15  20  25  30  35  40  45  50  55  60
 6  12  18  24  30  36  42  48  54  60  66  72
 7  14  21  28  35  42  49  56  63  70  77  84
 8  16  24  32  40  48  56  64  72  80  88  96
 9  18  27  36  45  54  63  72  81  90  99 108
10  20  30  40  50  60  70  80  90 100 110 120
11  22  33  44  55  66  77  88  99 110 121 132
12  24  36  48  60  72  84  96 108 120 132 144

نُظِّمت البيانات بالجدول إلى ١٢ صف و ١٢ عمود. اُنظر الخوارزمية التالية -بالشيفرة الوهمية (pseudocode)- لطباعة جدول مُشابه:

for each rowNumber = 1, 2, 3, ..., 12:
    // اطبع بسَطر منفصل المضاعفات الاثنى عشر الأولى من قيمة المتغير 
    Print the first twelve multiples of rowNumber on one line
    // اطبع محرف العودة الى بداية السطر
    Output a carriage return

في الواقع، يُمكن للسطر الأول بمَتْن حَلْقة for بالأعلى "اطبع بسَطر منفصل المضاعفات الاثنى عشر الأولى من قيمة المتغير الحالية" أن يُكتَب على صورة حَلْقة for أخرى منفصلة كالتالي:

for N = 1, 2, 3, ..., 12:
   Print N * rowNumber

تحتوي الآن النسخة المُعدَّلة من خوارزمية طباعة جدول الضرب على حَلْقتي for مُتداخِلتين، كالتالي:

for each rowNumber = 1, 2, 3, ..., 12:
   for N = 1, 2, 3, ..., 12:
      Print N * rowNumber
   // اطبع محرف العودة الى بداية السطر
   Output a carriage return

يُمكن اِستخدَام مُحدِّدات الصيغة (format specifier) عند طباعة خَرْج ما (output)؛ بهدف تخصيص صيغة هذا الخَرْج، ولهذا سنَستخدِم مُحدِّد الصيغة ‎%4d‎ عند طباعة أيّ عَدَد بالجدول؛ وذلك لجعله يَحْتلَّ أربعة خانات دائمًا دون النظر لعَدَد الخانات المطلوبة فعليًا، مما يُحسِن من شكل الجدول النهائي. بفَرْض أنه قد تم الإعلان عن المُتَغيِّرين rowNumber و N بحيث يَكُونا من النوع العددي int، يمكن عندها كتابة الخوارزمية بلغة الجافا، كالتالي:

for ( rowNumber = 1;  rowNumber <= 12;  rowNumber++ ) {
    for ( N = 1;  N <= 12;  N++ ) {
        // اطبع الرقم بأربع خانات بدون طباعة محرف العودة الى بداية السطر
        System.out.printf( "%4d", N * rowNumber );  
    }
    // اطبع محرف العودة الى بداية السطر 
    System.out.println();  
}

ربما قد لاحظت أن جميع الأمثلة التي تَعْرَضنا لها -خلال هذا القسم- حتى الآن تَتعامَل فقط مع الأعداد، لذلك سننتقل خلال المثال التالي إلى معالجة النصوص (text processing). لنفْترِض أن لدينا سِلسِلة نصية (string)، ونريد كتابة برنامج لتَحْدِيد الحروف الأبجدية (letters of the alphabet) الموجودة بتلك السِلسِلة. فمثلًا، إذا كان لدينا السِلسِلة النصية "أهلًا بالعالم"، فإن الحروف الموجودة بها هي الألف، والباء، والعين، واللام، والميم، والهاء. سيَستقبِل البرنامج، بالتَحْدِيد، سِلسِلة نصية من المُستخدِم، ثم يَعرِض قائمة بكل تلك الحروف المختلفة الموجودة ضمن تلك السِلسِلة، بالإضافة إلى عَدَدها. كالعادة، سنبدأ أولًا بكتابة الخوارزمية بصيغة الشيفرة الوهمية (pseudocode)، كالتالي:

// اطلب من المستخدم إدخال سلسلة نصية
Ask the user to input a string
// ‫اقرأ رد المستخدم إلى المتغير str
Read the response into a variable, str
// هيئ عداد لعدّ الحروف المختلفة
Let count = 0  (for counting the number of different letters)
// لكل حرف أبجدي
for each letter of the alphabet:
   // إذا كان الحرف موجودًا بالسلسلة النصية
   if the letter occurs in str:
      // اطبع الحرف
      Print the letter
      // أزد قيمة العداد
      Add 1 to count
// اطبع قيمة العداد
Output the count

سنَستخدِم الدالة TextIO.getln()‎ لقراءة السَطْر الذي أَدْخَله المُستخدِم بالكامل؛ وذلك لحاجتنا إلى معالجته على خطوة واحدة. يُمكننا تَحْوِيل سَطْر الخوارزمية "لكل حرف أبجدي" إلى حَلْقة التَكْرار for كالتالي for (letter='A'; letter<='Z'; letter++)‎. في المقابل، سنحتاج إلى التفكير قليلًا بالطريقة التي سنكتب بها تَعْليمَة if الموجودة ضِمْن تلك الحَلْقة. نُريد تَحْدِيدًا إيجاد طريقة نتحقَّق من خلالها إذا ما كان الحرف الأبجدي الحالي بالتَكْرار (iteration)‏ letter موجودًا بالسِلسِلة النصية str أم لا. أحد الحلول هو المرور على جميع حروف السِلسِلة النصية str حرفًا حرفًا، لفَحْص ما إذا كان أيًا منها مُساو لقيمة الحرف الأبجدي الحالي‏ letter، ولهذا سنَستخدِم الدالة str.charAt(i)‎ لجَلْب الحرف الموجود بموقع معين i بالسِلسِلة النصية str، بحيث تَتراوح قيمة i من الصفر وحتى عدد حروف السِلسِلة النصية، والتي يُمكن حِسَابها باِستخدَام التعبير str.length() - 1.

سنواجه مشكلة أخرى، وهي إمكانية وجود الحرف الأبجدي بالسِلسِلة النصية str على صورتين، كحرف كبير (upper case) أو كحرف صغير (lower case). فمثلًا قد يكون الحرف A على الصورة A أو a، ولذلك نحن بحاجة لفَحْص كلتا الحالتين. قد نتجنب، في المقابل، هذه المشكلة بتَحْوِيل جميع حروف السِلسِلة النصية str إلى الحروف الكبيرة (upper case) قبل بدء المعالجة، وعندها نستطيع فَحْص الحروف الكبيرة (upper case) فقط. والآن نُعيد صياغة الخوارزمية كالتالي:

// اطلب من المستخدم إدخال سلسلة نصية
Ask the user to input a string
// ‫اقرأ رد المستخدم إلى المتغير str
Read the response into a variable, str
// كَبِّر حروف السلسلة النصية
Convert str to upper case
// هيئ عداد لعدّ الحروف المختلفة
Let count = 0
for letter = 'A', 'B', ..., 'Z':
    for i = 0, 1, ..., str.length()-1:
        if letter == str.charAt(i):
               // اطبع الحرف
            Print letter
            // أزد قيمة العداد
            Add 1 to count
            // اخرج من الحلقة لتجنب إعادة عدّ الحرف أكثر من مرة
            break  
Output the count

لاحظ اِستخدَامنا لتَعْليمَة break داخل حَلْقة تَكْرار for الداخلية؛ حتى نتجنب طباعة حرف الأبجدية الحالي‏ letter وعَدّه مجددًا إذا كان موجودًا أكثر من مرة بالسِلسِلة النصية. تُوقِف تَعْليمَة break حَلْقة التَكْرار for الداخلية فقط (inner loop)، وليس الخارجية (outer loop)، والتي ينتقل الحاسوب في الواقع إلى تَّنْفيذها بمجرد خروجه من الحَلْقة الداخلية، ولكن مع حرف الأبجدية التالي. حاول استكشاف القيمة النهائية للمُتَغيِّر count في حالة حَذْف تَعْليمَة break.

تَسْتَعْرِض الشيفرة التالية البرنامج بالكامل بلغة الجافا:

import textio.TextIO;

public class ListLetters {

    public static void main(String[] args) {

        String str;  // السطر المدخل من قبل المستخدم
        int count;   // عدد الحروف المختلفة الموجودة بالسلسلة النصية
        char letter;

        System.out.println("Please type in a line of text.");
        str = TextIO.getln();

        str = str.toUpperCase();

        count = 0;
        System.out.println("Your input contains the following letters:");
        System.out.println();
        System.out.print("   ");
        for ( letter = 'A'; letter <= 'Z'; letter++ ) {
            int i;  // موضع الحرف بالسِلسِلة النصية
            for ( i = 0; i < str.length(); i++ ) {
                if ( letter == str.charAt(i) ) {
                    System.out.print(letter);
                    System.out.print(' ');
                    count++;
                    break;
                }
            }
        }

        System.out.println();
        System.out.println();
        System.out.println("There were " + count + " different letters.");
    } // ‫نهاية البرنامج main

} // ‫نهاية الصنف ListLetters

في الواقع، تتوفَّر الدالة str.indexOf(letter)‎ المبنية مُسْبَّقًا (built-in function)، والمُستخدَمة لاختبار ما إذا كان الحرف letter موجودًا بالسِلسِلة النصية str أم لا. إذا لم يكن الحرف موجودًا بالسِلسِلة، ستُعيد الدالة القيمة -1، أما إذا كان موجودًا، فإنها ستُعيد قيمة أكبر من أو تُساوي الصفر. ولهذا كان يمكننا ببساطة إجراء عملية فَحْص وجود الحرف بالسِلسِلة باِستخدَام التعبير if (str.indexOf(letter) >= 0)‎، بدلًا من اِستخدَام حَلْقة تَكْرار مُتداخِلة (nested loop). يتضح لنا من خلال هذا المثال كيف يمكننا اِستخدَام البرامج الفرعية (subroutines)؛ لتبسيط المسائل المعقدة ولكتابة شيفرة مَقْرُوءة.

ترجمة -بتصرّف- للقسم Section 4: The for Statement من فصل Chapter 3: Programming in the Small II: Control من كتاب Introduction to Programming Using Java.


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...