آموزش جامع ساختارهای داده در پایتون برای مبتدیان

مقدمه‌ای بر ساختارهای داده در پایتون

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

در این مقاله، پنج ساختار داده اصلی پایتون را بررسی خواهیم کرد:

  • لیست (List)
  • تاپل (Tuple)
  • دیکشنری (Dictionary)
  • مجموعه (Set)
  • صف (Queue)

با یادگیری این ساختارهای داده، شما می‌توانید الگوریتم‌های کارآمدتری طراحی کنید و مسائل برنامه‌نویسی را با سهولت بیشتری حل کنید.

نکته: پایتون یک زبان سطح بالا با تایپ پویا است که ساختارهای داده آن به صورت پیش‌فرض بسیار قدرتمند هستند. شما می‌توانید از این ساختارها بدون نیاز به پیاده‌سازی مجدد آنها استفاده کنید.

لیست (List) – آرایه‌های قابل تغییر و انعطاف‌پذیر

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

ویژگی‌های لیست

  • قابل تغییر (Mutable) – می‌توان پس از ایجاد، عناصر آن را تغییر داد
  • مرتب (Ordered) – ترتیب عناصر حفظ می‌شود
  • اندیس‌گذاری شده (Indexed) – دسترسی به عناصر با اندیس
  • امکان ذخیره انواع مختلف داده‌ها در یک لیست

ایجاد لیست

# ایجاد لیست خالی
my_list = []

# ایجاد لیست با مقادیر اولیه
numbers = [1, 2, 3, 4, 5]
mixed = [1, "پایتون", 3.14, True]

# ایجاد لیست با استفاده از تابع list()
chars = list("پایتون")  # تبدیل رشته به لیست کاراکترها

دسترسی به عناصر لیست

fruits = ["سیب", "موز", "پرتقال", "انگور", "کیوی"]

# دسترسی با اندیس (از صفر شروع می‌شود)
print(fruits[0])  # سیب
print(fruits[2])  # پرتقال

# اندیس منفی (شمارش از انتها)
print(fruits[-1])  # کیوی
print(fruits[-2])  # انگور

# برش (Slicing)
print(fruits[1:3])  # ['موز', 'پرتقال']
print(fruits[:3])   # ['سیب', 'موز', 'پرتقال']
print(fruits[2:])   # ['پرتقال', 'انگور', 'کیوی']

عملیات‌های پرکاربرد روی لیست

numbers = [1, 2, 3]

# افزودن عناصر
numbers.append(4)      # [1, 2, 3, 4]
numbers.insert(1, 10)  # [1, 10, 2, 3, 4]
numbers.extend([5, 6]) # [1, 10, 2, 3, 4, 5, 6]

# حذف عناصر
numbers.remove(10)     # [1, 2, 3, 4, 5, 6]
popped = numbers.pop() # حذف و برگرداندن آخرین عنصر
print(popped)          # 6
print(numbers)         # [1, 2, 3, 4, 5]
del numbers[0]         # [2, 3, 4, 5]

# سایر عملیات‌ها
numbers.sort()         # مرتب‌سازی صعودی
numbers.reverse()      # معکوس کردن ترتیب
count = numbers.count(3) # شمارش تعداد تکرار عدد 3
position = numbers.index(4) # یافتن اندیس عدد 4

مثال کاربردی: محاسبه میانگین نمرات

grades = [18, 15, 20, 17, 19, 14]

# محاسبه میانگین نمرات
average = sum(grades) / len(grades)
print(f"میانگین نمرات: {average}")  # میانگین نمرات: 17.166666666666668

# یافتن بالاترین و پایین‌ترین نمره
highest = max(grades)
lowest = min(grades)
print(f"بالاترین نمره: {highest}")  # بالاترین نمره: 20
print(f"پایین‌ترین نمره: {lowest}")  # پایین‌ترین نمره: 14

تاپل (Tuple) – آرایه‌های غیر قابل تغییر

تاپل‌ها مشابه لیست‌ها هستند، با این تفاوت اصلی که غیرقابل تغییر (immutable) می‌باشند. یعنی پس از ایجاد نمی‌توان عناصر آن را تغییر داد.

ویژگی‌های تاپل

  • غیرقابل تغییر (Immutable) – پس از ایجاد، نمی‌توان عناصر را تغییر داد
  • مرتب (Ordered) – ترتیب عناصر حفظ می‌شود
  • اندیس‌گذاری شده (Indexed) – دسترسی به عناصر با اندیس
  • سریع‌تر از لیست‌ها
  • امنیت بیشتر برای داده‌های ثابت

ایجاد تاپل

# ایجاد تاپل با پرانتز
coordinates = (10, 20)
person = ("علی", 30, "تهران")

# ایجاد تاپل بدون پرانتز
coordinates = 10, 20
person = "علی", 30, "تهران"

# ایجاد تاپل تک عنصری (حتماً با کاما)
single_item = (42,)  # تاپل تک عنصری
not_a_tuple = (42)   # این یک عدد است، نه تاپل!

# ایجاد تاپل با تابع tuple()
chars = tuple("پایتون")  # تبدیل رشته به تاپل

دسترسی به عناصر تاپل

rgb = (255, 0, 128)

# دسترسی با اندیس
red = rgb[0]    # 255
green = rgb[1]  # 0
blue = rgb[2]   # 128

# اندیس منفی و برش، مشابه لیست
print(rgb[-1])  # 128
print(rgb[0:2]) # (255, 0)
تفاوت با لیست: شما نمی‌توانید عناصر تاپل را پس از ایجاد تغییر دهید:

coordinates = (10, 20)
# این خط خطا می‌دهد:
# coordinates[0] = 15  # TypeError: 'tuple' object does not support item assignment

عملیات‌های پرکاربرد روی تاپل

# شمارش تعداد تکرار
counts = (1, 2, 3, 2, 2, 4, 2)
print(counts.count(2))  # 4

# یافتن اندیس اولین تکرار
print(counts.index(3))  # 2

# طول تاپل
print(len(counts))  # 7

# تبدیل به لیست (اگر نیاز به تغییر باشد)
counts_list = list(counts)
counts_list[0] = 10  # حالا می‌توان تغییر داد
modified_tuple = tuple(counts_list)  # تبدیل مجدد به تاپل

مثال کاربردی: نگهداری مختصات جغرافیایی

# ذخیره مختصات چند شهر
cities = [
    ("تهران", 35.6892, 51.3890),
    ("اصفهان", 32.6539, 51.6660),
    ("شیراز", 29.5918, 52.5837),
    ("مشهد", 36.2605, 59.6168)
]

# نمایش اطلاعات شهرها
for city, latitude, longitude in cities:
    print(f"شهر {city} در مختصات جغرافیایی {latitude}°N, {longitude}°E قرار دارد.")

دیکشنری (Dictionary) – ساختار کلید-مقدار

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

ویژگی‌های دیکشنری

  • قابل تغییر (Mutable) – امکان اضافه، حذف و تغییر عناصر
  • نگاشت کلید به مقدار (key-value mapping)
  • کلیدها باید منحصر به فرد و غیرقابل تغییر باشند (مثل رشته، عدد یا تاپل)
  • مقادیر می‌توانند هر نوع داده‌ای باشند
  • از پایتون 3.7 به بعد، ترتیب اضافه شدن عناصر حفظ می‌شود

ایجاد دیکشنری

# ایجاد دیکشنری خالی
empty_dict = {}
another_empty = dict()

# ایجاد دیکشنری با مقادیر اولیه
student = {
    "name": "سارا",
    "age": 20,
    "courses": ["ریاضی", "فیزیک", "برنامه‌نویسی"],
    "active": True
}

# ایجاد با تابع dict()
person = dict(name="علی", age=25, city="تهران")

دسترسی به عناصر دیکشنری

student = {
    "name": "سارا",
    "age": 20,
    "courses": ["ریاضی", "فیزیک", "برنامه‌نویسی"]
}

# دسترسی با کلید
print(student["name"])  # سارا

# دسترسی با متد get (امن‌تر - در صورت نبود کلید، خطا نمی‌دهد)
print(student.get("age"))  # 20
print(student.get("grade", "نامشخص"))  # نامشخص (مقدار پیش‌فرض)

تغییر و افزودن عناصر

student = {"name": "سارا", "age": 20}

# تغییر مقدار موجود
student["age"] = 21

# افزودن کلید-مقدار جدید
student["major"] = "کامپیوتر"

# افزودن چندین کلید-مقدار
student.update({"phone": "09123456789", "city": "تهران"})

print(student)
# {'name': 'سارا', 'age': 21, 'major': 'کامپیوتر', 'phone': '09123456789', 'city': 'تهران'}

حذف عناصر

student = {
    "name": "سارا",
    "age": 20,
    "major": "کامپیوتر",
    "city": "تهران"
}

# حذف با کلید مشخص
del student["city"]

# حذف و برگرداندن مقدار آن
major = student.pop("major")
print(major)  # کامپیوتر

# حذف و برگرداندن آخرین جفت کلید-مقدار
last_item = student.popitem()
print(last_item)  # ('age', 20)

# پاک کردن کل دیکشنری
student.clear()

روش‌های پیمایش دیکشنری

student = {
    "name": "سارا",
    "age": 20,
    "major": "کامپیوتر",
    "gpa": 18.5
}

# پیمایش کلیدها
for key in student:
    print(key, student[key])

# پیمایش با items() - دریافت کلید و مقدار همزمان
for key, value in student.items():
    print(f"{key}: {value}")

# دریافت همه کلیدها
keys = student.keys()
print(list(keys))  # ['name', 'age', 'major', 'gpa']

# دریافت همه مقادیر
values = student.values()
print(list(values))  # ['سارا', 20, 'کامپیوتر', 18.5]

مثال کاربردی: شمارش فراوانی کلمات

text = "پایتون یک زبان برنامه نویسی قدرتمند است پایتون برای تحلیل داده هم مناسب است"
words = text.split()

# شمارش فراوانی کلمات
word_count = {}
for word in words:
    if word in word_count:
        word_count[word] += 1
    else:
        word_count[word] = 1

# روش ساده‌تر با defaultdict
from collections import defaultdict
word_count = defaultdict(int)
for word in words:
    word_count[word] += 1

# نمایش نتایج
for word, count in word_count.items():
    print(f"{word}: {count}")

مجموعه (Set) – مجموعه‌های ریاضی در پایتون

مجموعه‌ها ساختارهای داده بدون ترتیب و بدون تکرار هستند که برای ذخیره مقادیر منحصر به فرد استفاده می‌شوند.

ویژگی‌های مجموعه

  • بدون ترتیب (Unordered) – ترتیب عناصر تضمین نمی‌شود
  • بدون تکرار (Unique) – هر عنصر فقط یک بار ظاهر می‌شود
  • قابل تغییر (Mutable) – امکان افزودن و حذف عناصر
  • عناصر باید غیرقابل تغییر باشند (مانند عدد، رشته، تاپل)
  • عملیات‌های مجموعه‌ای مانند اشتراک، اجتماع و تفاضل

ایجاد مجموعه

# ایجاد مجموعه خالی
empty_set = set()  # توجه: {} یک دیکشنری خالی است، نه مجموعه!

# ایجاد مجموعه با مقادیر اولیه
fruits = {"سیب", "موز", "پرتقال", "سیب"}  # تکرار "سیب" حذف می‌شود
print(fruits)  # {'پرتقال', 'سیب', 'موز'}

# ایجاد مجموعه از سایر ساختارهای داده
numbers = set([1, 2, 3, 2, 1, 4])
chars = set("پایتون")

عملیات‌های اصلی روی مجموعه‌ها

fruits = {"سیب", "موز", "پرتقال"}

# افزودن عناصر
fruits.add("انگور")  # {'سیب', 'موز', 'پرتقال', 'انگور'}
fruits.update(["کیوی", "هلو"])  # افزودن چند عنصر

# حذف عناصر
fruits.remove("موز")  # خطا اگر "موز" وجود نداشته باشد
fruits.discard("موز")  # بدون خطا حتی اگر وجود نداشته باشد
popped = fruits.pop()  # حذف و برگرداندن یک عنصر (عنصر انتخابی قابل پیش‌بینی نیست)
fruits.clear()  # پاک کردن کل مجموعه

عملیات‌های مجموعه‌ای

A = {1, 2, 3, 4, 5}
B = {4, 5, 6, 7, 8}

# اجتماع - عناصری که در A یا B هستند
union = A | B  # یا A.union(B)
print(union)  # {1, 2, 3, 4, 5, 6, 7, 8}

# اشتراک - عناصری که هم در A و هم در B هستند
intersection = A & B  # یا A.intersection(B)
print(intersection)  # {4, 5}

# تفاضل - عناصری که در A هستند ولی در B نیستند
difference = A - B  # یا A.difference(B)
print(difference)  # {1, 2, 3}

# تفاضل متقارن - عناصری که فقط در یکی از دو مجموعه هستند
symmetric_diff = A ^ B  # یا A.symmetric_difference(B)
print(symmetric_diff)  # {1, 2, 3, 6, 7, 8}

عملیات‌های منطقی و مقایسه‌ای

A = {1, 2, 3}
B = {1, 2, 3, 4, 5}
C = {1, 2, 3}

# بررسی زیرمجموعه بودن
print(A.issubset(B))  # True - A زیرمجموعه B است
print(B.issubset(A))  # False

# بررسی ابرمجموعه بودن
print(B.issuperset(A))  # True - B ابرمجموعه A است
print(A.issuperset(B))  # False

# بررسی برابری
print(A == C)  # True - A و C یکسان هستند

# بررسی وجود عنصر
print(1 in A)  # True
print(6 in A)  # False

مثال کاربردی: یافتن عناصر منحصر به فرد

# لیستی از ایمیل‌های کاربران
emails = [
    "user1@example.com",
    "user2@example.com",
    "user1@example.com",
    "user3@example.com",
    "user2@example.com",
    "user4@example.com"
]

# تبدیل به مجموعه برای حذف تکرارها
unique_emails = set(emails)
print(f"تعداد ایمیل‌های منحصر به فرد: {len(unique_emails)}")  # 4

# نمایش ایمیل‌های یکتا
for email in unique_emails:
    print(email)

صف (Queue) – ساختارهای FIFO در پایتون

صف یک ساختار داده FIFO (First-In-First-Out) است که در آن، اولین عنصری که وارد می‌شود، اولین عنصری است که خارج می‌شود. پایتون چندین روش برای پیاده‌سازی صف ارائه می‌دهد.

ویژگی‌های صف

  • ساختار FIFO (First-In-First-Out)
  • دو عملیات اصلی: enqueue (افزودن) و dequeue (حذف)
  • کاربرد در مدیریت وظایف، پردازش همزمان و الگوریتم‌های جستجو

پیاده‌سازی صف با کتابخانه collections.deque

from collections import deque

# ایجاد یک صف
queue = deque()

# افزودن عناصر به انتهای صف (enqueue)
queue.append("وظیفه 1")
queue.append("وظیفه 2")
queue.append("وظیفه 3")

print(queue)  # deque(['وظیفه 1', 'وظیفه 2', 'وظیفه 3'])

# حذف از ابتدای صف (dequeue)
first_task = queue.popleft()
print(f"انجام {first_task}")  # انجام وظیفه 1
print(queue)  # deque(['وظیفه 2', 'وظیفه 3'])

# بررسی طول صف
print(len(queue))  # 2

پیاده‌سازی صف با ماژول queue

import queue

# ایجاد یک صف
q = queue.Queue()

# افزودن عناصر (enqueue)
q.put("وظیفه 1")
q.put("وظیفه 2")
q.put("وظیفه 3")

# حذف عناصر (dequeue)
first_task = q.get()
print(f"انجام {first_task}")  # انجام وظیفه 1

# بررسی اندازه صف
print(q.qsize())  # 2 (اگر سیستم عامل پشتیبانی کند)

# بررسی خالی بودن
print(q.empty())  # False

# بررسی پر بودن (برای صف‌های با ظرفیت محدود)
print(q.full())  # False

انواع مختلف صف در ماژول queue

import queue

# صف معمولی (FIFO)
fifo_queue = queue.Queue()

# صف با اولویت (Priority Queue)
priority_queue = queue.PriorityQueue()
priority_queue.put((2, "وظیفه با اولویت متوسط"))
priority_queue.put((1, "وظیفه با اولویت بالا"))
priority_queue.put((3, "وظیفه با اولویت پایین"))

# عناصر به ترتیب اولویت خارج می‌شوند
while not priority_queue.empty():
    print(priority_queue.get())
# (1, 'وظیفه با اولویت بالا')
# (2, 'وظیفه با اولویت متوسط')
# (3, 'وظیفه با اولویت پایین')

# صف LIFO (پشته)
lifo_queue = queue.LifoQueue()
lifo_queue.put("عنصر 1")
lifo_queue.put("عنصر 2")
lifo_queue.put("عنصر 3")

# عناصر به ترتیب معکوس خارج می‌شوند
print(lifo_queue.get())  # عنصر 3

مثال کاربردی: شبیه‌سازی سیستم پردازش وظایف

import queue
import time
import threading
import random

# ایجاد صف وظایف
task_queue = queue.Queue()

# تابع تولیدکننده وظایف
def task_producer():
    for i in range(1, 6):
        task = f"وظیفه {i}"
        print(f"افزودن {task} به صف")
        task_queue.put(task)
        time.sleep(random.uniform(0.5, 1.5))  # شبیه‌سازی زمان تولید وظیفه

# تابع پردازش‌کننده وظایف
def task_consumer():
    while True:
        # بررسی اتمام وظایف
        if task_queue.empty() and producer_done:
            break
            
        try:
            # دریافت وظیفه بعدی
            task = task_queue.get(block=False)
            print(f"پردازش {task}")
            time.sleep(random.uniform(1, 2))  # شبیه‌سازی زمان پردازش
            task_queue.task_done()
        except queue.Empty:
            # صف خالی است، اندکی صبر کنیم
            time.sleep(0.5)

# اجرای تولیدکننده در یک thread جداگانه
producer_done = False
producer_thread = threading.Thread(target=task_producer)
producer_thread.start()

# اجرای مصرف‌کننده در thread اصلی
time.sleep(1)  # اندکی صبر برای شروع تولید وظایف
consumer_thread = threading.Thread(target=task_consumer)
consumer_thread.start()

# منتظر اتمام کار تولیدکننده
producer_thread.join()
producer_done = True

# منتظر اتمام کار مصرف‌کننده
consumer_thread.join()

print("تمام وظایف با موفقیت پردازش شدند.")

مقایسه ساختارهای داده و موارد استفاده

انتخاب ساختار داده مناسب می‌تواند تأثیر قابل توجهی بر کارایی و خوانایی کد شما داشته باشد. در این بخش، به مقایسه این ساختارها می‌پردازیم.

ساختار داده ویژگی‌های کلیدی زمان دسترسی موارد استفاده
لیست (List) مرتب، قابل تغییر، اندیس‌گذاری شده O(1) برای دسترسی به اندیس، O(n) برای جستجو
  • ذخیره مجموعه‌ای مرتب از عناصر
  • زمانی که ترتیب مهم است
  • زمانی که نیاز به تغییر مکرر دارید
تاپل (Tuple) مرتب، غیرقابل تغییر، اندیس‌گذاری شده O(1) برای دسترسی به اندیس، O(n) برای جستجو
  • داده‌های ثابت که نباید تغییر کنند
  • کلید دیکشنری یا عنصر مجموعه
  • بازگرداندن چندین مقدار از تابع
دیکشنری (Dictionary) کلید-مقدار، قابل تغییر، کلیدهای منحصر به فرد O(1) در حالت متوسط
  • نگاشت کلید به مقدار
  • جستجوی سریع با کلید
  • شمارش فراوانی
  • ذخیره ویژگی‌های اشیاء
مجموعه (Set) بدون ترتیب، بدون تکرار، عملیات‌های مجموعه‌ای O(1) در حالت متوسط
  • حذف تکرارها
  • تست عضویت سریع
  • عملیات‌های ریاضی (اشتراک، اجتماع، تفاضل)
صف (Queue) FIFO، اولین ورودی اولین خروجی O(1) برای افزودن و حذف
  • مدیریت وظایف به ترتیب ورود
  • الگوریتم‌های جستجوی سطحی (BFS)
  • پردازش همزمان و مدیریت رویداد

انتخاب ساختار داده مناسب:

  • لیست را زمانی انتخاب کنید که ترتیب مهم است و نیاز به اندیس‌گذاری دارید.
  • تاپل را زمانی انتخاب کنید که داده‌های شما ثابت هستند و نباید تغییر کنند.
  • دیکشنری را زمانی انتخاب کنید که نیاز به جستجوی سریع با کلید دارید.
  • مجموعه را زمانی انتخاب کنید که نیاز به حذف تکرارها یا عملیات‌های مجموعه‌ای دارید.
  • صف را زمانی انتخاب کنید که می‌خواهید عناصر را به ترتیب ورود پردازش کنید.

جمع‌بندی و قدم‌های بعدی

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

برای تسلط بیشتر بر ساختارهای داده در پایتون، موارد زیر را می‌توانید در نظر بگیرید:

  • تمرین با مثال‌های واقعی و حل مسائل برنامه‌نویسی
  • مطالعه ساختارهای داده پیشرفته‌تر مانند:
    • پشته (Stack)
    • درخت و گراف
    • هش‌مپ
    • لیست پیوندی
  • بررسی پیچیدگی زمانی و مکانی هر ساختار داده
  • آشنایی با کتابخانه‌های پیشرفته مانند NumPy و Pandas برای کار با داده‌های بزرگ

به یاد داشته باشید که انتخاب ساختار داده مناسب یکی از مهمترین تصمیمات در طراحی الگوریتم است. با تسلط بر این ساختارها، می‌توانید به عنوان یک برنامه‌نویس پایتون کارآمدتر عمل کنید.

منابع بیشتر برای مطالعه:

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *