برنامه‌نویسی پایتون فهرست مطالب
فصل ۱۵

ساختارهای داده تودرتو

داده واقعی هیچ‌وقت یک لیست ساده نیست. یک جدول دانشجویان، یک سبد خرید، پاسخ یک وب‌سرویس: همه‌شان ظرف‌های تودرتو هستند. این فصل درباره ساختن و خواندن آنهاست.

در این فصل می‌خوانید
  1. لیستی از لیست‌ها
  2. لیستی از دیکشنری‌ها
  3. دیکشنری از لیست‌ها
  4. دسترسی امن به عمق ساختار
  5. تبدیل به لیست
  6. تبدیل به تاپل و مجموعه
  7. تبدیل به دیکشنری و تابع zip
  8. حذف تکراری‌ها
  9. ماژول collections
  10. برنامه کامل گزارش دانشجویان

۱. لیستی از لیست‌ها

این را در فصل دهم دیدیم. اینجا فقط یادآوری می‌کنیم، چون پایه بقیه فصل است.

# The items of a list can themselves be lists.
def main() -> None:
    matrix = [
        [4, 17, 81],
        [11, 41, 17],
        [20, 12, 13],
    ]

    # A new row is appended like any other item.
    matrix.append([4, 5, 6])

    print(matrix[1][1])     # 41  -> row 1, column 1

    # Two loops to visit every value.
    for row in matrix:
        for value in row:
            print(value, end='\t')
        print()

if __name__ == '__main__':
    main()

ماتریس: نمونه 01_list_of_lists.py

۲. لیستی از دیکشنری‌ها

این مهم‌ترین ساختار این فصل است. هر جدول داده در دنیای واقعی به همین شکل نمایش داده می‌شود: هر سطر یک دیکشنری، و کلیدها نام ستون‌ها.

# A list of dictionaries is how a table of data is normally represented:
# one dictionary per row, the keys are the column names.
def main() -> None:
    students = [
        {'name': 'Ali', 'age': 18, 'email': 'ali@kadoosedu.ir'},
        {'name': 'Reza', 'age': 20, 'email': 'reza@kadoosedu.ir'},
        {'name': 'Ahmad', 'age': 23, 'email': 'ahmad@kadoosedu.ir'},
    ]

    print(students[1]['name'])     # Reza

    # NOTE: use double quotes OUTSIDE the f-string when the keys are written
    # with single quotes - it works on every version of Python.
    print('Name\tAge\tEmail')
    for student in students:
        print(f"{student['name']}\t{student['age']}\t{student['email']}")

    # Filtering the table.
    adults = []
    for student in students:
        if student['age'] >= 20:
            adults.append(student['name'])
    print('20 or older:', adults)

if __name__ == '__main__':
    main()

جدول داده: نمونه 02_list_of_dicts.py

شرح مثال

۳. دیکشنری از لیست‌ها

وقتی می‌خواهید چیزها را گروه‌بندی کنید، دیکشنری‌ای می‌سازید که مقدار هر کلیدش یک لیست است.

# A dictionary whose values are lists is used to GROUP things.
def main() -> None:
    classes = {
        'python': ['Ali', 'Sara'],
        'linux': ['Reza'],
    }

    # Adding a student to an existing group.
    classes['python'].append('Maryam')

    # Adding a brand new group.
    classes['csharp'] = ['Omid']

    for course, students in classes.items():
        print(f'{course} ({len(students)}): {", ".join(students)}')

    # A dictionary of dictionaries: scores per student per subject.
    scores = {
        'Ali': {'math': 20, 'physics': 18},
        'Sara': {'math': 19, 'physics': 20},
    }
    print(scores['Sara']['physics'])     # 20

    for name, subjects in scores.items():
        average = sum(subjects.values()) / len(subjects)
        print(f'{name}: average {average}')

if __name__ == '__main__':
    main()

گروه‌بندی: نمونه 03_dict_of_lists.py

دقت کنید classes['python'].append('Maryam') چطور کار می‌کند: اول دیکشنری را با کلید می‌خوانیم که یک لیست می‌دهد، بعد روی آن لیست append می‌زنیم. چون لیست تغییرپذیر است، تغییر واقعاً داخل دیکشنری اتفاق می‌افتد.

۴. دسترسی امن به عمق ساختار

# Reaching deep into a structure is easy, but every level can be missing.
def main() -> None:
    data = {
        'school': 'Kadoos',
        'classes': [
            {'title': 'Python', 'students': ['Ali', 'Sara']},
            {'title': 'Linux', 'students': []},
        ],
    }

    print(data['classes'][0]['students'][1])     # 'Sara'

    # A missing key raises KeyError, a missing index raises IndexError.
    # get() with a default protects us level by level.
    teacher = data.get('teacher', 'unknown')
    print(teacher)

    for course in data['classes']:
        students = course.get('students', [])
        if len(students) == 0:
            print(f"{course['title']}: no student yet")
        else:
            print(f"{course['title']}: {len(students)} student(s)")

if __name__ == '__main__':
    main()

دسترسی تودرتو: نمونه 04_nested_access.py

هر سطح یک احتمال خطاست

در عبارت data['classes'][0]['students'][1] چهار جا می‌تواند خطا بدهد: کلید classes نباشد، لیست خالی باشد، کلید students نباشد، یا دانشجوی دوم وجود نداشته باشد. وقتی داده از فایل یا از اینترنت می‌آید، در هر سطح از get با مقدار پیش‌فرض استفاده کنید. در فصل بیست و هفتم که با JSON کار می‌کنیم، این درس حیاتی می‌شود.

۵. تبدیل به لیست

# list() builds a list out of anything we can loop over.
def main() -> None:
    t = (1, 2, 3)
    print(list(t))                # [1, 2, 3]

    s = {4, 5, 6}
    print(list(s))                # order is not guaranteed

    d = {'a': 1, 'b': 2}
    print(list(d))                # ['a', 'b']  -> the KEYS
    print(list(d.values()))       # [1, 2]
    print(list(d.items()))        # [('a', 1), ('b', 2)]

    print(list('Kadoos'))         # ['K', 'a', 'd', 'o', 'o', 's']
    print(list(range(5)))         # [0, 1, 2, 3, 4]

if __name__ == '__main__':
    main()

تبدیل به لیست: نمونه 05_to_list.py

قاعده ساده: list() هر چیزی که بشود رویش حلقه زد را به لیست تبدیل می‌کند. روی دیکشنری، کلیدها را می‌دهد، چون پیمایش دیکشنری هم کلیدها را می‌دهد.

۶. تبدیل به تاپل و مجموعه

def main() -> None:
    l = [1, 2, 3]
    print(tuple(l))

    d = {'a': 1, 'b': 2}
    print(tuple(d))            # the keys
    print(tuple(d.items()))    # (('a', 1), ('b', 2))

    # set() drops the duplicates
    print(set([1, 2, 2, 3]))   # {1, 2, 3}

    # Only immutable values can go into a set:
    # set([[1, 2]])   ->  TypeError: unhashable type: 'list'

if __name__ == '__main__':
    main()

تبدیل به تاپل و مجموعه: نمونه‌های 06_to_tuple.py و 07_to_set.py

آن خطای unhashable را به خاطر داشته باشید: مجموعه فقط چیزهای تغییرناپذیر را قبول می‌کند. لیست داخل مجموعه نمی‌رود، ولی تاپل می‌رود.

۷. تبدیل به دیکشنری و تابع zip

def main() -> None:
    # dict() accepts a sequence of (key, value) pairs.
    pairs = [('a', 1), ('b', 2)]
    print(dict(pairs))

    pairs_tuple = (('x', 10), ('y', 20))
    print(dict(pairs_tuple))

    # zip() glues two sequences together, item by item.
    keys = ['name', 'age']
    values = ['Ali', 20]
    print(dict(zip(keys, values)))

    # zip() stops at the shortest sequence.
    print(list(zip([1, 2, 3], 'ab')))

    # It is also very handy in a loop.
    subjects = ['math', 'physics', 'english']
    scores = [20, 18, 19]
    for subject, score in zip(subjects, scores):
        print(f'{subject}: {score}')

if __name__ == '__main__':
    main()

zip و dict: نمونه 08_to_dict.py

{'a': 1, 'b': 2}
{'x': 10, 'y': 20}
{'name': 'Ali', 'age': 20}
[(1, 'a'), (2, 'b')]
math: 20
physics: 18
english: 19

شرح مثال

۸. حذف تکراری‌ها

# How do I remove the duplicates from a list?
def main() -> None:
    numbers = [1, 2, 3, 3, 4, 5, 5, 5]

    # set() drops the duplicates, list() turns it back into a list.
    print(list(set(numbers)))

    # A set has no order, so sort it when the order matters.
    print(sorted(set(numbers)))

    # A complete program: read numbers until 'stop', then show them
    # without duplicates and in order.
    values: list[float] = []
    while True:
        user_input = input('Enter a number (or "stop"): ')
        if user_input == 'stop':
            break
        values.append(float(user_input))

    print(sorted(set(values)))

if __name__ == '__main__':
    main()

حذف تکراری: نمونه 09_remove_duplicates.py

عبارت sorted(set(x)) را حفظ کنید: «یکتا و مرتب». از تو به بیرون خوانده می‌شود، یعنی اول مجموعه ساخته می‌شود و بعد مرتب.

۹. ماژول collections

پایتون ظرف‌های آماده‌ای دارد که کارهای رایج را ساده می‌کنند. سه تای پرکاربردش را ببینیم.

# The standard module 'collections' offers ready-made containers.
from collections import Counter, defaultdict, namedtuple

def main() -> None:
    # Counter counts everything for us.
    text = 'kadoos institute rasht'
    counter = Counter(text.replace(' ', ''))
    print(counter)
    print(counter.most_common(3))     # the three most frequent letters

    words = ['python', 'linux', 'python', 'sql', 'python']
    print(Counter(words))

    # defaultdict creates the missing value automatically.
    groups = defaultdict(list)
    students = [('python', 'Ali'), ('linux', 'Reza'), ('python', 'Sara')]
    for course, name in students:
        groups[course].append(name)   # no need to test whether the key exists
    print(dict(groups))

    # namedtuple: a tuple whose fields have names.
    Point = namedtuple('Point', ['x', 'y'])
    p = Point(3, 4)
    print(p, p.x, p.y)

if __name__ == '__main__':
    main()

ماژول collections: نمونه 10_collections_module.py

ابزاروظیفه آنجایگزین دستی‌اش
Counterشمردن همه چیز در یک خطحلقه با get(x, 0) + 1
most_common(n)n مورد پرتکرارمرتب‌سازی دیکشنری بر اساس مقدار
defaultdict(list)کلید نبود، خودش لیست خالی می‌سازدif key not in d: d[key] = []
namedtupleتاپلی که فیلدهایش نام دارندتاپل ساده با اندیس‌های گنگ

دستور from ... import ... اینجا برای اولین بار آمده است. در فصل شانزدهم و هفدهم کاملاً درباره‌اش صحبت می‌کنیم. فعلاً یعنی «از ماژول collections، این سه چیز را بیاور».

۱۰. برنامه کامل گزارش دانشجویان

'''A small report built on a list of dictionaries.'''

STUDENTS = [
    {'name': 'Ali', 'city': 'Rasht', 'scores': [20, 18, 15]},
    {'name': 'Sara', 'city': 'Rasht', 'scores': [19, 20, 20]},
    {'name': 'Reza', 'city': 'Lahijan', 'scores': [12, 14, 9]},
    {'name': 'Maryam', 'city': 'Anzali', 'scores': [17, 16, 18]},
]


def average(numbers: list[float]) -> float:
    'Return the average of a list of numbers (0 for an empty list).'
    if len(numbers) == 0:
        return 0
    return sum(numbers) / len(numbers)


def print_table() -> None:
    'Print one line per student with their average.'
    print(f"{'Name':<10}{'City':<10}{'Average':>8}")
    print('-' * 28)
    for student in STUDENTS:
        avg = average(student['scores'])
        print(f"{student['name']:<10}{student['city']:<10}{avg:>8.2f}")


def best_student() -> dict:
    'Return the student with the best average.'
    best = STUDENTS[0]
    for student in STUDENTS:
        if average(student['scores']) > average(best['scores']):
            best = student
    return best


def group_by_city() -> dict[str, list[str]]:
    'Return {city: [names]}.'
    groups: dict[str, list[str]] = {}
    for student in STUDENTS:
        city = student['city']
        if city not in groups:
            groups[city] = []
        groups[city].append(student['name'])
    return groups


def main() -> None:
    print_table()
    print()
    print('Best student:', best_student()['name'])
    print('By city:', group_by_city())
    print('Cities:', sorted({student['city'] for student in STUDENTS}))


if __name__ == '__main__':
    main()

گزارش دانشجویان: نمونه 11_students_report.py

Name      City       Average
----------------------------
Ali       Rasht         17.67
Sara      Rasht         19.67
Reza      Lahijan       11.67
Maryam    Anzali        17.00

Best student: Sara
By city: {'Rasht': ['Ali', 'Sara'], 'Lahijan': ['Reza'], 'Anzali': ['Maryam']}
Cities: ['Anzali', 'Lahijan', 'Rasht']

شرح مثال

فایل‌های تمرینی این فصل

پوشه S15-nested-data-structures شامل نمونه‌های 01_list_of_lists.py تا 11_students_report.py.

خلاصه فصل

ساختار یا ابزارکاربرد
لیستی از لیست‌هاماتریس، جدول عددی، صفحه بازی
لیستی از دیکشنری‌هاجدول داده. رایج‌ترین شکل داده واقعی
دیکشنری از لیست‌هاگروه‌بندی
دیکشنری از دیکشنری‌هاداده دوسطحی، مثل نمره هر درس هر دانشجو
get در هر سطحمحافظت در برابر کلید یا عضو گم‌شده
list tuple set dictتبدیل ظرف‌ها به هم
zip(a, b)جفت کردن دو دنباله. روی کوتاه‌ترین می‌ایستد
sorted(set(x))یکتا و مرتب
Counterشمردن، به‌علاوه most_common
defaultdict(list)گروه‌بندی بدون بررسی وجود کلید
namedtupleتاپل با فیلدهای نام‌دار

تمرین‌های فصل پانزده

  1. لیستی از دیکشنری برای پنج کالا بسازید (نام، قیمت، موجودی) و به شکل جدول چاپش کنید.
  2. گران‌ترین کالای همان لیست را پیدا کنید.
  3. کالاها را با دیکشنری از لیست‌ها بر اساس دسته‌بندی گروه کنید.
  4. یک جمله بگیرید و {کلمه: تعداد} بسازید، یک بار با دیکشنری ساده و یک بار با Counter.
  5. دو لیست names و scores دارید؛ دیکشنری {نام: نمره} بسازید.
  6. از دیکشنری {نام: نمره}، دیکشنری برعکس {نمره: نام} بسازید.
  7. یک ماتریس سه در سه از کاربر بگیرید و ترانهاده‌اش را چاپ کنید.
  8. تابع unique_sorted(values) را بنویسید که مقدارهای یکتا و مرتب را برگرداند.
فصل قبل: دیکشنری، تاپل و مجموعه فصل بعد: کتابخانه استاندارد