ساختارهای داده تودرتو
داده واقعی هیچوقت یک لیست ساده نیست. یک جدول دانشجویان، یک سبد خرید، پاسخ یک وبسرویس: همهشان ظرفهای تودرتو هستند. این فصل درباره ساختن و خواندن آنهاست.
۱. لیستی از لیستها
این را در فصل دهم دیدیم. اینجا فقط یادآوری میکنیم، چون پایه بقیه فصل است.
# 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
شرح مثال
students[1]['name']را از چپ بخوانید: سطر شماره ۱ را بگیر، بعد از آن دیکشنری، کلیدnameرا بخوان.- پیمایش با یک حلقه ساده انجام میشود و در هر دور،
studentیک دیکشنری کامل است. - نکته گیومهها مهم است: وقتی داخل f-string از کلیدی با تکگیومه استفاده میکنید، بیرونش جفتگیومه بگذارید. در پایتونهای پیش از ۳٫۱۲، یکسان بودنشان خطای نگارشی است. این دقیقاً یکی از باگهایی بود که در فایلهای اصلی این دوره وجود داشت.
- فیلتر کردن جدول همان الگوی «لیست خالی بساز و پرش کن» است. در فصل بیست و پنجم همین را در یک خط مینویسیم.
۳. دیکشنری از لیستها
وقتی میخواهید چیزها را گروهبندی کنید، دیکشنریای میسازید که مقدار هر کلیدش یک لیست است.
# 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
شرح مثال
zipدو یا چند دنباله را عضو به عضو کنار هم میگذارد، مثل زیپ لباس که دو طرف را جفت میکند.- اگر دنبالهها همطول نباشند،
zipروی کوتاهترین متوقف میشود و بقیه را نادیده میگیرد. این میتواند باگ پنهان بسازد، پس مراقب باشید. - ترکیب
dict(zip(keys, values))اصطلاح استانداردی است برای ساختن دیکشنری از دو لیست موازی. zipدر حلقه هم عالی است: وقتی دو لیست همطول دارید و میخواهید با هم پیمایششان کنید، به جای کار با اندیس ازzipاستفاده کنید.
۸. حذف تکراریها
# 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']
شرح مثال
- داده در بالای فایل و با حروف بزرگ نوشته شده، چون ثابت است.
- هر تابع یک کار میکند و
mainفقط آنها را کنار هم میگذارد. همان درس فصل دوازدهم. - قالببندی جدول با
{'Name':<10}انجام شده که از فصل سوم آمده است. ستونها با عرض ثابت مرتب میمانند. best_studentهمان الگوی «قهرمان فعلی» فصل دهم است، این بار روی دیکشنری.- خط آخر یک درک مجموعهای دارد که موضوع فصل بیست و پنجم است. فعلاً بدانید یعنی «مجموعه شهرهای همه دانشجویان».
پوشه 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 | تاپل با فیلدهای نامدار |
تمرینهای فصل پانزده
- لیستی از دیکشنری برای پنج کالا بسازید (نام، قیمت، موجودی) و به شکل جدول چاپش کنید.
- گرانترین کالای همان لیست را پیدا کنید.
- کالاها را با دیکشنری از لیستها بر اساس دستهبندی گروه کنید.
- یک جمله بگیرید و
{کلمه: تعداد}بسازید، یک بار با دیکشنری ساده و یک بار باCounter. - دو لیست
namesوscoresدارید؛ دیکشنری{نام: نمره}بسازید. - از دیکشنری
{نام: نمره}، دیکشنری برعکس{نمره: نام}بسازید. - یک ماتریس سه در سه از کاربر بگیرید و ترانهادهاش را چاپ کنید.
- تابع
unique_sorted(values)را بنویسید که مقدارهای یکتا و مرتب را برگرداند.