المجموعات والمكررات في Rust: معالجة بيانات خط الإنتاج بكفاءة
Vec: المصفوفة الديناميكية
Vec هو أكثر أنواع المجموعات استخداماً في Rust. يُخزّن عناصر من نوع واحد في ذاكرة متجاورة. في المصانع، نستخدمه لتخزين قراءات المستشعرات المتتالية.
// إنشاء مصفوفة لتخزين قراءات درجة حرارة الفرن
let mut readings: Vec<f64> = Vec::new();
// إضافة قراءات جديدة
readings.push(72.5);
readings.push(73.1);
readings.push(71.8);
// الوصول بالفهرس
let first = readings[0]; // 72.5
// الوصول الآمن - يُرجع Option
let maybe = readings.get(10); // None بدل انهيار البرنامج
// حذف آخر عنصر
let last = readings.pop(); // Some(71.8)
// إنشاء سريع بالماكرو
let alarms = vec![false, false, true, false];
println!("عدد القراءات: {}", readings.len());
HashMap<K, V>: تخزين المفتاح والقيمة
HashMap يربط مفتاحاً بقيمة، مثل ربط معرّف المستشعر بآخر قراءة له.
use std::collections::HashMap;
// ربط معرّف المستشعر بآخر قراءة
let mut last_reading: HashMap<String, f64> = HashMap::new();
last_reading.insert("TEMP-01".to_string(), 72.5);
last_reading.insert("PRESS-03".to_string(), 1.2);
last_reading.insert("HUMID-07".to_string(), 45.0);
// البحث عن قراءة مستشعر
if let Some(temp) = last_reading.get("TEMP-01") {
println!("درجة الحرارة: {temp}");
}
// التحديث فقط إذا لم يكن موجوداً
last_reading.entry("TEMP-01".to_string()).or_insert(0.0);
// تعديل قيمة موجودة
last_reading
.entry("TEMP-01".to_string())
.and_modify(|v| *v = 74.0);
// المرور على جميع العناصر
for (sensor_id, value) in &last_reading {
println!("{sensor_id} => {value}");
}
مجموعات أخرى: VecDeque و BTreeMap و HashSet
Rust يوفّر مجموعات متخصصة لحالات مختلفة:
use std::collections::{VecDeque, BTreeMap, HashSet};
// VecDeque: طابور ذو طرفين - مثالي لسجل الأحداث الدائري
let mut event_log: VecDeque<String> = VecDeque::with_capacity(100);
event_log.push_back("بدء التشغيل".to_string());
event_log.push_back("قراءة مستشعر".to_string());
// حذف الأقدم عند الامتلاء
if event_log.len() >= 100 {
event_log.pop_front();
}
// BTreeMap: خريطة مرتّبة - مفيدة لترتيب المستشعرات أبجدياً
let mut sorted_sensors: BTreeMap<String, f64> = BTreeMap::new();
sorted_sensors.insert("A-TEMP".to_string(), 70.0);
sorted_sensors.insert("B-PRESS".to_string(), 1.1);
// العناصر دائماً مرتبة بالمفتاح
// HashSet: مجموعة بدون تكرار - لتتبع الأجهزة النشطة
let mut active_machines: HashSet<u32> = HashSet::new();
active_machines.insert(101);
active_machines.insert(102);
active_machines.insert(101); // لا يُضاف مرة ثانية
println!("أجهزة نشطة: {}", active_machines.len()); // 2
المكررات: خط معالجة كسول
المكررات في Rust تشبه خط الإنتاج: كل خطوة تعالج عنصراً واحداً فقط عند الطلب.
لا يتم حساب أي شيء حتى نستدعي عملية جمع مثل collect().
let readings = vec![72.5, 73.1, 71.8, 85.0, 69.2];
// .iter() - ينشئ مكرراً على المرجعيات
// .map() - يحوّل كل عنصر
// .filter() - يحتفظ بالعناصر التي تحقق الشرط
// .collect() - يجمع النتائج في مجموعة
// تحويل من سلسيوس إلى فهرنهايت
let fahrenheit: Vec<f64> = readings
.iter()
.map(|c| c * 9.0 / 5.0 + 32.0)
.collect();
// عدّ القراءات المرتفعة
let high_count = readings
.iter()
.filter(|&&r| r > 75.0)
.count();
println!("قراءات مرتفعة: {high_count}");
سلسلة محولات المكررات
القوة الحقيقية تظهر عند سلسلة عدة عمليات معاً:
struct SensorReading {
sensor_id: String,
value: f64,
is_valid: bool,
}
let readings = vec![
SensorReading { sensor_id: "T-01".into(), value: 72.5, is_valid: true },
SensorReading { sensor_id: "T-02".into(), value: -999.0, is_valid: false },
SensorReading { sensor_id: "T-03".into(), value: 85.3, is_valid: true },
SensorReading { sensor_id: "T-04".into(), value: 71.0, is_valid: true },
];
// سلسلة كاملة: تصفية ثم تحويل ثم جمع
let valid_labels: Vec<String> = readings
.iter()
.filter(|r| r.is_valid) // فقط القراءات الصالحة
.filter(|r| r.value > 72.0) // فقط المرتفعة
.map(|r| format!("{}: {:.1}°", r.sensor_id, r.value))
.collect();
// ["T-01: 72.5°", "T-03: 85.3°"]
// إيجاد أعلى قيمة صالحة
let max_valid = readings
.iter()
.filter(|r| r.is_valid)
.map(|r| r.value)
.fold(f64::NEG_INFINITY, f64::max);
println!("أعلى قراءة صالحة: {max_valid}");
مثال عملي: تحليل دفعة قراءات إنتاجية
/// تحليل دفعة من قراءات خط الإنتاج:
/// حساب المتوسط، إيجاد القيم الشاذة، تصنيف الحالة
fn analyze_batch(readings: &[f64], threshold: f64) {
let count = readings.len() as f64;
// حساب المتوسط
let avg = readings.iter().sum::<f64>() / count;
// إيجاد القيم الشاذة (بعيدة عن المتوسط بأكثر من الحد)
let outliers: Vec<(usize, f64)> = readings
.iter()
.enumerate()
.filter(|(_, &val)| (val - avg).abs() > threshold)
.map(|(i, &val)| (i, val))
.collect();
// أدنى وأعلى قيمة
let min = readings.iter().cloned().fold(f64::INFINITY, f64::min);
let max = readings.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
println!("--- تقرير الدفعة ---");
println!("العدد: {} | المتوسط: {:.2}", readings.len(), avg);
println!("الأدنى: {:.2} | الأعلى: {:.2}", min, max);
println!("قيم شاذة: {}", outliers.len());
for (idx, val) in &outliers {
println!(" القراءة [{}] = {:.2} (انحراف: {:.2})", idx, val, val - avg);
}
}
fn main() {
let batch = vec![72.5, 73.1, 71.8, 85.0, 69.2, 72.0, 73.5, 90.1, 71.5];
analyze_batch(&batch, 5.0);
}
الخلاصة
- Vec هو المجموعة الأساسية لتخزين سلاسل القراءات المتتالية
- HashMap مثالي لربط معرّفات المستشعرات بقيمها الحالية
- VecDeque لسجلات الأحداث الدائرية، BTreeMap للبيانات المرتّبة، HashSet لتتبع العناصر الفريدة
- المكررات تعمل بالتقييم الكسول: لا حساب حتى استدعاء
collect()أوcount() - سلسلة المكررات تبني خط معالجة نظيف وفعّال بدون تخصيص ذاكرة وسيطة
- في الدرس القادم سنتعلم كيف ننظّم هذا الكود في وحدات وحزم مستقلة