دمج المصفوفات في Swift: من + إلى copy-on-write

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

كيفية دمج المصفوفات في Swift؟ تحليل كامل مع أمثلة وأسرار الأداء

\n\n

إذا كنت قد كتبت أي كود بلغة Swift من قبل، فبالتأكيد واجهت هذه المهمة: "لدي مصفوفتان، كيف أجعلهما مصفوفة واحدة؟". يبدو السؤال بسيطًا، لكنه يخفي عالمًا كاملاً من التحسينات والفروق الدقيقة. على Stack Overflow، حصل هذا السؤال على آلاف المشاهدات - وليس من دون سبب. اليوم لن نعرض فقط كيفية كتابة الكود، بل سننظر تحت غطاء المترجم (compiler) ونعرف ما يحدث حقًا عندما تكتب array1 + array2. هيا بنا!

\n\n

الطريقة الأكثر وضوحًا: العامل +

\n\n

لنبدأ بالكلاسيكيات. في Swift، يمكن جمع المصفوفات مثل الأرقام:

\n\n
let first = [1, 2, 3]\nlet second = [4, 5, 6]\nlet result = first + second\nprint(result) // [1, 2, 3, 4, 5, 6]
\n\n

جميل؟ نعم. سريع؟ ليس دائمًا. عند استخدام +، يقوم Swift بإنشاء مصفوفة جديدة ونسخ العناصر من كلا المصفوفتين الأصليتين إليها. إذا كانت مصفوفاتك كبيرة (لنقل 100,000 عنصر لكل منها)، فهذا عمل مزدوج للمعالج. لكن انتظر - هناك فارق دقيق يسمى copy-on-write (COW).

\n\n

سحر Copy-on-Write: كيف يوفر Swift الذاكرة

\n\n

Swift لغة ذكية. عندما تقوم ببساطة بتعيين مصفوفة لأخرى، فإنه لا ينسخ البيانات فورًا:

\n\n
var a = [1, 2, 3]\nvar b = a // طالما لم يتم تعديل البيانات، يشير a و b إلى نفس منطقة الذاكرة\nb.append(4) // هنا يحدث النسخ الفعلي: تحصل b على نسختها الخاصة
\n\n

هذا يسمى copy-on-write. لكن عند الدمج باستخدام +، لا يعمل هذا - النتيجة دائمًا مصفوفة جديدة. لماذا؟ لأننا لا نشير فقط إلى البيانات القديمة، بل ننشئ مجموعة جديدة من عناصر مصدرين مختلفين. لا يمكن للمترجم (compiler) ضمان عدم تغيير المصفوفات الأصلية، لذلك يقوم بنسخ كامل.

\n\n

طريقة append(contentsOf:): عندما تكون السرعة أهم

\n\n

إذا كنت بحاجة إلى إضافة عناصر مصفوفة إلى نهاية مصفوفة أخرى دون إنشاء مصفوفة ثالثة، استخدم append(contentsOf:):

\n\n
var numbers = [1, 2, 3]\nlet moreNumbers = [4, 5, 6]\nnumbers.append(contentsOf: moreNumbers)\nprint(numbers) // [1, 2, 3, 4, 5, 6]
\n\n

في هذه الحالة، لا ينشئ Swift مصفوفة جديدة - بل يوسع المصفوفة الموجودة. إذا كانت المصفوفة numbers تحتوي على ذاكرة محجوزة كافية (capacity)، فسيتم نسخ العناصر الجديدة إلى المساحة الفارغة. إذا لم يكن الأمر كذلك، يحدث إعادة توزيع للذاكرة، لكن هذا لا يزال أسرع من إنشاء مصفوفة من الصفر.

\n\n

نصيحة محترف: إذا كنت تعلم أنك ستضيف الكثير من العناصر، قم بحجز الذاكرة مسبقًا باستخدام reserveCapacity():

\n\n
var bigArray: [Int] = []\nbigArray.reserveCapacity(1000)\nfor i in 0..<1000 {\n    bigArray.append(i) // لا يوجد نسخ زائد!\n}
\n\n

طريقة flatMap: دمج المصفوفات مع التحويل

\n\n

أحيانًا لا تحتاج فقط إلى ربط المصفوفات، بل أيضًا إلى تحويل كل عنصر. هنا يأتي دور flatMap (أو compactMap للقيم الاختيارية). بالمعنى الدقيق، flatMap مصمم "لتسوية" المجموعات، لكن يمكن استخدامه أيضًا للدمج:

\n\n
let arrays = [[1, 2], [3, 4], [5, 6]]\nlet flattened = arrays.flatMap { $0 }\nprint(flattened) // [1, 2, 3, 4, 5, 6]
\n\n

تحت الغطاء، يفعل flatMap نفس الشيء الذي تفعله حلقة مع append(contentsOf:). لكن الكود يصبح أنظف وأكثر قابلية للقراءة. صحيح، إذا كنت تحتاج فقط إلى ربط مصفوفتين - لا تستخدم flatMap، فهو مبالغ فيه.

\n\n

reduce(into:) للدمج المرن

\n\n

إذا كان لديك مصفوفة من المصفوفات وتريد دمجها مع منطق إضافي، جرب reduce(into:):

\n\n
let arrayOfArrays = [[1, 2], [3, 4], [5, 6]]\nlet merged = arrayOfArrays.reduce(into: [Int]()) { r                

المدونات

توصيات الكتب