المزيد من السلاسل النصية والقوائم
أهداف التعلّم
بعد هذا القسم
- ستصبح على دراية بمزيد من طرق تقطيع السلاسل النصية والقوائم
- ستفهم ما يعنيه عدم قابلية السلاسل النصية للتغيير
- ستكون قادرًا على استخدام التابعين
countوreplace
لقد تعوّدت من قبل على صيغة [] للوصول إلى جزء من سلسلة نصية:
my_string = "exemplary"
print(my_string[3:7])
mpla
وتعمل الصيغة نفسها مع القوائم، إذ يمكن تقطيع القوائم تمامًا كما تُقطَّع السلاسل النصية:
my_list = [3,4,2,4,6,1,2,4,2]
print(my_list[3:7])
[4, 6, 1, 2]
مزيد من التقطيعات
في الواقع، تعمل صيغة [] بطريقة مشابهة جدًا للدالة range، ما يعني أننا يمكننا أيضًا أن نمنحها خطوة:
my_string = "exemplary"
print(my_string[0:7:2])
my_list = [1,2,3,4,5,6,7,8]
print(my_list[6:2:-1])
eepa
[7, 6, 5, 4]
وإذا أسقطنا أحد الفهرسين، فإن المشغّل يضمّ كل شيء افتراضيًا. وإلى جانب أمور أخرى، يتيح هذا كتابة برنامج قصير جدًا يعكس سلسلة نصية:
my_string = input("Please type in a string: ")
print(my_string[::-1])
Please type in a string: **exemplary**
yralpmexe
تحذير: استخدام المتغيّرات العامة داخل الدوال
نعلم أنه من الممكن إسناد متغيّرات جديدة داخل تعريفات الدوال، لكن الدالة تستطيع أيضًا أن ترى متغيّرات أُسندت خارجها، في الدالة الرئيسية. وتُسمّى هذه المتغيّرات متغيّرات عامة (global).
استخدام المتغيّرات العامة من داخل الدوال عادةً فكرة سيئة. وإلى جانب المشكلات الأخرى، قد يسبّب ذلك أخطاءً يصعب تتبّعها.
فيما يلي مثال على دالة تستخدم متغيّرًا عامًا «من باب الخطأ»:
def print_reversed(names: list):
# using the global variable instead of the parameter by accident
i = len(name_list) - 1
while i >= 0:
print(name_list[i])
i -= 1
# here the global variable is assigned
name_list = ["Steve", "Jean", "Katherine", "Paul"]
print_reversed(name_list)
print()
print_reversed(["Huey", "Dewey", "Louie"])
Paul
Katherine
Jean
Steve
Paul
Katherine
Jean
Steve
رغم أن استدعاءَي الدالة يمرّران الوسيط من النوع المناسب، فإن الدالة تطبع دائمًا ما هو مخزَّن في المتغيّر العام name_list.
ولزيادة الأمر تعقيدًا، تذكّر أن كل شيفرة لاختبار دوالك ينبغي أن توضع داخل كتلة if __name__ == "__main__": من أجل الاختبارات الآلية. وينبغي تعديل المثال السابق:
def print_reversed(names: list):
# using the global variable instead of the parameter by accident
i = len(name_list) - 1
while i>=0:
print(name_list[i])
i -= 1
# All the code for testing the function should be within this block
if __name__ == "__main__":
# here the global variable is assigned
name_list = ["Steve", "Jean", "Katherine", "Paul"]
print_reversed(name_list)
print()
print_reversed(["Huey", "Dewey", "Louie"])
لاحظ أن المتغيّر العام صار يُسنَد داخل كتلة if الآن.
تُنفَّذ الاختبارات الآلية في نظام TMC دون تشغيل أيٍّ من الشيفرة الموجودة في كتلة if. لذا، في هذا المثال الأخير لا تعمل الدالة حتى نظريًا، لأنها تشير إلى المتغيّر name_list الذي لا وجود له إطلاقًا عند تنفيذ الاختبارات.
تمرين برمجي: كل شيء معكوس
اكتب دالة اسمها
everything_reversed، تأخذ قائمة من السلاسل النصية كوسيط لها. وتُعيد الدالة قائمة جديدة تحوي جميع عناصر القائمة الأصلية معكوسة. كما ينبغي أن يكون ترتيب العناصر معكوسًا في القائمة الجديدة.
مثال على كيفية عمل الدالة:
my_list = ["Hi", "there", "example", "one more"]
new_list = everything_reversed(my_list)
print(new_list)
['erom eno', 'elpmaxe', 'ereht', 'iH']
السلاسل النصية غير قابلة للتغيير
تشترك السلاسل النصية والقوائم في الكثير، ولا سيما في طريقة سلوكها مع المشغّلات المختلفة. والفرق الرئيسي هو أن السلاسل النصية غير قابلة للتغيير (immutable). أي أنها لا يمكن تعديلها.
my_string = "exemplary"
my_string[0] = "a"
لا يمكن تغيير السلاسل النصية، لذا يسبّب تنفيذ هذا البرنامج خطأً:
TypeError: 'str' object does not support item assignment
ويتبعك خطأ مشابه إن حاولت ترتيب سلسلة نصية باستخدام تابع sort.
السلاسل النصية نفسها غير قابلة للتغيير، لكن المتغيّرات التي تحملها قابلة للتغيير. ويمكن استبدال سلسلة نصية بسلسلة نصية أخرى.
لذلك، يختلف المثالان التاليان جوهريًا:
my_list = [1,2,3]
my_list[0] = 10

my_string = "Hey"
my_string = my_string + "!"

يغيّر المثال الأول محتوى القائمة المُشار إليها. أما المثال الثاني فيستبدل المرجع إلى السلسلة النصية الأصلية بمرجع إلى سلسلة نصية أخرى. وما زالت السلسلة النصية الأصلية في مكان ما من ذاكرة الحاسوب، لكن لا يوجد أي مرجع إليها، ولم يعد من الممكن استخدامها في البرنامج.
سنعود إلى هذا الموضوع في الجزء التالي، حيث تُناقش المراجع إلى القوائم بمزيد من التفصيل.
مزيد من التوابع للقوائم والسلاسل النصية
يعدّ تابع count عدد مرات ورود العنصر أو السلسلة الفرعية المحدَّدة في الهدف. ويعمل هذا التابع بطريقة مماثلة مع السلاسل النصية والقوائم على حد سواء:
my_string = "How much wood would a woodchuck chuck if a woodchuck could chuck wood"
print(my_string.count("ch"))
my_list = [1,2,3,1,4,5,1,6]
print(my_list.count(1))
5
3
لن يعدّ هذا التابع الورودات المتداخلة. فمثلًا، في السلسلة النصية aaaa يعدّ هذا التابع ورودين فقط من السلسلة الفرعية aa، مع أنه سيكون هناك ثلاثة حقًا لو سُمِح بالورودات المتداخلة.
يستبدل تابع replace سلسلة نصية جديدة، تُستبدل فيها سلسلة فرعية محدَّدة بسلسلة نصية أخرى:
my_string = "Hi there"
new_string = my_string.replace("Hi", "Hey")
print(new_string)
Hey there
وسيستبدل هذا التابع جميع ورودات السلسلة الفرعية:
sentence = "sheila sells seashells on the seashore"
print(sentence.replace("she", "SHE"))
SHEila sells seaSHElls on the seashore
وعند استخدام تابع replace، يكون الخطأ الشائع هو نسيان أن السلاسل النصية غير قابلة للتغيير:
my_string = "Python is fun"
# Replaces the substring but doesn't store the result...
my_string.replace("Python", "Java")
print(my_string)
Python is fun
وإذا لم تعد السلسلة النصية القديمة ضرورية، فيمكن إسناد السلسلة النصية الجديدة إلى المتغيّر نفسه:
my_string = "Python is fun"
# Replaces the substring and stores the result in the same variable
my_string = my_string.replace("Python", "Java")
print(my_string)
Java is fun
تمرين برمجي: المحرف الأكثر شيوعًا
اكتب دالة اسمها
most_common_character، تأخذ سلسلة نصية كوسيط لها. وتُعيد الدالة المحرف الأكثر ورودًا داخل السلسلة النصية. وإذا كان هناك عدّة محارف متساوية في عدد الورودات، فينبغي إعادة المحرف الذي يظهر أولًا في السلسلة النصية.
مثال على السلوك المتوقع:
first_string = "abcdbde"
print(most_common_character(first_string))
second_string = "exemplaryelementary"
print(most_common_character(second_string))
b
e
تمرين برمجي: ممنوع حروف العلة
اكتب دالة اسمها
no_vowels، تأخذ سلسلة نصية كوسيط لها. وتُعيد الدالة سلسلة نصية جديدة، ينبغي أن تكون مطابقةً للأصلية مع حذف جميع حروف العلة منها.
يمكنك أن تفترض أن السلسلة النصية ستحتوي على محارف من الأبجدية الإنجليزية الصغيرة a...z فقط.
مثال على السلوك المتوقع:
my_string = "this is an example"
print(no_vowels(my_string))
ths s n xmpl
تمرين برمجي: ممنوع الصراخ
يُعيد تابع السلسلة النصية
isupper()في Python القيمةTrueإذا كانت السلسلة النصية تتكون من محارف كبيرة فقط.
بعض الأمثلة:
print("XYZ".isupper())
is_it_upper = "Abc".isupper()
print(is_it_upper)
True
False
يُرجى استخدام تابع isupper لكتابة دالة اسمها no_shouting، تأخذ قائمة من السلاسل النصية كوسيط لها. وتُعيد الدالة قائمة جديدة لا تحوي سوى تلك العناصر من القائمة الأصلية التي لا تتكون محارفها بالكامل من أحرف كبيرة.
مثال على السلوك المتوقع:
my_list = ["ABC", "def", "UPPER", "ANOTHERUPPER", "lower", "another lower", "Capitalized"]
pruned_list = no_shouting(my_list)
print(pruned_list)
['def', 'lower', 'another lower', 'Capitalized']
تمرين برمجي: المتجاورون في القائمة
لمعطى قائمة من الأعداد الصحيحة، لنقرّر أن العنصرين المتتاليين في القائمة يكونان متجاورين إذا كان الفرق بينهما 1. إذن يكون العنصران 1 و2 متجاورين، وكذلك العنصران 56 و55.
يُرجى كتابة دالة اسمها longest_series_of_neighbours، تبحث عن أطول سلسلة متجاورين داخل القائمة، وتُعيد طولها.
فمثلًا، في القائمة [1, 2, 5, 4, 3, 4] تكون أطول قائمة متجاورين هي [5, 4, 3, 4]، وطولها 4.
مثال على استدعاء الدالة:
my_list = [1, 2, 5, 7, 6, 5, 6, 3, 4, 1, 0]
print(longest_series_of_neighbours(my_list))
4
تطوير مشروع برمجي أكبر
يختتم هذا الجزء الرابع بمشروع برمجي أكبر قليلًا، حيث تطبّق كثيرًا من التقنيات التي تعلّمتها حتى الآن.
القاعدة الأولى في معالجة أي مشروع برمجي هي ألّا تحاول حلّ كل شيء دفعة واحدة. فبينبغي أن يُبنى البرنامج من أجزاء أصغر، مثل دوال مساعدة. وينبغي أن تتحقق من عمل كل جزء قبل الانتقال إلى الجزء الذي يليه. وإذا حاولت معالجة الكثير دفعة واحدة، فلا يُحسن أن يؤول الأمر إلا إلى الفوضى.
ولتحقيق ذلك ستحتاج إلى وسيلة لاختبار دوالك خارج الدالة الرئيسية. ويمكنك تحقيق ذلك بتعريف دالة رئيسية صراحةً، واستدعاء هذه الدالة من خارج أي دالة أخرى في البرنامج. عندئذٍ يصبح من السهل تعليق استدعاء الدالة الواحدة أثناء الاختبار. ويمكن أن تبدو الخطوات الأولى في بناء مشروع البرمجة التالي هكذا:
def main():
points = []
# your program code goes here
main()
والآن يمكن اختبار دوال المساعدة دون تشغيل الدالة الرئيسية:
# helper function for determining the grade based on the amount of points
def grade(points):
# more code
def main():
all_points = []
# your program code goes here
# comment out the main function
#main()
# test the helper function
student_points = 35
result = grade(student_points)
print(result)
تمرير البيانات من دالة إلى أخرى
عندما يحتوي البرنامج على عدّة دوال، يطرح السؤال نفسه: كيف تمرّر البيانات من دالة إلى أخرى؟
يسأل المثال التالي المستخدم عن بعض قيم الأعداد الصحيحة. ثم يطبع البرنامج هذه القيم ويُجري عليها «تحليلًا». وينقسم البرنامج إلى ثلاث دوال منفصلة:
def input_from_user(how_many: int):
print(f"Please type in {how_many} numbers:")
numbers = []
for i in range(how_many):
number = int(input(f"Number {i+1}: "))
numbers.append(number)
return numbers
def print_result(numbers: list):
print("The numbers are: ")
for number in numbers:
print(number)
def analyze(numbers: list):
mean = sum(numbers) / len(numbers)
return f"There are altogether {len(numbers)} numbers, the mean is {mean}, the smallest is {min(numbers)} and the greatest is {max(numbers)}"
# the "main function" using these functions
inputs = input_from_user(5)
print_result(inputs)
analysis_result = analyze(inputs)
print(analysis_result)
وعند تنفيذ البرنامج، يمكن أن يسير الأمر هكذا:
Please type in 5 numbers:
Number 1: **10**
Number 2: **34**
Number 3: **-32**
Number 4: **99**
Number 5: **-53**
The numbers are:
10
34
-32
99
-53
There are altogether 5 numbers, the mean is 11.6, the smallest is -53 and the greatest is 99
والفكرة هنا أن الدالة الرئيسية «تحفظ» كل البيانات التي يعالجها البرنامج. وفي هذه الحالة لا نحتاج سوى إلى مُدخَل المستخدم في المتغيّر inputs.
وإذا كانت المُدخَلات مطلوبة داخل دالة، فإنها تُمرَّر كوسيط. وهذا ما يحدث مع الدالتين print_result وanalyze. وإذا أنتجت الدالة بيانات يحتاجها البرنامج في مواضع أخرى، فإنها تُعيدها بالأمر return، فتُخزَّن في متغيّر داخل الدالة الرئيسية. وهذا ما يحدث مع الدالتين input_from_user وanalyze.
يمكنك استخدام المتغيّر العام inputs من الدالة الرئيسية مباشرةً داخل دوال المساعدة. وقد سبق أن شرحنا لماذا هي فكرة سيئة، لكن إليك شرحًا آخر. فإذا استطاعت الدوال تغيير متغيّر عام، فقد تبدأ أمور غير متوقعة بالحدوث في البرنامج، ولا سيما حين يصير عدد الدوال كبيرًا.
وأفضل طريقة للتعامل مع تمرير البيانات إلى الدوال وإعادتها منها هي الوسائط والقيم المعادة.
ويمكنك أيضًا فصل الدالة الرئيسية الضمنية في المثال أعلاه في دالة خاصة بها. عندئذٍ لا يعود المتغيّر inputs متغيّرًا عامًا، بل يصير متغيّرًا محليًا داخل دالة main:
# your main function goes here
def main():
inputs = input_from_user(5)
print_result(inputs)
analysis_result = analyze(inputs)
print(analysis_result)
# run the main function
main()
تمرين برمجي: إحصاءات الدرجات
في هذا التمرين ستكتب برنامجًا يطبع إحصاءات الدرجات لمقرر جامعي.
يسأل البرنامج المستخدم عن نتائج طلاب مختلفين في المقرر. وتشمل نقاط الاختبار وأعداد التمارين المنجزة. ثم يطبع البرنامج إحصاءات مبنية على هذه النتائج.
نقاط الاختبار أعداد صحيحة بين 0 و20. أما عدد التمارين المنجزة فهو عدد صحيح بين 0 و100.
يواصل البرنامج طلب المُدخَل حتى يكتب المستخدم سطرًا فارغًا. ويمكنك أن تفترض أن جميع الأسطر تحتوي على مُدخَل صحيح، ما يعني أنه يوجد عددان صحيحان في كل سطر، أو أن السطر فارغ.
ومثال على كيفية كتابة البيانات:
Exam points and exercises completed: **15 87**
Exam points and exercises completed: **10 55**
Exam points and exercises completed: **11 40**
Exam points and exercises completed: **4 17**
Exam points and exercises completed:
Statistics:
وحين يكتب المستخدم سطرًا فارغًا، يطبع البرنامج إحصاءات، تُصاغ على النحو التالي:
تُحوَّل التمارين المنجزة إلى نقاط تمارين، بحيث إن أنجزت 10% على الأقل من التمارين منحت نقطة واحدة، و20% منحت نقطتين، وهكذا. وإنجاز كل التمارين الـ100 يمنح 10 نقاط تمارين. وعدد نقاط التمارين الممنوحة قيمة صحيحة، مقرَّبة إلى الأسفل.
وتُحدَّد درجة المقرر بناءً على الجدول التالي:
| نقاط الاختبار + نقاط التمارين | الدرجة |
|---|---|
| 0–14 | 0 (أي رسوب) |
| 15–17 | 1 |
| 18–20 | 2 |
| 21–23 | 3 |
| 24–27 | 4 |
| 28–30 | 5 |
وهناك أيضًا حدّ أدنى لدرجات الاختبار. فإذا حصل الطالب على أقل من 10 نقاط في الاختبار، فإنه يرسب في المقرر تلقائيًا، مهما كان مجموع نقاطه الكلي.
وبالمُدخَلات المثال أعلاه، يطبع البرنامج الإحصاءات التالية:
Statistics:
Points average: 14.5
Pass percentage: 75.0
Grade distribution:
5:
4:
3: *
2:
1: **
0: *
ينبغي طباعة الأعداد العشرية بدقة خانة عشرية واحدة.
ملاحظة مهمة: لا يطلب منك هذا التمرين كتابة أي دوال بعينها، لذا ينبغي ألّا تضع أي شيفرة داخل كتلة if __name__ == "__main__". فإذا كانت أي وظيفة في برنامجك موجودة مثلًا في دالة main، فينبغي أن تُدرج الشيفرة التي تستدعي هذه الدالة بشكل عادي، وألّا تضعها داخل كتلة if كما في التمارين التي تحدّد دوال بعينها.
تلميح:
يتكوّن مُدخَل المستخدم في هذا البرنامج من أسطر تحتوي على قيمتين عدديتين صحيحتين:
Exam points and exercises completed: **15 87**
عليك أولًا أن تقسم سطر المُدخَل إلى قسمين، ثم أن تحوّل الأجزاء إلى أعداد صحيحة بالدالة int. ويمكن تقسيم المُدخَل بالطريقة نفسها المستخدمة في تمرين الكلمة الأولى والثانية والأخيرة، لكن هناك طريقة أبسط أيضًا. فالتابع split الخاص بالسلاسل النصية سيقسم المُدخَل تقسيمًا جيدًا. وستجد المزيد من المعلومات بالبحث عن python string split عبر الإنترنت.
<!-
اختبار لمراجعة محتويات هذا القسم:
اختبار: (مكون تفاعلي غير متاح في النسخة الثابتة - ID: 925f1715-d762-5e44-a812-be13bff1aa44)
-->
يُرجى الإجابة على استبيان سريع حول مواد هذا الأسبوع.
اختبار: (مكون تفاعلي غير متاح في النسخة الثابتة - ID: 1658e788-b6ce-50b0-93ee-151e9cb6a495)