كيفية ترتيب السلاسل النصية التي تحتوي على أرقام مضمنة بشكل صحيح
استخدم الترتيب الرقمي لترتيب أسماء الملفات وأرقام الإصدارات والسلاسل النصية الأخرى التي تحتوي على أرقام بترتيب طبيعي
مقدمة
عندما تقوم بترتيب سلاسل نصية تحتوي على أرقام، تتوقع أن تظهر file1.txt وfile2.txt وfile10.txt بهذا الترتيب. ومع ذلك، تنتج مقارنة السلاسل النصية الافتراضية في JavaScript الترتيب file1.txt وfile10.txt وfile2.txt بدلاً من ذلك. يحدث هذا لأن السلاسل النصية تتم مقارنتها حرفاً بحرف، والحرف 1 في 10 يأتي قبل الحرف 2.
تظهر هذه المشكلة كلما قمت بترتيب أسماء الملفات أو أرقام الإصدارات أو عناوين الشوارع أو رموز المنتجات أو أي سلاسل نصية أخرى تحتوي على أرقام مضمنة. يربك الترتيب غير الصحيح المستخدمين ويجعل التنقل في البيانات صعباً.
توفر JavaScript واجهة برمجة التطبيقات Intl.Collator مع خيار رقمي يحل هذه المشكلة. يشرح هذا الدرس كيفية عمل الترتيب الرقمي، ولماذا تفشل مقارنة السلاسل النصية الافتراضية، وكيفية ترتيب السلاسل النصية التي تحتوي على أرقام مضمنة بترتيب رقمي طبيعي.
ما هو الترتيب الرقمي
الترتيب الرقمي هو طريقة مقارنة تتعامل مع تسلسلات الأرقام كأرقام بدلاً من أحرف فردية. عند مقارنة السلاسل النصية، يحدد المرتب تسلسلات الأرقام ويقارنها بقيمتها الرقمية.
مع تعطيل الترتيب الرقمي، تأتي السلسلة النصية file10.txt قبل file2.txt لأن المقارنة حرفاً بحرف تجد أن 1 يأتي قبل 2 في أول موضع مختلف. لا يأخذ المرتب في الاعتبار أبداً أن 10 يمثل رقماً أكبر من 2.
مع تمكين الترتيب الرقمي، يتعرف المرتب على 10 و2 كأرقام كاملة ويقارنها رقمياً. نظراً لأن 10 أكبر من 2، فإن file2.txt تأتي بشكل صحيح قبل file10.txt.
ينتج هذا السلوك ما يسميه الناس الترتيب الطبيعي أو الترتيب الطبيعي، حيث يتم ترتيب السلاسل النصية التي تحتوي على أرقام بالطريقة التي يتوقعها البشر بدلاً من الترتيب الأبجدي الصارم.
لماذا تفشل مقارنة السلاسل النصية الافتراضية للأرقام
تستخدم مقارنة السلاسل النصية الافتراضية في JavaScript الترتيب المعجمي، الذي يقارن السلاسل النصية حرفاً بحرف من اليسار إلى اليمين باستخدام قيم نقاط Unicode. يعمل هذا بشكل صحيح للنصوص الأبجدية ولكنه ينتج نتائج غير متوقعة للأرقام.
لنتأمل كيف تتعامل المقارنة المعجمية مع هذه السلاسل النصية:
const files = ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'];
files.sort();
console.log(files);
// Output: ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt']
تفحص المقارنة كل موضع حرف بشكل مستقل. في أول موضع مختلف بعد file، تقارن 1 مقابل 2. نظراً لأن 1 لها قيمة Unicode أقل من 2، فإن أي سلسلة نصية تبدأ بـ file1 تأتي قبل أي سلسلة نصية تبدأ بـ file2، بغض النظر عما يليها.
ينتج هذا التسلسل file1.txt، file10.txt، file2.txt، file20.txt، مما يخالف توقعات البشر حول ترتيب الأرقام.
استخدام Intl.Collator مع الخيار الرقمي
يقبل منشئ Intl.Collator كائن خيارات مع خاصية numeric. يؤدي تعيين numeric: true إلى تمكين الترتيب الرقمي، مما يجعل المرتب يقارن تسلسلات الأرقام بقيمتها الرقمية.
const collator = new Intl.Collator('en-US', { numeric: true });
const files = ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'];
files.sort(collator.compare);
console.log(files);
// Output: ['file1.txt', 'file2.txt', 'file10.txt', 'file20.txt']
ترجع طريقة compare الخاصة بالمرتب رقماً سالباً عندما يجب أن يأتي الوسيط الأول قبل الثاني، وصفراً عندما يكونان متساويين، ورقماً موجباً عندما يجب أن يأتي الأول بعد الثاني. يطابق هذا التوقيع المتوقع من طريقة Array.sort() في JavaScript.
تضع النتيجة المرتبة الملفات بترتيب رقمي طبيعي. يتعرف المرتب على أن 1 < 2 < 10 < 20، منتجاً التسلسل الذي يتوقعه البشر.
ترتيب السلاسل النصية الأبجدية الرقمية المختلطة
تتعامل المقارنة الرقمية مع السلاسل النصية حيث تظهر الأرقام في أي موضع، وليس فقط في النهاية. يقارن المقارن الأجزاء الأبجدية بشكل طبيعي والأجزاء الرقمية بشكل رقمي.
const collator = new Intl.Collator('en-US', { numeric: true });
const addresses = ['123 Oak St', '45 Oak St', '1234 Oak St', '5 Oak St'];
addresses.sort(collator.compare);
console.log(addresses);
// Output: ['5 Oak St', '45 Oak St', '123 Oak St', '1234 Oak St']
يحدد المقارن تسلسلات الأرقام في بداية كل سلسلة نصية ويقارنها رقمياً. يتعرف على أن 5 < 45 < 123 < 1234، على الرغم من أن المقارنة المعجمية ستنتج ترتيباً مختلفاً.
ترتيب أرقام الإصدارات
أرقام الإصدارات هي حالة استخدام شائعة للمقارنة الرقمية. إصدارات البرامج مثل 1.2.10 يجب أن تأتي بعد 1.2.2، لكن المقارنة المعجمية تنتج الترتيب الخاطئ.
const collator = new Intl.Collator('en-US', { numeric: true });
const versions = ['1.2.10', '1.2.2', '1.10.5', '1.2.5'];
versions.sort(collator.compare);
console.log(versions);
// Output: ['1.2.2', '1.2.5', '1.2.10', '1.10.5']
يقارن المقارن كل مكون رقمي بشكل صحيح. في التسلسل 1.2.2، 1.2.5، 1.2.10، يتعرف على أن المكون الثالث يزداد رقمياً. في 1.10.5، يتعرف على أن المكون الثاني هو 10، وهو أكبر من 2.
العمل مع أكواد المنتجات والمعرفات
غالباً ما تخلط أكواد المنتجات وأرقام الفواتير والمعرفات الأخرى الحروف مع الأرقام. تضمن المقارنة الرقمية ترتيبها بشكل منطقي.
const collator = new Intl.Collator('en-US', { numeric: true });
const products = ['PROD-1', 'PROD-10', 'PROD-2', 'PROD-100'];
products.sort(collator.compare);
console.log(products);
// Output: ['PROD-1', 'PROD-2', 'PROD-10', 'PROD-100']
البادئة الأبجدية PROD- تتطابق في جميع السلاسل النصية، لذلك يقارن المقارن اللاحقة الرقمية. تعكس النتيجة الترتيب الرقمي المتزايد بدلاً من الترتيب المعجمي.
الترتيب مع لغات مختلفة
يعمل خيار numeric مع أي لغة. بينما قد يكون للغات المختلفة قواعد ترتيب مختلفة للأحرف الأبجدية، يظل سلوك المقارنة الرقمية متسقاً.
const enCollator = new Intl.Collator('en-US', { numeric: true });
const deCollator = new Intl.Collator('de-DE', { numeric: true });
const items = ['item1', 'item10', 'item2'];
console.log(items.sort(enCollator.compare));
// Output: ['item1', 'item2', 'item10']
console.log(items.sort(deCollator.compare));
// Output: ['item1', 'item2', 'item10']
تنتج كلتا اللغتين نفس النتيجة لأن السلاسل النصية تحتوي فقط على أحرف ASCII وأرقام. عندما تتضمن السلاسل النصية أحرفاً خاصة باللغة، تتبع المقارنة الأبجدية قواعد اللغة بينما تظل المقارنة الرقمية متسقة.
مقارنة السلاسل النصية دون الترتيب
يمكنك استخدام طريقة compare الخاصة بالمُرتِّب مباشرةً لتحديد العلاقة بين سلسلتين نصيتين دون ترتيب مصفوفة كاملة.
const collator = new Intl.Collator('en-US', { numeric: true });
console.log(collator.compare('file2.txt', 'file10.txt'));
// Output: -1 (negative number means first argument comes before second)
console.log(collator.compare('file10.txt', 'file2.txt'));
// Output: 1 (positive number means first argument comes after second)
console.log(collator.compare('file2.txt', 'file2.txt'));
// Output: 0 (zero means arguments are equal)
هذا مفيد عندما تحتاج إلى التحقق من الترتيب دون تعديل مصفوفة، مثل إدراج عنصر في قائمة مرتبة أو التحقق مما إذا كانت قيمة تقع ضمن نطاق معين.
فهم القيود المتعلقة بالأرقام العشرية
تقارن المقارنة الرقمية تسلسلات الأرقام، لكنها لا تتعرف على النقاط العشرية كجزء من الأرقام. يتم التعامل مع حرف النقطة كفاصل، وليس كفاصل عشري.
const collator = new Intl.Collator('en-US', { numeric: true });
const measurements = ['0.5', '0.05', '0.005'];
measurements.sort(collator.compare);
console.log(measurements);
// Output: ['0.005', '0.05', '0.5']
يتعامل المُرتِّب مع كل قياس كثلاثة مكونات رقمية منفصلة: الجزء قبل النقطة، والنقطة نفسها، والجزء بعد النقطة. يقارن 0 مع 0 (متساوية)، ثم يقارن الأجزاء بعد النقطة كأرقام منفصلة: 5 و5 و5 (متساوية). ثم يقارن المنزلة العشرية الثانية: لا شيء و5 ولا شيء. ينتج عن هذا ترتيب غير صحيح للأرقام العشرية.
لترتيب الأرقام العشرية، قم بتحويلها إلى أرقام فعلية وترتيبها رقمياً، أو استخدم حشو السلاسل النصية لضمان ترتيب معجمي صحيح.
دمج المقارنة الرقمية مع خيارات أخرى
يعمل خيار numeric جنباً إلى جنب مع خيارات المقارنة الأخرى مثل sensitivity وcaseFirst. يمكنك التحكم في كيفية تعامل المُرتِّب مع حالة الأحرف والعلامات الإعرابية مع الحفاظ على سلوك المقارنة الرقمية.
const collator = new Intl.Collator('en-US', {
numeric: true,
sensitivity: 'base'
});
const items = ['Item1', 'item10', 'ITEM2'];
items.sort(collator.compare);
console.log(items);
// Output: ['Item1', 'ITEM2', 'item10']
يجعل خيار sensitivity: 'base' المقارنة غير حساسة لحالة الأحرف. يتعامل المُرتِّب مع Item1 وitem1 وITEM1 على أنها متكافئة مع الاستمرار في مقارنة الأجزاء الرقمية بشكل صحيح.
إعادة استخدام المقارنات لتحسين الأداء
إنشاء نسخة جديدة من Intl.Collator يتضمن تحميل بيانات اللغة ومعالجة الخيارات. عندما تحتاج إلى ترتيب مصفوفات متعددة أو إجراء العديد من المقارنات، قم بإنشاء المقارن مرة واحدة وأعد استخدامه.
const collator = new Intl.Collator('en-US', { numeric: true });
const files = ['file1.txt', 'file10.txt', 'file2.txt'];
const versions = ['1.2.10', '1.2.2', '1.10.5'];
const products = ['PROD-1', 'PROD-10', 'PROD-2'];
files.sort(collator.compare);
versions.sort(collator.compare);
products.sort(collator.compare);
هذا النهج أكثر كفاءة من إنشاء مقارن جديد لكل عملية ترتيب. يصبح الفرق في الأداء كبيراً عند ترتيب العديد من المصفوفات أو إجراء مقارنات متكررة.