مصفوفة مكررة: لماذا جمعت هذه المهمة آلاف المشاهدات على Stack Overflow
\n\nتخيل: أنت تكتب متجرًا إلكترونيًا، ويضيف المستخدم منتجات إلى سلة التسوق. ولكن بسبب خطأ أو خلل، يظهر نفس الهاتف iPhone في القائمة ثلاث مرات. أو تقوم بتحليل قائمة من العلامات من قاعدة البيانات، وتجد أن "JavaScript" تتكرر 15 مرة متتالية. ماذا تفعل؟ صحيح — اترك القيم الفريدة فقط.
\n\nسؤال "Get all unique values in a JavaScript array (remove duplicates)" على Stack Overflow جمع مئات الآلاف من المشاهدات. وهذا ليس من قبيل الصدفة. تبدو المهمة بسيطة، ولكن تحت الغطاء تختبئ ترسانة كاملة من الأساليب: من الأسطر الفردية في ES6 الحديث إلى الحيل الذكية مع filter و indexOf. في هذه المقالة، لن نقدم حلولًا جاهزة فحسب، بل سنشرح كيف يعمل كل منها على مستوى محرك JavaScript.
هل أنت مستعد للنظر تحت الغطاء؟ هيا بنا!
\n\n\n\n
الطريقة رقم 1: Set — ملك الأداء
\n\nالطريقة الأكثر أناقة وسرعة للحصول على القيم الفريدة في مصفوفة JavaScript هي استخدام كائن Set. ظهر في ES6 وهو مصمم حرفيًا لهذه المهمة.
const numbers = [1, 2, 2, 3, 4, 4, 5];\nconst uniqueNumbers = [...new Set(numbers)];\n\nconsole.log(uniqueNumbers); // [1, 2, 3, 4, 5]\n\nكيف يعمل هذا تحت الغطاء؟
\nكائن Set يخزن القيم الفريدة فقط. عندما نمرر المصفوفة إلى المُنشئ new Set(numbers)، يمر محرك JavaScript عبر كل عنصر ويضيفه إلى جدول التجزئة الداخلي. إذا كانت القيمة موجودة بالفعل — يتم تجاهلها. ثم يقوم عامل الانتشار ... بتحويل Set مرة أخرى إلى مصفوفة.
لماذا هذا سريع؟ لأن البحث والإدراج في Set يتمان في وقت ثابت O(1). بالنسبة لمصفوفة من 10,000 عنصر، سيكون الفرق مع الطرق الأخرى هائلاً.
\n\nهام: Set يحافظ على ترتيب العناصر، ولكنه يعمل فقط مع الأنواع البدائية (الأرقام، السلاسل النصية، القيم المنطقية). بالنسبة للكائنات، تتم المقارنة حسب المرجع وليس حسب المحتوى.
\n\nمتى تستخدم؟
\n- \n
- إذا كانت المصفوفة تتكون من أرقام أو سلاسل نصية. \n
- إذا كانت سرعة معالجة المصفوفات الكبيرة مهمة. \n
- إذا كنت تكتب كودًا حديثًا ولا تخشى ES6. \n
\n\n
الطريقة رقم 2: filter + indexOf — كلاسيكيات هذا النوع
\n\nهذه الطريقة هي المخضرم في JavaScript. تعمل في جميع المتصفحات بدون polyfills ولا تزال شائعة على Stack Overflow.
\n\nconst numbers = [1, 2, 2, 3, 4, 4, 5];\nconst uniqueNumbers = numbers.filter((value, index, arr) => {\n return arr.indexOf(value) === index;\n});\n\nconsole.log(uniqueNumbers); // [1, 2, 3, 4, 5]\n\nتحليل المنطق
\nطريقة filter تنشئ مصفوفة جديدة، تاركة فقط العناصر التي يُرجع لها callback القيمة true. داخل callback نتحقق: هل الفهرس الحالي هو أول ظهور لهذه القيمة في المصفوفة؟ إذا كان الجواب نعم — نترك العنصر. إذا كانت القيمة قد ظهرت من قبل — سيعيد indexOf فهرسًا أصغر، ولن يتحقق الشرط، وسيتم تجاهل التكرار.
المأزق: indexOf يمر عبر المصفوفة من البداية في كل مرة. هذا يعطي تعقيدًا O(n²). بالنسبة للمصفوفات الصغيرة (حتى 1000 عنصر) الفرق غير ملحوظ، ولكن على 100,000 عنصر قد يتجمد المتصفح لثانية.
متى تستخدم؟
\n- \n
- إذا كنت بحاجة لدعم المتصفحات القديمة (IE11). \n
- إذا كانت المصفوفة صغيرة. \n
- إذا كنت تريد عرض منطق عمل "أول ظهور" بشكل واضح. \n
\n\n
الطريقة رقم 3: reduce + المُراكم — المرونة والتحكم
\n\nطريقة reduce تمنحك تحكمًا كاملاً في العملية. نحن نقرر بأنفسنا كيفية جمع النتيجة.
const numbers = [1, 2, 2, 3, 4, 4, 5];\nconst uniqueNumbers = numbers.reduce((acc, value) => {\n if (!acc.includes(value)) {\n acc.push