🐍 8.5: Python'da Hash Table

dict - En Çok Kullanılan Veri Yapısı!

🎯 Python dict Nedir?

Python'un yerleşik dictionary (dict) veri yapısı, hash table implementasyonudur!

✨ Neden Bu Kadar Önemli?

  • Python'un en hızlı veri yapısı (O(1) erişim)
  • JSON, API, veritabanı - her yerde kullanılır
  • Modül import sistemi bile dict kullanır
  • Class'ların attribute'ları dict olarak saklanır

📚 Bu Maddeleri Daha Detaylı Anlayalım

1️⃣ O(1) Erişim Ne Demek?

O(1) demek, dict'te kaç eleman olursa olsun (1000 veya 1 milyon), bir anahtara erişim aynı sürede olur demektir.

Karşılaştırma:
Liste: 1 milyon elemandan birini bulmak için belki 1 milyon adım gerekir (O(n))
Dict: 1 milyon elemandan birini bulmak 1 adımda olur (O(1))

Düşünün: Telefon rehberinizde 1000 kişi var. Liste ile "Zeynep"i bulmak için A'dan Z'ye kadar tek tek bakarsınız. Dict ile direkt "Zeynep" deyince bulursunuz!

2️⃣ JSON, API, Veritabanı Nerede?

JSON (JavaScript Object Notation): İnternetteki veri değişiminin standart formatı. Bir web sitesinden hava durumu bilgisi aldığınızda, veri JSON olarak gelir ve Python'da otomatik olarak dict'e dönüşür.

Örnek:

# Hava durumu API'sinden gelen JSON
hava_durumu = {
    "sehir": "İstanbul",
    "sicaklik": 22,
    "durum": "Güneşli"
}
print(hava_durumu["sicaklik"])  # 22
3️⃣ Modül Import Sistemi?

import math yazdığınızda Python ne yapar? Arka planda bir dict'e bakar!

Python, yüklenen tüm modülleri sys.modules adlı bir dict'te tutar. Böylece aynı modülü tekrar import ettiğinizde, diskten yeniden okumak yerine dict'ten hemen alır (çok hızlı!).

import sys
print("math" in sys.modules)  # True (import edildiyse)
4️⃣ Class Attribute'ları Dict mi?

Bir class oluşturduğunuzda, o class'ın tüm özellikleri (attribute) aslında bir dict içinde saklanır!

object.__dict__ ile herhangi bir nesnenin iç yapısını görebilirsiniz:

class Ogrenci:
    def __init__(self, ad, yas):
        self.ad = ad
        self.yas = yas

ali = Ogrenci("Ali", 20)
print(ali.__dict__)  # {'ad': 'Ali', 'yas': 20}

Bu yüzden Python'da her şey dict üzerine kurulu diyebiliriz!

dict Temel Kullanım
# dict oluşturma yöntemleri
print("=== dict Oluşturma ===")

# 1. Süslü parantez
ogrenci = {
    "ad": "Ahmet",
    "soyad": "Yılmaz",
    "yas": 20,
    "not": 85
}
print(f"1. Yöntem: {ogrenci}")

# 2. dict() fonksiyonu
ogrenci2 = dict(ad="Mehmet", soyad="Kaya", yas=22)
print(f"2. Yöntem: {ogrenci2}")

# 3. Listeden
veriler = [("ad", "Ayşe"), ("yas", 19)]
ogrenci3 = dict(veriler)
print(f"3. Yöntem: {ogrenci3}")

print("\n=== Erişim ===")
# Erişim - O(1)
print(f"Öğrencinin adı: {ogrenci['ad']}")
print(f"Öğrencinin notu: {ogrenci['not']}")

# Güvenli erişim (yoksa None döner)
print(f"Şube: {ogrenci.get('sube', 'Belirtilmemiş')}")

print("\n=== Ekleme/Güncelleme ===")
# Ekleme/Güncelleme - O(1)
ogrenci["sube"] = "A"
print(f"Şube eklendi: {ogrenci}")

ogrenci["not"] = 90  # Güncelleme
print(f"Not güncellendi: {ogrenci}")

print("\n=== Silme ===")
# Silme - O(1)
del ogrenci["sube"]
print(f"Şube silindi: {ogrenci}")

# pop ile silme (değer döndürür)
yas = ogrenci.pop("yas")
print(f"Yaş silindi (değer: {yas}): {ogrenci}")

print("\n=== Kontroller ===")
# Anahtar var mı? - O(1)
print(f"'ad' var mı? {'ad' in ogrenci}")
print(f"'yas' var mı? {'yas' in ogrenci}")

# Boyut
print(f"Eleman sayısı: {len(ogrenci)}")
Çıktı bekleniyor...

🔄 dict Döngüleri

dict Üzerinde Döngü
notlar = {
    "Ali": 85,
    "Veli": 92,
    "Ayşe": 78,
    "Fatma": 95
}

print("=== Sadece Anahtarlar ===")
for isim in notlar:
    print(isim)

print("\n=== Sadece Değerler ===")
for not_degeri in notlar.values():
    print(not_degeri)

print("\n=== Anahtar-Değer Çiftleri ===")
for isim, not_degeri in notlar.items():
    print(f"{isim}: {not_degeri}")

print("\n=== Şartlı Filtre (comprehension) ===")
# 90'dan yüksek notlar
yuksek_notlar = {isim: not_degeri 
                 for isim, not_degeri in notlar.items() 
                 if not_degeri >= 90}
print(f"90+ notlar: {yuksek_notlar}")

# Not ortalaması
ortalama = sum(notlar.values()) / len(notlar)
print(f"\nOrtalama: {ortalama:.1f}")
Çıktı bekleniyor...

⚡ dict Performansı

⏱️ Zaman Karmaşıklığı

İşlem Ortalama En Kötü
d[key] (erişim) O(1) O(n)
d[key] = val (ekleme) O(1) O(n)
del d[key] O(1) O(n)
key in d O(1) O(n)
len(d) O(1) O(1)

*En kötü durum: Çok kötü hash fonksiyonu veya tüm çakışmalar

dict vs list Hız Karşılaştırması
import time

# 10,000 elemanlı veri
n = 10000
liste = list(range(n))
sozluk = {i: i for i in range(n)}

print(f"=== {n} Elemanda Arama Hızı ===\n")

# Liste'de arama (O(n))
aranan = 9999
start = time.time()
for _ in range(1000):
    result = aranan in liste
end = time.time()
list_time = (end - start) * 1000
print(f"Liste (O(n)): {list_time:.2f} ms")

# dict'te arama (O(1))
start = time.time()
for _ in range(1000):
    result = aranan in sozluk
end = time.time()
dict_time = (end - start) * 1000
print(f"dict (O(1)): {dict_time:.2f} ms")

speedup = list_time / dict_time
print(f"\n⚡ dict {speedup:.0f}x daha hızlı!")

# Bellek kullanımı
import sys
print(f"\n=== Bellek Kullanımı ===")
print(f"Liste: {sys.getsizeof(liste):,} byte")
print(f"dict: {sys.getsizeof(sozluk):,} byte")
print(f"dict ~{sys.getsizeof(sozluk) / sys.getsizeof(liste):.1f}x daha fazla bellek")
Çıktı bekleniyor...

💡 dict İpuçları ve Yaygın Hatalar

❌ Yaygın Hatalar

  1. Liste anahtarı kullanmak: Liste hash'lenemez!
    # ❌ YANLIŞ
    d = {[1, 2]: "değer"}  # TypeError!
    
    # ✅ DOĞRU (tuple kullan)
    d = {(1, 2): "değer"}
  2. Döngü sırasında değiştirmek:
    # ❌ YANLIŞ
    for key in d:
        del d[key]  # RuntimeError!
    
    # ✅ DOĞRU
    for key in list(d.keys()):
        del d[key]

✅ İyi Pratikler

  • Yoksa default değer: d.get(key, default)
  • Yoksa ekle: d.setdefault(key, [])
  • Birleştir: d1.update(d2) veya {**d1, **d2}
  • Tersine çevir: {v: k for k, v in d.items()}

📖 Bu Pratikleri Detaylı Anlayalım

1️⃣ d.get(key, default) - Güvenli Erişim

Normalde olmayan bir anahtara erişirseniz KeyError hatası alırsınız. get() ile bu hatadan kaçınırsınız:

ogrenci = {"ad": "Ali", "yas": 20}

# ❌ Hatalı - KeyError verir!
# print(ogrenci["not"])  

# ✅ Güvenli - Yoksa 0 döner
print(ogrenci.get("not", 0))  # 0

# ✅ Yoksa "Bilinmiyor" yaz
print(ogrenci.get("sube", "Bilinmiyor"))  # Bilinmiyor

💡 İpucu: API'den gelen verilerde bazı alanlar eksik olabilir. get() ile programınız çökmez!

2️⃣ d.setdefault(key, []) - Yoksa Ekle

Gruplama yaparken çok kullanışlı! Anahtar yoksa oluşturur, varsa dokunmaz:

# Öğrencileri sınıflarına göre grupla
ogrenciler = [("A", "Ali"), ("B", "Veli"), ("A", "Ayşe"), ("B", "Fatma")]

siniflar = {}
for sinif, isim in ogrenciler:
    # sinif yoksa boş liste oluştur, sonra ekle
    siniflar.setdefault(sinif, []).append(isim)

print(siniflar)
# {'A': ['Ali', 'Ayşe'], 'B': ['Veli', 'Fatma']}

💡 Alternatif: from collections import defaultdict kullanabilirsiniz.

3️⃣ Dict Birleştirme

İki dict'i birleştirmenin birkaç yolu var:

varsayilan = {"renk": "mavi", "boyut": "M", "fiyat": 100}
kullanici = {"renk": "kırmızı", "indirim": True}

# Yöntem 1: update() - d1'i değiştirir
d1 = varsayilan.copy()
d1.update(kullanici)
print(d1)  # {'renk': 'kırmızı', 'boyut': 'M', 'fiyat': 100, 'indirim': True}

# Yöntem 2: ** unpacking - yeni dict oluşturur
d2 = {**varsayilan, **kullanici}
print(d2)  # Aynı sonuç

# Python 3.9+ için:
d3 = varsayilan | kullanici  # Pipe operatörü!

💡 İkinci dict'teki değerler birincinin üzerine yazar (renk: kırmızı oldu)

4️⃣ Dict'i Tersine Çevirme

Anahtar-değer çiftlerini yer değiştirmek için dict comprehension:

# Ülke kodları
kodlar = {"TR": "Türkiye", "US": "Amerika", "DE": "Almanya"}

# Tersine çevir: Ülke adından koda
ulke_kod = {v: k for k, v in kodlar.items()}
print(ulke_kod)  
# {'Türkiye': 'TR', 'Amerika': 'US', 'Almanya': 'DE'}

# Artık ülke adıyla kodu bulabiliriz
print(ulke_kod["Türkiye"])  # TR

⚠️ Dikkat: Eğer değerler benzersiz değilse (tekrar ediyorsa), veri kaybı olur!

🔑 Hashable Tipler Nedir?

📚 Tanım

Hashable = Hash'lenebilir = Hash fonksiyonu uygulanabilir demektir.

Bir nesnenin hashable olması için iki şart gerekir:

  1. Değişmez (immutable) olmalı: Oluşturulduktan sonra değeri değişmemeli
  2. __hash__() metodu olmalı: Python'un hash değeri hesaplayabilmesi için

✅ Hashable (Dict Anahtarı Olabilir)

int42, 0, -17
float3.14, 2.718
str"merhaba", "Ali"
boolTrue, False
tuple(1, 2, 3)
frozensetfrozenset({1, 2})
NoneNone

Ortak özellik: Hepsi değişmez (immutable)!

❌ Hashable DEĞİL (Dict Anahtarı OLAMAZ)

list[1, 2, 3]
dict{"a": 1}
set{1, 2, 3}

Ortak özellik: Hepsi değişebilir (mutable)!

🤔 Neden Değişebilir Tipler Hashable Değil?

Düşünün: Bir dict'e anahtar olarak liste eklediniz. Sonra o listeyi değiştirdiniz...

# Hayal edin ki bu çalışsaydı (aslında hata verir)
anahtar = [1, 2, 3]
d = {anahtar: "değer"}  # hash([1,2,3]) = 123 diyelim

# Sonra listeyi değiştirdik
anahtar.append(4)  # Artık [1, 2, 3, 4]

# Problem: Hash değeri değişti! (hash = 456 oldu)
# d[anahtar] artık bulunamaz çünkü yanlış yere bakıyor!

Sonuç: Python, veri tutarsızlığını önlemek için mutable tipleri anahtar olarak yasaklar!

🧪 Bir Nesnenin Hashable Olduğunu Nasıl Anlarız?

# hash() fonksiyonu ile test et
print(hash(42))        # ✅ Çalışır: -123456...
print(hash("merhaba")) # ✅ Çalışır: 789012...
print(hash((1, 2, 3))) # ✅ Çalışır: -345678...

# Hashable değilse TypeError verir
try:
    print(hash([1, 2, 3]))
except TypeError as e:
    print(f"❌ Hata: {e}")
    # "unhashable type: 'list'"

💡 İpucu: hash(nesne) hata vermezse, o nesne dict anahtarı olabilir!

🔧 Liste Yerine Tuple Kullan

Liste anahtar olarak kullanılamaz ama tuple kullanılabilir:

# Koordinatları anahtar olarak kullanalım
koordinatlar = {}

# ❌ Liste ile - HATA!
# koordinatlar[[10, 20]] = "Ev"  # TypeError!

# ✅ Tuple ile - ÇALIŞIR!
koordinatlar[(10, 20)] = "Ev"
koordinatlar[(30, 40)] = "Okul"
koordinatlar[(50, 60)] = "Market"

print(koordinatlar[(10, 20)])  # "Ev"

# Pratik kullanım: Satranç tahtası
tahta = {}
tahta[("a", 1)] = "Kale"
tahta[("e", 1)] = "Kral"

📋 Özet

✅ Bu Bölümde Öğrendikleriniz

  • dict: Python'un hash table implementasyonu
  • O(1): Erişim, ekleme, silme çok hızlı
  • Anahtar: Sadece hashable tipler (int, str, tuple)
  • Döngü: keys(), values(), items()
  • Performans: Liste'den çok daha hızlı arama