Rust على LeetCode: الانتقال من Python إلى التفكير النظامي
\n\nإذا كنت قد دخلت إلى PythonLib، فمن المرجح أنك معتاد على Python. أنت تعرف كيفية كتابة حلول موجزة في سطر واحد وتستخدم الكتابة الديناميكية كأمر مسلم به. لكن العالم لا يقف ساكنًا. في كثير من الأحيان في المقابلات في الشركات الكبرى (وليس فقط) يُطلب منك كتابة كود بلغة Rust. لماذا؟ لأن Rust هي لغة تجمع بين سرعة C وأمان الذاكرة، وLeetCode هي ساحة مثالية لصقل هذه المهارة.
\n\nفي هذه المقالة، لن نمر فقط على بناء الجملة. سنقوم بتحليل كيفية التفكير في نموذج Rust عند حل المشكلات الخوارزمية، وما هي هياكل البيانات التي يجب استخدامها، وكيف لا تغرق في borrow checker. هيا بنا!
لماذا LeetCode بلغة Rust هو تحدٍ وميزة؟
\n\nمعظم الحلول على LeetCode مكتوبة بلغة Python أو Java. أما Rust فتجبرك على التفكير في الذاكرة، والملكية، وعمر المتغيرات. يبدو هذا صعبًا، لكن هذا هو بالضبط ما يجعلك أقوى كمهندس. من خلال حل المشكلات بلغة Rust، تتعلم تلقائيًا:
\n\n- \n
- نظام الملكية (Ownership) — الميزة الرئيسية للغة. \n
- الاقتراض (Borrowing) والمراجع القابلة للتغيير/غير القابلة للتغيير. \n
- المكررات الفعالة دون تخصيصات زائدة. \n
- العمل مع
OptionوResultبدلاً من الاستثناءات. \n
إذا كنت قادمًا من Python، فستبدو لك المشكلات العشر الأولى بمثابة عذاب. ولكن عندما تفهم كيف يحميك المترجم من null pointer وسباقات البيانات، فستقع في حب هذه اللغة.
ما الذي يجب تعلمه قبل البدء؟
\n\nقبل أن تندفع في مجموعة المشكلات، دعنا نستعرض الأدوات. بدونها، ستكتب كودًا كما لو كنت تكتب بلغة C++ مع وحدات ماكرو — غير فعال ومؤلم.
\n\n1. المكررات والإغلاقات (Closures)
\nفي Rust، المكررات ليست مجرد حلقة for. إنها أداة وظيفية قوية. بدلاً من كتابة for i in 0..vec.len()، تعلم استخدام .iter()، .map()، .filter()، .fold().
// Python: [x*2 for x in list if x > 0]\n// Rust:\nlet result: Vec<i32> = list.iter()\n .filter(|&x| *x > 0)\n .map(|x| x * 2)\n .collect();\n\nهذا الكود ليس فقط أقصر، بل يعمل بشكل أسرع أيضًا، لأن المكررات كسولة ويقوم المترجم بتحسينها إلى حلقة خطية.
\n\n2. HashMap و BTreeMap
\nللمشكلات التي تتطلب حساب التكرارات (مثل "Two Sum" أو "Group Anagrams")، HashMap هو أفضل صديق لك. لكن تذكر: في Rust، يتطلب الإدراج في HashMap ملكية المفتاح. إذا كنت تستخدم &str، فسيتعين عليك استنساخ السلسلة أو استخدام String.
use std::collections::HashMap;\n\nfn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {\n let mut map = HashMap::new();\n for (i, &num) in nums.iter().enumerate() {\n let complement = target - num;\n if let Some(&j) = map.get(&complement) {\n return vec![j as i32, i as i32];\n }\n map.insert(num, i);\n }\n vec![]\n}\n\nلاحظ: enumerate() تُرجع زوجًا (usize, &i32). نستخدم &num لإلغاء مرجعية المؤشر.
3. Vec والشرائح
\nVec هي مصفوفة ديناميكية. لكن في مشكلات LeetCode، غالبًا ما تحتاج إلى العمل مع المصفوفات الفرعية. لهذا، استخدم الشرائح &[T]. لا تتطلب نسخًا وتسمح بتمرير جزء من المصفوفة إلى دالة.
fn max_subarray_sum(arr: &[i32]) -> i32 {\n let mut max_ending = arr[0];\n let mut max_so_far = arr[0];\n for &x in arr[1..].iter() {\n max_ending = std::cmp::max(x, max_ending + x);\n max_so_far = std::cmp::max(max_so_far, max_ending);\n }\n max_so_far\n}\n\n