لفرز مصفوفة Swift محلية حسب عدة حقول، قارِن كل مفتاح وفق ترتيب الأولوية، وانتقل إلى التالي فقط عند التعادل. عالِج القيم الاحتياطية الاختيارية بشكل منفصل، ثم حدّد موضع القيم المفقودة. يرتّب هذا المثال تسميات أشكال اصطناعية لقائمة سطح مكتب على Mac.
حدّد قواعد الفرز قبل كتابة الإغلاق
لنفترض أن قائمة سطح المكتب تحتوي على تسميات أشكال تضم رقم مجموعة، وموضعًا مفضّلًا اختياريًا، وموضعًا احتياطيًا اختياريًا، وعنوانًا. تتمثل المهمة في إنتاج ترتيب عرض واحد يمكن التنبؤ به انطلاقًا من هذه القيم الموجودة في الذاكرة.
يحتاج قراران إلى قاعدتين منفصلتين. يختار البديل القيمة التي تمثّل عنصرًا واحدًا: استخدم موضعه المفضّل إن كان موجودًا، وإلا فاستخدم موضعه الاحتياطي. ويقارن مفتاح كسر التعادل حقلًا آخر فقط بعد تساوي المواضع الفعلية لعنصرين. يدعم عامل Swift للإبدال عند انعدام القيمة ?? القرار الأول باختيار القيمة المغلّفة أو القيمة البديلة. المعاملات الأساسية — عامل الإبدال عند انعدام القيمة
استخدم هذه القواعد الصريحة للمثال:
| الأولوية | المفتاح | القاعدة |
|---|---|---|
| 1 | المجموعة | الأرقام الأصغر أولًا |
| 2 | الموضع الفعلي | preferredPosition ?? backupPosition |
| 3 | موضع القيم التي تفتقد إلى الموضع | تأتي المواضع المفقودة بعد الموجودة ضمن كل مجموعة |
| 4 | العنوان | مقارنة سلاسل Swift تصاعديًا عند تعادل المواضع |
| 5 | المعرّف | المعرّفات الأصغر أولًا عند تعادل جميع المفاتيح السابقة |
يُعد موضع القيم التي تفتقد إلى الموضع جزءًا من مقارنة الموضع، لا حقلًا مستقلًا يتجاوز المجموعة. يظل العنصر في المجموعة صفر متقدمًا على عنصر في المجموعة واحد حتى لو لم يكن للأول موضع.
تستخدم مقارنة العنوان عمدًا الترتيب العادي للسلاسل في Swift لهذا الحساب الاصطناعي. حافظ على هذا الاختيار إذا استبدلت الإغلاق لاحقًا بمقارِنات Foundation.
اكتب فرزًا كاملًا متعدد الحقول مع معالجة صريحة للقيم الفارغة
يستخدم البرنامج الكامل التالي مكتبة Swift القياسية ولا يتطلب أي استيرادات. شغّله في بيئة تدعم Swift على Mac. جميع المدخلات معرّفة في البرنامج؛ ولا يستخدم الحساب أي مدخلات خارجية أو تخزينًا دائمًا. تُرجع عملية الفرز مصفوفة جديدة عبر sorted(by:). توثيق sorted(by:) من Apple
struct ShapeItem {
let id: Int
let group: Int
let preferredPosition: Int?
let backupPosition: Int?
let title: String
var effectivePosition: Int? {
preferredPosition ?? backupPosition
}
}
func comesBefore(_ lhs: ShapeItem, _ rhs: ShapeItem) -> Bool {
if lhs.group != rhs.group {
return lhs.group < rhs.group
}
switch (lhs.effectivePosition, rhs.effectivePosition) {
case let (.some(left), .some(right)):
if left != right {
return left < right
}
case (.some(_), .none):
return true
case (.none, .some(_)):
return false
case (.none, .none):
break
}
if lhs.title != rhs.title {
return lhs.title < rhs.title
}
return lhs.id < rhs.id
}
let items = [
ShapeItem(id: 10, group: 1, preferredPosition: nil,
backupPosition: 2, title: "Circle"),
ShapeItem(id: 20, group: 1, preferredPosition: 1,
backupPosition: 9, title: "Square"),
ShapeItem(id: 30, group: 1, preferredPosition: nil,
backupPosition: nil, title: "Arc"),
ShapeItem(id: 40, group: 1, preferredPosition: 2,
backupPosition: 0, title: "Arc"),
ShapeItem(id: 50, group: 0, preferredPosition: nil,
backupPosition: nil, title: "Dot"),
ShapeItem(id: 60, group: 1, preferredPosition: 2,
backupPosition: nil, title: "Arc"),
ShapeItem(id: 70, group: 1, preferredPosition: 0,
backupPosition: 8, title: "Zero")
]
let ordered = items.sorted(by: comesBefore)
let expectedIDs = [50, 70, 20, 40, 60, 10, 30]
precondition(ordered.map(\.id) == expectedIDs)
precondition(ordered.count == items.count)
precondition(items.reversed().sorted(by: comesBefore).map(\.id)
== expectedIDs)
print(ordered.map(\.id))
المخرجات المتوقعة:
[50, 70, 20, 40, 60, 10, 30]
تمت مراجعة البرنامج والترتيب المتوقع، لكن لم يُنفّذا هنا لأن بيئة البحث لا تحتوي على مترجم Swift. تتيح لك الشروط المسبقة المضمّنة التحقق من النتيجة عند تشغيله محليًا.
سبب وصول كل عنصر إلى موضعه
يأتي العنصر 50 أولًا لأن المجموعة صفر لها الأولوية قبل النظر إلى الموضع. ثم يوضح العنصر 70 أن الصفر موضع مفضّل صالح: فقيمته الاحتياطية، وهي ثمانية، لا تُستخدم. ويستخدم العنصر 20 موضعه المفضّل، وهو واحد.
للعناصر 40 و60 و10 جميعًا موضع فعلي قيمته اثنان. يسبق العنوانان Arc العنوان Circle، وتحسم المعرّفات التعادل المتبقي بين 40 و60. يأتي العنصر 30 أخيرًا لأن حقلي الموضع كليهما مفقودان ضمن المجموعة واحد.
وهذا يجيب أيضًا عن سؤال شائع بشأن البدائل: لا يستخدم العنصر 40 موضعه الاحتياطي، وهو صفر، عندما يتعادل موضعه المفضّل مع موضع عنصر آخر. وفقًا لهذه القواعد، لا تحل القيمة الاحتياطية محل القيمة المفضّلة إلا إذا كانت غائبة. وإذا كان ينبغي للقيمة الاحتياطية أيضًا أن تكسر التعادل، فذلك يتطلب قاعدة مقارنة إضافية.
حافظ على صلاحية المقارِن عند تعادل القيم
تتطلب Swift أن يشكّل مسند الفرز ترتيبًا ضعيفًا صارمًا. عمليًا، يجب ألا يسبق عنصر نفسه أبدًا، وأن تظل قرارات الترتيب متسقة عبر المقارنات، وأن تشكّل العناصر المتكافئة مجموعات متسقة. توثّق Apple هذه المتطلبات لـ sorted(by:). متطلبات المسند
يتحقق المثال من اختلاف مفتاح سابق قبل إرجاع نتيجة الترتيب الخاصة به. ولا يصل إلى العنوان إلا عند تعادل المجموعة والموضع الفعلي، ثم لا يصل إلى المعرّف إلا عند تعادل العنوان أيضًا. وعندما تتساوى المعرّفات والمفاتيح السابقة، تُرجع المقارنة الصارمة الأخيرة false.
تجنّب ربط مقارنات الحقول المستقلة باستخدام ||. فقد تكون المجموعة في أحد العنصرين أسبق بينما يكون موضعه لاحقًا، ويكون للعنصر الآخر مزيج معاكس. عندها قد يقول مسند يعتمد على OR إن كل عنصر يسبق الآخر. تمنع معالجة التعادل بالتتابع هذا التناقض.
وتجنّب أيضًا استخدام <= بوصفه المقارنة الأخيرة: فالقيم المتساوية ستُرجع true، ما يخالف قاعدة عدم جواز أن يسبق العنصر نفسه. اجعل كل مقارنة معتمدة على القيم المعطاة وقواعد الفرز الثابتة فقط.
اختر بين التعادلات المستقرة والمعرّف النهائي
فرز Swift الموثّق مستقر: تحتفظ العناصر التي يتركها المسند متكافئة بترتيبها النسبي في الإدخال. إذا كان هذا هو السلوك المقصود، فاحذف مقارنة المعرّف النهائية وأعِد false بعد تعادل العنوان. ضمان الفرز المستقر
يستخدم هذا المثال بدلًا من ذلك معرّفات اصطناعية فريدة لحسم التعادلات المتبقية. لذلك ينتج عن عكس ترتيب الإدخال في المثال تسلسل المعرّفات نفسه. وهذه الخاصية ناتجة عن هذه القواعد وقيم المثال. وإذا تكررت المعرّفات مع تساوي كل المفاتيح الأخرى، فستظل تلك العناصر متكافئة وتحافظ على ترتيبها في الإدخال.
عبّر عن القواعد نفسها باستخدام مقارِنات مسارات المفاتيح في Foundation
توفّر Foundation النوع KeyPathComparator لمقارنة الخصائص، وsorted(using:) لتطبيق سلسلة من المقارِنات. يحدد المقارِن الأول الترتيب الأساسي، ثم تنقّح المقارِنات اللاحقة حالات التعادل. تتوفر هذه الواجهات بدءًا من macOS 12. KeyPathComparator، sorted(using:)
لإنشاء إصدار مكافئ باستخدام Foundation، أظهِر مفتاحين للموضع: رتبة للقيمة المفقودة وموضعًا رقميًا. تضع الرتبة المواضع المفقودة في النهاية. وعند تعادل الرتبة، لا يمكن أن تتعارض القيمة الرقمية البديلة للمواضع المفقودة مع صفر موجود، لأن هذه العناصر تختلف مسبقًا في رتبها.
ألحِق الامتداد والحساب التاليين بالبرنامج الكامل أعلاه. هذا مثال يعتمد على تعريفات ShapeItem وitems وexpectedIDs الموجودة فيه. ويتطلب macOS 14 أو إصدارًا أحدث لأنه ينشئ ComparableComparator صراحةً باستخدام init(order:). توفر المُهيّئ
import Foundation
extension ShapeItem {
var missingPositionRank: Int {
effectivePosition == nil ? 1 : 0
}
var numericPositionKey: Int {
effectivePosition ?? 0
}
}
if #available(macOS 14.0, *) {
let comparators: [KeyPathComparator<ShapeItem>] = [
KeyPathComparator(\ShapeItem.group, order: .forward),
KeyPathComparator(\ShapeItem.missingPositionRank,
order: .forward),
KeyPathComparator(\ShapeItem.numericPositionKey,
order: .forward),
KeyPathComparator(
\ShapeItem.title,
comparator: ComparableComparator<String>(order: .forward)
),
KeyPathComparator(\ShapeItem.id, order: .forward)
]
let foundationOrdered = items.sorted(using: comparators)
precondition(foundationOrdered.map(\.id) == expectedIDs)
print(foundationOrdered.map(\.id))
} else {
print(items.sorted(by: comesBefore).map(\.id))
}
يحافظ مقارِن العنوان الصريح على الاختيار السابق لترتيب سلاسل Swift. توثّق Foundation أن ComparableComparator يستخدم تنفيذ Comparable للنوع المُقارَن، وأن KeyPathComparator يقبل مقارِن خصائص مقدّمًا. ComparableComparator، مُهيّئ مقارِن مسار المفتاح
في ترتيب ثابت واحد، يجعل الإغلاق كل فرع واضحًا. وتكون مصفوفة المقارِنات مفيدة عندما تريد تمثيل ترتيب المفاتيح المدعومة كقيم. في الحالتين، أبقِ اختيار القيمة البديلة في الخاصية المحسوبة للنموذج كي يستخدم التنفيذان التعريف نفسه.
كيّف القواعد دون تغيير معناها
لترتيب المواضع تنازليًا مع إبقاء المواضع المفقودة في النهاية، غيّر المقارنة بين موضعين موجودين فقط إلى left > right. وفي إصدار Foundation، اعكس numericPositionKey فقط، وأبقِ missingPositionRank تصاعديًا. فعكس النتيجة بأكملها سيعكس أيضًا المجموعات والعناوين والمعرّفات ومواضع القيم المفقودة.
إذا أصبحت سلسلة اختيارية مفتاحًا احتياطيًا، فحدّد بشكل منفصل ما إذا كانت السلسلة الفارغة تُعد مفقودة. يستجيب الإبدال عند انعدام القيمة إلى nil؛ ولا يستبدل سلسلة فارغة موجودة. عالِج أي قاعدة تطبيع إضافية قبل الفرز، واستخدم المفتاح الفعلي نفسه في كل المواضع. سلوك الإبدال عند انعدام القيمة
أبقِ هذا الحساب الصغير متزامنًا. يميّز دليل التزامن في Swift بين الفرز المتزامن العادي ونقاط التعليق، كما أن مسند sorted(by:) الموثّق متزامن. إذا احتاج التطبيق إلى قيم من عمل غير متزامن، فأكمِل الحصول على تلك القيم قبل استدعاء أداة الفرز هذه. إن إضافة await داخل إغلاق المقارنة ليست امتدادًا مدعومًا لتوقيعها. التزامن، توقيع sorted(by:)
قبل تكييف المثال لقائمة سطح مكتب أخرى، أبقِ على اختبارات لمفتاح مفضّل مفقود، وحقلي موضع مفقودين، وصفر صالح، ومواضع فعلية متساوية، وحقول عرض متساوية تمامًا. قرّر ما إذا كان ينبغي للتعادل الأخير أن يحافظ على ترتيب الإدخال أو أن يُحسم بمعرّف. تمنح هذه الخيارات القائمة قواعد ترتيب دقيقة تظل مفهومة في كلا التنفيذين.
المصادر
- sorted(by:) — Apple
- المعاملات الأساسية — مشروع Swift
- KeyPathComparator — Apple
- sorted(using:) — Apple
- ComparableComparator — Apple
- init(order:) — Apple
- init(_:comparator:) — Apple
- التزامن — مشروع Swift