البرمجة

تخزين القيم باستخدام Vectors

تخزين لائحة من القيم باستخدام الأشعة (Vectors) في لغة رست Rust

تُعد لغة رست Rust واحدة من اللغات الحديثة التي اكتسبت شعبية واسعة بفضل ميزاتها القوية في إدارة الذاكرة والأداء العالي مع الحفاظ على أمان الذاكرة. من بين الأدوات الأساسية التي توفرها لغة رست للتعامل مع مجموعات البيانات الديناميكية هي الأشعة (Vectors)، والتي تمثل بنية بيانات مرنة تسمح بتخزين لائحة من القيم مع إمكانية التوسع أو التصغير أثناء وقت التشغيل.

في هذا المقال سنغوص عميقاً في مفهوم الأشعة (Vectors) في رست، وسنشرح كيفية تخزين لائحة من القيم باستخدامها، بالإضافة إلى توضيح المزايا التي تقدمها، والطرق المتنوعة للتعامل معها، وكيفية الاستفادة من ميزات لغة رست في هذا السياق. سنناقش كذلك الجانب الداخلي لبنية الـ Vector من حيث الذاكرة وإدارة الموارد، مما يثري فهم القارئ لهذه البنية الحيوية.


مقدمة عن الأشعة (Vectors) في رست

في لغات البرمجة التقليدية، يُستخدم المصفوفات Arrays لتخزين مجموعة من القيم ذات النوع الواحد. إلا أن المصفوفات في رست مثل معظم اللغات تكون ذات حجم ثابت يعرف أثناء وقت الترجمة (compile time)، مما يعني عدم إمكانية تغيير حجمها بعد إنشائها. في المقابل، الأشعة (Vectors) في رست تمثل بنية بيانات ديناميكية، حيث يمكن إضافة أو إزالة العناصر أثناء وقت التشغيل (runtime)، مما يجعلها مثالية لتخزين القوائم التي قد تتغير أحجامها.

الأشعة (Vectors) في رست عبارة عن حاويات ديناميكية تُخزن مجموعة متجانسة من القيم من نفس النوع، وتوفر إمكانية الوصول العشوائي للعناصر، كما تدعم العديد من العمليات مثل الإضافة، الحذف، التكرار، والفرز.


كيفية إنشاء وتخزين لائحة من القيم باستخدام Vector

إنشاء Vector فارغ

لبداية تخزين لائحة من القيم باستخدام Vector، يمكن إنشاء Vector فارغ من نوع معين كالتالي:

rust
let mut numbers: Vec<i32> = Vec::new();

في هذا المثال، numbers هو متجه من الأعداد الصحيحة (i32)، والـ mut تعني أنه متغير قابل للتغيير (mutable)، أي يمكن تعديل محتوى المتجه لاحقاً.

إضافة عناصر إلى Vector

بعد إنشاء المتجه، يمكن إضافة العناصر إليه باستخدام الدالة push:

rust
numbers.push(10); numbers.push(20); numbers.push(30);

بهذا الشكل أصبح المتجه يحتوي على ثلاث قيم: 10، 20، و30.

إنشاء Vector مباشرة مع قيم مبدئية

يمكن أيضاً إنشاء Vector وتعبئته بالقيم مباشرة عند الإنشاء باستخدام الماكرو vec!:

rust
let mut numbers = vec![10, 20, 30];

يتم هنا إنشاء متجه يحتوي على القيم الثلاثة المذكورة، ويمكن تعديل هذا المتجه لاحقاً إذا كان معرفاً كـ mut.


الوصول إلى عناصر Vector

الوصول باستخدام الفهرس Indexing

يمكن الوصول إلى أي عنصر في الـ Vector عبر الفهرس:

rust
let first = numbers[0]; // القيمة 10 let second = numbers[1]; // القيمة 20

ولكن يجب الحذر من أن الفهرس يجب أن يكون ضمن حدود الـ Vector، لأن محاولة الوصول إلى فهرس خارج الحدود يؤدي إلى حدوث خطأ وقت التشغيل.

استخدام الدالة get

للتعامل مع إمكانية عدم وجود عنصر عند فهرس معين بطريقة أكثر أماناً، يمكن استخدام الدالة get التي تعيد Option:

rust
match numbers.get(2) { Some(value) => println!("القيمة عند الفهرس 2 هي {}", value), None => println!("لا يوجد قيمة عند هذا الفهرس"), }

التكرار على عناصر Vector

يمكن التكرار على محتوى الـ Vector بعدة طرق:

باستخدام حلقة for التقليدية

rust
for num in &numbers { println!("{}", num); }

هنا &numbers تعني أننا نأخذ مرجعاً غير قابل للتغيير على المتجه، مما يسمح بالقراءة فقط دون تعديل.

التكرار مع تعديل القيم

إذا كان المتجه معرفاً كـ mut، يمكن التكرار على مراجع قابلة للتغيير وتعديل القيم:

rust
for num in &mut numbers { *num += 5; // زيادة كل عنصر بمقدار 5 }

حذف العناصر من Vector

يمكن حذف العناصر من Vector بعدة طرق:

حذف العنصر الأخير باستخدام pop

rust
let last = numbers.pop(); match last { Some(value) => println!("تم حذف القيمة: {}", value), None => println!("المتجه فارغ"), }

pop تعيد آخر عنصر في المتجه وتزيله منه.

حذف عنصر في موقع معين

باستخدام remove يمكن حذف عنصر عند فهرس معين:

rust
numbers.remove(1); // يحذف العنصر عند الفهرس 1 (العنصر الثاني)

تغيير حجم Vector

ميزة الـ Vector الأساسية هي القدرة على تغيير الحجم ديناميكياً. يمكن إضافة عناصر جديدة باستخدام push أو حذفها باستخدام pop أو remove. هذا يتيح المرونة في تخزين لوائح قيم متغيرة الحجم دون الحاجة إلى إنشاء مصفوفات جديدة.


كيفية تخزين أنواع مختلفة من القيم

عادةً، الـ Vector يخزن عناصر من نفس النوع. ومع ذلك، يمكن تخزين أنواع مختلفة عبر استخدام النوع enum أو نوع صناديق ذكية مثل Box، مما يوسع إمكانيات التخزين.

مثال باستخدام enum

rust
enum Value { Integer(i32), Float(f64), Text(String), } let mut values: Vec = Vec::new(); values.push(Value::Integer(42)); values.push(Value::Float(3.14)); values.push(Value::Text(String::from("مرحبا")));

الأداء الداخلي للـ Vector

من حيث الأداء، يستخدم الـ Vector مصفوفة مخصصة في الذاكرة، وعندما يمتلئ الـ Vector، يقوم بتخصيص مساحة ذاكرة أكبر نسبيًا (عادة ضعف الحجم السابق) ويقوم بنسخ العناصر إلى الموقع الجديد، ثم يحرر الموقع القديم. هذه التقنية تضمن أن عملية الإضافة push تكون فعالة بشكل متوسط (amortized constant time).


الذاكرة وإدارة الموارد في Vector

لغة رست تشتهر بنموذج الملكية Ownership، وهذا ينعكس في كيفية إدارة Vector للذاكرة. عندما يتم تدمير Vector، تتحرر الذاكرة المخصصة له تلقائياً. كذلك، عند نقل Vector إلى متغير آخر (move)، تنتقل الملكية دون نسخ البيانات، مما يحسن الأداء.


أمثلة عملية متقدمة على استخدام Vector

دمج عدة متجهات في متجه واحد

rust
let mut v1 = vec![1, 2, 3]; let v2 = vec![4, 5, 6]; v1.extend(v2); println!("{:?}", v1); // [1, 2, 3, 4, 5, 6]

تصفية قيم معينة من Vector

rust
let numbers = vec![1, 2, 3, 4, 5, 6]; let even_numbers: Vec<i32> = numbers.into_iter().filter(|x| x % 2 == 0).collect(); println!("{:?}", even_numbers); // [2, 4, 6]

تحويل Vector إلى مجموعة من البيانات المهيكلة Structs

rust
struct Person { name: String, age: u32, } let names = vec!["أحمد", "سارة", "يوسف"]; let ages = vec![30, 25, 40]; let people: Vec = names.into_iter().zip(ages.into_iter()) .map(|(name, age)| Person { name: name.to_string(), age }) .collect();

جدول مقارنة بين Array و Vector في رست

الخاصية Array Vector
الحجم ثابت (معروف أثناء الترجمة) ديناميكي (يتغير أثناء التشغيل)
قابلية التوسع لا نعم
الأداء عند الوصول سريع جدًا سريع جدًا
الاستخدام عند الحاجة إلى حجم ثابت عند الحاجة إلى حجم متغير
قابلية التعديل نعم (إذا كان mut) نعم (إذا كان mut)
إمكانية التخزين المختلط لا (يجب أن تكون نفس النوع) ممكن باستخدام enum أو Traits

الخلاصة

الأشعة (Vectors) في لغة رست تشكل إحدى الأدوات الجوهرية والفعالة لتخزين لوائح من القيم بشكل ديناميكي ومرن. بفضل إدارة الذاكرة الذكية التي توفرها رست، يمكن للمبرمجين العمل بثقة على لوائح بيانات متغيرة الحجم دون القلق من مشاكل تسرب الذاكرة أو التداخلات غير الآمنة. كما أن التوافق الكبير مع ميزات لغة رست الأخرى مثل نموذج الملكية Ownership والتعامل مع الأنواع الديناميكية يجعل الـ Vector حلاً مثالياً لمختلف التطبيقات التي تتطلب تخزين بيانات متغيرة الحجم.

استخدام الـ Vector في رست ليس فقط لتخزين القيم، بل يشمل قدرات واسعة من التكرار، التعديل، التصفية، والدمج مما يجعله من الأدوات التي لا غنى عنها في بناء البرمجيات الحديثة.


المصادر والمراجع