كيفية فرز السلاسل النصية التي تحتوي على أرقام مضمنة بشكل صحيح
استخدم الترتيب الرقمي لفرز أسماء الملفات وأرقام الإصدارات والسلاسل النصية الأخرى التي تحتوي على أرقام بترتيب طبيعي
مقدمة
عند فرز السلاسل النصية التي تحتوي على أرقام، تتوقع أن تظهر file1.txt، وfile2.txt، وfile10.txt بهذا الترتيب. ومع ذلك، فإن مقارنة السلاسل النصية الافتراضية في جافا سكريبت تنتج file1.txt، وfile10.txt، وfile2.txt بدلاً من ذلك. يحدث هذا لأن السلاسل النصية تتم مقارنتها حرفاً بحرف، والحرف 1 في 10 يأتي قبل الحرف 2.
تظهر هذه المشكلة كلما قمت بفرز أسماء الملفات، أو أرقام الإصدارات، أو عناوين الشوارع، أو رموز المنتجات، أو أي سلاسل نصية أخرى تحتوي على أرقام مضمنة. يؤدي الترتيب غير الصحيح إلى إرباك المستخدمين ويجعل البيانات صعبة التصفح.
توفر جافا سكريبت واجهة برمجة التطبيقات Intl.Collator مع خيار رقمي يحل هذه المشكلة. يشرح هذا الدرس كيفية عمل التجميع الرقمي، ولماذا تفشل مقارنة السلاسل النصية الافتراضية، وكيفية فرز السلاسل النصية ذات الأرقام المضمنة بترتيب رقمي طبيعي.
ما هو التجميع الرقمي
التجميع الرقمي هو طريقة مقارنة تعامل تسلسلات الأرقام كأرقام بدلاً من أحرف فردية. عند مقارنة السلاسل النصية، يحدد المجمّع تسلسلات الأرقام ويقارنها حسب قيمتها الرقمية.
عندما يكون التجميع الرقمي معطلاً، تأتي السلسلة النصية file10.txt قبل file2.txt لأن المقارنة حرفاً بحرف تجد أن 1 يأتي قبل 2 في أول موضع مختلف. لا يأخذ المجمّع في الاعتبار أبداً أن 10 تمثل رقماً أكبر من 2.
عندما يكون التجميع الرقمي ممكّناً، يتعرف المجمّع على 10 و2 كأرقام كاملة ويقارنها رقمياً. وبما أن 10 أكبر من 2، فإن file2.txt تأتي بشكل صحيح قبل file10.txt.
ينتج عن هذا السلوك ما يسميه الناس الفرز الطبيعي أو الترتيب الطبيعي، حيث يتم فرز السلاسل النصية التي تحتوي على أرقام بالطريقة التي يتوقعها البشر بدلاً من الفرز الأبجدي الصارم.
لماذا تفشل مقارنة السلاسل النصية الافتراضية للأرقام
تستخدم المقارنة الافتراضية للسلاسل النصية في جافاسكريبت الترتيب المعجمي، الذي يقارن السلاسل النصية حرفًا بحرف من اليسار إلى اليمين باستخدام قيم نقاط الترميز في يونيكود. هذا يعمل بشكل صحيح للنصوص الأبجدية ولكنه ينتج نتائج غير متوقعة للأرقام.
لنتأمل كيف تتعامل المقارنة المعجمية مع هذه السلاسل النصية:
const files = ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt'];
files.sort();
console.log(files);
// الناتج: ['file1.txt', 'file10.txt', 'file2.txt', 'file20.txt']
تفحص المقارنة كل موضع حرف بشكل مستقل. عند أول موضع مختلف بعد file، تقارن بين 1 و 2. وبما أن 1 لها قيمة يونيكود أقل من 2، فإن أي سلسلة نصية تبدأ بـ file1 تأتي قبل أي سلسلة نصية تبدأ بـ file2، بغض النظر عما يتبعها.
هذا ينتج التسلسل file1.txt، file10.txt، file2.txt، file20.txt، وهو ما يخالف توقعات البشر حول ترتيب الأرقام.
استخدام Intl.Collator مع خيار numeric
يقبل منشئ 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);
// الناتج: ['file1.txt', 'file2.txt', 'file10.txt', 'file20.txt']
تعيد طريقة compare الخاصة بالمجمّع رقمًا سالبًا عندما يجب أن تأتي الوسيطة الأولى قبل الثانية، وصفرًا عندما تكونان متساويتين، ورقمًا موجبًا عندما يجب أن تأتي الأولى بعد الثانية. هذا يتطابق مع التوقيع المتوقع من طريقة Array.sort() في جافاسكريبت.
تضع النتيجة المرتبة الملفات في ترتيب رقمي طبيعي. يتعرف المجمّع على أن 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 (الرقم السالب يعني أن الوسيط الأول يأتي قبل الثاني)
console.log(collator.compare('file10.txt', 'file2.txt'));
// Output: 1 (الرقم الموجب يعني أن الوسيط الأول يأتي بعد الثاني)
console.log(collator.compare('file2.txt', 'file2.txt'));
// Output: 0 (الصفر يعني أن الوسيطين متساويان)
هذا مفيد عندما تحتاج إلى التحقق من الترتيب دون تعديل مصفوفة، مثل عند إدراج عنصر في قائمة مرتبة أو التحقق مما إذا كانت القيمة تقع ضمن نطاق معين.
فهم القيود مع الأرقام العشرية
تقارن المقارنة الرقمية تسلسلات الأرقام، لكنها لا تتعرف على النقاط العشرية كجزء من الأرقام. تتم معاملة حرف النقطة كفاصل، وليس كفاصل عشري.
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);
هذا النهج أكثر كفاءة من إنشاء مرتب جديد لكل عملية فرز. يصبح الفرق في الأداء كبيرًا عند فرز العديد من المصفوفات أو إجراء مقارنات متكررة.