القوائم المتقدمة والصفوف

في الدرس السابق، تعلمنا عمليات القوائم الأساسية. الآن دعنا نستكشف بعض المفاهيم الأكثر "بايثونية" — روعة القوائم الشاملة (سطر واحد يفعل عمل خمسة)، و الصفوف ("القائمة غير القابلة للتعديل") ومتى تستخدمها. بعد هذا، سيبدو كودك أكثر فأكثر كمبرمج بايثون حقيقي.


1. الترتيب المتقدم للقوائم

في الدرس 10، تعلمنا أساسيات sort() و sorted(). كما يقبلان معامل key لتحديد "أي قاعدة للترتيب."

PYTHON
# الترتيب حسب طول السلسلة النصية
words = ["Python", "Java", "JavaScript", "C", "Go"]
sorted_by_len = sorted(words, key=len)
print(sorted_by_len)          # ['C', 'Go', 'Java', 'Python', 'JavaScript']

# الترتيب حسب آخر حرف
words = ["banana", "apple", "cherry", "date"]
sorted_by_last = sorted(words, key=lambda w: w[-1])
print(sorted_by_last)         # ['banana', 'date', 'apple', 'cherry']

# الترتيب حسب عنصر معين في صف
scores = [("Zhang San", 85), ("Li Si", 92), ("Wang Wu", 78)]
sorted_scores = sorted(scores, key=lambda s: s[1], reverse=True)
print(sorted_scores)          # [('Li Si', 92), ('Zhang San', 85), ('Wang Wu', 78)]
💡 معامل key مفيد جداً — يمكنك تمرير أي دالة تأخذ وسيطة واحدة وتُرجع قيمة. sorted() ستستخدم هذه القيمة المعادة للترتيب. lambda أعلاه هي دالة مجهولة، سنغطيها بالتفصيل لاحقاً.

مثال: إنشاء لوحة صدارة (الصعوبة ⭐⭐)

PYTHON
# لوحة صدارة اللعبة — ترتيب حسب النقاط تنازلياً
players = [
    ("Player 1", 3500),
    ("Player 2", 5200),
    ("Player 3", 2800),
    ("Player 4", 4100),
]

# ترتيب حسب النقاط تنازلياً
ranked = sorted(players, key=lambda p: p[1], reverse=True)

print("=== Leaderboard ===")
for i, (name, score) in enumerate(ranked, 1):
    print(f"#{i}: {name} — {score} points")
▶ جرّب الكود

المخرجات:

TEXT
=== Leaderboard ===
#1: Player 2 — 5200 points
#2: Player 4 — 4100 points
#3: Player 1 — 3500 points
#4: Player 3 — 2800 points

2. القوائم الشاملة

القوائم الشاملة هي واحدة من أكثر ميزات بايثون المحبوبة — إنشاء قائمة جديدة بسطر واحد من الكود.

الصيغة الأساسية

PYTHON
[expression for variable in iterable]

الطريقة التقليدية مقابل الشاملة

PYTHON
# إنشاء مربعات الأرقام 0~9 — الطريقة التقليدية
squares = []
for i in range(10):
    squares.append(i ** 2)
print(squares)                # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# باستخدام القائمة الشاملة — سطر واحد
squares = [i ** 2 for i in range(10)]
print(squares)                # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

القائمة الشاملة مع الشروط

PYTHON
# فلترة الأرقام الزوجية
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = [n for n in numbers if n % 2 == 0]
print(evens)                  # [2, 4, 6, 8, 10]

# تحويل السلاسل النصية إلى أحرف كبيرة
words = ["hello", "world", "python"]
upper_words = [w.upper() for w in words]
print(upper_words)            # ['HELLO', 'WORLD', 'PYTHON']

# فلترة الكلمات بطول >= 5
words = ["cat", "elephant", "dog", "giraffe", "ant"]
long_words = [w for w in words if len(w) >= 5]
print(long_words)             # ['elephant', 'giraffe']
💡 القوائم الشاملة موجزة، لكن لا تفرط في استخدامها. إذا كان المنطق معقداً (مثل حلقات متداخلة أو شروط متعددة)، حلقة for العادية أكثر قابلية للقراءة. الإيجاز لا يعني قابلية القراءة.

مثال: معالجة البيانات (الصعوبة ⭐⭐)

PYTHON
# البيانات الخام — قد تحتوي على قيم فارغة ومسافات إضافية
raw_data = ["  Zhang San  ", "", "  Li Si  ", "  Wang Wu  ", "", "  Zhao Liu  "]

# تنظيف بخطوة واحدة باستخدام الشاملة: strip + فلترة الفارغ
cleaned = [name.strip() for name in raw_data if name.strip()]
print(cleaned)                # ['Zhang San', 'Li Si', 'Wang Wu', 'Zhao Liu']

# معالجة رقمية
temps_c = [0, 10, 20, 30, 40]
temps_f = [c * 9 / 5 + 32 for c in temps_c]
print(temps_f)                # [32.0, 50.0, 68.0, 86.0, 104.0]
▶ جرّب الكود

3. ما هو الصف (Tuple)

يُعرّف الصف بـ الأقواس (). إنه مشابه للقوائم، لكن مع فرق رئيسي — الصفوف غير قابلة للتعديل؛ بمجرد إنشائها، لا يمكن تعديلها.

PYTHON
# تعريف الصفوف
point = (3, 5)
color = (255, 0, 0)
empty = ()
single = (42,)          # ملاحظة: الصفوف ذات العنصر الواحد يجب أن تحتوي على فاصلة!

print(point)            # (3, 5)
print(type(point))      # <class 'tuple'>

الصفوف غير قابلة للتعديل

PYTHON
point = (3, 5)
# point[0] = 10   ← خطأ! لا يمكن تعديل الصفوف
print(point[0])         # 3 — يمكن القراءة، لا يمكن التعديل

لماذا نحتاج الصفوف

عدم القابلية للتعديل يجلب ميزتين:

1. يمكن استخدامها كمفاتيح قاموس (القوائم لا يمكن، لأنها قابلة للتعديل)

PYTHON
# الصفوف يمكن أن تكون مفاتيح قاموس
locations = {
    (35.68, 139.76): "Tokyo",
    (31.23, 121.47): "Shanghai",
}
print(locations[(35.68, 139.76)])   # Tokyo

# القوائم لا يمكن أن تكون مفاتيح
# d = {[1, 2]: "error"}    ← سيُسبب خطأ!

2. تمثل بيانات لا ينبغي تعديلها

PYTHON
# الإحداثيات، الألوان، الإعدادات — البيانات "غير المتغيرة" مناسبة للصفوف
RGB_RED = (255, 0, 0)        # قيمة الأحمر لا ينبغي تعديلها
DEFAULT_CONFIG = ("localhost", 8080, "admin")
💡 متى تستخدم الصفوف مقابل القوائم؟ قاعدة بسيطة: استخدم القوائم إذا كانت البيانات تحتاج تعديلاً، استخدم الصفوف إذا لم تكن تحتاج. الدوال التي تُرجع قيماً متعددة تستخدم عادة الصفوف.


4. تفكيك الصفوف

واحدة من أكثر ميزات الصفوف فائدة — التفكيك، تعيين عناصر الصف مباشرة إلى متغيرات متعددة:

PYTHON
point = (3, 5)
x, y = point               # تفكيك
print(x)                   # 3
print(y)                   # 5

# تبديل المتغيرات — أساساً تفكيك صف
a, b = 10, 20
a, b = b, a                # الجانب الأيمن يُنشئ صفاً (20, 10)، ثم يُفكّك
print(a, b)                # 20 10

# دالة تُرجع قيماً متعددة
def get_min_max(numbers):
    return min(numbers), max(numbers)

result = get_min_max([3, 1, 4, 1, 5])
print(result)              # (1, 5)

low, high = get_min_max([3, 1, 4, 1, 5])
print(f"Min: {low}, Max: {high}")   # Min: 1, Max: 5

استخدام _ لتجاهل القيم غير المرغوب فيها

PYTHON
data = ("Zhang San", 25, "Beijing", "Engineer")
name, _, city, _ = data     # استخدم _ لتعني "لا يهمني هذا"
print(f"{name} lives in {city}")  # Zhang San lives in Beijing

مثال: حساب الإحداثيات (الصعوبة ⭐⭐)

PYTHON
# عرّف نقطتين
p1 = (1, 2)
p2 = (4, 6)

# فكّك لحساب المسافة
x1, y1 = p1
x2, y2 = p2

distance = ((x2 - x1)  2 + (y2 - y1)  2) ** 0.5
print(f"Distance: {distance:.2f}")     # 5.00

# استخدام enumerate للتكرار المفهرس
fruits = ["apple", "banana", "orange"]
for i, fruit in enumerate(fruits, 1):
    print(f"{i}. {fruit}")
▶ جرّب الكود

المخرجات:

TEXT
Distance: 5.00
1. apple
2. banana
3. orange

5. دليل اختيار القائمة مقابل الصف

الميزة القائمة الصف
قابل للتعديل ✅ يمكن إضافة/حذف/تغيير ❌ غير قابل للتعديل
السرعة أبطأ قليلاً أسرع قليلاً
الذاكرة أكبر قليلاً أصغر قليلاً
يمكن أن يكون مفتاح قاموس
حالة الاستخدام بيانات تُعدّل frequently بيانات ثابتة غير متغيرة
الاستخدامات النموذجية عربة تسوق، قائمة مهام، بيانات ديناميكية إحداثيات، ألوان، قيم إرجاع الدوال
💡 عادة عملية: إذا كنت غير متأكد أيهما تستخدم، ابدأ بقائمة. القوائم أكثر مرونة في معظم السيناريوهات. عندما تجد أن بعض البيانات لا ينبغي تعديلها حقاً، انتقل إلى صف.


حالات الاستخدام الشائعة


❓ أسئلة شائعة

س أيهما أسرع — القوائم الشاملة أو حلقات for العادية؟
ج القوائم الشاملة عادة أسرع لأنها تنفذ الحلقة على مستوى C بدلاً من مستوى بايثون. ومع ذلك، الفرق ملحوظ فقط مع البيانات الكبيرة (عشرات الآلاف من العناصر). في البرمجة اليومية، أعط الأولوية لقابلية القراءة أولاً. س:لماذا يجب أن يحتوي الصف ذو العنصر الواحد على فاصلة؟ ما الفرق بين (42) و (42,)؟ ج:الأقواس في (42) تُفسر كتجميع رياضي، وليس صفاً — type((42)) يُرجع <class 'int'>. (42,) هو صف. لذا الصفوف ذات العنصر الواحد يجب أن تتضمن فاصلة: (42,). س:كيف يعمل معامل key في sorted()؟ أعط مثالاً عملياً. ج:السيناريو الأكثر شيوعاً هو الترتيب حسب سمة معينة. مثلاً، الترتيب حسب طول السلسلة: sorted(words, key=len). الترتيب حسب العنصر الثاني من الصفوف في قائمة: sorted(data, key=lambda x: x[1]). الترتيب حسب قيم القاموس: sorted(dict.items(), key=lambda x: x[1]). المفتاح هو فهم أن key يقبل دالة تُرسم كل عنصر إلى مفتاح ترتيب.

📖 ملخص

  • معامل key في sorted() يحدد قواعد الترتيب؛ مدمجاً مع lambda فهو مرن جداً
  • القائمة الشاملة [expression for variable in iterable if condition] تنشئ قائمة جديدة في سطر واحد
  • الصفوف تستخدم صيغة ()، غير قابلة للتعديل، ويمكن استخدامها كمفاتيح قاموس
  • الصفوف ذات العنصر الواحد يجب أن تحتوي على فاصلة: (42,) — وإلا فهي عدد صحيح
  • تفكيك الصف يُسند قيماً متعددة لمتغيرات متعددة؛ تبديل المتغيرات يستفيد من هذا
  • القوائم للبيانات القابلة للتعديل، الصفوف للبيانات الثابتة

📝 تمارين

  1. مبتدئ (الصعوبة ⭐): استخدم قائمة شاملة لإنشاء مربعات جميع الأرقام بين 1 و 20 القابلة للقسمة على 3. تلميح: [expression for n in range(...) if condition]

  2. متوسط (الصعوبة ⭐⭐): بالنظر إلى قائمة أسماء names = [" alice ", "BOB", " Charlie", " david "]، استخدم قائمة شاملة في خطوة واحدة: strip للمسافات + كتابة أول حرف كبير وباقي الأحرف صغيرة. تلميح: strip() للمسافات، title() أو capitalize() لحالة الأحرف.

  3. متقدم (الصعوبة ⭐⭐⭐): اكتب دالة analyze_numbers(*args) تقبل أي عدد من الوسائط الرقمية وتُرجع صفاً (max, min, average, count_above_average). تلميح: استخدم *args للوسائط متغيرة الطول؛ استخدم قائمة شاملة لحساب القيم فوق المتوسط.

100%