أساسيات برمجة Dart

الحلقات: for و while و do-while و for-in

40 دقيقة الدرس 7 من 13

لماذا نحتاج الحلقات؟

تخيل أنك تحتاج طباعة "مرحبا" 100 مرة أو معالجة كل عنصر في قائمة من 1000 منتج. كتابة كل سطر يدوياً سيكون أمراً سخيفاً. الحلقات تتيح لك تكرار كتلة من الكود عدد المرات المطلوب -- إما عدداً ثابتاً من المرات أو حتى يصبح شرط معين خاطئاً. الحلقات هي من أهم الأدوات الأساسية في كل لغة برمجة.

حلقة for

حلقة for هي أشهر حلقة في Dart. لها ثلاثة أجزاء: التهيئة (تعمل مرة واحدة) و الشرط (يُفحص قبل كل تكرار) و التحديث (يعمل بعد كل تكرار).

حلقة for الأساسية

void main() {
  // طباعة الأرقام من 1 إلى 5
  for (int i = 1; i <= 5; i++) {
    print(i);
  }
  // المخرجات: 1  2  3  4  5

  // العد بمقدار اثنين
  for (int i = 0; i <= 10; i += 2) {
    print(i);
  }
  // المخرجات: 0  2  4  6  8  10

  // العد التنازلي
  for (int i = 5; i >= 1; i--) {
    print('العد التنازلي: $i');
  }
  // المخرجات: العد التنازلي: 5  العد التنازلي: 4  ... العد التنازلي: 1
}
ملاحظة: متغير الحلقة i محدود النطاق داخل حلقة for. لا يوجد خارج جسم الحلقة. هذا يمنع إعادة الاستخدام العرضي ويبقي كودك نظيفاً.

التكرار عبر النصوص

يمكنك استخدام حلقة for للتكرار على كل حرف في نص باستخدام خاصية length وترميز الفهرس:

التكرار على النصوص

void main() {
  String word = 'Dart';

  for (int i = 0; i < word.length; i++) {
    print('الحرف $i: ${word[i]}');
  }
  // المخرجات:
  // الحرف 0: D
  // الحرف 1: a
  // الحرف 2: r
  // الحرف 3: t
}

حلقة while

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

حلقة while الأساسية

void main() {
  int count = 1;

  while (count <= 5) {
    print('العدد: $count');
    count++;  // لا تنسَ هذا! بدونه ستعمل الحلقة للأبد.
  }
  // المخرجات: العدد: 1  العدد: 2  العدد: 3  العدد: 4  العدد: 5

  // مثال عملي: القسمة حتى أقل من 1
  double value = 100.0;
  int divisions = 0;

  while (value >= 1) {
    value /= 2;
    divisions++;
  }
  print('قسّمنا $divisions مرات. القيمة النهائية: ${value.toStringAsFixed(4)}');
  // المخرجات: قسّمنا 7 مرات. القيمة النهائية: 0.7813
}
تحذير: إذا لم يصبح الشرط في حلقة while أبداً false ستحصل على حلقة لا نهائية وبرنامجك سيتوقف أو ينهار. تأكد دائماً أن شيئاً داخل الحلقة يغير الشرط نحو false.

حلقة do-while

حلقة do-while مشابهة لـ while لكن مع فرق رئيسي: تنفذ الجسم مرة واحدة على الأقل قبل فحص الشرط. يُفحص الشرط بعد كل تكرار.

do-while مقابل while

void main() {
  // حلقة while تعمل 0 مرات (الشرط خاطئ فوراً)
  int x = 10;
  while (x < 5) {
    print('while: $x');  // لا تطبع أبداً
    x++;
  }

  // حلقة do-while تعمل مرة واحدة (الجسم ينفذ قبل فحص الشرط)
  int y = 10;
  do {
    print('do-while: $y');  // تطبع: do-while: 10
    y++;
  } while (y < 5);
}

do-while عملي: محاكاة التحقق من المدخلات

void main() {
  // محاكاة قائمة يجب عرضها مرة واحدة على الأقل
  List<String> simulatedInputs = ['invalid', 'wrong', 'quit'];
  int attempt = 0;

  do {
    String input = simulatedInputs[attempt];
    print('المحاولة ${attempt + 1}: أدخلت "$input"');
    if (input == 'quit') {
      print('وداعاً!');
      break;
    }
    print('إدخال غير صالح حاول مرة أخرى.');
    attempt++;
  } while (attempt < simulatedInputs.length);
}
متى تستخدم do-while: استخدمها عندما يجب أن يعمل جسم الحلقة مرة واحدة على الأقل -- مثل عرض قائمة أو طلب إدخال أو تنفيذ إجراء قبل التحقق من الاستمرار. إذا كان عدم التكرار نتيجة صالحة استخدم حلقة while عادية بدلاً من ذلك.

حلقة for-in

حلقة for-in مصممة للتكرار على المجموعات (مثل Lists و Sets وغيرها من العناصر القابلة للتكرار). هي أنظف من حلقة for التقليدية عندما لا تحتاج الفهرس.

for-in مع القوائم

void main() {
  List<String> fruits = ['تفاح', 'موز', 'كرز', 'تمر'];

  // for-in -- نظيفة وقابلة للقراءة
  for (String fruit in fruits) {
    print(fruit);
  }
  // المخرجات: تفاح  موز  كرز  تمر

  // مقارنة مع حلقة for التقليدية
  for (int i = 0; i < fruits.length; i++) {
    print('${i + 1}. ${fruits[i]}');
  }
  // المخرجات: 1. تفاح  2. موز  3. كرز  4. تمر
}

for-in مع الأعداد

void main() {
  List<int> numbers = [10, 20, 30, 40, 50];

  // حساب المجموع
  int sum = 0;
  for (int number in numbers) {
    sum += number;
  }
  print('المجموع: $sum');  // المجموع: 150

  // إيجاد القيمة القصوى
  int max = numbers[0];
  for (int number in numbers) {
    if (number > max) {
      max = number;
    }
  }
  print('الأقصى: $max');  // الأقصى: 50
}
ملاحظة: استخدم for-in عندما تحتاج فقط كل عنصر. استخدم حلقة for التقليدية عندما تحتاج الفهرس أو عندما تحتاج تعديل القائمة أثناء التكرار (وهو ما لا تسمح به for-in).

طريقة forEach

مجموعات Dart لديها أيضاً طريقة forEach التي تقبل دالة. تفعل نفس الشيء مثل for-in لكن تستخدم استدعاء خلفي:

طريقة forEach

void main() {
  List<String> colors = ['أحمر', 'أخضر', 'أزرق'];

  // استخدام forEach مع دالة مجهولة
  colors.forEach((color) {
    print('اللون: $color');
  });

  // استخدام forEach مع صيغة السهم (للتعبيرات المفردة)
  colors.forEach((color) => print('أحب $color'));

  // مع الفهرس باستخدام asMap()
  colors.asMap().forEach((index, color) {
    print('$index: $color');
  });
  // المخرجات: 0: أحمر  1: أخضر  2: أزرق
}

break و continue

أحياناً تحتاج تغيير التدفق الطبيعي للحلقة. break تخرج من الحلقة بالكامل بينما continue تتخطى بقية التكرار الحالي وتنتقل للتالي.

break -- الخروج من الحلقة مبكراً

void main() {
  // إيجاد أول رقم يقبل القسمة على 7
  for (int i = 1; i <= 100; i++) {
    if (i % 7 == 0) {
      print('أول رقم يقبل القسمة على 7: $i');
      break;  // أوقف الحلقة فوراً
    }
  }
  // المخرجات: أول رقم يقبل القسمة على 7: 7

  // البحث عن عنصر في قائمة
  List<String> names = ['علي', 'أحمد', 'خالد', 'سارة'];
  String searchFor = 'خالد';
  bool found = false;

  for (String name in names) {
    if (name == searchFor) {
      found = true;
      break;  // لا حاجة لفحص العناصر المتبقية
    }
  }
  print('$searchFor موجود: $found');  // خالد موجود: true
}

continue -- التخطي إلى التكرار التالي

void main() {
  // طباعة الأرقام الفردية فقط من 1 إلى 10
  for (int i = 1; i <= 10; i++) {
    if (i % 2 == 0) {
      continue;  // تخطي الأرقام الزوجية
    }
    print(i);
  }
  // المخرجات: 1  3  5  7  9

  // معالجة الدرجات الصالحة فقط
  List<int> scores = [85, -1, 92, 0, 78, -5, 95];

  for (int score in scores) {
    if (score <= 0) {
      continue;  // تخطي الدرجات غير الصالحة
    }
    print('درجة صالحة: $score');
  }
  // المخرجات: درجة صالحة: 85  درجة صالحة: 92  درجة صالحة: 78  درجة صالحة: 95
}

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

يمكنك وضع حلقة داخل حلقة أخرى. الحلقة الداخلية تكمل جميع تكراراتها لكل تكرار واحد من الحلقة الخارجية.

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

void main() {
  // جدول الضرب (1-5)
  for (int i = 1; i <= 5; i++) {
    String row = '';
    for (int j = 1; j <= 5; j++) {
      row += '${(i * j).toString().padLeft(3)}';
    }
    print(row);
  }
  // المخرجات:
  //   1  2  3  4  5
  //   2  4  6  8 10
  //   3  6  9 12 15
  //   4  8 12 16 20
  //   5 10 15 20 25
}

الخروج من الحلقات المتداخلة بالتسميات

void main() {
  // التسميات تتيح لك الخروج من حلقة محددة
  outerLoop:
  for (int i = 0; i < 5; i++) {
    for (int j = 0; j < 5; j++) {
      if (i + j == 6) {
        print('خروج عند i=$i, j=$j');
        break outerLoop;  // يكسر الحلقة الخارجية
      }
    }
  }
  // المخرجات: خروج عند i=2, j=4
  // بدون التسمية break سيخرج فقط من الحلقة الداخلية
}
تحذير الأداء: الحلقات المتداخلة تضرب عدد التكرارات. حلقة من 1000 داخل حلقة أخرى من 1000 تعطيك 1000000 تكرار. كن حذراً من الأداء عند تداخل الحلقات خاصة مع مجموعات البيانات الكبيرة.

أنماط الحلقات الشائعة

نمط: المراكم (مجموع، عدّاد، بناء نص)

void main() {
  List<double> prices = [9.99, 24.50, 3.75, 15.00, 42.99];

  // مراكم المجموع
  double total = 0;
  for (double price in prices) {
    total += price;
  }
  print('الإجمالي: \$${total.toStringAsFixed(2)}');  // الإجمالي: $96.23

  // مراكم العدّاد
  int expensiveCount = 0;
  for (double price in prices) {
    if (price > 20) {
      expensiveCount++;
    }
  }
  print('العناصر الغالية: $expensiveCount');  // العناصر الغالية: 2

  // بناء نص
  List<String> words = ['Dart', 'رائعة', 'جداً'];
  String sentence = '';
  for (int i = 0; i < words.length; i++) {
    sentence += words[i];
    if (i < words.length - 1) sentence += ' ';
  }
  print(sentence);  // Dart رائعة جداً
}

نمط: البحث والتصفية

void main() {
  List<int> numbers = [12, 5, 8, 130, 44, 3, 99, 67];

  // إيجاد جميع الأرقام الأكبر من 50
  List<int> large = [];
  for (int n in numbers) {
    if (n > 50) {
      large.add(n);
    }
  }
  print('الأرقام > 50: $large');  // الأرقام > 50: [130, 99, 67]

  // إيجاد أول رقم زوجي
  for (int n in numbers) {
    if (n % 2 == 0) {
      print('أول زوجي: $n');  // أول زوجي: 12
      break;
    }
  }
}

اختيار الحلقة المناسبة

دليل سريع:
for -- عندما تعرف عدد التكرارات بالضبط أو تحتاج فهرساً.
while -- عندما تكرر حتى يتغير شرط (عدد تكرارات غير معروف).
do-while -- نفس while لكن الجسم يجب أن يعمل مرة واحدة على الأقل.
for-in -- عند التكرار على مجموعة ولا تحتاج الفهرس.
forEach -- أسلوب وظيفي جيد للعمليات البسيطة على كل عنصر.

أمثلة عملية

مثال: FizzBuzz

void main() {
  // تحدي برمجي كلاسيكي
  for (int i = 1; i <= 20; i++) {
    if (i % 3 == 0 && i % 5 == 0) {
      print('FizzBuzz');
    } else if (i % 3 == 0) {
      print('Fizz');
    } else if (i % 5 == 0) {
      print('Buzz');
    } else {
      print(i);
    }
  }
}

مثال: منطق تخمين الأرقام

void main() {
  int secretNumber = 7;
  List<int> guesses = [3, 9, 5, 7, 10];
  int attempts = 0;

  for (int guess in guesses) {
    attempts++;
    if (guess == secretNumber) {
      print('صحيح! وجدنا $secretNumber في $attempts محاولات.');
      break;
    } else if (guess < secretNumber) {
      print('$guess منخفض جداً.');
    } else {
      print('$guess مرتفع جداً.');
    }
  }
}

مثال: عكس نص

void main() {
  String original = 'Hello, Dart!';
  String reversed = '';

  for (int i = original.length - 1; i >= 0; i--) {
    reversed += original[i];
  }

  print('الأصلي: $original');   // الأصلي: Hello, Dart!
  print('المعكوس: $reversed');   // المعكوس: !traD ,olleH
}

تمرين عملي

افتح DartPad وأنشئ برنامجاً: (1) استخدم حلقة for لحساب مضروب 10 (10! = 10 × 9 × 8 × ... × 1). (2) استخدم حلقة while لإيجاد أصغر قوة من 2 أكبر من 1000. (3) استخدم for-in للتكرار على قائمة أسماء وطباعة فقط الأسماء التي تحتوي أكثر من 4 أحرف. (4) استخدم حلقات متداخلة لطباعة مثلث قائم من النجوم (5 صفوف). (5) نفذ FizzBuzz من 1 إلى 30 باستخدام أي نوع حلقة.