← Kursa Dön
📄 Text · 12 min

Veri Yapıları Karşılaştırması

Python'da dört temel veri yapısı öğrendin: list, tuple, set ve dict. Her birinin güçlü ve zayıf yanları var. Doğru veri yapısını seçmek, kodunun hem performansını hem okunabilirliğini dramatik şekilde etkiler. Bu derste hepsini bir araya getirip karşılaştıracağız ve "ne zaman hangisini kullanmalıyım" sorusuna net cevaplar vereceğiz.

Bir alet çantası düşün: Çekiç, tornavida, pense, testere — hepsi aletler ama hepsi farklı işler için. Vidayı çekiçle çakmaya çalışırsan, işi bitirirsin belki ama hem vida zarar görür hem sen yorulursun. Doğru alet doğru iş için. Veri yapıları da öyle — her birinin parladığı bir alan var.


Büyük Karşılaştırma Tablosu

ÖzellikListTupleSetDict
Sıralı✅ Evet✅ Evet❌ Hayır✅ Evet (3.7+)
Mutable✅ Evet❌ Hayır✅ Evet✅ Evet
Duplicate✅ İzin verir✅ İzin verir❌ Yok❌ Key yok*
İndekslemea[0]a[0]❌ Yokd["key"]
Hashable❌ Hayır✅ Evet**❌ Hayır❌ Hayır
Arama hızıO(n)O(n)O(1)O(1) key
Ekleme hızıO(1) sonaO(1)O(1)
Silme hızıO(n)O(1)O(1)
BellekOrtaDüşükYüksekYüksek
Söz dizimi[1, 2](1, 2){1, 2}{"a": 1}
Boş[]()set(){}

\* Dict key'leri benzersiz, value'lar tekrar edebilir \** Tüm elemanları hashable ise


Big-O: Zaman Karmaşıklığı Detayları

List

Erişim (indeks):     O(1)   — a[i] anında
Arama (in):          O(n)   — en kötü durumda tüm listeyi tarar
Append (sona ekle):  O(1)   — amortized (bazen O(n) resize)
Insert (başa ekle):  O(n)   — tüm elemanlar kaydırılır
Delete (indeks):     O(n)   — kaydırma gerekir
Delete (değer):      O(n)   — önce ara, sonra kaydır
Sort:                O(n log n) — Timsort

Tuple

Erişim (indeks):     O(1)   — a[i] anında
Arama (in):          O(n)   — tüm elemanları kontrol eder
Oluşturma:           O(n)   — ama list'ten ~5-10x hızlı (constant factor)

Set

Arama (in):          O(1)   — hash tablosu
Ekleme (add):        O(1)   — amortized
Silme (remove):      O(1)   — amortized
Union (|):           O(len(a) + len(b))
Intersection (&):    O(min(len(a), len(b)))
Difference (-):      O(len(a))

Dict

Erişim (key):        O(1)   — hash tablosu
Arama (in):          O(1)   — sadece key'lerde
Ekleme:              O(1)   — amortized
Silme:               O(1)   — amortized
Keys/Values/Items:   O(1)   — view döndürür (lazy)
İterasyon:           O(n)

Somut Performans Karşılaştırması

import time

N = 1_000_000

# Oluşturma
data = list(range(N))

# Arama performansı
hedef = N - 1  # En kötü durum

# List
start = time.perf_counter()
hedef in data
list_time = time.perf_counter() - start

# Set
data_set = set(data)
start = time.perf_counter()
hedef in data_set
set_time = time.perf_counter() - start

# Dict
data_dict = {x: True for x in data}
start = time.perf_counter()
hedef in data_dict
dict_time = time.perf_counter() - start

print(f"List arama:  {list_time:.6f}s")    # ~0.01s
print(f"Set arama:   {set_time:.8f}s")     # ~0.0000001s
print(f"Dict arama:  {dict_time:.8f}s")    # ~0.0000001s
print(f"\nSet, listeden {list_time/set_time:.0f}x hızlı!")

Ne Zaman Hangi Yapı? Karar Ağacı

Hangi veri yapısını seçeceğini belirlemek için şu soruları sırayla sor:

1. Anahtar-değer çifti mi?

EVETdict kullan

# İsim-not eşleştirmesi
notlar = {"Ahmet": 85, "Ayşe": 92}

2. Elemanlar benzersiz olmalı mı?

EVETset kullan

# Benzersiz IP adresleri
aktif_ipler = {"192.168.1.1", "10.0.0.1", "192.168.1.1"}
# Otomatik: {'192.168.1.1', '10.0.0.1'}

3. Veri değişecek mi?

HAYIRtuple kullan

# Koordinat — değişmemeli
konum = (41.01, 28.97)

4. Sıralı ve değişken koleksiyon mu?

EVETlist kullan

# Yapılacaklar listesi — ekleme/çıkarma var
gorevler = ["alışveriş", "spor", "ders"]
gorevler.append("temizlik")

Karar Ağacı Diyagramı

                    Başla
                      │
                 Key-value mi?
                    / \
                  Evet  Hayır
                   │      │
                 dict   Benzersiz mi?
                         / \
                       Evet  Hayır
                        │      │
                       set   Değişecek mi?
                              / \
                            Evet  Hayır
                             │      │
                           list   tuple

Daha Detaylı Senaryolar

# Sıklık sayımı → Counter (dict alt sınıfı)
from collections import Counter
frekans = Counter(["a", "b", "a", "c", "a"])

# FIFO kuyruğu → deque (list yerine!)
from collections import deque
kuyruk = deque(["a", "b", "c"])
kuyruk.append("d")      # Sağa ekle
kuyruk.appendleft("z")  # Sola ekle
kuyruk.popleft()         # Soldan çıkar — O(1)!

# Sabit anahtar seti → frozenset
IZINLER = frozenset({"read", "write", "execute"})

# Birden fazla config katmanı → ChainMap
from collections import ChainMap
varsayilan = {"tema": "light", "dil": "tr"}
kullanici = {"tema": "dark"}
config = ChainMap(kullanici, varsayilan)

collections Modülü: Gelişmiş Veri Yapıları

Python'un collections modülü, temel veri yapılarının özelleştirilmiş versiyonlarını sunar.

deque (Double-Ended Queue)

Liste iki uçtan ekleme/çıkarma için optimize değildir — başa ekleme O(n). deque iki uçtan da O(1):

from collections import deque

# Oluşturma
d = deque([1, 2, 3, 4, 5])

# İki uçtan ekleme — O(1)
d.append(6)        # Sağa: [1, 2, 3, 4, 5, 6]
d.appendleft(0)    # Sola: [0, 1, 2, 3, 4, 5, 6]

# İki uçtan çıkarma — O(1)
d.pop()             # Sağdan: 6
d.popleft()         # Soldan: 0

# Döndürme (rotate)
d = deque([1, 2, 3, 4, 5])
d.rotate(2)         # Sağa 2 adım: [4, 5, 1, 2, 3]
d.rotate(-2)        # Sola 2 adım: [1, 2, 3, 4, 5]

# Maksimum boyut sınırı (circular buffer)
son_5 = deque(maxlen=5)
for i in range(10):
    son_5.append(i)
print(son_5)  # deque([5, 6, 7, 8, 9], maxlen=5)
# Eski elemanlar otomatik düşer!

Ne zaman deque kullan?

  • Baştan ekleme/çıkarma sık yapılıyorsa (list'te O(n), deque'te O(1))

  • Son N elemanı tutmak istiyorsan (maxlen)

  • BFS (Breadth-First Search) algoritmasında kuyruk olarak

# List vs Deque performans
import timeit

def list_sol_ekleme():
    l = []
    for i in range(10000):
        l.insert(0, i)

def deque_sol_ekleme():
    d = deque()
    for i in range(10000):
        d.appendleft(i)

print(f"List:  {timeit.timeit(list_sol_ekleme, number=10):.3f}s")
print(f"Deque: {timeit.timeit(deque_sol_ekleme, number=10):.3f}s")
# List: ~1.5s | Deque: ~0.005s — 300x fark!

OrderedDict

Python 3.7+ sonrasında dict ekleme sırasını garanti eder, o zaman neden OrderedDict var?

from collections import OrderedDict

# Normal dict'ten farkı: karşılaştırma sırayı dikkate alır
d1 = {"a": 1, "b": 2}
d2 = {"b": 2, "a": 1}
print(d1 == d2)  # True — normal dict sırayı karşılaştırmaz

od1 = OrderedDict(a=1, b=2)
od2 = OrderedDict(b=2, a=1)
print(od1 == od2)  # False — OrderedDict sıra önemli!

# move_to_end: Elemanı sona veya başa taşı
od = OrderedDict(a=1, b=2, c=3)
od.move_to_end("a")          # Sona taşı
print(list(od.keys()))        # ['b', 'c', 'a']
od.move_to_end("a", last=False)  # Başa taşı
print(list(od.keys()))        # ['a', 'b', 'c']

ChainMap

Birden fazla dictionary'yi zincirleme arar — kopyalamadan:

from collections import ChainMap

# Config katmanları: kullanıcı > proje > global
global_config = {"tema": "light", "dil": "tr", "font": "Arial", "size": 14}
proje_config = {"tema": "dark", "font": "Fira Code"}
kullanici_config = {"size": 16}

config = ChainMap(kullanici_config, proje_config, global_config)

print(config["size"])   # 16 (kullanıcı)
print(config["tema"])   # dark (proje)
print(config["dil"])    # tr (global)
print(config["font"])   # Fira Code (proje)

# Tüm benzersiz key'ler
print(list(config.keys()))  # Tüm katmanlardaki tüm key'ler

ChainMap gerçek birleştirme yapmaz — her aramada zinciri baştan tarar. Bu, dinamik config sistemlerinde çok kullanışlı: bir katmanı değiştirdiğinde hemen yansır.

Counter (Hatırlatma)

from collections import Counter

kelimeler = "the quick brown fox jumps over the lazy dog the fox".split()
c = Counter(kelimeler)

print(c.most_common(3))   # [('the', 3), ('fox', 2), ('quick', 1)]

# Aritmetik
c1 = Counter(a=4, b=2, c=0, d=-2)
c2 = Counter(a=1, b=2, c=3)
print(c1 + c2)  # Counter({'a': 5, 'b': 4, 'c': 3})
print(c1 - c2)  # Counter({'a': 3}) — 0 ve negatifler atılır

array Modülü: Numpy'a Köprü

Python'un array modülü, C tipi dizi sunar — tüm elemanlar aynı tipte olmalı. List'ten daha az bellek kullanır ama çok da kullanışlı değildir. Asıl güç numpy'dadır.

import array

# Tip kodu ile oluştur
# 'i' = signed int, 'f' = float, 'd' = double
int_dizisi = array.array('i', [1, 2, 3, 4, 5])
float_dizisi = array.array('d', [1.0, 2.5, 3.7])

print(int_dizisi)  # array('i', [1, 2, 3, 4, 5])
print(float_dizisi[0])  # 1.0

# Tip kısıtlaması
# int_dizisi.append(3.14)  # TypeError — sadece int!
int_dizisi.append(6)
print(int_dizisi)  # array('i', [1, 2, 3, 4, 5, 6])

Bellek Karşılaştırması

import sys
import array

# 1000 int
liste = list(range(1000))
arr = array.array('i', range(1000))

print(f"List:  {sys.getsizeof(liste):,} byte")   # ~8,856 byte
print(f"Array: {sys.getsizeof(arr):,} byte")     # ~4,064 byte
# array yaklaşık yarı bellek kullanıyor!

Numpy'a Geçiş

# array modülü → sınırlı
# Sayısal hesaplamalar için numpy çok daha güçlü

# import numpy as np
# arr = np.array([1, 2, 3, 4, 5])
# print(arr * 2)        # [2, 4, 6, 8, 10] — element-wise!
# print(arr.mean())     # 3.0
# print(arr.std())      # 1.414...
# print(arr.reshape(5, 1))  # 5x1 matris

# numpy'ı ileriki derslerde detaylı göreceğiz

array modülü nadiren doğrudan kullanılır. Ama "neden list yerine numpy array?" sorusunun temeli burada: tip kısıtlaması sayesinde bellek tasarrufu ve C-seviyesi hız.


Mutable vs Immutable

Bu ayrım Python'un en temel kavramlarından biri ve veri yapısı seçimini doğrudan etkiler:

Mutable (Değiştirilebilir)

# List — mutable
liste = [1, 2, 3]
liste[0] = 99    # ✅ Değiştirilebilir
liste.append(4)  # ✅ Eklenebilir

# Dict — mutable
d = {"a": 1}
d["b"] = 2       # ✅ Eklenebilir
d["a"] = 99      # ✅ Değiştirilebilir

# Set — mutable
s = {1, 2, 3}
s.add(4)         # ✅ Eklenebilir
s.remove(1)      # ✅ Silinebilir

Immutable (Değiştirilemez)

# Tuple — immutable
t = (1, 2, 3)
# t[0] = 99     # ❌ TypeError
# t.append(4)   # ❌ AttributeError

# String — immutable
s = "merhaba"
# s[0] = "M"    # ❌ TypeError
yeni = "M" + s[1:]  # ✅ Yeni string oluştur

# frozenset — immutable
fs = frozenset({1, 2, 3})
# fs.add(4)     # ❌ AttributeError

# int, float, bool — immutable
x = 5
x = x + 1  # Yeni nesne oluşturur, eski 5'i değiştirmez

Neden Önemli?

# 1. Dict key ve set elemanı olabilme
d = {(1, 2): "tuple OK"}       # ✅ Immutable → hashable
# d = {[1, 2]: "list NO"}      # ❌ Mutable → unhashable

# 2. Fonksiyon argümanı güvenliği
def islem(veri):
    veri.append(99)  # Orijinali de değiştirir!

original = [1, 2, 3]
islem(original)
print(original)  # [1, 2, 3, 99] — Dikkat!

# Tuple olsaydı bu mümkün değildi
def guvenli_islem(veri):
    # veri.append(99)  # AttributeError — tuple immutable!
    pass

# 3. Thread safety
# Immutable nesneler thread-safe — yarış koşulu yok

Karşılaştırma Tablosu

TipMutableHashableDict Key?Set Eleman?
int
float
str
bool
tuple✅*✅*✅*
frozenset
list
dict
set

\* Tüm elemanları da hashable ise


Bellek Kullanımı Karşılaştırması

import sys

# Aynı veriyi farklı yapılarda tut
n = 1000
veriler_liste = list(range(n))
veriler_tuple = tuple(range(n))
veriler_set = set(range(n))
veriler_dict = {i: i for i in range(n)}

print(f"List:  {sys.getsizeof(veriler_liste):>8,} byte")
print(f"Tuple: {sys.getsizeof(veriler_tuple):>8,} byte")
print(f"Set:   {sys.getsizeof(veriler_set):>8,} byte")
print(f"Dict:  {sys.getsizeof(veriler_dict):>8,} byte")

Tipik sonuçlar (1000 eleman):

List:    8,856 byte
Tuple:   8,048 byte
Set:    32,984 byte
Dict:   36,968 byte

Dikkat et: Set ve dict, list/tuple'dan 4x daha fazla bellek kullanır. Bu hash tablosu yapısının maliyeti — hızlı arama (O(1)) için ödenen bedel.

Elemanların Bellek Kullanımı

import sys

# Temel tipler
print(f"int (0):      {sys.getsizeof(0)} byte")
print(f"int (1):      {sys.getsizeof(1)} byte")
print(f"int (büyük):  {sys.getsizeof(10**100)} byte")
print(f"float:        {sys.getsizeof(3.14)} byte")
print(f"bool:         {sys.getsizeof(True)} byte")
print(f"str (boş):    {sys.getsizeof('')} byte")
print(f"str (5 harf): {sys.getsizeof('merhb')} byte")
print(f"None:         {sys.getsizeof(None)} byte")

⚠️ Dikkat: sys.getsizeof() sadece nesnenin kendi boyutunu verir, içindeki nesnelerin boyutunu hesaplamaz. Gerçek bellek kullanımını ölçmek için pympler veya tracemalloc modüllerini kullan:

>

```python import tracemalloc tracemalloc.start()

>

buyuk_liste = [list(range(100)) for _ in range(1000)]

>

current, peak = tracemalloc.get_traced_memory() print(f"Şu an: {current / 1024:.1f} KB") print(f"Zirve: {peak / 1024:.1f} KB") tracemalloc.stop() ```


Pratik Karşılaştırma: Aynı Problem, Farklı Yapılar

Problem: Benzersiz Kelime Sayımı

metin = "python güzel python harika güzel bir dil python harika bir güzel dil"
kelimeler = metin.split()

# List ile — O(n²) zaman
benzersiz_list = []
for kelime in kelimeler:
    if kelime not in benzersiz_list:  # O(n) arama her seferinde
        benzersiz_list.append(kelime)
print(f"Benzersiz (list): {benzersiz_list}")

# Set ile — O(n) zaman
benzersiz_set = set(kelimeler)  # O(1) ekleme ve dedup
print(f"Benzersiz (set):  {benzersiz_set}")

# Dict ile — O(n) zaman + frekans bilgisi
from collections import Counter
frekans = Counter(kelimeler)
print(f"Frekans (dict):   {frekans}")

Bu örnekte:

  • List: O(n²) — her kelime için tüm listeyi tarar

  • Set: O(n) — hash tablosu ile anında dedup

  • Counter/Dict: O(n) — dedup + sayım bilgisi

Problem: Öğrenci Verisi Saklama

# List of tuples — basit ama erişim indeksle
ogrenciler_lot = [
    ("Ahmet", 85, "A"),
    ("Ayşe", 92, "B"),
]
# ogrenciler_lot[0][1] → 85 (ama 1 ne anlama geliyor?)

# List of dicts — okunabilir
ogrenciler_lod = [
    {"isim": "Ahmet", "not": 85, "sinif": "A"},
    {"isim": "Ayşe", "not": 92, "sinif": "B"},
]
# ogrenciler_lod[0]["not"] → 85 (net!)

# Dict of dicts — key ile hızlı erişim
ogrenciler_dod = {
    "Ahmet": {"not": 85, "sinif": "A"},
    "Ayşe": {"not": 92, "sinif": "B"},
}
# ogrenciler_dod["Ahmet"]["not"] → 85 (O(1) erişim!)

# Named tuple — struct benzeri, immutable
from collections import namedtuple
Ogrenci = namedtuple("Ogrenci", "isim not_ sinif")
ogrenciler_nt = [
    Ogrenci("Ahmet", 85, "A"),
    Ogrenci("Ayşe", 92, "B"),
]
# ogrenciler_nt[0].not_ → 85 (okunabilir + immutable)

Her yaklaşımın avantajı var — senaryona göre seç:

  • Sıra önemliyse: list of dicts

  • Key ile erişim gerekiyorsa: dict of dicts

  • Immutability istiyorsan: named tuple

  • Basit ve hafif: list of tuples


Dönüşüm Rehberi

Bir yapıdan diğerine kolayca geçiş yapabilirsin:

# List ↔ Tuple
liste = [1, 2, 3]
demet = tuple(liste)    # (1, 2, 3)
liste2 = list(demet)    # [1, 2, 3]

# List ↔ Set
liste = [1, 2, 2, 3, 3]
kume = set(liste)       # {1, 2, 3} — duplicate temizlendi
liste2 = list(kume)     # [1, 2, 3] — sıra garanti değil

# List ↔ Dict (iki yönlü)
ciftler = [("a", 1), ("b", 2)]
sozluk = dict(ciftler)  # {'a': 1, 'b': 2}
ciftler2 = list(sozluk.items())  # [('a', 1), ('b', 2)]

# Dict → List (key'ler veya value'lar)
keyler = list(sozluk.keys())    # ['a', 'b']
degerler = list(sozluk.values()) # [1, 2]

# Set ↔ Frozenset
kume = {1, 2, 3}
donmus = frozenset(kume)  # frozenset({1, 2, 3})
kume2 = set(donmus)       # {1, 2, 3}

# Sırayı koruyarak list → benzersiz list
orijinal = [3, 1, 4, 1, 5, 9, 2, 6, 5]
benzersiz = list(dict.fromkeys(orijinal))
print(benzersiz)  # [3, 1, 4, 5, 9, 2, 6]

Sonuç: Hızlı Referans Kartı

📋 LIST  — Sıralı, değişebilir, duplicate OK
   En iyi: Sıralı koleksiyon, indeksle erişim, sıralama
   Kaçın: Sık arama (in), başa ekleme

📌 TUPLE — Sıralı, değişmez, duplicate OK
   En iyi: Sabit veri, dict key, fonksiyon return
   Kaçın: Ekleme/çıkarma gereken yerler

🎯 SET   — Sırasız, değişebilir, benzersiz
   En iyi: Üyelik testi, dedup, küme operasyonları
   Kaçın: İndeksle erişim, sıra gerektiren yerler

📖 DICT  — Key-value, değişebilir, key benzersiz
   En iyi: Lookup, mapping, config, cache
   Kaçın: Sadece değer listesi gerektiğinde (list yeterli)

Özet

  • List vs Tuple: Veri değişecekse list, değişmeyecekse tuple. Tuple daha hızlı, daha az bellek, hashable.

  • List vs Set: Sıra önemliyse ve duplicate izin veriliyorsa list; benzersizlik ve hızlı arama (O(1)) gerekiyorsa set.

  • Dict: Key-value mapping gereken her yerde birinci tercih. O(1) erişim, defaultdict ve Counter ile genişletilebilir.

  • collections modülü: deque (iki uçtan O(1) ekleme/çıkarma), OrderedDict (sıra duyarlı karşılaştırma), ChainMap (config katmanları) özel senaryolarda standart yapılardan üstün.

  • Bellek: Tuple < List < Set < Dict. Hash tablosu yapıları (set, dict) 3-4x daha fazla bellek kullanır — hız için ödenen bedel.

  • Karar ağacı: Key-value → dict, benzersizlik → set, değişmez → tuple, geri kalan → list. Basit ama etkili.