٥.١. المزيد عن القوائم #
يصف هذا الفصل بعض الأمور التي تعلمتها بالفعل بمزيد من التفصيل، ويضيف بعض الأمور الجديدة أيضًا، والبداية مع القوائم List.
يحتوي نوع بيانات القائمة على دوال إضافية. إليك جميع دوال كائنات القائمة:
list.append(x)
إضافة عنصر إلى نهاية القائمة. يكافئ a[len(a):] = [x].
list.extend(iterable)
توسيع القائمة بإضافة جميع العناصر من iterable. يكافئ a[len(a):] = iterable.
list.insert(i, x)
إدراج عنصر في موضع محدد. الوسيطة الأولى هي فهرس العنصر الذي يجب إدراجه قبله، لذا تُدرج a.insert(0, x) في بداية القائمة، وتُكافئ a.insert(len(a), x) a.append(x).
list.remove(x)
احذف أول عنصر من القائمة الذي تساوي قيمته x. يُثير هذا خطأ ValueError في حال عدم وجود هذا العنصر.
list.pop([i])
احذف العنصر من الموضع المحدد في القائمة، وأرجعه. في حال عدم تحديد أي فهرس، تحذف a.pop() العنصر الأخير في القائمة وتُرجعه. (الأقواس المربعة حول i في توقيع الدالة تشير إلى أن المعامل اختياري، وليس أنه يجب عليك كتابة الأقواس المربعة في هذا الموضع. سترى هذا الترميز كثيرًا في مرجع مكتبة بايثون.)
list.clear()
احذف جميع العناصر من القائمة. يُعادل del a[:].
list.index(x[, start[, end]])
ارجع فهرسًا صفريًا في قائمة أول عنصر تساوي قيمته x. يُثير هذا خطأ ValueError في حال عدم وجود هذا العنصر.
تُفسَّر الوسيطتان الاختياريتان start وend كما في تدوين الشريحة، وتُستخدمان لحصر البحث في تسلسل فرعي مُحدد من القائمة. يُحسب الفهرس المُعاد نسبةً إلى بداية التسلسل الكامل بدلاً من وسيطة البداية.
list.count(x)
تُرجع عدد مرات ظهور x في القائمة.
list.sort(*, key=None, reverse=False)
تُرتِّب عناصر القائمة في أماكنها (يمكن استخدام الوسيطتين لتخصيص الفرز، راجع sorted() لشرحها).
list.reverse()
تُعكس عناصر القائمة في أماكنها.
list.copy()
تُرجع نسخة سطحية من القائمة. تُعادل a[:].
مثال يستخدم معظم طرق القائمة:
>>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
>>> fruits.count('apple')
2
>>> fruits.count('tangerine')
0
>>> fruits.index('banana')
3
>>> fruits.index('banana', 4) # Find next banana starting a position 4
6
>>> fruits.reverse()
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
>>> fruits.append('grape')
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
>>> fruits.sort()
>>> fruits
['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
>>> fruits.pop()
'pear'
ربما لاحظتَ أن دوال مثل insert وremove وsort التي تُعدّل القائمة فقط لا تُطبع أي قيمة إرجاع – بل تُرجع القيمة الافتراضية None. 1 هذا مبدأ تصميم لجميع هياكل البيانات القابلة للتغيير في بايثون.
شيء آخر قد تلاحظه هو أنه لا يُمكن فرز أو مقارنة جميع البيانات. على سبيل المثال، [None, ‘hello’, 10] لا تُفرز لأنه لا يُمكن مقارنة الأعداد الصحيحة بالسلاسل النصية، ولا يُمكن مقارنة None بأنواع أخرى. أيضًا، هناك بعض الأنواع التي لا تحتوي على علاقة ترتيب مُحددة. على سبيل المثال، 3+4j < 5+7j ليست مقارنة صحيحة.
5.1.1. استخدام القوائم كمكدسات #
تُسهّل دوال القائمة استخدام القائمة كمكدس، حيث يكون آخر عنصر مُضاف هو أول عنصر يتم استرجاعه (“آخر ما يدخل، أول ما يخرج”). لإضافة عنصر إلى أعلى المكدس، استخدم الدالة append(). لاسترجاع عنصر من أعلى المكدس، استخدم الدالة pop() بدون فهرس صريح. على سبيل المثال:
>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6
>>> stack.pop()
5
>>> stack
[3, 4]
5.1.2. استخدام القوائم كقوائم انتظار #
من الممكن أيضًا استخدام قائمة كقوائم انتظار، حيث يكون أول عنصر مُضاف هو أول عنصر يُسترجع (“الأول في الداخل، الأول في الخارج”)؛ إلا أن القوائم ليست فعّالة لهذا الغرض. فبينما تكون عمليات الإضافة والإخراج من نهاية القائمة سريعة، فإن عمليات الإدراج أو الإخراج من بدايتها بطيئة (لأن جميع العناصر الأخرى يجب أن تُزاح بعنصر واحد).
لتنفيذ قائمة انتظار، استخدم collections.deque المُصممة لسرعة الإضافة والإخراج من كلا الطرفين. على سبيل المثال:
>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry") # Terry arrives
>>> queue.append("Graham") # Graham arrives
>>> queue.popleft() # The first to arrive now leaves
'Eric'
>>> queue.popleft() # The second to arrive now leaves
'John'
>>> queue # Remaining queue in order of arrival
deque(['Michael', 'Terry', 'Graham'])
5.1.3. فهم القوائم #
يوفر فهم القوائم طريقة موجزة لإنشاء القوائم. التطبيقات الشائعة هي إنشاء قوائم جديدة حيث يكون كل عنصر هو نتيجة لبعض العمليات المطبقة على كل عضو في تسلسل أو عنصر قابل للتكرار آخر، أو إنشاء تسلسل فرعي من تلك العناصر التي تستوفي شرط معين.
على سبيل المثال، لنفترض أننا نريد إنشاء قائمة مربعات، كما يلي:
>>> squares = []
>>> for x in range(10):
... squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
لاحظ أن هذا يُنشئ (أو يُستبدل) متغيرًا باسم x، والذي سيظل موجودًا بعد انتهاء الحلقة. يمكننا حساب قائمة المربعات دون أي آثار جانبية باستخدام:
squares = list(map(lambda x: x**2, range(10)))
أو، على نحو مكافئ:
squares = [x**2 for x in range(10)]
وهو أكثر إيجازًا وسهولة في القراءة.
يتكون فهم القائمة من أقواس تحتوي على تعبير متبوع بجملة for، ثم صفر أو أكثر من جمل for أو if. ستكون النتيجة قائمة جديدة ناتجة عن تقييم التعبير في سياق جملتي for وif اللتين تتبعانه. على سبيل المثال، تجمع هذه القائمة عناصر قائمتين إذا لم تكونا متساويتين:
>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
وهو يعادل:
>>> combs = []
>>> for x in [1,2,3]:
... for y in [3,1,4]:
... if x != y:
... combs.append((x, y))
...
>>> combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
لاحظ كيف يكون ترتيب جملتا for وif متطابقتان في كلا المقطعين.
إذا كان التعبير عبارة عن مجموعة (مثل (x, y) في المثال السابق)، فيجب وضعه بين قوسين.
>>> vec = [-4, -2, 0, 2, 4] >>> # create a new list with the values doubled >>> [x*2 for x in vec] [-8, -4, 0, 4, 8] >>> # filter the list to exclude negative numbers >>> [x for x in vec if x >= 0] [0, 2, 4] >>> # apply a function to all the elements >>> [abs(x) for x in vec] [4, 2, 0, 2, 4] >>> # call a method on each element >>> freshfruit = [' banana', ' loganberry ', 'passion fruit '] >>> [weapon.strip() for weapon in freshfruit] ['banana', 'loganberry', 'passion fruit'] >>> # create a list of 2-tuples like (number, square) >>> [(x, x**2) for x in range(6)] [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)] >>> # the tuple must be parenthesized, otherwise an error is raised >>> [x, x**2 for x in range(6)] File "<stdin>", line 1, in <module> [x, x**2 for x in range(6)] ^ SyntaxError: invalid syntax >>> # flatten a list using a listcomp with two 'for' >>> vec = [[1,2,3], [4,5,6], [7,8,9]] >>> [num for elem in vec for num in elem] [1, 2, 3, 4, 5, 6, 7, 8, 9]
يمكن أن تحتوي فهم القوائم على تعبيرات معقدة ودوال متداخلة:
>>> from math import pi >>> [str(round(pi, i)) for i in range(1, 6)] ['3.1', '3.14', '3.142', '3.1416', '3.14159']
5.1.4. فهم القوائم المتداخلة #
يمكن أن يكون التعبير الابتدائي في فهم القوائم أي تعبير عشوائي، بما في ذلك فهم قوائم آخر.
لننظر إلى المثال التالي لمصفوفة 3X4 مُنفَّذة كقائمة من 3 قوائم بطول 4:
>>> matrix = [
... [1, 2, 3, 4],
... [5, 6, 7, 8],
... [9, 10, 11, 12],
... ]
سيُنقِل فهم القائمة التالي الصفوف والأعمدة:
>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
كما رأينا في القسم السابق، يتم تقييم listcomp المتداخلة في سياق for الذي يليها، لذا فإن هذا المثال يُعادل:
>>> transposed = []
>>> for i in range(4):
... transposed.append([row[i] for row in matrix])
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
والذي بدوره يُشابه:
>>> transposed = []
>>> for i in range(4):
... # the following 3 lines implement the nested listcomp
... transposed_row = []
... for row in matrix:
... transposed_row.append(row[i])
... transposed.append(transposed_row)
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
في الواقع، يُفضّل استخدام الدوال المُدمجة على عبارات التدفق المُعقدة. ستكون دالة zip() مفيدة جدًا في حالة الاستخدام هذه:
>>> list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]
راجع “تفريغ قوائم الوسيطات” لمزيد من التفاصيل حول علامة النجمة في هذا السطر.