الدوال والمصفوفات والنصوص

التكرار على المصفوفات ومعالجتها

15 دقيقة الدرس 6 من 14

التكرار على المصفوفات ومعالجتها

تعرّفتَ في الدرس السابق على كيفية تعريف المصفوفات وملئها. الآن يأتي دور المهارة العملية: المرور على كل عنصر لقراءته أو تعديله أو البحث فيه، واستخدام صنف الأداة المدمج java.util.Arrays حتى لا تضطر إلى إعادة تنفيذ العمليات الشائعة من الصفر.

التكرار على المصفوفات

تمنحك Java ثلاث طرق طبيعية للتكرار على المصفوفة. فهم متى تستخدم كل طريقة هو ما يميّز الكود المقروء عن الكود الفوضوي.

1. حلقة for الكلاسيكية — استخدمها حين تحتاج إلى الفهرس (رقم الموضع).

int[] scores = {85, 92, 78, 90, 88}; for (int i = 0; i < scores.length; i++) { System.out.println("الطالب " + (i + 1) + " حصل على " + scores[i]); }

تُعيد scores.length دائمًا العدد الإجمالي للعناصر. لاحظ أن الشرط هو i < scores.length لا i <= scores.length — الفهارس الصحيحة تمتد من 0 حتى length - 1.

ArrayIndexOutOfBoundsException هو أكثر أخطاء المبتدئين شيوعًا مع المصفوفات. يحدث فور محاولة الوصول إلى الفهرس scores.length أو أي فهرس سالب. تحقّق دائمًا من حدود حلقتك.

2. حلقة for المحسّنة (for-each) — استخدمها حين تحتاج القيمة فقط دون الموضع.

for (int score : scores) { System.out.println("الدرجة: " + score); }

تُقرأ هذه الصيغة كالتالي: "لكل score في scores". إنها أقصر وتُلغي خطر تجاوز الحدود بالكامل. المقايضة هنا أنك لا تستطيع تعديل المصفوفة من خلال متغير الحلقة — التغييرات على score داخل الحلقة لا تؤثر في المصفوفة الأصلية.

3. تعديل العناصر — يجب استخدام صيغة الفهرس.

// مضاعفة كل درجة for (int i = 0; i < scores.length; i++) { scores[i] = scores[i] * 2; }
القاعدة العملية: إن كنت تريد قراءة العناصر فضّل حلقة for المحسّنة — فهي أوضح. أما إن احتجت إلى الكتابة في العناصر أو إلى الفهرس لأي سبب، فاستخدم حلقة for الكلاسيكية.

صنف الأداة Arrays

يأتي صنف java.util.Arrays مع JDK ويوفّر توابع جاهزة ومُحسَّنة لأكثر مهام المصفوفات شيوعًا. استورده مرة واحدة في أعلى ملفك:

import java.util.Arrays;

Arrays.toString — طباعة المصفوفة

لو حاولت System.out.println(scores) مباشرةً طبعت Java شيئًا كـ [I@6d06d69c — وهو عنوان الذاكرة لا القيم. يُعطيك Arrays.toString() تمثيلًا مقروءًا:

int[] numbers = {3, 1, 4, 1, 5, 9, 2, 6}; System.out.println(Arrays.toString(numbers)); // الناتج: [3, 1, 4, 1, 5, 9, 2, 6]

Arrays.sort — فرز المصفوفة

يفرز Arrays.sort() المصفوفة في موضعها (يعدّل المصفوفة الأصلية). للأنواع الأولية يستخدم خوارزمية فرز سريع ثنائي المحور — سريعة للغاية عمليًا.

int[] numbers = {3, 1, 4, 1, 5, 9, 2, 6}; Arrays.sort(numbers); System.out.println(Arrays.toString(numbers)); // الناتج: [1, 1, 2, 3, 4, 5, 6, 9]

يمكنك أيضًا فرز نطاق جزئي فقط دون المساس ببقية المصفوفة:

// فرز الفهارس من 2 إلى 4 فقط (البداية شاملة، النهاية غير شاملة) int[] data = {50, 40, 30, 20, 10, 60}; Arrays.sort(data, 2, 5); System.out.println(Arrays.toString(data)); // الناتج: [50, 40, 10, 20, 30, 60]

Arrays.fill — ملء المصفوفة بقيمة واحدة

بدلًا من التكرار يدويًا لتهيئة كل خلية، يُنجز Arrays.fill() المهمة بنداء واحد:

int[] grid = new int[5]; Arrays.fill(grid, -1); System.out.println(Arrays.toString(grid)); // الناتج: [-1, -1, -1, -1, -1]

Arrays.copyOf — إنشاء نسخة بحجم مختلف

ينشئ Arrays.copyOf(original, newLength) مصفوفة جديدة تمامًا. إذا كان newLength أكبر من الأصل تُملأ الخانات الإضافية بالصفر؛ وإذا كان أصغر تُبتر النتيجة.

int[] original = {10, 20, 30}; // تكبير المصفوفة int[] grown = Arrays.copyOf(original, 5); System.out.println(Arrays.toString(grown)); // الناتج: [10, 20, 30, 0, 0] // تصغير المصفوفة int[] shrunk = Arrays.copyOf(original, 2); System.out.println(Arrays.toString(shrunk)); // الناتج: [10, 20]
لماذا نسخ بدلًا من تغيير الحجم مباشرة؟ المصفوفات في Java ذات طول ثابت بمجرد إنشائها — لا يمكن تغيير original.length لاحقًا. Arrays.copyOf() هو النمط المعياري لـ"تغيير حجم" المصفوفة بإنتاج مصفوفة جديدة تحمل البيانات المطلوبة.

Arrays.binarySearch — البحث في مصفوفة مرتّبة

بمجرد ترتيب المصفوفة يمكنك البحث فيها بزمن O(log n) باستخدام البحث الثنائي بدلًا من مسح كل عنصر:

int[] sorted = {1, 3, 5, 7, 9, 11}; int index = Arrays.binarySearch(sorted, 7); System.out.println("وُجدت 7 عند الفهرس: " + index); // الناتج: وُجدت 7 عند الفهرس: 3

إن لم تُوجد القيمة يُعيد binarySearch رقمًا سالبًا. يجب أن تكون المصفوفة مرتّبة مسبقًا قبل استدعاء هذا التابع — استدعاؤه على مصفوفة غير مرتّبة يُعطي نتائج غير متوقعة.

رتّب دائمًا قبل البحث. خطأ شائع هو استدعاء Arrays.binarySearch() على مصفوفة غير مرتّبة والحصول على نتيجة خاطئة أو سالبة. إن لم تكن متأكدًا من ترتيب المصفوفة استدعِ Arrays.sort() أولًا.

مثال شامل

import java.util.Arrays; public class ArrayDemo { public static void main(String[] args) { int[] temps = {28, 15, 33, 22, 18, 29, 11}; // إيجاد الأدنى والأعلى بحلقة int min = temps[0]; int max = temps[0]; for (int t : temps) { if (t < min) min = t; if (t > max) max = t; } System.out.println("الأدنى: " + min + "، الأعلى: " + max); // الفرز والطباعة Arrays.sort(temps); System.out.println("مرتّبة: " + Arrays.toString(temps)); // البحث عن القيمة 22 int pos = Arrays.binarySearch(temps, 22); System.out.println("22 عند الفهرس: " + pos); } } // الأدنى: 11، الأعلى: 33 // مرتّبة: [11, 15, 18, 22, 28, 29, 33] // 22 عند الفهرس: 3

الخلاصة

استخدم حلقة for الكلاسيكية حين تحتاج الفهرس، وحلقة for-each المحسّنة حين تحتاج القيم فقط. يوفّر صنف Arrays توابع sort وfill وcopyOf وtoString وbinarySearch — كلها مُحسَّنة ومختبرة، لذا فضّلها على كتابة حلقاتك الخاصة لهذه المهام. في الدرس التالي ستنتقل إلى المصفوفات متعددة الأبعاد وترى كيف تنطبق هذه الأدوات نفسها على صفوف وأعمدة البيانات.