(10, 20, 30, 40, 50, 10, 20, 30, 40, 50)
6 المجموعة المرتبة
كثيرًا ما نحتاج للتعامل مع الأشياء في مجموعة. وذلك مثلاً لترتيب المجموعة أو عكسها أو ربطها مع مجموعة أخرى، أو البحث فيها، أو تصفيتها، أو تحويلها جميعًا بنفس الطريقة، أو استخلاص قيمة منها، …إلخ من العمليات التي تعمل على جميع عناصر المجموعة.
وكل ما هو من جنس المجموعة (Collection
) فإنه يقبل الأفعال التالية:
- العضوية:
x not in s
- العد:
len(s)
- التكرار:
for x in s
انظر Collection في خريطة المجموعات: شكل 1.
6.1 التسلسل
التسلسل (Sequence
) هو أي مجموعة مرتبة من الأشياء.
- مجموعة: يعني قبوله الأفعال الثلاثة السابق ذكرها.
- مرتبة: يعني أن لكل عنصر موضعًا فيها، وله ما قبله وما بعده.
وسوف نرمز للمفرد بـx
ولما يدل على التسلسل بـs
.
والأنواع الأربعة التي من جنس التسلسل هي:
- القائمة (
list
) ويُعبَّرُ عنه بالقوسين المربعين[]
. - الصف (
tuple
) ويُعبَّرُ عنه بالقوسين المنحنيين()
. - المجال (
range
) ويُعبَّرُ عنه بالفعل المنشئrange()
. - النص (
str
) ويُعبَّرُ عنه بالتنصيص المفرد''
أو المزدوج""
.
فهذه الأربعة تقبل الأفعال التالية:
- الإشارة:
- بالموضع:
s[i]
- بالقطعة:
s[i:j]
- بالقطعة مع خطوة:
s[i:j:k]
- بالموضع:
- معرفة موضع شيء (إن وجد):
s.index(x)
- عد تكرارات شيء:
s.count(x)
- البحث عن الأصغر والأكبر:
min(s)
وmax(s)
وتقبل من أفعال الإنشاء:
- الدمج:
s1 + s2
- التكرار:
s * n
أما تخصيص حرف +
للدمج (لا للجمع) ، وحرف *
للتكرار (لا للضرب)؛ فسيأتي معنا -إن شاء الله- في فصل تعريف الأفعال المخصوصة في باب الأنواع.
والأنواع على قسمين من حيث قبول التغير بعد الإنشاء:
- متغير (Mutable): يعني قبوله الإضافة والحذف والتعديل على عناصرها.
- جامد (Immutable): لا يقبل التغير
ومن جهة كونها عوامل للفعل؛ فإن الجامد لا يقبل أن يكون مفعولاً.
الإنشاء
- تنشأ القائمة بوضع العناصر بين القوسين المربعين
[]
أو باستعمال الفعل المنشئlist()
، وهي تسلسل متغير. - ينشأ الصف بوضع العناصر بين القوسين المنحنيين
()
أو باستعمال الفعل المنشئtuple()
، وهو تسلسل جامد.
ويقبل إنشاء مجموعة من العناصر مختلفة النوع، بما في ذلك القائمة والصف كعنصر:
نستعرض هنا العضوية والعد والتكرار
الإشارة
تستعمل الإشارة الموضعية لقراءة عنصر من التسلسل. ويجب أن يكون المؤشر رقمًا صحيحًا لا يتجاوز نطاق التسلسل على النحو التالي:
0 1 2 3 4 5
+----+----+----+----+----+
| 10 | 20 | 30 | 40 | 50 |
+----+----+----+----+----+
-5 -4 -3 -2 -1
شكل الإشارة بالقطعة على نحو: s[start : end : step]
. والقيم الابتدائية عند الإغفال هي: s[0:len(s):1]
.
لاحظت استعمال الفعل المنشئ slice()
في الإشارة بالقطعة، وقد جعلت بايثون علامة :
بديلاً عنه.
وجاز للعنصر الواحد أن يكون مجموعة؛ ومثاله المصفوفة (صفٌّ من صفوف):
0 1 2
+--------------+--------------+--------------+
| (10, 20, 30) | (40, 50, 60) | (70, 80, 90) |
+--------------+--------------+--------------+
-3 -2 -1
الإشارة لعناصر الصف الواحد:
0 1 2
+----+----+----+
| 10 | 20 | 30 |
+----+----+----+
-3 -2 -1
0 1 2
+----+----+----+
| 40 | 50 | 60 |
+----+----+----+
-3 -2 -1
0 1 2
+----+----+----+
| 70 | 80 | 90 |
+----+----+----+
-3 -2 -1
عناصر نصوص:
0 1 2 3 4
+-------+--------+--------+-------+
| Apple | Banana | Orange | Lemon |
+-------+--------+--------+-------+
-4 -3 -2 -1
الإشارة لصف الأحرف في النص الواحد:
0 1 2 3 4
+---+---+---+---+---+
| L | e | m | o | n |
+---+---+---+---+---+
-5 -4 -3 -2 -1
وسيأتي التفصيل في باب النص.
البحث
6.2 القائمة
القائمة (list
) تسلسل متغير. وهذا يعني:
- مُتَغَيِّرَة: يعني أنها تقبل الإضافة والحذف والتعديل على عناصرها
- مُرَتَّبَة: يعني أن العناصر مرقَّمة بالتسلسل هكذا:
[0, 1, 2, ...]
- ويترتب عليه قبولها الإشارة بالموضع
i
أو بالقطعة[i:j]
أو بالقطعة بالخطوة[i:j:k]
- ويترتب عليه قبولها الإشارة بالموضع
انظر MutableSequence في خريطة المجموعات: شكل 1.
التغير
التغير هي الخاصية التي تختلف فيها القائمة عن قسيماتها التسلسلية. ومعناه قبولها الأفعال التالية (نستعمل في المثال حرف l
للقائمة):
- الاستبدال:
- لموضع:
l[i] = x
- لقطعة:
l[i:j] = t
- لقطعة بخطوة:
l[i:j:k] = t
- لموضع:
- الحذف:
- لموضع:
del l[i]
- لقطعة:
del l[i:j]
- لقطعة بخطوة:
del l[i:j:k]
- لموضع:
- الإزالة:
l.remove(x)
لحذف أول ورود للعنصر - النزع:
l.pop([i])
حذف العنصر من الموضع مع إرجاعه- إن لم يحدد الموضع: نزع الأخير. إذ القوسان
[i]
هنا في التعريف يعبران عن عامل اختياري وهو الموضعi
- إن لم يحدد الموضع: نزع الأخير. إذ القوسان
- الإدراج:
l.insert(i, x)
لإضافة عنصر في موضع محدد - الإلحاق:
l.append(x)
لإضافة عنصر في النهاية - الترتيب:
l.sort()
أو بالفعل المبنيsorted(l)
- العكس:
l.reverse()
أو بالفعل المبنيreversed(l)
لاحظ رسالة الخطأ عند محاولة التعديل على الصف، الذي نعرفه بالقوسين المنحنيين ()
، إذْ هو جامد لا يقبل التغير:
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[10], line 2 1 t = (10, 20, 30, 40, 50) ----> 2 t[0] = 100 3 print(t) TypeError: 'tuple' object does not support item assignment
لكن هذا مقبول في القائمة، التي نعرفها بالقوسين المربعين []
، لأنها متغيرة:
الاستبدال بالموضع والحذف منه:
الاستبدال بالقطعة والحذف منها
الإدراج:
الإزالة:
الإلحاق:
الترتيب والعكس:
نزع العنصر الأخير وإرجاعه:
6.3 النطاق
يمثل النطاق (range
) مولِّدًا لسلسلة أعداد في نطاق محدد ببداية ونهاية، وبين كل عدد والذي يليه مسافة محددة. فثلاثة عوامل تحدده:
- البداية (
start=0
):- مشمولة
- (إن لم تعيَّن) وهي صفر بالابتداء
- النهاية (
stop
):- غير مشمولة
- وهي واجبة (إهمالها ممتنع)
- الخطوة (
step=1
):- مقدار الزيادة أو النقص للعدد في كل كرة
- (إن لم تعيَّن) وهي واحد بالابتداء
دعونا الآن نلقي نظرة على التعريف كما هو موجود في وثائق بايثون، وذلك لنتعلم كيف نقرؤ التعريف. ادخل الرابط وتأمل معي ..
class range(stop)
class range(start, stop[, step])
أولا: تدل كلمة class
على أنها معرَّفة كنوع، فيكون طلب الفعل بنفس الاسم range
للإنشاء.
ثانيًا: نلاحظ أن لدينا تعريفان؛ وهما مختلفان، فأيهما يكون؟
نجيب عن ذلك فنقول: التعريف الأوَّل يُعمل به إذا حددنا عاملاً واحدًا؛ فيكون العامل هو stop
وتأخذ البداية والخطوة قيمتهما الابتدائية: start=0
و step=1
حسب ما كُتب:
If the step argument is omitted, it defaults to 1.
If the start argument is omitted, it defaults to 0
أما التعريف الثاني فيجب تفكيكه لنفهمه: class range(start, stop[, step])
.
وجود الأقواس المربعة [ ]
يعني أجزاءً اختياريَّة. فإذًا؛ الجزء الإلزامي هو start, stop
؛ فإن عينَّا قيمتين، فتكون الأولى البداية، والثانية النهاية، وتبقى الخطوة على قيمتها الابتدائية step=1
.
أما إذا عينت الثلاثة جميعًا فسيكون الأول start
والثاني stop
والثالث step
:
ولك أن تعكس النطاق بتعيين step
بقيمة سالبة، لكن يجب حينها أن تجعل البداية أعلى من النهاية:
التكرار والإشارة
وتُسرَد المتسلسلة بكلمة for
، نحو:
أو بسرد نطاقٍ واستعمال الإشارة بالموضع، نحو:
فهذا يفيد في التحكم في السرد، فلو أردنا كل عنصرٍ ثانٍ، نجعل الخطوة 2
ابتداء من العنصر الثاني 1
، فنكتبها هكذا:
أو أردنا قراءة الموضع والذي قبله، فهكذا:
فإن جوَّزنا التداخل، جعلنا الخطوة 1
، هكذا:
وهلم جرا..
تأجيل النتيجة
ويجدر بالذكر أن النطاق لا يولد عناصره التي في النطاق فعليًّا؛ بل يحسبها عند الحاجة إليها. فهو بذلك لا يشغل حيِّزًا في الذاكرة إلا لحدوده الثلاثة والرقم المطلوب حالًا. وهو كالصف لا يقبل التعديل.
نستعمل فعل الإنشاء range()
لإنشاء نطاق:
فحين نسألن عن عضوية عنصر ما في النطاق؛ يتم حساب النطاق بحسبه:
كذلك الفعل عند البحث عن موضع رقمٍ ما:
والإشارة لموضع ما أو قطعة كذلك:
تحقيق النطاق
المولِّد لا تتحقق عناصره إلا عند الحاجة إليها؛ أي: عند قراءتها. فإذا جعلناه فاعلاً في جملة الإنشاء list
؛ تولَّدَت جميع عناصره ووُضِعَت في قائمة:
6.4 التسلسلات المرتبطة
هذان تسلسلان مرتبطان:
لو أردنا أن نمر على التسلسلين في نفس الوقت نستعمل الفعل المبني zip()
الذي يظل يولد صفًا عناصره من كل تسلسل:
[('Ahmad', 90), ('Belal', 80), ('Camal', 75), ('Dawud', 85), ('Emad', 95)]
وحاجتنا للفعل المنشئ list()
بسبب أن zip
مولِّدٌ مثل range
لا يحسب العناصر إلا عند قراءتها. والإنشاء يقرأ جميعها لتظهر.
وعند سياقها في جملة التكرار فإنها تولد زوجًا في كل كرة، إذ هي متوالية (Iterable):
ولو كان لدينا ثلاثة تسلسلات فإنها تولد ثلاثة عناصر في كل كرة:
students = ['Ahmad', 'Belal', 'Camal', 'Dawud', 'Emad']
marks = range(75, 95+1, 5)
classes = ('A-1', 'A-1', 'A-2', 'A-1', 'A-2')
assert list == type(students)
assert tuple == type(classes)
assert range == type(marks)
for x, y, z in zip(students, marks, classes):
print(x, y, z)
Ahmad 75 A-1
Belal 80 A-1
Camal 85 A-2
Dawud 90 A-1
Emad 95 A-2
ولاحظ أن نوع students
قائمة (list
) ونوع marks
نطاق (range
)، ونوع classes
صف (tuple
)، لكن الفعل zip
يقبل متسلسلات من أي نوع. بل هو في الحقيقة يقبل أي متوالية (Iterable)؛ والتسلسل متوالية (Sequence -> Iterable
).
فتلك الطريقة البايثونية. انظر التوثيق للمزيد عن zip()
.
ويكون قراءة التسلسلات المرتبطة أيضًا بسرد متوالية النطاق، والإشارة إلى كل عنصر بالموضع:
6.5 الإنشاء المختصر: الجملة الثلاثية
مما تميزت به لغة بايثون عن غيرها: مختصرة الإنشاء (Comprehension)؛ وهي جملة تُنشئ مجموعة مستمَدَّة من متوالية في ثلاث جُمَل في سطرٍ واحدٍ -غالبًا- وهدفها: إنشاء مجموعة مستمَدَّة من متوالية.
وليسَت زيادتها في اللغة من باب الضرورة وإنما من باب التحسين. إذْ فيها قوة في التعبير عن جمل كثيرة في مساحة صغيرة. فهذا المثال يعبر عن إنشاء قائمة كل عنصرٍ فيها مربَّعٌ من المتوالية range(10)
في سطرٍ واحد:
فهي جملة إنشاء مركَّبة من ثلاث جمل:
- تعبير (
x ** 2
) ، الذي يشتمل غالبًا على متغير التكرار (x
) - تكرار: (
for x in range(10)
) - وشرط: والشرطُ ليسَ بشرط؛ فلم يظهر في هذا المثال
فهي مكافئة للقطعة التالية:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
ولو أردنا ترشيح الأعداد الزوجية من قائمة، نستطيع استعمال جملة الشرط في الاختصار على النحو التالي:
[0, 2, 4, 6, 8]
- التعبير: (
x
) فقط - التكرار: (
for x in numbers
)، وتذكر أن القائمة متوالية - الشرط: (
if x % 2 == 0
)
وهي مكافئة للقطعة التالية:
وأما القوسان المربعان [ ]
-في كلا المثالين- فلإنشاء قائمة. وبحسب ما يُراد إنشاؤه تختلف الأقواس:
[expression for item in iterable if condition]
للقائمة (list
)(expression for item in iterable if condition)
للمولِّد (Generator){expression for item in iterable if condition}
لمجموعة الفرائد (set
) وسيأتي الكلام عنها في الباب القادم{expression: expression for item in iterable if condition}
للقاموس (dict
) وسيأتي الكلام عنه في الباب القادم
وقد يكون التعبير غير مشتمل على متغير التكرار، نحو:
فقد أنشأنا بالقوسين الدائريين ( )
مولِّدًا يُنتج 1
لكل عنصر زوجي، بعدد العناصر الزوجية في المتوالية numbers
. ثم نحقق وجوده بالقراءة، فنقول مثلاً: sum
لجمعها كلها:
أو نضعها في جملة واحدة: