ویژگیهای لیست در پایتون چیست؟
لیستها (List) در زبان برنامهنویسی پایتون یکی از انواع دادههای ترکیبی (Compound Data Types) بسیار پرکاربرد و انعطافپذیر هستند. لیست مجموعهای ترتیبدار از مقادیر است که داخل علامتهای [ ] نوشته میشوند و با ویرگول از هم جدا میگردند . این ساختار داده به شما امکان میدهد چندین مقدار (حتی از انواع مختلف) را در یک متغیر واحد ذخیره کنید، هرچند معمولاً تمام اعضای یک لیست از یک نوع هستند.
numbers = [1, 2, 3, 4] # لیستی از اعداد
fruits = ["apple", "banana", "cherry"] # لیستی از رشتهها
mixed = [7, "python", True] # لیستی با انواع مختلف داده
empty = [] # لیست تهی
در مثالهای بالا، numbers یک لیست چهارتایی از اعداد صحیح، fruits لیستی از رشتهها، mixed ترکیبی از یک عدد، یک رشته و یک مقدار بولین، و empty یک لیست بدون عنصر است. پایتون چهار نوع ساختار داده از جنس کالکشن بصورت درونی دارد که لیست یکی از آنهاست؛ سه نوع دیگر تاپل (Tuple)، مجموعه (Set) و دیکشنری هستند که هر کدام ویژگیهای خاص خود را دارند . لیستها از منعطفترین نوع محسوب میشوند و به طور گسترده برای نگهداری مجموعهای از آیتمها که قابل تغییر در طول اجرای برنامه باشند، استفاده میشوند.
اندیسگذاری (Indexing) لیستها در پایتون
مانند اکثر زبانهای برنامهنویسی، اندیسگذاری در پایتون هم از ۰ شروع میشود. هر عنصر لیست با یک اندیس مشخص قابل دسترسی است. برای مثال در لیست numbers = [10, 20, 30, 40, 50] اندیس ۰ مقدار 10 و اندیس 3 مقدار 40 را برمیگرداند. پایتون همچنین اندیسهای منفی را پشتیبانی میکند که از انتهای لیست شمارش میشوند؛ مثلاً اندیس 1- به آخرین عنصر اشاره میکند . در همان لیست بالا، numbers[-1] مقدار 50 (آخرین عنصر) و numbers[-2] مقدار 40 را نتیجه میدهد .
برش (Slicing) در لیست
برش (slicing) مکانیزمی قدرتمند برای گرفتن زیرلیستی (sub list) از عناصر است. نحوه ی برش به صورت [start:stop:step] است که یک لیست جدید شامل عناصر از اندیس start تا اندیس ماقبل stop را برمیگرداند (با گام افزایشی step اختیاری) . برای مثال:
letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
print(letters[2:5]) # خروجی: ['c', 'd', 'e']
print(letters[:3]) # خروجی: ['a', 'b', 'c'] (از ابتدا تا اندیس 2)
print(letters[4:]) # خروجی: ['e', 'f', 'g'] (از اندیس 4 تا انتها)
print(letters[::2]) #خروجی: ['a', 'c', 'e', 'g'] (گام 2 - هر دو آیتم یک بار)
print(letters[::-1]) # خروجی: ['g', 'f', 'e', 'd', 'c', 'b', 'a'] (لیست معکوس)
در پایتون، بسیاری از عملیاتهایی که روی لیستها انجام میدهیم (مثل برش یا slice)، در نگاه اول ساده به نظر میرسند؛ اما اگر دقیقتر به نحوهی کار آنها توجه نکنیم، ممکن است رفتارهای غیرمنتظرهای ببینیم.یکی از مهمترین نکاتی که باید بدانیم این است که برش لیست همیشه یک لیست جدید میسازد و روی لیست اصلی تغییری ایجاد نمیکند. با این حال، این «جدید بودن» همیشه به معنی «کاملاً مستقل بودن» نیست.برش لیست یک لیست جدید میسازد. به مثال زیر توجه کنید:
letters = ['a', 'b', 'c', 'd', 'e', 'f']
sub = letters[2:5]
در اینجا لیست letters بدون تغییر باقی میماند و لیست sub شامل ['c', 'd', 'e'] میشود.این دو لیست جدا از هم در حافظه نگهداری میشوند.اگر یکی از عناصر sub را تغییر دهیم مانند مثال زیر:
sub[0] = 'x'
در نتیجه ی کد بالا:
sub -> ['x', 'd', 'e']
letters -> ['a', 'b', 'c', 'd', 'e', 'f']
همانطور که میبینید، تغییر در لیست جدید هیچ اثری روی لیست اصلی ندارد.
تا اینجا گفتیم که وقتی روی یک لیست برش (slice) انجام میدهیم، یک لیست جدید ساخته میشود.
اما سؤال مهم این است: اگر لیست جدید است، چرا گاهی با تغییر آن، لیست اصلی هم تغییر میکند؟
پاسخ این سؤال به نوع عناصری که داخل لیست قرار دارند برمیگردد.
ابتدا این مثال را ببینیم:
letters = [['a', 'b'], ['c', 'd'], ['e', 'f']]
sub = letters[1:3] # [['c', 'd'], ['e', 'f']]
در اینجا دو نکتهی مهم وجود دارد: sub یک لیست جدید است (یعنی خودش جدا از letters است) اما لیستهای داخل آن کپی نشدهاند.یعنی sub[0] دقیقاً همان لیستی است که در letters[1] وجود دارد.
حالا تغییر را انجام میدهیم:
sub[0].append('x')
خروجی sub:
sub
# [['c', 'd', 'x'], ['e', 'f']]
letters
# [['a', 'b'], ['c', 'd', 'x'], ['e', 'f']]
⚠️ همانطور که میبینید، عنصری که به sub اضافه کردیم، در letters هم اضافه شده است.
علت این رفتار این است که برش لیست فقط خود لیست اصلی را کپی میکند اما عناصر داخل آن را کپی نمیکند بنابراین هر دو لیست (letters و sub) به یک عنصر مشترک در حافظه اشاره میکنند .به زبان ساده ظرفها جدا هستند اما بعضی از محتواهای داخل آنها مشترکاند.
وقتی میخواهیم هم خود لیست و هم تمام عناصر داخل آن کاملاً مستقل باشند، باید از کپی عمیق (Deep Copy) استفاده کنیم که در بخش های بعدی مقاله توضیح داده شده است.
عملیات در لیست های پایتون
۱-تغییر مقدار یک عنصر در لیست
به کمک عملگر انتساب(Assigning a value) میتوان یک عنصر را در محل خودش تغییر داد، چرا که لیست mutable است یعنی لیست قابل تغییر است؛ یعنی میتوان محتوای داخل آن را تغییر داد بدون اینکه لیست جدیدی بسازیم. مثال:
cubes = [1, 8, 27, 65, 125]
cubes[3] = 64 # مقدار عنصر با اندیس 3 را اصلاح میکنیم
print(cubes) #[1, 8, 27, 64, 125]
۲-اضافه کردن یک عنصر به آخر لیست
برای اضافهکردن آیتم جدید در انتهای لیست از متد ()append استفاده میکنیم. این متد یک آرگومان را گرفته و به انتهای لیست فعلی اضافه میکند. مثال:
names = ["Ali", "Sara"]
names.append("Reza")
print(names) # خروجی: ["Ali", "Sara", "Reza"]
پس از اجرای append، لیست names یک عنصر بیشتر خواهد داشت. توجه کنید که append لیست اصلی را بهروز میکند و مقدار بازگشتی ندارد (همان لیست اصلی را تغییر میدهد).مقدار برگشت یعنی اینکه بتوان متد را در پرینت گذاشت و یک خروجی به ما نشان دهد.
افزودن چندین عنصر (concat یا الحاق لیستها)
متد extend تمامی عناصر یک iterable (مثلاً یک لیست دیگر) را به انتهای لیست اضافه میکند(.iterable یعنی بتوان عناصر آن را بهصورت ترتیبی و یکییکی پیمایش کرد.) . به طور معادل میتوان از عملگر + برای اتصال دو لیست بهره بردبرای مثال:
odds = [1, 3, 5]
evens = [2, 4, 6]
numbers = []
numbers.extend(odds) # odds افزودن عناصر لیست
numbers.extend(evens) # evens افزودن عناصر لیست
print(numbers) # [1, 3, 5, 2, 4, 6]
روش معادل با عملگر ‘+’
combined = odds + evens
print(combined) # [1, 3, 5, 2, 4, 6]
پس از دو بار extend، لیست numbers حاوی تمام عناصر زوج و فرد است. در عمل، listA.extend(listB) شبیه انجام listA = listA + listB است، با این تفاوت که extend درجا لیست را تغییر میدهد و نیاز به اشغال فضای بیشتر برای ساختن یک لیست جدید ندارد و کارآمدتر و سریعتر است.
اضافه کردن عنصر در موقعیت دلخواه
برای اضافه کردن عنصر جدید در هر جایی که ما بخواهیم، از متد insert(index, item) استفاده میکنیم. این متد دو آرگومان میگیرد: اندیسی که باید عنصر در آن وارد شود و خود مقدار عنصر . مثال:
letters = ['a', 'b', 'd', 'e']
letters.insert(2, 'c') #در اندیس 2 مقدار سی را درج میکند
print(letters) # خروجی: ['a', 'b', 'c', 'd', 'e']
letters.insert(0, 'z') # درج در ابتدای لیست (اندیس 0)
print(letters) # خروجی: ['z', 'a', 'b', 'c', 'd', 'e']
با letters.insert(2, ‘c’) حرف ‘c’ در مکان اندیس 2 قرار گرفته و بقیه ی عناصر بعدی یک پله به جلو شیفت یافتهاند. همچنین اضافه کردن در ایندکس ۰ باعث میشود آیتم جدید اول لیست قرار گیرد (معادل اضافهکردن در ابتدای لیست).
حذف کردن عنصر با مقدار مشخص در لیست
برای پاک کردن یک عنصر مشخص از متد remove(x) استفاده می کنیم.اگر از این عنصر چندتا در لیست داشته باشیم ،متد remove اولین عنصر را پاک می کند.
اگر عنصری برابر با مقدار دادهشده پیدا نکند، خطای ValueError رخ میدهد. مثال:
colors = ["red", "green", "blue", "green"]
colors.remove("green")
print(colors) # خروجی: ["red", "blue", "green"]
در اینجا remove(“green”) اولین رشتهٔ “green” را که در لیست پیدا شد حذف کرد و لیست بهروز شد. توجه کنید عنصر “green” دومی همچنان در انتهای لیست باقیست.
حذف بر اساس اندیس
متد pop(index) عنصری که در ایندکس درون پرانتز وجود دارد را پاک می کند و همان را برمیگرداند . اگر اندیسی ندهیم،دیفالت آخرین عنصر لیست پاک میشود . همچنین میتوان از دستور del list[index] برای حذف بدون بازگشت مقدار استفاده کرد. مثال:
animals = ["cat", "dog", "bird", "fish"]
x = animals.pop(1)
print(x) # خروجی: "dog" (عنصر حذفشده)
print(animals) # خروجی: ["cat", "bird", "fish"]
del animals[2]
print(animals) # خروجی: ["cat", "bird"] ("fish" حذف شد)
پس از pop(1) لیست یک عنصر در اندیس 1 را از دست داد (“dog”) و سه عنصر باقیماند. سپس با del animals[2] عنصر با اندیس 2 (“fish”) حذف گردید. متد ()pop اگر اندیس خارج از محدوده بدهیم IndexError صادر میکند
پاک کردن کل اعضای لیست
متد ()clear همه عناصر لیست را حذف کرده و آن را تهی میکند .
items = [1, 2, 3]
items.clear()
print(items) # خروجی: []
پیدا کردن اندیس یک عنصر
تابع list.index(x) اندیس اولین x را در لیست بازمیگرداند (در صورت وجود) . این متد میتواند بازهٔ جستجو را با آرگومانهای اختیاری start و end محدود کند . اگر مقدار پیدا نشود، ValueError Exception رخ میدهد. مثال:
letters = ['a', 'b', 'c', 'a']
i = letters.index('a') # i = 0 (اولین 'a')
j = letters.index('a', 1) # j = 3 (جستجو از اندیس 1 به بعد)
شمارش تعداد تکرار یک عنصر
متد count(x) در پایتون تعداد دفعات تکرار مقدار x در لیست را برمیگرداند.
nums = [1, 2, 2, 3, 2]
print(nums.count(2)) # خروجی: 3 (عدد 2 سهبار آمده است)
طول لیست
تابع len(list) تعداد عناصر لیست را به ما می دهد.
len([5,6,7]) # خروجی: 3
len([]) # خروجی: 0 (لیست تهی)
عملیات جمع و تکرار
همانطور که دیدیم عملگر + دو لیست را به هم متصل میکند و یک لیست جدید میدهد. عملگر * نیز میتواند برای تکرار لیست استفاده شود. مثلاً [0]*5 لیستی معادل [0,0,0,0,0] میسازد.وقتی از عملگر * برای تکرار یک لیست ساده استفاده میکنیم، پایتون عناصر آن لیست را چند بار پشت سر هم قرار میدهد. اما زمانی که این لیست شامل لیستهای دیگر باشد( لیستهای تو در تو)، رفتار پایتون با آن چیزی که انتظار داریم متفاوت است. برای درک بهتر، ابتدا به مثال زیر توجه کنید:
matrix = [[0] * 3] * 3
print(matrix)
خروجی :
[[0, 0, 0],
[0, 0, 0],
[0, 0, 0]]
در ظاهر، این کد یک ماتریس ۳×۳ از صفرها ساخته است؛ اما در واقع پایتون سه سطر مستقل ایجاد نکرده است.پایتون این کد را به این شکل تفسیر میکند ابتدا فقط یک لیست داخلی ساخته میشود:
[0, 0, 0]
سپس عملگر * 3 همان لیست را سه بار کپی واقعی نمیکند،بلکه فقط سه اشارهگر به همان لیست ایجاد میکند.به بیان سادهتر، هر سه سطر ماتریس به یک لیست مشترک اشاره میکنند.حالا اگر یکی از عناصر این لیست داخلی را تغییر دهیم:
matrix[0][0] = 1
print(matrix)
#خروجی
[[1, 0, 0],
[1, 0, 0],
[1, 0, 0]]
با تغییر یک مقدار، تمام سطرها تغییر کردهاند؛
زیرا در واقع فقط یک لیست وجود دارد و همهی سطرها به آن وابسته هستند.با تغییر یک مقدار، تمام سطرها تغییر کردهاند؛زیرا در واقع فقط یک لیست وجود دارد و همهی سطرها به آن وابسته هستند.
دلیل این اشتباه رایج این است که ظاهر خروجی شبیه یک ماتریس واقعی است اما ساختار حافظهای آن متفاوت است .پایتون برای صرفهجویی در حافظه، لیست داخلی را کپی نمیکند به همین دلیل، هر تغییری روی یک سطر، روی تمام سطرها دیده میشود.
روش درست ساخت لیستهای چندبعدی
برای ساخت یک ماتریس واقعی، باید هر سطر را جداگانه بسازیم، نه اینکه یک سطر را تکرار کنیم.
بهترین و خواناترین روش، استفاده از List Comprehension است:
matrix = [[0] * 3 for _ in range(3)]
print(matrix)
خروجی#
[[0, 0, 0],
[0, 0, 0],
[0, 0, 0]]
در این حالت، پایتون در هر بار تکرار یک لیست جدید میسازد و آن را به ماتریس اضافه میکند.
پس ،برای ساخت صحیح ساختارهای چندبعدی مانند ماتریسها، لازم است هر زیرلیست بهصورت مستقل در حافظه ایجاد شود. استفاده از List Comprehension یا حلقههای تکرار، راهکار استاندارد و ایمن برای جلوگیری از اشتراک ناخواستهی دادهها و بروز خطاهای منطقی در برنامه است.
پیمایش در لیستها (Iterating)
برای دیدن تک تک عناصر(پیمایش) یا پردازش تکتک عناصر لیست میتوان از حلقههای پایتون استفاده کرد. سادهترین راه، استفاده از حلقه for است.کد زیر تمام عناصر یک لیست را به ترتیب چاپ میکند:
cities = ["Tehran", "London", "New York"]
for city in cities:
print("City:", city)
خروجی:
City: Tehran
City: London
City: New York
با این روش، در هر تکرار متغیر city به یکی از عناصر لیست اشاره میکند. اگر به اندیس هر عنصر در حین پیمایش نیاز دارید، میتوانید از تابع enumerate در حلقه for کمک بگیرید که زوجهای (index, value) تولید میکند:
for idx, city in enumerate(cities):
print(f"{idx}: {city}")
خروجی:
0: Tehran
1: London
2: New York
جستجو در لیست و عملگر in در پایتون
برای بررسی کردن وجود یک مقدا یا نبودش در لیست از عملگر in استفاده می کنیم . عبارت منطقی x in list در صورت وجود آیتم x در لیست True و در غیر این صورت False برمیگردد. همچنین x not in list برای حالت عکس است. برای مثال:
nums = [10, 20, 30]
print(20 in nums) # خروجی: True
print(5 in nums) # خروجی: False
if "apple" in fruits:
print("Found apple")
عملگر in از ابتدای لیست شروع به جستجو میکند و تا پیدا کردن اولین عنصر ادامه میدهد. از نظر کارایی، جستجوی یک عنصر در لیست دارای پیچیدگی زمانی خطی O(n) است (زیرا در بدترین حالت باید تمام n عنصر را چک کند) . لذا اگر نیاز دارید membership testing (Membership Testing در پایتون به معنی بررسی عضویت یک مقدار در یک مجموعه از دادهها است؛ یعنی بررسی کنیم آیا یک عنصر داخل یک لیست، رشته، تاپل، مجموعه یا دیکشنری وجود دارد یا نه.)زیادی انجام دهید، شاید ساختار دادهی دیگری مثل set مناسبتر باشد که این عملیات را به طور میانگین در زمان O(1) انجام میدهد .علاوه بر in، گاهی از متد ()index که قبلاً ذکر شد نیز برای پیدا کردن مکان عنصر استفاده میشود. ولی توجه داشته باشید که ()index در صورت نبود عنصر، خطا ایجاد میکند، در حالی که ترکیب in و سپس انجام عملیات مد نظر رویکرد امنتری است.
مرتبسازی لیستها
مرتبسازی (Sorting) لیست یکی از عملیات متداول است. پایتون دو روش اصلی برای مرتبسازی ارائه میدهد:
۱-متد ()list.sort که in-place، لیست را مرتب میکند و مقدار بازگشتی ندارد . این متد پارامترهای اختیاری key و reverse رادارد تا به ترتیب ملاک مرتبسازی سفارشی و وارونهکردن ترتیب را امکانپذیر کنند . به عنوان مثال:
numbers = [5, 2, 9, 1]
numbers.sort()
print(numbers) # [1, 2, 5, 9]
names = ["Alice", "Bob", "alex"]
names.sort(key=str.lower)
print(names) #['alex', 'Alice', 'Bob']
numbers.sort(reverse=True) # [9, 5, 2, 1]
در نمونه بالا، دستور ()numbers.sort لیست اعداد را بهصورت صعودی (از کوچک به بزرگ) مرتب کرده است. در مورد لیست اسامی هم، names.sort(key=str.lower) باعث شده ترتیب حروف بدون توجه به بزرگ یا کوچک بودن آنها بررسی شود؛ یعنی پایتون هنگام مقایسه، همهی اسمها را به حالت lowercase تبدیل میکند. به همین دلیل “alex” قبل از “Alice” قرار گرفته، چون بعد از تبدیل به حروف کوچک، “alex” زودتر از “alice” در ترتیب حروف الفبا میآید . دقت کنید که متد ()sort مقدار None برمیگرداند؛ پس نباید آن را به متغیر دیگری نسبت دهید یا در محاسبهای استفاده کنید، چون این متد لیست را بهصورت درجا (in-place) تغییر میدهد.
وقتی پارامتر reverse=True را به متد ()sort بدهیم، لیست بهجای صعودی، بهصورت نزولی (از بزرگ به کوچک) مرتب میشود. این پارامتر فقط جهت مرتبسازی را عوض میکند و مثل همیشه، ()sort لیست را درجا (in-place) تغییر میدهد و مقدار برنمیگرداند (None).
۲-تابع sorted(iterable) که یک لیست جدید مرتبشده از iterable ورودی برمیگرداند و لیست اصلی را دستنخورده باقی میگذارد. این تابع نیز آرگومانهای اختیاری key و reverse را دارد. مثال معادل:
numbers = [5, 2, 9, 1]
sorted_numbers = sorted(numbers)
print(sorted_numbers) # [1, 2, 5, 9]
print(numbers) # خروجی: [1, 9, 2, 5] (لیست اصلی تغییری نکرده)
در اینجا تابع ()sorted لیست مرتبشده را در متغیر جدید sorted_numbers ذخیره کرده، در حالی که لیست اصلی numbers به همان صورت قبل باقی است این رویکرد وقتی مفید است که نمیخواهیم ترتیب اصلی را از دست بدهیم.
پشت صحنه، الگوریتم مرتبسازی پایتون Timsort است که عملکرد خوبی دارد و در بیشتر حالتها با پیچیدگی زمانی O(n logn) کار میکند. همچنین متد ()sort با برگرداندن None به شما یادآوری میکند که لیست مستقیماً تغییر کرده است؛ الگویی که در بسیاری از متدهای مربوط به ساختارهای قابلتغییر (mutable) دیده میشود.
عملیات درجا (In-Place) روی لیستها در پایتون
در پایتون، بعضی از عملیاتها لیست جدیدی نمیسازند، بلکه همان لیست موجود را تغییر میدهند. به این نوع عملیاتها، عملیات درجا (In-Place) گفته میشود.
به زبان ساده، در عملیات in-place ،لیست همان است ،فقط محتویات آن تغییر میکند.فرض کنید یک لیست داریم:numbers = [3, 1, 2]
اگر عملیاتی انجام دهیم که لیست را تغییر دهد اما لیست جدیدی نسازد. میگوییم آن عملیات in-place است.
numbers = [3, 1, 2]
numbers.sort()
print(numbers)
خروجی:
[1, 2, 3]
در این مثال متد sort() لیست را مرتب کرده است ولی لیست جدیدی ساخته نشده،همان لیست numbers تغییر کرده است.
یکی از ویژگیهای مهم متدهای in-place این است که چیزی برنمیگردانند. خروجی آنها همیشه None است.
numbers = [3, 1, 2]
result = numbers.sort()
print(result)
خروجی:
None
متد های inplace در لیست ها در پایتون:
numbers = [3, 1, 4, 2]
numbers.append(5)
print(numbers)
[3, 1, 4, 2, 5]
numbers.extend([6, 7])
print(numbers)
[3, 1, 4, 2, 5, 6, 7]
numbers.insert(1, 10)
print(numbers)
[3, 10, 1, 4, 2, 5, 6, 7]
numbers.remove(4)
print(numbers)
[3, 10, 1, 2, 5, 6, 7]
last_item = numbers.pop()
print(last_item)
7
print(numbers)
[3, 10, 1, 2, 5, 6]
numbers.sort()
print(numbers)
[1, 2, 3, 5, 6, 10]
numbers.reverse()
print(numbers)
[10, 6, 5, 3, 2, 1]
numbers.clear()
print(numbers)
[]
یک نکته ی مهم دیگر که باید توجه کنید این است که لیستها در پایتون ساختارهایی قابلتغییر (mutable) دارند. به همین دلیل، اگر هنگام پیمایش یک لیست (مثلاً با حلقه for) همان لیست را همزمان تغییر دهیم—مثل حذف یا اضافه کردن عنصر—ممکن است نتیجهی نهایی مطابق انتظار نباشد.دلیل این رفتار آن است که با حذف یا اضافه شدن عناصر، اندیس عناصر بعدی تغییر میکند و حلقه ممکن است بعضی عناصر را بررسی نکند یا از روی آنها عبور کند.
items = [1, 2, 3, 4]
for x in items:
if x % 2 == 0:
items.remove(x)
print(items)
خروجی:
[1, 3]
در این مثال، عدد 4 هرگز بررسی نشده است. دلیل این اتفاق این است که پس از حذف عدد 2، طول لیست کوتاهتر میشود و عناصر به جلو جابهجا میشوند. در نتیجه، حلقه مستقیماً به عنصر بعدی میرود و یکی از اعضا از قلم میافتد.
برای جلوگیری از چنین خطاهایی، چند روش استاندارد وجود دارد:
۱- پیمایش روی یک کپی از لیست
با این روش، تغییرات روی لیست اصلی انجام میشود اما پیمایش روی نسخهی ثابت صورت میگیرد.در این روش، عملیات پیمایش (iteration) روی نسخهای ثابت از لیست انجام میشود، در حالی که تغییرات موردنظر (مانند حذف عناصر) روی لیست اصلی اعمال میگردد.
علت کارآمد بودن این روش آن است که هنگام پیمایش مستقیم روی یک لیست، حذف یا اضافه شدن عناصر باعث تغییر طول لیست و جابهجایی ایندکسها میشود. این تغییرات میتوانند باعث شوند برخی عناصر توسط حلقه بررسی نشوند و در نتیجه، خطاهای منطقی و نتایج نادرست به وجود آید. با استفاده از یک کپی، ساختار لیستی که حلقه روی آن حرکت میکند ثابت باقی میماند و ترتیب پیمایش دچار اختلال نمیشود.
numbers = [1, 2, 3, 4, 5]
for n in numbers[:]:
if n % 2 == 0:
numbers.remove(n)
در این مثال، عبارت numbers[:] یک کپی سطحی از لیست اصلی ایجاد میکند. حلقهی for روی این کپی پیمایش میکند، در حالی که عملیات remove روی لیست اصلی (numbers) انجام میشود. به این ترتیب، حذف عناصر باعث تغییر ترتیب یا طول لیستی که حلقه روی آن حرکت میکند نمیشود و همهی عناصر بهدرستی بررسی خواهند شد.
۲-استفاده از List Comprehension
List Comprehension یک ساختار مختصر و قدرتمند در پایتون است که امکان ساخت لیست جدید بر اساس یک شرط مشخص را فراهم میکند.
numbers = [1, 2, 3, 4, 5]
new_numbers = [n for n in numbers if n % 2 != 0]
۳-استفاده از تابع filter
تابع filter نیز برای انتخاب عناصر بر اساس یک شرط استفاده میشود، با این تفاوت که نتیجهی آن بهصورت مستقیم یک لیست نیست و ابتدا یک iterable برمیگرداند.
numbers = [1, 2, 3, 4, 5]
new_numbers = list(filter(lambda n: n % 2 != 0, numbers))
List Comprehension معمولاً خواناتر و پایتونیکتر است.تابع filter بیشتر در سبک برنامهنویسی تابعی استفاده میشود. هر دو روش از نظر منطقی امن هستند، زیرا تغییری روی لیست اصلی ایجاد نمیکنند.
کپیکردن لیستها در پایتون
یکی از نکات بسیار مهم در کار با لیستها این است که انتساب ساده، کپی ایجاد نمیکند. وقتی یک لیست را به متغیر دیگری نسبت میدهیم، هر دو متغیر به یک شیء در حافظه اشاره میکنند.
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4]
print(b) #[1, 2, 3, 4]
در این حالت، متغیرهای a و b فقط دو نام متفاوت برای یک لیست هستند. بنابراین هر تغییری از طریق یکی، در دیگری هم دیده میشود. در پایتون، متغیرها خودشان داده را نگه نمیدارند؛ بلکه فقط به اشیا اشاره میکنند.
کپی سطحی (Shallow Copy)
برای ساخت یک لیست جدید که مستقل از لیست اصلی باشد، باید بهصورت صریح کپی انجام دهیم. سادهترین روشها برای کپی سطحی عبارتاند از:
new_list = old_list[:]
new_list = list(old_list)
new_list = old_list.copy()
در کپی سطحی ،خود لیست جدید ساخته میشود اما عناصر داخل آن کپی نمیشوند٫عناصر فقط ارجاع به اشیای قبلی هستند.اگر لیست شامل عناصر ساده مثل عدد یا رشته باشد (که immutable هستند)، کپی سطحی کاملاً کافی است. اما در لیستهای تودرتو یا شامل اشیای قابلتغییر، این روش میتواند مشکلساز شود.
کپی عمیق (Deep Copy)
برای زمانی که لیست شامل ساختارهای پیچیده یا لیستهای تودرتو است و میخواهیم تمام سطوح بهصورت مستقل کپی شوند، از کپی عمیق استفاده میکنیم.
در پایتون این کار با ماژول copy انجام میشود:
import copy
new_list = copy.deepcopy(old_list)
در کپی عمیق ،یک لیست جدید ساخته میشود و تمام عناصر قابلتغییر داخل آن نیز بهصورت بازگشتی کپی میشوند وهیچ ارجاع مشترکی با لیست اصلی باقی نمیماند.
import copy
a = [[1, 2], [3, 4]]
b = copy.copy(a)
c = copy.deepcopy(a)
a[0].append(5)
print(b) #[[1, 2, 5], [3, 4]]
print(c) #[[1, 2], [3, 4]]
در این مثال ،b یک کپی سطحی است و زیرلیستها همچنان مشترک هستند.c یک کپی عمیق است و کاملاً مستقل باقی میماند.
تمام متدهای لیست در پایتون
متد append در پایتون:
متد append برای اضافهکردن یک عنصر به انتهای لیست استفاده میشود. این عنصر میتواند عدد، رشته، لیست یا هر شیء دیگری باشد. این متد لیست جدیدی نمیسازد و مستقیماً همان لیست موجود را تغییر میدهد.اگر یک لیست را با append اضافه کنید، خود لیست بهعنوان یک عنصر اضافه میشود، نه عناصر داخل آن.
numbers = [1, 2, 3]
numbers.append(4)
print(numbers)
متد extend در لیست ها:
متد extend برای اضافهکردن چند عنصر بهصورت همزمان به لیست استفاده میشود. این متد عناصر iterable را یکییکی به انتهای لیست اضافه میکند.
numbers = [1, 2, 3]
numbers.append(4)
print(numbers)
نکته :
اگر ورودی شما یک iterable باشد با append کل iterable یک عنصر محسوب میشود ولی باextend ، عناصر به آن اضافه میشود.دقت کنید که استفاده زیاد از insert روی لیستهای بزرگ میتواند کند باشد، چون جابهجایی عناصر انجام میشود.
letters = ['a', 'b']
letters.extend('cd')
خروجی#
#['a', 'b', 'c', 'd']
letters.append('cd')
خروجی#
#['a', 'b', 'cd']
متد insert(index, value) در لیست:
این متد یک عنصر را در موقعیت مشخصشده در لیست قرار میدهد. عناصر بعد از آن به سمت راست جابهجا میشوند.
letters = ['a', 'c']
letters.insert(1, 'b')
print(letters)
#خروجی
#['a', 'b', 'c']
متد remove(value) در لیست
این متد اولین عنصری که مقدارش برابر با مقدار دادهشده باشد را از لیست حذف میکند و اگر مقدار موردنظر در لیست وجود نداشته باشد، برنامه با خطا متوقف میشود (ValueError).
items = [1, 2, 3, 2]
items.remove(2)
print(items)
#خروجی
#[1, 3, 2]
متد pop(index) در لیست پایتون
یک عنصر را از لیست حذف میکند و همان عنصر حذفشده را برمیگرداند. اگر پرانتز خالی باشد، آخرین عنصر حذف میشود.
numbers = [10, 20, 30]
x = numbers.pop()
print(x) #30
print(numbers) #[10, 20]
pop متدی هست که قابلیت برگرداندن مقدار را دارد .یعنی میتوان بفهمیم چه عنصری پاک می شود.در کد زیر میتوان منظور این جمله را بهتر درک کنید.
numbers = [10, 20, 30, 40]
value = numbers.pop()
print(value) #numbers = [10, 20, 30]
print(numbers) #40
numbers = [10, 20, 30, 40]
value = numbers.pop(1)
print(numbers) #numbers = [10, 30, 40]
print(value) #20
متد clear در لیست پایتون
این متد تمام عناصر داخل لیست را حذف میکند، اما خود لیست همچنان وجود دارد.
items = [1, 2, 3]
items.clear()
print(items) #[ ]
متد index در لیست
این متد،ایندکس اولین عضوی که درون پرانتز هست را نشان می دهد. اگر مقدار وجود نداشته باشد، خطا ایجاد میشود.
names = ['Ali', 'Sara', 'Reza']
print(names.index('Sara'))#1
متد count در لیست
متد count مشخص میکند یک مقدار چند بار در لیست تکرار شده است.
numbers = [1, 2, 2, 3, 2]
print(numbers.count(2)) #3
این متد عناصر لیست را در همان لیست مرتب میکند و لیست جدیدی نمیسازد.اگر در پرانتز چیزی نگذاریم از کم به زیاد مرتب می کند.واگر در پرانتز reverse=True بگذاریم از زیاد به کم مرتب می شود.
numbers = [2, 3, 1]
numbers.sort()
print(numbers) #[1, 2, 3]
numbers = [2, 3, 1]
numbers.sort(reverse=True)
print(numbers) #[3,2,1]
متد reverse در لیست
این متد ترتیب فعلی عناصر لیست را برعکس میکند، بدون توجه به مقدار آنها.اگر بخواهیم لیست را بدون متد مرتب کنیم از روش اسلایسینگ می رویم. [1-: : ]a
letters = ['a', 'b', 'c']
letters.reverse()
print(letters) #['c', 'b', 'a']
متد copy:
این متد یک کپی ازلیست میسازد.
a = [1, 2, 3]
b = a.copy() #b = [1, 2, 3]
تابع del در پایتون:
del در پایتون یک تابع یا متد نیست، بلکه یک دستور زبانی (keyword) است که برای حذف ایندکس عناصر یا خود لیست استفاده میشود. وقتی از del استفاده میکنیم، ساختار لیست بهطور مستقیم تغییر میکند و عنصر موردنظر بهطور کامل حذف میشود. نتیجهی این دستور بهصورت مقدار بازگشتی نمایش داده نمیشود و تنها از طریق تغییر ایجادشده در لیست قابل مشاهده است.
numbers = [1, 2, 3, 4]
del numbers[1]
print(numbers) #[1, 3, 4]
تابع len:
تابع len تعداد عناصر داخل لیست را برمیگرداند. دقت کنید که len متد لیست نیست، بلکه یک تابع سراسری در پایتون است.
numbers = [11, 120, 13, 4]
print(len(numbers)) #4
تابع max:
تابع max در پایتون برای پیدا کردن بزرگترین مقدار موجود در یک لیست .
numbers = [3, 7, 2, 9]
print(max(numbers)) #9
تابع min:
تابع min کوچکترین مقدار موجود در لیست را برمیگرداند.
numbers = [3, 7, 2, 9]
print(min(numbers)) #2
تابع sum:
تابع sum برای محاسبهی مجموع عناصر عددی یک لیست استفاده میشود. این تابع تمام مقادیر عددی را با هم جمع میکند و نتیجهی نهایی را برمیگرداند، بدون آنکه تغییری در لیست ایجاد شود. به همین دلیل، sum فقط یک عملیات محاسباتی است و اثری روی ساختار داده ندارد.
numbers = [1, 2, 3, 4]
print(sum(numbers)) #10
مقایسه لیست با تاپل و Set در پایتون
| ویژگیها | لیست (List) | تاپل (Tuple) | مجموعه (Set) |
|---|---|---|---|
| ترتیب (Ordering) | ترتیب حفظ میشود (ایندکسپذیر). | ترتیب حفظ میشود (ایندکسپذیر). | بدون ترتیب خاص (نامرتّب). |
| تغییرپذیری | تغییرپذیر – قابل اصلاح پس از ایجاد. | تغییرناپذیر – پس از ایجاد قابل تغییر نیست. | تغییرپذیر – میتوان افزود/حذف کرد (اما نوع خاص frozenset تغییرناپذیر است). |
| اجازهی تکرار مقادیر | بله، میتواند مقادیر تکراری داشته باشد. | بله، میتواند مقادیر تکراری داشته باشد. | خیر، منحصربفرد – ورود تکراری نادیده گرفته میشود. |
| اندیسگذاری و برش | پشتیبانی میشود. | پشتیبانی میشود (همانند لیست). | پشتیبانی نمیشود (به علت عدم ترتیب). |
| عملیات عضویت (in) | O(n) خطی (باید بگردد). |
O(n) خطی (مشابه لیست). |
به طور میانگین O(1) (خیلی سریع). |