برمجة بايثون 2026 — جامعة هلسنكي › الجزء 5: هياكل البيانات 5 / 15 السابق التالي

المزيد عن القوائم

أهداف التعلّم

بعد إتمام هذا القسم

  • ستكون قادرًا على إنشاء قوائم من أنواع مختلفة من العناصر
  • ستعرف كيف تستخدم القوائم لتنظيم البيانات
  • ستكون قادرًا على تخزين مصفوفة كقائمة ثنائية الأبعاد

عن تمارين هذه الدورة

يتطلب أن تصير مبرمجًا ماهرًا الكثير من التدريب، بل أحيانًا تدريبًا ميكانيائيًّا في جوهره. كما أنه ينطوي على تنمية مهارات حلّ المشكلات وتطبيق الحدس. ولهذا تحتوي هذه الدورة على الكثير من التمارين بأنواع مختلفة. فبعضها يطلب منك تطبيق ما تعلّمته في المادة تطبيقًا مباشرًا إلى حدٍّ كبير، لكن بعضها الآخر متعمَّد أكثر ويتّسم بأنه مفتوح النهاية.

قد يبدو بعض التمارين مربكًا في البداية، لكن لا داعي للقلق. فليس أيٌّ من التمارين إلزاميًّا بشكل قاطع، وفي الحقيقة لا تحتاج سوى إلى 25 % من نقاط كل جزء لاجتياز الدورة. ويمكنك العثور على مزيد من التفاصيل حول اجتياز الدورة في صفحة الدرجات.

التمارين ليست مرتّبة بأي ترتيب معيّن من حيث الصعوبة. فكل قسم يقدّم عادةً بعض المفاهيم الجديدة في البرمجة، ثم تُمارَس هذه المفاهيم بتمارين أبسط وآخر أعقد. وإذا صادفتك تمرين يبدو صعبًا أكثر من اللازم، فانتقل إلى التمرين التالي. ويمكنك دائمًا العودة إلى التمارين الأصعب لاحقًا إن كان لديك الوقت.

وحين يصبح الأمر صعبًا حتمًا، فعليك بكلمة عزاء: فالمهمة التي تبدو صعبة إلى حدٍّ مستحيل هذا الأسبوع من المرجح أن تبدو سهلة إلى حدٍّ كبير بعد نحو أربعة أسابيع.

قوائم بأنواع بيانات مختلفة

في الجزء السابق تعاملنا في الغالب مع قوائم تحتوي على أعداد صحيحة، لكن يمكن تخزين أي أنواع من القيم في القوائم. ويمكن أن تبدو قائمة من السلاسل النصية هكذا:

names = ["Marlyn", "Ruth", "Paul"]
print(names)
names.append("David")
print(names)

print("Number of names on the list:", len(names))
print("Names in alphabetical order:")
names.sort()
for name in names:
  print(name)
['Marlyn', 'Ruth', 'Paul']
['Marlyn', 'Ruth', 'Paul', 'David']
Number of names on the list: 4
Names in alphabetical order:
David
Marlyn
Paul
Ruth

الأعداد العشرية عناصر صالحة في القوائم أيضًا:

measurements = [-2.5, 1.1, 7.5, 14.6, 21.0, 19.2]

for measure in measurements:
    print(measure)

mean = sum(measurements) / len(measurements)

print("The mean is:", mean)
-2.5
1.1
7.5
14.6
21.0
19.2
The mean is: 10.15

تذكير: استخدام المتغيّرات العامة داخل الدوال

نعرف أنه من الممكن إسناد متغيّرات جديدة داخل تعريفات الدوال، لكن الدالة ترى أيضًا المتغيّرات المسنَدة خارجها، في الدالة الرئيسية. وتُسمّى هذه المتغيّرات متغيّرات عامة (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 الذي لا يوجد إطلاقًا وقت تنفيذ الاختبارات.

تحذير: الكتابة فوق معامل والعودة مبكرًا

هناك بضعة مصادر جديدة للأخطاء ينبغي أن نطّلع عليها قبل أن ننتقل إلى تمارين هذا الجزء. ولنعُد إلى دالة تخبرنا ما إذا كان عدد صحيح موجودًا في قائمة. وكلاهما معرَّف كمعامل للدالة:

def number_in_list(numbers: list, number: int):
    for number in numbers:
        if number == number:
            return True
        else:
            return False

تبدو هذه الدالة وكأنها تُرجع True دائمًا. والسبب أن حلقة for تكتب فوق القيمة المخزّنة في المعامل number. وبذلك يكون الشرط في جملة if صحيحًا دائمًا.

وإعادة تسمية المعامل تحلّ المشكلة:

def number_in_list(numbers: list, searched_number: int):
    for number in numbers:
        if number == searched_number:
            return True
        else:
            return False

يبدو الشرط في جملة if الآن أفضل. لكن هناك مشكلة جديدة، إذ لا تزال الدالة تبدو غير عاملة بشكل صحيح. وتُظهر تجربة ما يلي وجود خلل:

found = number_in_list([1, 2, 3, 4], 3)
print(found)  # prints out False

المشكلة هنا أن الدالة تعود مبكرًا، دون فحص جميع الأعداد في القائمة. فالواقع أن الدالة تأخذ العنصر الأول في القائمة فقط، وتُرجع True أو False بحسب قيمته. ولا يمكننا معرفة ما إذا كان عدد ما غير موجود في القائمة قبل أن نفحص جميع عناصر القائمة. وينبغي أن يوضع الأمر return False خارج حلقة for:

def number_in_list(numbers: list, searched_number: int):
    for number in numbers:
        if number == searched_number:
            return True

    return False

ولنُعُد إلى دالة معطوبة أخرى:

def unique_numbers(numbers: list):
    # a helper variable to store all the numbers we've already checked
    numbers = []
    for number in numbers:
        # have we seen this number already?
        if number in numbers:
            return False
        numbers.append(number)

    return True

unique = unique_numbers([1, 2, 2])
print(unique)  # prints out True

ينبغي أن تفحص هذه الدالة ما إذا كانت جميع الأعداد في القائمة مختلفة عن بعضها بعض، لكنها تُرجع True دائمًا.

هنا أيضًا تكتب الدالة عن طريق الخطأ فوق القيمة المخزّنة في معاملها. فتحاول الدالة استخدام المتغيّر numbers لتخزين جميع الأعداد التي فحصتها بالفعل، لكن هذا يكتب فوق قائمة الوسائط الأصلية. وإعادة تسمية المتغيّر المساعد حلٌّ سهل:

def unique_numbers(numbers: list):
    # a helper variable to store all the numbers we've already checked
    numbers_checked = []
    for number in numbers:
        # have we seen this number already?
        if number in numbers_checked:
            return False
        numbers_checked.append(number)

    return True

unique = unique_numbers([1, 2, 2])
print(unique)  # prints out False

يمكن تحديد مشكلات كهذه، وكثير غيرها، وإصلاحها بمساعدة أداة التنقيح أو أداة التصوير المرئي. ولا يمكن الإفراط في التأكيد على أهمية تعلّم استخدام هاتين الأداتين بكفاءة.

تمرين برمجي: السلسلة النصية الأطول

اكتب دالة اسمها longest(strings: list)، تأخذ قائمة من السلاسل النصية كوسيط لها. وتجد الدالة أطول سلسلة نصية في القائمة وتُرجعها. ويمكنك أن تفترض أن أطول سلسلة نصية في القائمة فريدة تمامًا.

مثال على السلوك المتوقّع:


if __name__ == "__main__":
    strings = ["hi", "hiya", "hello", "howdydoody", "hi there"]
    print(longest(strings))

howdydoody

القوائم داخل القوائم

يمكن لعناصر القائمة أن تكون قوائم في ذاتها:

my_list = [[5, 2, 3], [4, 1], [2, 2, 5, 1]]
print(my_list)
print(my_list[1])
print(my_list[1][0])
[[5, 2, 3], [4, 1], [2, 2, 5, 1]]
[4, 1]
4

لماذا تكون القوائم داخل القوائم مفيدة؟

تذكّر أن القوائم يمكن أن تحتوي على عناصر من أنواع مختلفة. ويمكنك تخزين معلومات عن شخص ما في قائمة. فمثلًا يمكنك أن تجعل اسمه العنصر الأول، وعمره العنصر الثاني، وطوله بالأمتار العنصر الثالث:

["Anne", 12, 1.45]

ومن ثمّ يمكن أن تكون قاعدة بيانات الأشخاص قائمة، تكون عناصرها قوائم تحتوي على معلومات عن شخص واحد:

persons = [["Betty", 10, 1.37], ["Peter", 7, 1.25], ["Emily", 32, 1.64], ["Alan", 39, 1.78]]

for person in persons:
  name = person[0]
  age = person[1]
  height = person[2]
  print(f"{name}: age {age} years, height {height} meters")
Betty: age 10 years, height 1.37 meters
Peter: age 7 years, height 1.25 meters
Emily: age 32 years, height 1.64 meters
Alan: age 39 years, height 1.78 meters

تمرّ حلقة for على عناصر القائمة الخارجية واحدًا تلو الآخر. أي أن كل قائمة تحتوي على معلومات عن شخص واحد تُسنَد بالتبادل إلى المتغيّر person.

ليست القوائم دائمًا أفضل طريقة لعرض البيانات، مثل معلومات عن شخص. وسنصادف قريبًا قاموسات Python (dictionaries)، وهي أنسب كثيرًا لهذه الحالات.

المصفوفات

المصفوفة ثنائية الأبعاد، أو ما يُسمّى مصفوفة (matrix)، تطبيق طبيعي آخر لقائمة داخل قائمة.

فمثلًا، المصفوفة التالية

5_1_1.png

يمكن عرضها في Python كقائمة ثنائية الأبعاد هكذا:

my_matrix = [[1, 2, 3], [3, 2, 1], [4, 5, 6]]

ولأن المصفوفة قائمة تحتوي على قوائم، يمكن الوصول إلى العناصر الفردية داخل المصفوفة باستخدام أقواس مربّعة متتالية. فيُشير الفهرس الأول إلى الصف، ويشير الثاني إلى العمود. ويبدأ الترقيم من الصفر، فمثلًا يشير my_matrix[0][1] إلى العنصر الثاني في الصف الأول.

my_matrix = [[1, 2, 3], [3, 2, 1], [4, 5, 6]]

print(my_matrix[0][1])
my_matrix[1][0] = 10
print(my_matrix)
2
[[1, 2, 3], [10, 2, 1], [4, 5, 6]]

كما في أي قائمة أخرى، يمكن المرور على صفوف المصفوفة بحلقة for. وتطبع الشيفرة التالية كل صف من المصفوفة في سطر منفصل:

my_matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

for row in my_matrix:
    print(row)
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

وبالمثل، يمكن استخدام الحلقات المتداخلة للوصول إلى العناصر الفردية. وتطبع الشيفرة التالية كل عنصر في المصفوفة في سطر منفصل بمساعدة حلقتي for:

my_matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

for row in my_matrix:
    print("a new row")
    for element in row:
        print(element)
a new row
1
2
3
a new row
4
5
6
a new row
7
8
9

تصوير شيفرة تحتوي على قوائم داخل قوائم

قد تبدو البرامج التي تحتوي على قوائم داخل قوائم صعبة الفهم في البداية. وتساعدك أداة التصوير المرئي من Python Tutor كثيرًا في فهم كيفية عملها. وفيما يلي تصوير مرئي للمثال أعلاه:

5_1_2.png

تكشف الصورة أعلاه أن مصفوفة 3 في 3 تتكون تقنيًا من أربع قوائم. ويمثّل أول هذه القوائم المصفوفة بأكملها. أما القوائم الثلاث المتبقية فهي عناصر في القائمة الأولى، وتمثّل الصفوف.

ولأن القوائم متعددة الأبعاد يمكن المرور عليها بحلقات متداخلة، من الطبيعي أن نفكر في القوائم نفسها على أنها متداخلة، لكن الصورة أعلاه تبيّن لنا أن الأمر ليس كذلك في الواقع. وبدلًا من ذلك، فإن القائمة التي تمثّل المصفوفة كاملة «تشير» إلى كل قائمة منفردة تمثّل صفًّا في المصفوفة. ويُسمّى هذا مرجعًا (reference)، وسيُبحث في الفكرة بمزيد من التفصيل في القسم التالي.

في الصورة أعلاه يكون التنفيذ قد تقدّم إلى الصف الثاني من المصفوفة، وهذه القائمة هي ما يشير إليه المتغيّر row حاليًا. أما المتغيّر element فيحتوي على العنصر الذي يقف عنده التنفيذ حاليًا. والقيمة المخزّنة في element هي العنصر الأوسط في القائمة، أي 5.

الوصول إلى عناصر المصفوفة

الوصول إلى صف واحد داخل المصفوفة بسيط - يكفي اختيار الصف المطلوب. وتحسب الدالة التالية مجموع عناصر الصف المختار:

def sum_of_row(my_matrix, row_no: int):
    # choose the desired row from within the matrix
    row = my_matrix[row_no]
    row_sum = 0
    for item in row:
        row_sum += item

    return row_sum

m = [[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 5], [2, 9, 15, 1]]

my_sum = sum_of_row(m, 1)
print(my_sum) # prints out 33 (which equals 9 + 1 + 12 + 11)

العمل بالأعمدة داخل المصفوفة أعقد قليلًا، لأن المصفوفة مخزّنة صفوفًا:

def sum_of_column(my_matrix, column_no: int):
    # go through each row and select the item at the chosen position
    column_sum = 0
    for row in my_matrix:
        column_sum += row[column_no]

    return column_sum

m = [[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 5], [2, 9, 15, 1]]

my_sum = sum_of_column(m, 2)
print(my_sum) # prints out 39 (which equals 3 + 12 + 9 + 15)

يتكوّن العمود المعالَج هنا من العناصر الموجودة عند الفهرس 2 في كل صف.

يُنصح بشدة باستخدام أداة التصوير المرئي لفهم كيفية عمل هذه الدوال.

تغيير قيمة عنصر واحد داخل المصفوفة بسيط: اختر صفًّا داخل المصفوفة، ثم اختر عمودًا داخل الصف:

def change_value(my_matrix, row_no: int, column_no: int, new_value: int):
    # choose the desired row
    row = my_matrix[row_no]
    # select the correct item within the row
    row[column_no] = new_value

m = [[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 5], [2, 9, 15, 1]]

print(m)
change_value(m, 2, 3, 1000)
print(m)
[[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 5], [2, 9, 15, 1]]
[[4, 2, 3, 2], [9, 1, 12, 11], [7, 8, 9, 1000], [2, 9, 15, 1]]

لاحظ كيف استخدمنا في الأعلى فهرسي الصف والعمود للوصول إلى عنصر مختار. وإذا أردنا تغيير محتويات المصفوفة، فعلينا الوصول إلى العناصر بفهارسها. وهذا يعني أننا لا نستطيع استخدام حلقة for item in list بسيطة للمرور على المصفوفة إذا أردنا تغيير محتوياتها.

وبدلًا من ذلك، علينا تتبّع فهارس العناصر، مثلًا بحلقة while أو حلقة for تستخدم الدالة range. وتزيد الشيفرة التالية قيمة كل عنصر في المصفوفة بمقدار واحد:

m = [[1,2,3], [4,5,6], [7,8,9]]

for i in range(len(m)): # using the number of rows in the matrix
    for j in range(len(m[i])): # using the number of items on each row 
        m[i][j] += 1

print(m)
[[2, 3, 4], [5, 6, 7], [8, 9, 10]]

تمرّ الحلقة الخارجية على الفهارس من الصفر إلى طول المصفوفة، أي عدد صفوف المصفوفة. أما الحلقة الداخلية فتتمرّ على الفهارس من الصفر إلى طول كل صف داخل المصفوفة.

تمرين برمجي: عدد العناصر المتطابقة

اكتب دالة اسمها count_matching_elements(my_matrix: list, element: int)، تأخذ مصفوفة ثنائية الأبعاد من الأعداد الصحيحة وقيمة عدد صحيح واحد كوسيطين لها. ثم تعدّ الدالة كم عنصرًا داخل المصفوفة يطابق قيمة الوسيط.

مثال على كيفية عمل الدالة:

m = [[1, 2, 1], [0, 3, 4], [1, 0, 0]]
print(count_matching_elements(m, 1))
3

مصفوفة ثنائية الأبعاد كهيكلة بيانات في لعبة

يمكن أن تكون المصفوفة بنية بيانات مفيدة جدًا في ألعاب مختلفة كثيرة. فمثلًا، شبكة لعبة سودوكو في الصورة أدناه

5_1_3.png

يمكن تمثيلها بصيغة مصفوفة هكذا:

sudoku = [
  [9, 0, 0, 0, 8, 0, 3, 0, 0],
  [0, 0, 0, 2, 5, 0, 7, 0, 0],
  [0, 2, 0, 3, 0, 0, 0, 0, 4],
  [0, 9, 4, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 7, 3, 0, 5, 6, 0],
  [7, 0, 5, 0, 6, 0, 4, 0, 0],
  [0, 0, 7, 8, 0, 3, 9, 0, 0],
  [0, 0, 1, 0, 0, 0, 0, 0, 3],
  [3, 0, 0, 0, 0, 0, 0, 0, 2]
]

هنا تمثّل القيمة صفر مربعًا فارغًا، لأن الصفر ليست قيمة مقبولة في لغز سودوكو مكتمل.

وفيما يلي دالة بسيطة لطبع شبكة سودوكو أعلاه:

def print_grid(sudoku):
    for row in sudoku:
        for square in row:
            if square > 0:
                print(f" {square}", end="")
            else:
                print(" _", end="")
        print()

print_grid(sudoku)

ينبغي أن يبدو المخرج كالتالي::


 9 _ _ _ 8 _ 3 _ _
 _ _ _ 2 5 _ 7 _ _
 _ 2 _ 3 _ _ _ _ 4
 _ 9 4 _ _ _ _ _ _
 _ _ _ 7 3 _ 5 6 _
 7 _ 5 _ 6 _ 4 _ _
 _ _ 7 8 _ 3 9 _ _
 _ _ 1 _ _ _ _ _ 3
 3 _ _ _ _ _ _ _ 2

يمكن نمذجة أي لعبة شائعة ذات لوحة لعب بالأسلوب نفسه. فمن بين غيرها، تُبنى لعبة الشطرنج (chess)، ولعبة Minesweeper، ولعبة Battleship، ولعبة Mastermind جميعًا على شبكة ثنائية الأبعاد. أما في سودوكو فمن الطبيعي استخدام الأعداد لتمثيل حالة اللعبة، لكن في غيرها من الألعاب قد تكون الأساليب الأخرى أفضل.

تمرين برمجي: Go

في لعبة Go يتناوب لاعبان على وضع حجرين أسود وأبيض على لوحة اللعب. والفائز هو اللاعب الذي يتمكّن من تطويق مساحة أكبر على اللوحة بقطعه اللعبية الخاصة.

اكتب دالة اسمها who_won(game_board: list)، تأخذ مصفوفة ثنائية الأبعاد كوسيط لها. وتتكون المصفوفة من قيم عددية تمثّل الحالات التالية:

  • 0: مربع فارغ
  • 1: قطعة اللاعب 1
  • 2: قطعة اللاعب 2

قد تكون قواعد حساب النقاط في لعبة Go معقّدة إلى حدٍّ ما، لكن في هذا التمرين يكفي مقارنة عدد القطع التي يملكها كل لاعب على لوحة اللعب. كما أن حجم لوحة اللعب غير محدود.

ينبغي أن تُرجع الدالة القيمة 1 إذا فاز اللاعب 1، والقيمة 2 إذا فاز اللاعب 2. وإذا تملك كلا اللاعبين العدد نفسه من القطع على اللوحة، فينبغي أن تُرجع الدالة القيمة 0.

تمرين برمجي: سودوكو: فحص الصف

اكتب دالة اسمها row_correct(sudoku: list, row_no: int)، تأخذ مصفوفة ثنائية الأبعاد تمثّل شبكة سودوكو، وعددًا صحيحًا يشير إلى صف واحد، كوسيطين لها. وتُفهرَس الصفوف من 0.

ينبغي أن تُرجع الدالة True أو False، بحسب ما إذا كان الصف معبَّأ بشكل صحيح، أي ما إذا كان يحتوي على كل الأعداد من 1 إلى 9 مرة واحدة على الأكثر.

sudoku = [
  [9, 0, 0, 0, 8, 0, 3, 0, 0],
  [2, 0, 0, 2, 5, 0, 7, 0, 0],
  [0, 2, 0, 3, 0, 0, 0, 0, 4],
  [2, 9, 4, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 7, 3, 0, 5, 6, 0],
  [7, 0, 5, 0, 6, 0, 4, 0, 0],
  [0, 0, 7, 8, 0, 3, 9, 0, 0],
  [0, 0, 1, 0, 0, 0, 0, 0, 3],
  [3, 0, 0, 0, 0, 0, 0, 0, 2]
]

print(row_correct(sudoku, 0))
print(row_correct(sudoku, 1))
True
False

تمرين برمجي: سودوكو: فحص العمود

اكتب دالة اسمها column_correct(sudoku: list, column_no: int)، تأخذ مصفوفة ثنائية الأبعاد تمثّل شبكة سودوكو، وعددًا صحيحًا يشير إلى عمود واحد، كوسيطين لها. وتُفهرَس الأعمدة من 0.

ينبغي أن تُرجع الدالة True أو False، بحسب ما إذا كان العمود معبَّأ بشكل صحيح، أي ما إذا كان يحتوي على كل الأعداد من 1 إلى 9 مرة واحدة على الأكثر.

sudoku = [
  [9, 0, 0, 0, 8, 0, 3, 0, 0],
  [2, 0, 0, 2, 5, 0, 7, 0, 0],
  [0, 2, 0, 3, 0, 0, 0, 0, 4],
  [2, 9, 4, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 7, 3, 0, 5, 6, 0],
  [7, 0, 5, 0, 6, 0, 4, 0, 0],
  [0, 0, 7, 8, 0, 3, 9, 0, 0],
  [0, 0, 1, 0, 0, 0, 0, 0, 3],
  [3, 0, 0, 0, 0, 0, 0, 0, 2]
]

print(column_correct(sudoku, 0))
print(column_correct(sudoku, 1))
False
True

تمرين برمجي: سودوكو: فحص الكتلة

اكتب دالة اسمها block_correct(sudoku: list, row_no: int, column_no: int)، تأخذ مصفوفة ثنائية الأبعاد تمثّل شبكة سودوكو، وعددين صحيحين يشيران إلى فهرسي الصف والعمود لمربع واحد، كوسيطين لها. وتُفهرَس الصفوف والأعمدة من 0.

ينبغي أن تُرجع الدالة True أو False بحسب ما إذا كانت الكتلة 3 في 3 الواقعة إلى اليمين وإلى أسفل الفهارس المعطاة معبَّأة بشكل صحيح. أي ما إذا كانت الكتلة تحتوي على كل الأعداد من 1 إلى 9 مرة واحدة على الأكثر.

لاحظ أن هذه الدالة لا تتبع قواعد سودوكو بدقة. ففي لعبة سودوكو الحقيقية توجد فقط 9 كتل لفحصها، وتقع عند الفهارس (0, 0) و(0, 3) و(0, 6) و(3, 0) و(3, 3) و(3, 6) و(6, 0) و(6, 3) و(6, 6). ولا ينبغي تنفيذ قيود من هذا النوع على الفهارس هنا.

sudoku = [
  [9, 0, 0, 0, 8, 0, 3, 0, 0],
  [2, 0, 0, 2, 5, 0, 7, 0, 0],
  [0, 2, 0, 3, 0, 0, 0, 0, 4],
  [2, 9, 4, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 7, 3, 0, 5, 6, 0],
  [7, 0, 5, 0, 6, 0, 4, 0, 0],
  [0, 0, 7, 8, 0, 3, 9, 0, 0],
  [0, 0, 1, 0, 0, 0, 0, 0, 3],
  [3, 0, 0, 0, 0, 0, 0, 0, 2]
]

print(block_correct(sudoku, 0, 0))
print(block_correct(sudoku, 1, 2))
False
True

ينبغي أن يفحص استدعاء الدالة الأول الكتلة 3 في 3 التي تبدأ بالمربع ذي الفهرسين 0, 0:

9 0 0
2 0 0
0 2 0

ينبغي أن يفحص استدعاء الدالة الثاني الكتلة 3 في 3 التي تبدأ بالمربع في الصف 1 والعمود 2:

0 2 5
0 3 0
4 0 0

لن تكون هذه الكتلة الثانية مفحوصة في لعبة سودوكو حقيقية، لكن دالتك ينبغي أن تسمح بفحصها.

تمرين برمجي: سودوكو: فحص الشبكة

اكتب دالة اسمها sudoku_grid_correct(sudoku: list)، تأخذ مصفوفة ثنائية الأبعاد تمثّل شبكة سودوكو كوسيط لها. وينبغي أن تستخدم الدالة دوال التمارين الثلاثة السابقة لتحديد ما إذا كانت شبكة سودوكو كاملة معبَّأة بشكل صحيح. وانسخ دوال التمارين أعلاه إلى ملف شيفرة Python الخاص بهذا التمرين.

ينبغي أن تفحص الدالة كلًّا من الصفوف التسعة والأعمدة التسعة والكتل 3 في 3 في الشبكة. فإذا كانت جميعها تحتوي على كل الأعداد من 1 إلى 9 مرة واحدة على الأكثر، تُرجع الدالة True. وإذا كانت واحدة منها معبَّأة بشكل خاطئ، تُرجع الدالة False.

في صورة شبكة سودوكو الموجودة أعلى هذه التمارين، تظهر الكتل التسع داخل الشبكة بحدود أكثر سماكة. وهذه هي الكتل التي ينبغي أن تفحصها الدالة، وتبدأ عند الفهارس (0, 0) و(0, 3) و(0, 6) و(3, 0) و(3, 3) و(3, 6) و(6, 0) و(6, 3) و(6, 6).

sudoku1 = [
  [9, 0, 0, 0, 8, 0, 3, 0, 0],
  [2, 0, 0, 2, 5, 0, 7, 0, 0],
  [0, 2, 0, 3, 0, 0, 0, 0, 4],
  [2, 9, 4, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 7, 3, 0, 5, 6, 0],
  [7, 0, 5, 0, 6, 0, 4, 0, 0],
  [0, 0, 7, 8, 0, 3, 9, 0, 0],
  [0, 0, 1, 0, 0, 0, 0, 0, 3],
  [3, 0, 0, 0, 0, 0, 0, 0, 2]
]

print(sudoku_grid_correct(sudoku1))

sudoku2 = [
  [2, 6, 7, 8, 3, 9, 5, 0, 4],
  [9, 0, 3, 5, 1, 0, 6, 0, 0],
  [0, 5, 1, 6, 0, 0, 8, 3, 9],
  [5, 1, 9, 0, 4, 6, 3, 2, 8],
  [8, 0, 2, 1, 0, 5, 7, 0, 6],
  [6, 7, 4, 3, 2, 0, 0, 0, 5],
  [0, 0, 0, 4, 5, 7, 2, 6, 3],
  [3, 2, 0, 0, 8, 0, 0, 5, 7],
  [7, 4, 5, 0, 0, 3, 9, 0, 1]
]

print(sudoku_grid_correct(sudoku2))
False
True