التكرار عبر نطاق بخطوة غير قياسية في Rust: 3 طرق

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

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

ابدأ الدورة

كيف تتكرر عبر الأرقام بخطوة 3 أو 5 أو 100 في Rust؟ تحليل كامل



تخيل: أنت تكتب بلغة Rust وتحتاج إلى طباعة جميع الأرقام الزوجية من 0 إلى 20. أو، لنقل، بناء تسلسل 0, 5, 10, 15, 20. في Python كنت ستكتب ببساطة range(0, 21, 5) - وسينتهي الأمر. وماذا عن Rust؟ هناك بعض التفاصيل الدقيقة، وسنقوم بتحليلها اليوم.



هذا السؤال - من أكثر الأسئلة شيوعاً على Stack Overflow (آلاف المشاهدات). وليس من المستغرب: الحلقة القياسية for i in 0..10 تعمل فقط بخطوة 1. ولكن ماذا لو كنت بحاجة إلى خطوة مختلفة؟ اتضح أن هناك ثلاث طرق، ولكل منها مزاياها وعيوبها.



لن نعرض الكود فقط - بل سنقارن الطرق من حيث الأداء، وسهولة القراءة، والمرونة. هيا بنا!



لماذا لا يوجد نطاق مدمج مع خطوة في Rust؟



الإجابة المختصرة: لأن Range في Rust هو مكرر بسيط يولد تسلسلاً من الأعداد الصحيحة. لا يدعم الخطوة افتراضياً ليظل خفيفاً وقابلاً للتنبؤ قدر الإمكان. لكن المطورين وفروا طرقاً تحل هذه المشكلة.



الإجابة الطويلة: في نظام Rust البيئي، فلسفة "الصريح أفضل من الضمني". بدلاً من تضمين الخطوة في صياغة النطاق، اقترح المبتكرون أدوات مرنة لتكييف المكررات. وهذا يفتح إمكانيات أكثر مما يبدو.



الطريقة 1: step_by() - الطريق الأكثر مباشرة



في المكتبة القياسية لـ Rust، توجد طريقة step_by(n) التي تنشئ مكرراً يتخطى كل n-1 من العناصر. إليك كيفية عملها:



fn main() {    // طباعة الأرقام من 0 إلى 20 بخطوة 5    for i in (0..=20).step_by(5) {        println!("{}", i);    }    // الإخراج: 0, 5, 10, 15, 20}


لاحظ: استخدمت 0..=20 (شامل)، وليس 0..20 (غير شامل). إذا كنت بحاجة إلى خطوة 3 حتى 10، اكتب (0..10).step_by(3) - سنحصل على 0, 3, 6, 9.



مزايا step_by():

  • البساطة - سطر واحد، لا حاجة للإغلاقات
  • سهولة القراءة - واضح فوراً ما نقوم به
  • الأداء - تكاليف إضافية ضئيلة


العيوب:

  • يعمل فقط مع الأعداد الصحيحة (i32, u64، إلخ)
  • لا يمكن تعيين خطوة ديناميكية في كل تكرار
  • ظهر فقط في Rust 1.28 (لكن هذا منذ فترة طويلة)


هذه الطريقة - الخيار المثالي لـ 90% من المهام. إذا كنت تحتاج فقط إلى التكرار عبر الأرقام بخطوة ثابتة - استخدم step_by().



الطريقة 2: filter() - عندما تكون الخطوة شرطاً



ماذا لو كانت الخطوة غير ثابتة؟ على سبيل المثال، تحتاج إلى طباعة الأرقام التي تقبل القسمة على 3 بدون باقٍ. أو الأرقام التي مجموع أرقامها أكبر من 5. هنا يأتي دور filter():



fn main() {    // جميع الأرقام من 0 إلى 30، المضاعفات للرقم 4    for i in (0..=30).filter(|x| x % 4 == 0) {        println!("{}", i);    }    // الإخراج: 0, 4, 8, 12, 16, 20, 24, 28

// والآن - الأرقام التي لا تقبل القسمة على 3 for i in (0..20).filter(|x| x % 3 != 0) { println!("{}", i); } // الإخراج: 1, 2, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17, 19}


هنا نمرر إغلاقاً (closure) يتحقق من كل رقم. filter() يبقي فقط العناصر التي يكون الشرط فيها صحيحاً.



متى يكون filter() أفضل من step_by():

  • الخطوة تعتمد على القيمة (مثل "جميع الأعداد الأولية")
  • تحتاج إلى الدمج مع شروط أخرى
  • الخطوة غير ثابتة (مثل أرقام فيبوناتشي)


العيوب:

  • أبطأ قليلاً من step_by() - يجب التحقق من كل رقم
  • الكود قد يكون أقل وضوحاً للمبتدئين


شخصياً، أستخدم filter() عندما لا أحتاج فقط إلى "كل n-ث"، بل "كل من يطابق القاعدة". هذا أقوى، لكنه يتطلب المزيد من الاهتمام.

المدونات

توصيات الكتب