سرنخ — خودآموز برنامه‌نویسی برای هوش مصنوعی (مقدماتی)

فصل ۳ از ۱۱

پیشرفت ترم
۰٪

ترم ۱ · پایتون از صفر، داخلِ Colab

فهرست: وقتی یک مقدار کافی نیست

فصل ۳پیش‌نمایش رایگان
۱۱ دقیقه مطالعه فصل ۳

در این فصل چه یاد می‌گیری#

دادهٔ واقعی هیچ‌وقت یک عدد نیست. قیمتِ صد محصول است، دمای سیصدوشصت‌وپنج روز، نامِ هزار مشتری. اگر بخواهی برای هرکدام یک متغیرِ جدا بسازی، کارت همان‌جا تمام است. در این فصل list را می‌بینی — ظرفی که هر تعداد مقدار را به‌ترتیب نگه می‌دارد — و یاد می‌گیری چطور به یک عنصر، به یک تکه، یا به کلِ آن دسترسی پیدا کنی. این اولین ساختارِ دادهٔ دوره است و تا آخرین فصل همراهت می‌ماند.

یک ریلِ افقی با خانه‌های شماره‌دار که اولین خانه‌اش با صفر شروع می‌شود و یک بازوی متحرک روی یکی از خانه‌ها ایستاده

آخر این فصل می‌توانی:

  • یک list بسازی و به هر عنصرش با اندیس دسترسی داشته باشی
  • بگویی چرا شمارش در پایتون از صفر شروع می‌شود و اندیسِ منفی چه می‌کند
  • با برش (slice) یک تکه از فهرست را جدا کنی
  • عنصر اضافه و کم کنی و طولِ فهرست را بگیری
  • بگویی «تغییرپذیر بودن» یعنی چه و چرا مهم است

قبل از شروع#

از فصل ۲: متغیر یک مقدار را نگه می‌دارد، و type() نوعش را می‌گوید.

از فصل ۲: f-string برای ساختنِ جمله از مقدارها.

📓 نوت‌بوک: نوت‌بوک این فصل را در Colab باز کن — همهٔ کدهای این فصل آماده و به‌ترتیب داخلش هست.

۱. ساختنِ یک فهرست#

temperatures = [18, 21, 25, 24, 19, 17, 22]
print(temperatures)
print(type(temperatures))
print(len(temperatures))
[18, 21, 25, 24, 19, 17, 22]
<class 'list'>
7

کروشه‌ها یک list می‌سازند و ویرگول عنصرها را جدا می‌کند. تابعِ len() می‌گوید چند عنصر دارد.

فهرست می‌تواند هر نوعی را نگه دارد — عدد، متن، حتی مخلوط:

cities = ["تهران", "شیراز", "تبریز", "مشهد"]
print(cities)
print(len(cities))
['تهران', 'شیراز', 'تبریز', 'مشهد']
4

چک کن: توجه کن پایتون هنگامِ چاپِ فهرست از گیومهٔ تکی (') استفاده می‌کند حتی اگر تو گیومهٔ دوتایی نوشته باشی. این فقط شکلِ نمایش است؛ هیچ فرقی بینِ "تهران" و 'تهران' نیست.

len() تنها تابعی نیست که کلِ فهرست را می‌گیرد و یک عدد پس می‌دهد. سه تای دیگر را از همین حالا لازم می‌شوی:

print(max(temperatures))
print(min(temperatures))
print(sum(temperatures))
print(sum(temperatures) / len(temperatures))
25
17
146
20.857142857142858

max() بزرگ‌ترین عنصر را می‌دهد، min() کوچک‌ترین، و sum() جمعِ همه را. هر سه — مثلِ len()تابعِ مستقل‌اند، پس فهرست را داخلِ پرانتزشان می‌گذاری، نه با نقطه بعد از فهرست.

و خطِ آخر یک الگوی بسیار پرتکرار است: میانگین = جمع تقسیم بر تعداد. پایتون تابعِ آماده‌ای برای میانگین ندارد، ولی این ترکیب همه‌جا کار می‌کند.

۲. اندیس: رسیدن به یک عنصرِ خاص#

print(temperatures[0])
print(temperatures[2])
print(cities[1])
18
25
شیراز

عددِ داخلِ کروشه اندیس (index) است — یعنی «چندمین عنصر». و اینجا اولین چیزی است که تقریباً هر تازه‌کاری را یک بار می‌لغزاند: شمارش از صفر شروع می‌شود. اولین عنصر [0] است، دومی [1]، سومی [2].

عجیب به‌نظر می‌رسد ولی منطقِ خودش را دارد: اندیس در واقع می‌گوید «چند قدم از ابتدای فهرست جلو برو». عنصرِ اول صفر قدم فاصله دارد.

اندیسِ منفی از آخر می‌شمارد، و این خیلی به‌کار می‌آید:

print(temperatures[-1])
print(temperatures[-2])
22
17

[-1] همیشه آخرین عنصر است، بدونِ اینکه لازم باشد بدانی فهرست چند تا عنصر دارد، و [-2] یکی مانده به آخر.

قبل از ادامه، جواب بده: فهرستی با ۷ عنصر داری. بزرگ‌ترین اندیسِ مجاز چند است؟ جواب: 6. چون از صفر شروع می‌شود، هفت عنصر اندیس‌های 0 تا 6 را می‌گیرند. [7] وجود ندارد — و بخشِ بعد نشان می‌دهد پایتون چه می‌گوید وقتی سراغش بروی.

۳. یک خطای واقعی: IndexError#

print(temperatures[7])
IndexError: list index out of range

«اندیسِ فهرست خارج از محدوده است» — یعنی چیزی خواستی که وجود ندارد. این پیام را خیلی خواهی دید و تقریباً همیشه یک دلیل دارد: یکی بیشتر از چیزی که هست خواسته‌ای. آن‌قدر رایج است که اسمِ خودش را دارد: off-by-one error. اگر روزی خطایت را در اینترنت جست‌وجو کردی، با همین نام پیدایش می‌کنی.

عادتِ درست: به‌جای اینکه عددِ آخر را حفظ کنی، از [-1] یا از len() استفاده کن.

🔧 اگر کار نکرد: اگر TypeError: list indices must be integers or slices, not str گرفتی، یعنی داخلِ کروشه متن گذاشته‌ای (cities["تهران"]). فهرست فقط با عدد اندیس می‌خورد. اگر می‌خواهی با اسم به چیزی برسی، ابزارش dict است — فصلِ بعد.

۴. برش: یک تکه از فهرست#

گاهی یک عنصر نمی‌خواهی، یک بازه می‌خواهی.

print(temperatures[0:3])
print(temperatures[2:5])
print(temperatures[:3])
print(temperatures[4:])
[18, 21, 25]
[25, 24, 19]
[18, 21, 25]
[19, 17, 22]

قاعدهٔ برش (slice) این است: [start:end] از start شروع می‌کند و قبل از end تمام می‌شود. یعنی [0:3] سه عنصرِ اول را می‌دهد: اندیس‌های ۰ و ۱ و ۲ — نه ۳.

این هم اولش آزاردهنده است ولی یک خاصیتِ خوب دارد: [0:3] و [3:6] هیچ هم‌پوشانی ندارند و با هم دقیقاً شش عنصر می‌دهند. اگر انتهای بازه شامل می‌شد، هر بار باید یکی کم و زیاد می‌کردی.

اگر start را ننویسی یعنی «از اول»، و اگر end را ننویسی یعنی «تا آخر». نتیجهٔ یک برش، خودش یک list تازه است.

💡 نکته: temperatures[:] کلِ فهرست را می‌دهد — که در نگاهِ اول بی‌فایده است، ولی در واقع یک کپیِ تازه می‌سازد. چرا این مهم است، در بخشِ ۶ می‌بینی.

۵. اضافه و کم کردن#

temperatures.append(20)
print(temperatures)
print(len(temperatures))
[18, 21, 25, 24, 19, 17, 22, 20]
8

append یک عنصر به آخرِ فهرست اضافه می‌کند. نقطه‌ای که بینِ temperatures و append است شکلِ تازه‌ای است که تا حالا ندیده‌ای: به آن متد (method) می‌گویند — تابعی که به یک شیء وصل است و رویش کار می‌کند. len(x) یک تابعِ مستقل است، ولی x.append(...) متدی است که مالِ خودِ فهرست است.

temperatures.remove(17)
last = temperatures.pop()
print(temperatures)
print(f"عنصری که برداشته شد: {last}")
[18, 21, 25, 24, 19, 22]
عنصری که برداشته شد: 20

remove(x) اولین عنصری را که مقدارش x است حذف می‌کند (نه اندیسِ x را). pop() آخرین عنصر را برمی‌دارد و برمی‌گرداند، پس می‌توانی هم حذفش کنی و هم نگهش داری.

⚠️ مواظب باش: remove روی مقدار کار می‌کند و pop روی اندیس. temperatures.remove(0) یعنی «عنصری را که مقدارش صفر است حذف کن» — نه «عنصرِ اول را». اگر مقدارِ صفر در فهرست نباشد، ValueError: list.remove(x): x not in list می‌گیری.

۶. تغییرپذیری: فهرست جای خودش عوض می‌شود#

به بخشِ قبل دقت کن: بعد از append مقدارِ temperatures عوض شد، بدونِ اینکه چیزی به آن نسبت داده باشیم. این خاصیت را تغییرپذیری (mutable) می‌گویند و برای فهرست برقرار است.

روشن‌ترین نشانه‌اش این است که می‌توانی یک عنصر را سرِ جای خودش عوض کنی. همان کروشه‌ای که تا حالا برای خواندن به‌کار می‌بردی، سمتِ چپِ = هم می‌نشیند:

print(temperatures)
temperatures[0] = 30
print(temperatures)
[18, 21, 25, 24, 19, 22]
[30, 21, 25, 24, 19, 22]

temperatures[0] = 30 یعنی «عنصرِ شمارهٔ صفر را با ۳۰ عوض کن». هیچ فهرستِ تازه‌ای ساخته نشد و اسمِ متغیر هم عوض نشد؛ محتوای همان فهرست تغییر کرد. این کار روی متن ممکن نیست و بخشِ ۷ می‌گوید چرا.

پیامدش گاهی غافلگیرکننده است:

a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(b)
[1, 2, 3, 4]
[1, 2, 3, 4]

به a دست نزدیم ولی عوض شد. دلیلش این است که b = a یک کپی نساخت؛ فقط برچسبِ دومی روی همان فهرست چسباند. هر دو اسم به یک شیء اشاره می‌کنند، پس تغییر از هر دو طرف دیده می‌شود.

اگر کپیِ واقعی می‌خواهی، همان برشِ بخشِ ۴:

a = [1, 2, 3]
b = a[:]
b.append(4)
print(a)
print(b)
[1, 2, 3]
[1, 2, 3, 4]

📏 اندازه بگیر: هر وقت فهرستی «خودبه‌خود» عوض شد، اول این را چک کن که آیا جای دیگری اسمِ دومی رویش گذاشته‌ای. در ترمِ ۲ که با جدول‌های بزرگ کار کنی، همین الگو دوباره برمی‌گردد و آنجا خیلی گران‌تر تمام می‌شود.

۷. فهرست‌ها معمولاً از متن متولد می‌شوند#

تا اینجا فهرست‌ها را با دست نوشتیم. در کارِ واقعی تقریباً هیچ‌وقت این‌طور نیست: داده به‌صورتِ یک متن می‌رسد و تو باید بشکنی‌اش. سه متدِ رشته این کار را می‌کنند و از همین‌جا تا آخرِ دوره همراهت‌اند.

line = "  تهران,شیراز,تبریز,مشهد  "

print(line.strip())
print(line.strip().split(","))
print("یک جملهٔ ساده فارسی".split())
تهران,شیراز,تبریز,مشهد
['تهران', 'شیراز', 'تبریز', 'مشهد']
['یک', 'جملهٔ', 'ساده', 'فارسی']
  • strip() فاصله‌های اضافیِ ابتدا و انتها را می‌بُرد. در دادهٔ واقعی این فاصله‌ها همه‌جا هستند و اگر نبُری‌شان، "تهران" و " تهران" دو چیزِ متفاوت به‌حساب می‌آیند.
  • split(",") متن را سرِ هر ویرگول می‌شکند و یک list می‌دهد. این دقیقاً کاری است که هر فایلِ CSV با آن خوانده می‌شود.
  • split() بدونِ آرگومان سرِ هر فاصله می‌شکند — ساده‌ترین راهِ رسیدن از یک جمله به فهرستِ کلمه‌هایش.

و متدِ چهارم که در پاک‌سازی مرتب لازمش می‌شوی:

messy = "قیمت: 1,200,000 تومان"
print(messy.replace(",", ""))
print(messy.replace(",", "").replace("تومان", "").strip())
قیمت: 1200000 تومان
قیمت: 1200000

replace(old, new) هر بارِ ظهورِ یک متن را با متنِ دیگری عوض می‌کند. آن ویرگولِ هزارگان یکی از رایج‌ترین دلیل‌هایی است که یک ستونِ عددی به int تبدیل نمی‌شود، و در ترمِ ۲ روی دادهٔ واقعی دقیقاً به آن برمی‌خوری.

چک کن: توجه کن که این متدها متنِ اصلی را عوض نمی‌کنند؛ نسخهٔ تازه‌ای برمی‌گردانند. messy بعد از هر دو خط هنوز همان متنِ کثیفِ اول است. این دقیقاً برعکسِ list.append است که فهرست را جای خودش عوض می‌کرد — و همان تفاوتِ تغییرپذیری است که در بخشِ ۶ دیدی: متن در پایتون تغییرناپذیر است، فهرست نه.

🤖 از دستیارت بپرس: «در پایتون فرقِ b = a و b = a[:] برای یک list چیست؟ با یک مثال توضیح بده.» بعد خودت هر دو را در نوت‌بوک اجرا کن. عادت کن جوابِ دستیار را با اجرای واقعی مقایسه کنی — این کاری است که در کلِ دوره از تو می‌خواهیم.

واژه‌های تازهٔ این فصل#

کلمه تلفظ به حروف فارسی یعنی چه
list لیست ظرفی که چند مقدار را به‌ترتیب نگه می‌دارد
index ایندِکس شمارهٔ جایگاهِ یک عنصر؛ از صفر شروع می‌شود
slice اسلایس برشی از فهرست؛ [start:end] که end را شامل نمی‌شود
method مِتُد تابعی که به یک شیء وصل است، مثل list.append
mutable میوتِبِل تغییرپذیر — شیء جای خودش عوض می‌شود، نه اینکه نسخهٔ تازه بسازد
IndexError ایندِکس اِرور اندیسی خواسته‌ای که در فهرست وجود ندارد
max / min / sum مَکس / مین / سام بیشینه / کمینهٔ / جمعِ عنصرهای یک فهرست
split / strip / replace اسپلیت / استریپ / ری‌پلیس متن را به فهرست بشکن / فاصلهٔ اضافی را ببُر / یک تکه را عوض کن

تمرین‌ها

اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.

در فصل بعد#

فهرست عالی است وقتی ترتیب مهم باشد، ولی یک ضعفِ بزرگ دارد: برای رسیدن به یک عنصر باید شماره‌اش را بدانی. در دادهٔ واقعی معمولاً شماره را نمی‌دانی، اسم را می‌دانی: «قیمتِ این محصول»، «سنِ این مشتری». فصلِ بعد dict را می‌آورد — ظرفی که هر مقدار را زیرِ یک کلید نگه می‌دارد و دقیقاً همان ساختاری است که دادهٔ واقعی با آن منتقل می‌شود.

به آخر این فصل رسیدی!

اگر ساختی و جواب داد، این دکمه مال توست.