Python والخوارزميات لـ ML
Python لمقابلات ML
لماذا إتقان Python مهم
في مقابلات هندسة ML، من المتوقع أن تكتب كوداً بطلاقة في Python. على عكس مقابلات هندسة البرمجيات العامة حيث تعمل أي لغة، تتطلب أدوار ML بشكل شبه عالمي إتقان Python لأن:
- 90%+ من مكتبات ML هي Python-أولاً (TensorFlow، PyTorch، scikit-learn)
- معالجة البيانات تحدث في Python (pandas، NumPy)
- سيقيم المحاورون أساليب وأنماط Python الخاصة بك
أنماط Python الأساسية
1. List Comprehensions
List comprehensions ليست مجرد سكر نحوي—إنها تظهر التفكير Pythonic وغالباً ما تكون أسرع من الحلقات.
النمط الأساسي:
# سيئ: استخدام الحلقات
result = []
for x in range(10):
if x % 2 == 0:
result.append(x * x)
# جيد: List comprehension
result = [x * x for x in range(10) if x % 2 == 0]
Comprehensions المتداخلة:
# تسطيح مصفوفة ثنائية الأبعاد
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# سيئ
flat = []
for row in matrix:
for val in row:
flat.append(val)
# جيد
flat = [val for row in matrix for val in row]
# الإخراج: [1, 2, 3, 4, 5, 6, 7, 8, 9]
Dictionary Comprehensions:
# إنشاء تعيين اسم الميزة إلى الفهرس
features = ['age', 'income', 'score']
feature_to_idx = {name: idx for idx, name in enumerate(features)}
# الإخراج: {'age': 0, 'income': 1, 'score': 2}
سؤال المقابلة:
"معطى قائمة من الأرقام، أنشئ قاموساً يعيّن كل رقم إلى مربعه، ولكن فقط للأرقام الزوجية."
def create_square_dict(numbers):
return {n: n**2 for n in numbers if n % 2 == 0}
# اختبار
print(create_square_dict([1, 2, 3, 4, 5, 6]))
# الإخراج: {2: 4, 4: 16, 6: 36}
2. وحدة Collections
توفر وحدة collections هياكل بيانات قوية تحل مشاكل مقابلة ML الشائعة.
Counter - لعد التكرار:
from collections import Counter
# مهمة مقابلة شائعة: إيجاد العناصر الأكثر شيوعاً
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
counter = Counter(words)
print(counter.most_common(2))
# الإخراج: [('apple', 3), ('banana', 2)]
# مفيد لـ: تكرار الميزة، توزيع الفئة، عدد الكلمات
defaultdict - تجنب KeyError:
from collections import defaultdict
# سيئ: فحص المفتاح يدوياً
word_indices = {}
for idx, word in enumerate(['cat', 'dog', 'cat', 'bird']):
if word not in word_indices:
word_indices[word] = []
word_indices[word].append(idx)
# جيد: defaultdict
word_indices = defaultdict(list)
for idx, word in enumerate(['cat', 'dog', 'cat', 'bird']):
word_indices[word].append(idx)
# الإخراج: {'cat': [0, 2], 'dog': [1], 'bird': [3]}
deque - للنوافذ المنزلقة:
from collections import deque
def moving_average(values, window_size):
"""حساب المتوسط المتحرك باستخدام deque"""
window = deque(maxlen=window_size)
result = []
for val in values:
window.append(val)
result.append(sum(window) / len(window))
return result
# اختبار
print(moving_average([1, 2, 3, 4, 5], 3))
# الإخراج: [1.0, 1.5, 2.0, 3.0, 4.0]
3. عمليات بأسلوب NumPy
تتضمن العديد من أسئلة برمجة ML عمليات مصفوفة/مصفوفة. اعرف هذه الأنماط جيداً.
إنشاء المصفوفة:
import numpy as np
# الأصفار، الآحاد، الهوية
zeros = np.zeros((3, 3))
ones = np.ones((2, 4))
identity = np.eye(3)
# من القوائم
arr = np.array([[1, 2], [3, 4]])
# النطاقات
linear = np.arange(0, 10, 2) # [0, 2, 4, 6, 8]
linspace = np.linspace(0, 1, 5) # [0.0, 0.25, 0.5, 0.75, 1.0]
إعادة التشكيل والتبديل:
# إعادة التشكيل
arr = np.arange(12)
matrix = arr.reshape(3, 4) # مصفوفة 3x4
# التبديل
transposed = matrix.T # مصفوفة 4x3
# التسطيح
flat = matrix.flatten() # العودة إلى مصفوفة أحادية البعد
Broadcasting:
# إضافة عددي إلى مصفوفة (broadcasting)
matrix = np.array([[1, 2, 3], [4, 5, 6]])
result = matrix + 10
# [[11, 12, 13], [14, 15, 16]]
# تطبيع كل صف (شائع في ML)
row_means = matrix.mean(axis=1, keepdims=True)
normalized = matrix - row_means
الفهرسة والتقطيع:
# الفهرسة المنطقية (شائعة جداً في ML)
arr = np.array([1, 2, 3, 4, 5, 6])
even = arr[arr % 2 == 0] # [2, 4, 6]
# الفهرسة الفاخرة
indices = [0, 2, 4]
selected = arr[indices] # [1, 3, 5]
# تقطيع ثنائي الأبعاد
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
sub = matrix[:2, 1:] # أول صفين، الأعمدة 1 وما بعدها
# [[2, 3], [5, 6]]
سؤال المقابلة:
"طبّع كل عمود من مصفوفة ليكون متوسطه 0 وانحرافه المعياري 1 (تطبيع z-score)."
def normalize_columns(X):
"""
X: مصفوفة numpy بشكل (n_samples, n_features)
يُرجع: مصفوفة مطبّعة
"""
mean = X.mean(axis=0)
std = X.std(axis=0)
return (X - mean) / std
# اختبار
X = np.array([[1, 2], [3, 4], [5, 6]])
normalized = normalize_columns(X)
print(normalized.mean(axis=0)) # ~[0, 0]
print(normalized.std(axis=0)) # ~[1, 1]
4. أنماط التكرار الفعالة
enumerate() - الحصول على الفهرس والقيمة:
# سيئ
for i in range(len(items)):
print(i, items[i])
# جيد
for i, item in enumerate(items):
print(i, item)
# البدء من فهرس مخصص
for i, item in enumerate(items, start=1):
print(i, item)
zip() - تكرار قوائم متعددة:
names = ['Alice', 'Bob', 'Charlie']
scores = [85, 92, 78]
# إنشاء أزواج الاسم-النتيجة
for name, score in zip(names, scores):
print(f"{name}: {score}")
# إنشاء قاموس
name_to_score = dict(zip(names, scores))
itertools - تركيبات قوية:
from itertools import combinations, permutations, product
# جميع الأزواج من قائمة (شائع في حسابات التشابه)
items = [1, 2, 3]
pairs = list(combinations(items, 2))
# [(1, 2), (1, 3), (2, 3)]
# المنتج الديكارتي (grid search)
learning_rates = [0.01, 0.1]
batch_sizes = [32, 64]
configs = list(product(learning_rates, batch_sizes))
# [(0.01, 32), (0.01, 64), (0.1, 32), (0.1, 64)]
5. الوعي بالتعقيد الزمني والمكاني
قم دائماً بتحليل كفاءة كودك—سيسأل المحاورون.
أنماط التعقيد الشائعة:
| العملية | التعقيد الزمني | ملاحظة |
|---|---|---|
x in list |
O(n) | مسح خطي |
x in set |
O(1) متوسط | بحث تجزئة |
list.append() |
O(1) مطفأ | أحياناً O(n) عند إعادة الحجم |
list.insert(0, x) |
O(n) | يزيح جميع العناصر |
dict[key] |
O(1) متوسط | بحث تجزئة |
sorted(list) |
O(n log n) | خوارزمية Timsort |
list comprehension |
O(n) | لـ n عنصر |
مثال على التعقيد المكاني:
# O(1) مساحة - في المكان
def reverse_in_place(arr):
left, right = 0, len(arr) - 1
while left < right:
arr[left], arr[right] = arr[right], arr[left]
left += 1
right -= 1
# O(n) مساحة - ينشئ قائمة جديدة
def reverse_new_list(arr):
return arr[::-1] # ينشئ نسخة
6. المخاطر الشائعة لتجنبها
المخاطرة 1: الوسيطات الافتراضية القابلة للتغيير
# خطأ!
def add_sample(sample, samples=[]):
samples.append(sample)
return samples
# كل استدعاء يعدل نفس القائمة
print(add_sample(1)) # [1]
print(add_sample(2)) # [1, 2] - غير متوقع!
# الإصلاح
def add_sample(sample, samples=None):
if samples is None:
samples = []
samples.append(sample)
return samples
المخاطرة 2: نسخة ضحلة مقابل عميقة
import copy
# نسخة ضحلة - الكائنات المتداخلة مشتركة
original = [[1, 2], [3, 4]]
shallow = original.copy()
shallow[0][0] = 999
print(original) # [[999, 2], [3, 4]] - الأصل تغير!
# نسخة عميقة - مستقلة تماماً
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0][0] = 999
print(original) # [[1, 2], [3, 4]] - الأصل لم يتغير
المخاطرة 3: قسمة الأعداد الصحيحة في Python 3
# Python 3 يستخدم القسمة الحقيقية
result = 5 / 2 # 2.5 (float)
# للقسمة الصحيحة
result = 5 // 2 # 2 (int)
# شائع في ML: حساب فهارس الدفعة
batch_size = 32
total_samples = 100
num_batches = total_samples // batch_size # 3، وليس 3.125
المخاطرة 4: تعديل القائمة أثناء التكرار
# خطأ!
numbers = [1, 2, 3, 4, 5]
for i, n in enumerate(numbers):
if n % 2 == 0:
numbers.pop(i) # هذا يكسر التكرار!
# الإصلاح 1: التكرار إلى الوراء
for i in range(len(numbers) - 1, -1, -1):
if numbers[i] % 2 == 0:
numbers.pop(i)
# الإصلاح 2: List comprehension (الأفضل)
numbers = [n for n in numbers if n % 2 != 0]
أفضل ممارسات البرمجة في المقابلة
1. فكّر بصوت عالٍ
لا تكتب كوداً في صمت. اشرح نهجك:
- "سأستخدم قاموساً لتخزين عدد التكرار"
- "هذا سيكون تعقيد زمني O(n)"
- "دعني أتعامل مع الحالة الحدية حيث المصفوفة فارغة"
2. ابدأ بأمثلة
def function(arr):
"""
مثال:
الإدخال: [1, 2, 2, 3, 3, 3]
الإخراج: {1: 1, 2: 2, 3: 3}
النهج: استخدم Counter من collections
"""
# كودك
3. اكتب كوداً نظيفاً وقابلاً للقراءة
# سيئ: أسماء متغيرات غامضة
def f(x):
return sum([i*i for i in x if i%2==0])
# جيد: أسماء وصفية، منطق واضح
def sum_of_even_squares(numbers):
"""إرجاع مجموع مربعات الأرقام الزوجية"""
even_numbers = [n for n in numbers if n % 2 == 0]
squares = [n ** 2 for n in even_numbers]
return sum(squares)
4. اختبر كودك
اختبر دائماً مع:
- حالة عادية:
[1, 2, 3, 4] - حالات حدية:
[],[1] - قيم خاصة:
[0], أرقام سالبة
مشكلة ممارسة
المشكلة: طبّق دالة لإيجاد العناصر الأكثر تكراراً K في مصفوفة.
from collections import Counter
import heapq
def top_k_frequent(nums, k):
"""
معطى مصفوفة أعداد صحيحة nums وk، أرجع العناصر الأكثر تكراراً k.
مثال:
الإدخال: nums = [1,1,1,2,2,3], k = 2
الإخراج: [1, 2]
الوقت: O(n log k)
المساحة: O(n)
"""
# عد التكرارات
counts = Counter(nums)
# استخدم heap لإيجاد أعلى k
# Counter.most_common(k) يعمل أيضاً، لكن دعنا نستخدم نهج heap
return heapq.nlargest(k, counts.keys(), key=counts.get)
# اختبار
print(top_k_frequent([1,1,1,2,2,3], 2)) # [1, 2]
print(top_k_frequent([1], 1)) # [1]
النقاط الرئيسية
- أتقن list comprehensions - تظهر في 70% من أسئلة برمجة ML
- اعرف وحدة collections - Counter، defaultdict، deque هي المفضلة في المقابلة
- تمرن على عمليات NumPy - معالجة المصفوفة أساسية لـ ML
- حلل التعقيد - اذكر دائماً التعقيد الزمني والمكاني
- اكتب كوداً نظيفاً - الكود القابل للقراءة يظهر النضج الهندسي
- اختبر بدقة - امسك الأخطاء قبل المحاور
ما التالي؟
في الدرس التالي، سنركز بشكل خاص على أنماط معالجة المصفوفات التي تظهر بشكل متكرر في مقابلات ML.
:::