Uygun veri yapısı ve algoritma seçimi

Bu derste öğrendiğiniz yapıların hiçbiri tek başına “en iyi” değildir. Liste, sözlük, küme, yığın, kuyruk, doğrusal arama, ikili arama ve sıralama farklı gereksinimlere hizmet eder. Bu hafta yeni bir veri yapısı öğrenmek yerine seçim yapmayı ve seçimi gerekçelendirmeyi çalışacağız.

Aynı zamanda hazır veya AI tarafından üretilmiş bir çözümün yalnızca çalışıp çalışmadığına değil, gereksiz veri yapısı seçimi, yanlış maliyet varsayımı ve ön koşul ihlali açısından da bakacağız.

1 Bu hafta neleri yapabilmelisiniz?

Bölümün sonunda:

  • erişim, üyelik, benzersizlik, sıra, güncelleme ve sıralama gereksinimlerini ayırt edebilmeli,
  • problem gereksiniminden uygun Python koleksiyonuna geçebilmeli,
  • veri yapısı seçimini üç adımlı bir karar yöntemiyle gerekçelendirebilmeli,
  • arama yöntemini verinin düzenine ve kullanım sıklığına göre seçebilmeli,
  • yığın ve kuyruk arasında işlem sırasına göre karar verebilmeli,
  • bir çözümün maliyetini nitel düzeyde gerekçelendirebilmeli,
  • çalışan fakat gereksiz veya yanlış yapı kullanan kodu eleştirebilmeli,
  • AI üretimi bir çözümde ön koşul ve maliyet hatalarını fark edebilmelisiniz.

2 Seçime problemden başlayın

Yanlış soru:

Bu problemi dict ile yapabilir miyim?

Daha iyi soru:

Program verinin üzerinde en sık hangi işlemleri yapacak ve bu işlemler için hangi kaynak/maliyet sınırları önemli?

Veri yapısı seçimini üç adımda düşünmek yararlıdır.

3 Üç adımlı seçim yöntemi

3.1 1. Gerekli işlemleri belirle

Önce programın ne yapacağını açıkça yazın:

  • sırayla dolaşma mı?
  • anahtardan kayda erişme mi?
  • üyelik kontrolü mü?
  • benzersizlik mi?
  • ilk geleni önce işleme mi?
  • son geleni önce işleme mi?
  • sıralı rapor üretme mi?

3.2 2. İşlemlerin önemini ve sıklığını belirle

Aynı işlem bir kez yapılacaksa ve binlerce kez yapılacaksa karar aynı olmak zorunda değildir.

Örneğin:

Bir kez ürün kodu ara             → doğrusal arama yeterli olabilir
Her istekte ürün kodu ara         → dict ile indekslemek daha anlamlı olabilir

Burada veri boyutu, işlem sıklığı ve ek bellek kullanımı birlikte düşünülür.

3.3 3. Gereksinime en uygun yapıyı seç ve gerekçelendir

Seçim yalnızca “daha hızlı” diye yapılmamalıdır. Yapı, problemin anlamını da korumalıdır.

flowchart LR
    A["1. İşlemleri belirle"] --> B["2. Sıklık / maliyet gereksinimini belirle"]
    B --> C["3. Yapı veya algoritmayı seç"]
    C --> D["Seçimi doğruluk + maliyet ile gerekçelendir"]

Important

Daha karmaşık bir veri yapısı otomatik olarak daha iyi değildir. En basit yapı gereksinimleri yeterince karşılıyorsa onu seçmek çoğu zaman daha doğrudur.

4 Temel karar ölçütleri

Gereksinim Sık kullanılan seçenek
sırayı ve tekrarları koruma list
sabit, değişmeyecek sıralı grup tuple
anahtar → değer ilişkisi dict
benzersizlik set
çok sık üyelik kontrolü çoğu zaman set / dict
son giren önce çıksın yığın: list / deque
ilk giren önce çıksın kuyruk: deque
sıralı veride tekrar eden arama ikili arama düşünülebilir
küçük veya sırasız veride basit arama doğrusal arama
kayıtları ölçüte göre düzenleme sorted() / .sort()

Bu tablo otomatik karar vermez. Veri boyutu, mevcut veri düzeni ve yapılan işlemlerin sıklığı da önemlidir.

5 Senaryo 1 — kullanıcı adları

Bir sistem yeni kullanıcı adı eklenirken adın daha önce kullanılıp kullanılmadığını kontrol ediyor. Sıra önemli değil; benzersizlik önemli.

usernames = {"ada", "mert", "ece"}

if "ada" in usernames:
    print("Kullanılıyor")

Üç adımlı gerekçe:

  1. Temel işlem: üyelik kontrolü ve benzersizlik.
  2. İşlem sık yapılacak.
  3. set, hem anlamı doğrudan temsil eder hem de ortalama durumda hızlı üyelik kontrolü sağlar.

Liste de çalışabilir:

usernames = ["ada", "mert", "ece"]

ancak tekrar eden çok sayıda üyelik kontrolü için daha zayıf bir seçim olabilir.

6 Senaryo 2 — öğrenci sırası

Bir sınıftaki öğrencilerin kayıt sırası korunacak ve aynı isim birden çok kez bulunabilir. Burada set veri kaybına yol açabilir:

#| edit: false
#| completion: false
names = ["Ada", "Mert", "Ada", "Ece"]
print("Liste:", names)
print("Küme:", set(names))

Küme tekrarları kaldırdığı için problem anlamını değiştirir. “Daha hızlı” olması, onu otomatik olarak doğru yapı yapmaz.

7 Senaryo 3 — ürün kodundan ürüne erişim

#| edit: false
#| completion: false
products = {
    "K101": {"name": "Kalem", "stock": 20},
    "D205": {"name": "Defter", "stock": 8},
}

print(products["D205"])

Anahtar tabanlı erişim gereksinimi sözlükle doğal biçimde ifade edilir.

8 Senaryo 4 — undo ve müşteri sırası

Aynı “eleman ekle ve çıkar” problemi iki farklı sıraya sahip olabilir:

Undo geçmişi      → son yapılan önce geri alınır → LIFO → yığın
Müşteri kuyruğu   → ilk gelen önce işlenir       → FIFO → kuyruk

Burada veri türleri aynı olsa bile işlem sırası veri modelini belirler.

9 Arama seçimi

Arama kararında şu soruları sorun:

  1. Veri sıralı mı?
  2. Kaç kez arama yapılacak?
  3. Arama ölçütüne göre farklı bir indeks oluşturmak mümkün mü?
  4. İndeks oluşturmanın bellek ve kurulum maliyeti kabul edilebilir mi?

9.1 Bir kez arama

Sırasız küçük listede doğrusal arama yeterli olabilir.

9.2 Çok sayıda arama

Aynı anahtara göre tekrar tekrar arama yapılıyorsa:

  • veriyi sözlükle indekslemek,
  • veya veri zaten sıralıysa ikili arama kullanmak

anlamlı olabilir.

10 Sıralama her zaman gerekli mi?

“En küçük öğeyi bul” problemi için bütün listeyi sıralamak zorunda değiliz:

#| edit: false
#| completion: false
values = [18, 4, 27, 2, 11]

print("min:", min(values))
print("sorted ilk:", sorted(values)[0])

İki sonuç aynı olabilir; ancak yapılan iş aynı değildir. Sadece minimum gerekiyorsa tüm veriyi sıralamak gereksiz olabilir.

Gereksinim “en küçük öğe” ise çözümü “tam sıralama” problemi hâline getirmeyin.

11 Karar akışı

flowchart TD
    A["Gereksinimi belirle"] --> B{"Anahtardan değere erişim?"}
    B -- Evet --> C["dict düşün"]
    B -- Hayır --> D{"Benzersizlik / sık üyelik?"}
    D -- Evet --> E["set düşün"]
    D -- Hayır --> F{"Öğe çıkarma sırası önemli mi?"}
    F -- Son giren önce --> G["stack"]
    F -- İlk giren önce --> H["queue / deque"]
    F -- Hayır --> I{"Sıralı dizi üzerinde arama?"}
    I -- Evet --> J["binary search düşünülebilir"]
    I -- Hayır --> K["list + linear processing"]

Bu şema başlangıç için yardımcıdır; gerçek problemler birden çok yapıyı birlikte kullanabilir.

12 Aynı programda birden çok yapı

Bir e-ticaret uygulamasında:

  • ürünlerin gösterim sırası için list,
  • ürün kodundan erişim için dict,
  • kullanılmış kupon kodları için set,
  • işlenecek siparişler için deque

aynı anda bulunabilir. İyi tasarım, “tek veri yapısı seçmek” değil; her gereksinime uygun yapı kombinasyonu kurmaktır.

13 Çalışan ama zayıf çözüm 1

Aşağıdaki kod, yasaklı kullanıcı adını bulmak için her sorguda liste dolaşıyor:

#| edit: false
#| completion: false
blocked = ["bot1", "spam2", "fake3", "bot4", "spam5"]

def is_blocked(username):
    return username in blocked

for username in ["ada", "bot4", "ece"]:
    print(username, is_blocked(username))

Beş elemanda sorun görünmez. Ancak yüz binlerce isim ve çok sayıda sorgu varsa, set daha uygun olabilir:

blocked = {"bot1", "spam2", "fake3", "bot4", "spam5"}

Burada değiştirdiğimiz şey programın dış davranışı değil, veri yapısı seçimidir.

14 Çalışan ama zayıf çözüm 2

queue = []
queue.append("A")
queue.append("B")
current = queue.pop(0)

Bu kod FIFO açısından doğrudur. Ancak çok büyük ve yoğun bir kuyrukta deque.popleft() daha uygun olacaktır.

15 Çalışmayan varsayım: sırasız veride ikili arama

Şu “AI önerisi” verildiğini düşünün:

def fast_search(values, target):
    low = 0
    high = len(values) - 1

    while low <= high:
        mid = (low + high) // 2
        if values[mid] == target:
            return mid
        if target < values[mid]:
            high = mid - 1
        else:
            low = mid + 1

    return -1

Fonksiyonun kendisi standart ikili aramaya benziyor. Fakat kullanıcı bu fonksiyona şu veriyi gönderiyor:

values = [50, 3, 90, 12, 40]

Sorun kodun sözdiziminde değil, ön koşulun eksik olmasındadır. Veri sıralı değilse ikili aramanın kararları geçersizdir.

Important

AI veya insan tarafından üretilen kodu değerlendirirken yalnızca “çalışıyor mu?” sorusunu sormayın. Hangi ön koşullarda doğru? sorusunu da sorun.

16 AI çıktısını inceleme kontrol listesi

Bir veri yapısı/algoritma önerisini şu sorularla inceleyin:

  1. Problem gereksinimi doğru anlaşılmış mı?
  2. Veri sırası veya benzersizlik gereksinimi korunuyor mu?
  3. Algoritmanın ön koşulu sağlanıyor mu?
  4. Gereksiz sıralama, kopyalama veya dönüştürme var mı?
  5. Sık yapılan işlem için uygun koleksiyon kullanılmış mı?
  6. Boş veri, bulunamama ve tekrar gibi sınır durumları düşünülmüş mü?
  7. Daha basit bir çözüm aynı işi yapabilir mi?
  8. Daha hızlı görünen çözüm ek bellek veya kurulum maliyeti oluşturuyor mu?

17 Alıştırma — veri yapısını düzelt

Aşağıdaki fonksiyon, yüz binlerce kayıtta çok sık üyelik kontrolü yapılan bir sistemi temsil ediyor. allowed verisini ve fonksiyonu uygun biçimde düzenleyin. Fonksiyonun dış davranışı değişmemelidir.

#| exercise: hafta13-membership
#| completion: false
#| persist: true
allowed = ["python", "data", "web", "network", "linux"]

def is_allowed(tag):
    return tag in allowed

print(is_allowed("data"))
print(is_allowed("java"))
#| exercise: hafta13-membership
#| check: true
scope = {}
try:
    exec(user_code, scope)
    fn = scope.get("is_allowed")
    allowed = scope.get("allowed")
    correct = (
        callable(fn)
        and isinstance(allowed, set)
        and fn("data") is True
        and fn("java") is False
    )
except Exception:
    correct = False

feedback = (
    {"correct": True, "message": "Üyelik ağırlıklı gereksinim için set seçildi ve davranış korundu."}
    if correct
    else {"correct": False, "message": "allowed verisini set olarak temsil edin; is_allowed aynı üyelik kontrolünü kullanabilir."}
)
feedback

Köşeli parantez yerine küme sözdizimini kullanın: {"python", ...}.

Solution.

allowed = {"python", "data", "web", "network", "linux"}

def is_allowed(tag):
    return tag in allowed

18 Alıştırma — üç adımlı seçim gerekçesi

Bir çağrı merkezinde gelen talepler geliş sırasıyla işlenecek. Sistem aynı anda yaklaşık 50.000 bekleyen talep tutabilir ve her işlemde sıradaki talep baştan çıkarılacak.

Aşağıdaki taslağı tamamlayın:

1. Temel işlem(ler): ______________________________
2. Sıklık / maliyet gereksinimi: __________________
3. Seçim: _________________________________________
Gerekçe: __________________________________________

Beklenen teknik gerekçede şu unsurlar bulunmalıdır:

  • FIFO davranışı,
  • baştan sık çıkarma,
  • deque ve popleft(),
  • list.pop(0) işleminin O(n) sezgisi.

19 Gerekçelendirme dili

set daha iyi” demek yerine daha teknik ve ölçülebilir bir gerekçe yazın:

Kullanıcı adlarının sırası ve tekrarları önemli değil; temel işlem üyelik kontrolü. Bu nedenle set, listeye göre gereksinimi daha doğrudan temsil eder ve ortalama üyelik kontrolü maliyeti açısından daha uygundur.

Benzer şekilde:

Veri zaten sıralı ve aynı liste üzerinde çok sayıda arama yapılacak. Bu nedenle O(n) doğrusal arama yerine O(log n) sezgisine sahip ikili arama uygundur.

Bu ders için kısa ama işlem gereksinimi + yapı/algoritma + maliyet üçlüsünü içeren gerekçe yeterlidir.

20 Kısa senaryo çalışması

Her senaryo için üç adımlı yöntemi kullanarak yapı/algoritma seçin ve bir cümle gerekçe yazın:

  1. Son 20 hata mesajını geliş sırasına göre tutmak; yeni mesaj geldikçe en eskisi atılacak.
  2. Bir kitap ISBN’sinden kitap kaydına çok sık ulaşmak.
  3. Kullanılmış davet kodlarının tekrar kullanımını engellemek.
  4. Sırasız 30 elemanlı listede bir kez hedef aramak.
  5. Sıralı 500.000 kimlik üzerinde çok sayıda arama yapmak.
  6. Her işlemde en son eklenen düzenleme adımını geri almak.

21 Bölüm özeti

  • Veri yapısı seçimi problem gereksiniminden başlamalıdır.
  • Sistematik seçim için üç adım kullanılabilir: işlemleri belirle → sıklık/maliyet gereksinimini belirle → yapıyı seç ve gerekçelendir.
  • Daha karmaşık veri yapısı otomatik olarak daha iyi değildir.
  • Sıra, benzersizlik, erişim biçimi ve çıkarma sırası semantik gereksinimlerdir; yalnızca performans ayrıntısı değildir.
  • Arama yöntemi verinin sıralı olup olmamasına ve arama sıklığına bağlıdır.
  • Çalışan bir çözüm, uygun veri yapısı kullandığını garanti etmez.
  • Algoritmanın ön koşulları mutlaka kontrol edilmelidir.
  • AI üretimi kod da aynı doğruluk, ön koşul, sınır durumu ve maliyet ölçütleriyle değerlendirilmelidir.

22 Kendinizi kontrol edin

  1. Veri yapısı seçiminin üç adımı nelerdir?
  2. Neden “en hızlı veri yapısı” diye tek bir doğru cevap yoktur?
  3. set hangi semantik gereksinimleri doğrudan temsil eder?
  4. Çok sık kimlik araması için dict indeksi ne kazandırır ve ne tür ek maliyet getirir?
  5. deque ile normal liste arasında kuyruk bağlamındaki temel maliyet farkı nedir?
  6. İkili arama önerisinde hangi ön koşulu mutlaka doğrulamak gerekir?
  7. Bir AI çözümünü incelerken yalnızca çıktının doğru olması neden yeterli değildir?
Back to top