حلقات التكرار المتداخلة (Nested For Loops) في JavaScript: دليل شامل
إذا كنت تواجه صعوبة في فهم تحدي حلقات التكرار المتداخلة (Nesting For Loops) في منصة freeCodeCamp، فلا تقلق. نحن هنا لمساعدتك. في هذا المقال، سنقوم بشرح كيفية إكمال دالة multiplyAll() التي تستقبل مصفوفة متعددة الأبعاد (multi-dimensional array) كوسيط. تذكر أن المصفوفة متعددة الأبعاد، والتي تُسمى أحيانًا مصفوفة ثنائية الأبعاد (2D array)، هي ببساطة مصفوفة تحتوي على مصفوفات أخرى بداخلها، على سبيل المثال: [[1,2], [3,4], [5,6]].
في محرر الأكواد، يتم تعريف دالة multiplyAll() على النحو التالي:
function multiplyAll ( arr ) {
var product = 1 ;
// Only change code below this line
// Only change code above this line
return product;
}
multiplyAll([[ 1 , 2 ],[ 3 , 4 ],[ 5 , 6 , 7 ]]);
المهمة هي إكمال هذه الدالة بحيث تقوم بضرب المتغير product بكل رقم موجود داخل المصفوفات الفرعية للمصفوفة arr التي تم تمريرها. هناك العديد من الطرق لحل هذه المشكلة، لكننا سنركز على الطريقة الأبسط والأكثر شيوعًا باستخدام حلقات التكرار for loops المتداخلة.
بناء حلقات التكرار المتداخلة لمعالجة المصفوفات متعددة الأبعاد
نظرًا لأن arr هي مصفوفة متعددة الأبعاد، ستحتاج إلى حلقتين تكرار for loops: واحدة للتكرار عبر كل مصفوفة فرعية، وأخرى للتكرار عبر العناصر داخل كل مصفوفة فرعية.
حلقة التكرار الخارجية: استعراض المصفوفات الفرعية
للقيام بذلك، قم بإعداد حلقة تكرار for كما فعلت في التحديات السابقة:
function multiplyAll ( arr ) {
let product = 1 ;
// Only change code below this line
for ( let i = 0 ; i < arr.length; i++) {
}
// Only change code above this line
return product;
}
multiplyAll([[ 1 , 2 ],[ 3 , 4 ],[ 5 , 6 , 7 ]]);
لاحظ أننا نستخدم let بدلاً من var لحلقة التكرار ولتعريف المتغير product. في هذا التحدي، قد لا تلاحظ فرقًا كبيرًا بينهما، ولكن بشكل عام، يُعد استخدام const و let من ES6 ممارسة جيدة كلما أمكن ذلك لتحسين نطاق المتغيرات (scoping) وتجنب الأخطاء المحتملة.
الآن، لنتتبع كل مصفوفة فرعية عن طريق طباعتها في وحدة التحكم (console):
function multiplyAll ( arr ) {
let product = 1 ;
// Only change code below this line
for ( let i = 0 ; i < arr.length; i++) {
console .log(arr[i]);
}
// Only change code above this line
return product;
}
multiplyAll([[ 1 , 2 ],[ 3 , 4 ],[ 5 , 6 , 7 ]]);
نظرًا لأننا نستدعي الدالة multiplyAll() بالوسيط [[1,2],[3,4],[5,6,7]]، يجب أن ترى المخرجات التالية في وحدة التحكم:
[ 1 , 2 ]
[ 3 , 4 ]
[ 5 , 6 , 7 ]
حلقة التكرار الداخلية: الوصول إلى العناصر الفردية
الآن، بعد أن أصبح لدينا القدرة على الوصول إلى كل مصفوفة فرعية، نحتاج إلى التكرار عبر كل رقم داخل هذه المصفوفات الفرعية. قم بإزالة سطر console.log(arr[i]); وأنشئ حلقة تكرار for أخرى داخل الحلقة التي كتبتها للتو:
function multiplyAll ( arr ) {
let product = 1 ;
// Only change code below this line
for ( let i = 0 ; i < arr.length; i++) {
for ( let j = 0 ; j < arr[i].length; j++) {
}
}
// Only change code above this line
return product;
}
multiplyAll([[ 1 , 2 ],[ 3 , 4 ],[ 5 , 6 , 7 ]]);
تذكر أنه بالنسبة للحلقة الداخلية، نحتاج إلى التحقق من خاصية .length للمتغير arr[i]، لأن arr[i] يمثل إحدى المصفوفات الفرعية التي نظرنا إليها سابقًا. الآن اطبع arr[i][j] في وحدة التحكم لترى كل عنصر على حدة:
function multiplyAll ( arr ) {
let product = 1 ;
// Only change code below this line
for ( let i = 0 ; i < arr.length; i++) {
for ( let j = 0 ; j < arr[i].length; j++) {
console .log(arr[i][j]);
}
}
// Only change code above this line
return product;
}
multiplyAll([[ 1 , 2 ],[ 3 , 4 ],[ 5 , 6 , 7 ]]);
يجب أن يظهر لك في وحدة التحكم:
1
2
3
4
5
6
7
إتمام عملية الضرب: دمج المنطق
أخيرًا، قم بضرب المتغير product بكل عنصر في كل من المصفوفات الفرعية:
function multiplyAll ( arr ) {
let product = 1 ;
// Only change code below this line
for ( let i = 0 ; i < arr.length; i++) {
for ( let j = 0 ; j < arr[i].length; j++) {
product *= arr[i][j];
}
}
// Only change code above this line
return product;
}
multiplyAll([[ 1 , 2 ],[ 3 , 4 ],[ 5 , 6 , 7 ]]);
إذا قمت بطباعة قيمة product في وحدة التحكم بعد تنفيذ الدالة، فسترى الإجابة الصحيحة لكل حالة اختبار:
function multiplyAll ( arr ) {
let product = 1 ;
// Only change code below this line
for ( let i = 0 ; i < arr.length; i++) {
for ( let j = 0 ; j < arr[i].length; j++) {
product *= arr[i][j];
}
}
// Only change code above this line
console .log(product);
return product;
}
multiplyAll([[ 1 , 2 ],[ 3 , 4 ],[ 5 , 6 , 7 ]]);
بالنسبة للمثال [[1, 2], [3, 4], [5, 6, 7]]، ستكون النتيجة:
5040
بالنسبة لحالات اختبار أخرى:
6 // [[1], [2], [3]]
5040 // [[1, 2], [3, 4], [5, 6, 7]]
54 // [[5, 1], [0.2, 4, 0.5], [3, 9]]
نظرة أعمق: تتبع تدفق البيانات في الحلقات المتداخلة
إذا كنت لا تزال غير متأكد من سبب عمل الكود أعلاه بهذه الطريقة، فلا تقلق – هذا أمر طبيعي. التعامل مع الحلقات المتداخلة معقد، وحتى المطورين ذوي الخبرة قد يجدون صعوبة في تتبعها. في مثل هذه الحالات، قد يكون من المفيد طباعة معلومات أكثر تفصيلاً في وحدة التحكم.
تتبع المصفوفات الفرعية باستخدام console.log()
عد إلى الكود الخاص بك واطبع السلسلة النصية `Sub-array ${i}: ${arr[i]}` في وحدة التحكم قبل حلقة التكرار الداخلية for مباشرة:
function multiplyAll ( arr ) {
let product = 1 ;
// Only change code below this line
for ( let i = 0 ; i < arr.length; i++) {
console .log( `Sub-array ${i} : ${arr[i]} ` );
for ( let j = 0 ; j < arr[i].length; j++) {
product *= arr[i][j];
}
}
// Only change code above this line
return product;
}
multiplyAll([[ 1 , 2 ],[ 3 , 4 ],[ 5 , 6 , 7 ]]);
في حلقة التكرار الخارجية for، تمر كل دورة عبر المصفوفات الفرعية في arr. يجب أن ترى هذا في وحدة التحكم:
Sub-array 0 : 1 , 2
Sub-array 1 : 3 , 4
Sub-array 2 : 5 , 6 , 7
لاحظ أننا نستخدم هنا قوالب السلاسل النصية (template literals). التعبير `Sub-array ${i}: ${arr[i]}` هو نفسه 'Sub-array ' + i + ': ' + arr[i]، ولكنه أسهل بكثير في الكتابة والقراءة.
تتبع العناصر الفردية داخل المصفوفات الفرعية
الآن في حلقة التكرار الداخلية for، اطبع `Element ${j}: ${arr[i][j]}` في وحدة التحكم:
function multiplyAll ( arr ) {
let product = 1 ;
// Only change code below this line
for ( let i = 0 ; i < arr.length; i++) {
console .log( `Sub-array ${i} : ${arr[i]} ` );
for ( let j = 0 ; j < arr[i].length; j++) {
console .log( `Element ${j} : ${arr[i][j]} ` );
product *= arr[i][j];
}
}
// Only change code above this line
return product;
}
multiplyAll([[ 1 , 2 ],[ 3 , 4 ],[ 5 , 6 , 7 ]]);
تمر حلقة التكرار الداخلية for عبر كل عنصر في كل مصفوفة فرعية (arr[i])، لذلك يجب أن ترى هذا في وحدة التحكم:
Sub-array 0 : 1 , 2
Element 0 : 1
Element 1 : 2
Sub-array 1 : 3 , 4
Element 0 : 3
Element 1 : 4
Sub-array 2 : 5 , 6 , 7
Element 0 : 5
Element 1 : 6
Element 2 : 7
في التكرار الأول للمتغير i، يتم الحصول على المصفوفة الفرعية الأولى، وهي [1, 2]. ثم يمر التكرار الأول للمتغير j عبر كل عنصر في تلك المصفوفة الفرعية:
// عندما يكون i يساوي 0
arr[ 0 ] // [1, 2];
// عندما يكون j يساوي 0
arr[ 0 ][ 0 ] // 1
// عندما يكون j يساوي 1
arr[ 0 ][ 1 ] // 2
-----
// عندما يكون i يساوي 1
arr[ 1 ] // [3, 4]
// عندما يكون j يساوي 0
arr[ 1 ][ 0 ] // 3
// عندما يكون j يساوي 1
arr[ 1 ][ 1 ] // 4
...
هذا المثال بسيط جدًا، ولكن فهم arr[i][j] قد يظل صعبًا بدون طباعة عدة أشياء في وحدة التحكم. لتحسين الكود وجعله أكثر قابلية للقراءة، يمكننا تعريف متغير subArray في حلقة التكرار الخارجية for وتعيينه ليكون مساويًا لـ arr[i]:
function multiplyAll ( arr ) {
let product = 1 ;
// Only change code below this line
for ( let i = 0 ; i < arr.length; i++) {
const subArray = arr[i];
for ( let j = 0 ; j < arr[i].length; j++) {
product *= arr[i][j];
}
}
// Only change code above this line
return product;
}
multiplyAll([[ 1 , 2 ],[ 3 , 4 ],[ 5 , 6 , 7 ]]);
ثم قم بإجراء بعض التعديلات البسيطة على الكود لاستخدام المتغير الجديد subArray بدلاً من arr[i]:
function multiplyAll ( arr ) {
let product = 1 ;
// Only change code below this line
for ( let i = 0 ; i < arr.length; i++) {
const subArray = arr[i];
for ( let j = 0 ; j < subArray.length; j++) {
product *= subArray[j];
}
}
// Only change code above this line
return product;
}
multiplyAll([[ 1 , 2 ],[ 3 , 4 ],[ 5 , 6 , 7 ]]);
هذا كل ما تحتاج لمعرفته حول المصفوفات متعددة الأبعاد وحلقات التكرار for المتداخلة. الآن، انطلق وطبق ما تعلمته لتصبح محترفًا في التعامل مع التكرارات!
الخلاصة التقنية
تُعد حلقات التكرار المتداخلة أداة أساسية في JavaScript لمعالجة هياكل البيانات المعقدة مثل المصفوفات متعددة الأبعاد. من خلال استخدام حلقة خارجية للتنقل بين المصفوفات الفرعية وحلقة داخلية للوصول إلى عناصرها الفردية، يمكن للمطورين تنفيذ عمليات معقدة بكفاءة. يساهم استخدام let و const في تحسين إدارة نطاق المتغيرات، بينما تساعد تقنيات التصحيح مثل console.log() وقوالب السلاسل النصية في فهم تدفق التنفيذ بشكل أفضل. كما أن إعادة هيكلة الكود باستخدام متغيرات وسيطة مثل subArray يعزز من قابلية القراءة والصيانة، مما يجعل الكود أكثر وضوحًا حتى للمطورين الأقل خبرة.