Bütünleştirme ve final hazırlığı

Bu hafta yeni veri yapısı veya yeni algoritma öğrenmeyeceğiz. Amaç, dönem boyunca öğrendiğimiz kavramları tek bir problem üzerinde birlikte kullanmak ve hangi kararı neden verdiğimizi açıklamaktır.

Bu bölümde küçük bir kütüphane kayıt sistemi üzerinden şu becerileri birleştireceğiz:

Bölümün sonunda ise farklı bir bağlama geçerek transfer görevi yapacağız. Böylece yalnızca kütüphane örneğini ezberleyip ezberlemediğinizi değil, aynı düşünme biçimini yeni bir probleme taşıyıp taşıyamadığınızı sınayacağız.

1 Bu hafta neleri yapabilmelisiniz?

Bölümün sonunda:

  • bir problem metninden veri ve işlem gereksinimlerini çıkarabilmeli,
  • birden çok koleksiyonu birlikte kullanabilmeli,
  • hazır kod tabanında sınırlı bir değişiklik yapabilmeli,
  • arama ve sıralama yöntemlerini uygun bağlamda seçebilmeli,
  • sınır durumlarını assert ile doğrulayabilmeli,
  • çözümünüzü veri yapısı ve maliyet açısından kısa biçimde gerekçelendirebilmeli,
  • aynı karar yöntemini daha önce görmediğiniz yeni bir problem bağlamına aktarabilmelisiniz.

2 Problem: küçük kütüphane sistemi

Sistemde kitaplar şu alanlara sahiptir:

  • id
  • title
  • category
  • available

Ayrıca öğrenciler ödünç almak istedikleri kitaplar için bekleme sırasına girebilir.

Başlangıç verisi:

#| edit: false
#| completion: false
books = [
    {"id": "B101", "title": "Python'a Giriş", "category": "Programlama", "available": True},
    {"id": "B205", "title": "Ağ Temelleri", "category": "Ağ", "available": False},
    {"id": "B310", "title": "Veri Yapıları", "category": "Programlama", "available": True},
    {"id": "B415", "title": "Linux Araçları", "category": "Sistem", "available": True},
]

print(books)

3 1. Gereksinimleri ayıralım

Sistem şu işlemleri yapacak:

  1. bütün kitapları sırayla göstermek,
  2. kitap kimliğine göre sık erişim yapmak,
  3. belirli kategorideki kitapları filtrelemek,
  4. başlığa göre sıralı rapor üretmek,
  5. ödünçteki kitap için öğrencileri geliş sırasıyla bekletmek.

Tek bir veri yapısı bütün gereksinimler için en iyi değildir.

Gereksinim Uygun yapı / yaklaşım
kitapların genel listesi list
kimliğe göre erişim dict indeks
kategori filtresi doğrusal dolaşma
başlığa göre rapor sorted(..., key=...)
bekleme sırası deque

Bu tablo, dönem boyunca vurguladığımız gereksinim → seçim ilişkisini gösterir.

4 2. Kimliğe göre indeks oluşturma

#| edit: false
#| completion: false
books = [
    {"id": "B101", "title": "Python'a Giriş", "category": "Programlama", "available": True},
    {"id": "B205", "title": "Ağ Temelleri", "category": "Ağ", "available": False},
    {"id": "B310", "title": "Veri Yapıları", "category": "Programlama", "available": True},
]

books_by_id = {}
for book in books:
    books_by_id[book["id"]] = book

print(books_by_id["B310"])

Bu indeks, aynı liste üzerinde kimliğe göre tekrar tekrar doğrusal arama yapma ihtiyacını azaltır.

5 3. Kategoriye göre filtreleme

#| edit: false
#| completion: false
def books_in_category(books, category):
    result = []

    for book in books:
        if book["category"] == category:
            result.append(book)

    return result

books = [
    {"id": "B101", "title": "Python'a Giriş", "category": "Programlama"},
    {"id": "B205", "title": "Ağ Temelleri", "category": "Ağ"},
    {"id": "B310", "title": "Veri Yapıları", "category": "Programlama"},
]

print(books_in_category(books, "Programlama"))

Bu işlem bütün kayıtları görebilmek zorunda olduğu için doğrusal dolaşma doğaldır.

6 4. Sıralı rapor

#| edit: false
#| completion: false
books = [
    {"id": "B101", "title": "Python'a Giriş"},
    {"id": "B205", "title": "Ağ Temelleri"},
    {"id": "B310", "title": "Veri Yapıları"},
]

ordered = sorted(books, key=lambda book: book["title"])

for book in ordered:
    print(book["title"])

sorted() kullandığımız için orijinal books listesinin sırası değişmez.

7 5. Bekleme sırası

Bir kitap ödünçteyse öğrenciler FIFO sırasıyla beklesin:

#| edit: false
#| completion: false
from collections import deque

waiting = deque()
waiting.append("Ayşe")
waiting.append("Bora")
waiting.append("Cem")

print("Sıradaki:", waiting[0])
print("Kitabı alan:", waiting.popleft())
print("Kalan:", list(waiting))

Bu problem yığın değil kuyruk davranışı ister; ilk gelen öğrencinin önce hizmet alması gerekir.

8 6. Aynı verinin birden çok görünümü

Bir kitabın aynı sözlük nesnesi hem books listesinde hem books_by_id sözlüğünde tutulabilir:

#| edit: false
#| completion: false
books = [
    {"id": "B101", "title": "Python'a Giriş", "available": True},
    {"id": "B205", "title": "Ağ Temelleri", "available": False},
]

books_by_id = {book["id"]: book for book in books}

books_by_id["B101"]["available"] = False

print(books[0])
print(books_by_id["B101"])

Neden iki yerde de değişiklik görünür? Çünkü iki koleksiyon aynı kitap sözlüğüne referans verir.

Bu davranış bazen yararlıdır; bazen bağımsız kopya bekleyen programcı için hata kaynağı olabilir. Referans sezgisini unutmayın.

9 7. Hazır kod tabanına özellik ekleme

Aşağıdaki kod küçük bir başlangıç sistemidir:

#| edit: false
#| completion: false
from collections import deque

books = [
    {"id": "B101", "title": "Python'a Giriş", "category": "Programlama", "available": True},
    {"id": "B205", "title": "Ağ Temelleri", "category": "Ağ", "available": False},
    {"id": "B310", "title": "Veri Yapıları", "category": "Programlama", "available": True},
]

books_by_id = {book["id"]: book for book in books}
waiting_lists = {
    "B101": deque(),
    "B205": deque(),
    "B310": deque(),
}


def find_book(book_id):
    return books_by_id.get(book_id)


def add_to_waiting_list(book_id, student_name):
    book = find_book(book_id)
    if book is None:
        return False

    waiting_lists[book_id].append(student_name)
    return True

print(find_book("B310"))
print(add_to_waiting_list("B205", "Ayşe"))
print(list(waiting_lists["B205"]))

Şimdi buna “kitap iade edildiğinde sıradaki öğrenciye ver” özelliği ekleyeceğiz.

10 Alıştırma — return_book fonksiyonu

Kurallar:

  1. kitap kimliği yoksa None döndür,
  2. bekleme listesinde öğrenci varsa ilk öğrenciyi çıkar ve adını döndür,
  3. bekleyen yoksa kitabı available = True yap ve None döndür,
  4. bekleyen öğrenciye kitap verildiğinde kitap kullanılabilir (available=True) olmamalıdır.
#| exercise: hafta14-return-book
#| completion: false
#| persist: true
from collections import deque

books = [
    {"id": "B101", "title": "Python'a Giriş", "available": False},
    {"id": "B205", "title": "Ağ Temelleri", "available": False},
]
books_by_id = {book["id"]: book for book in books}
waiting_lists = {
    "B101": deque(["Ayşe", "Bora"]),
    "B205": deque(),
}


def return_book(book_id):
    # Burayı tamamlayın.
    pass

print(return_book("B101"))
print(list(waiting_lists["B101"]))
print(books_by_id["B101"]["available"])
#| exercise: hafta14-return-book
#| check: true
scope = {}
try:
    exec(user_code, scope)

    from collections import deque
    scope["books_by_id"] = {
        "X": {"id": "X", "title": "X", "available": False},
        "Y": {"id": "Y", "title": "Y", "available": False},
    }
    scope["waiting_lists"] = {
        "X": deque(["Ali", "Ece"]),
        "Y": deque(),
    }
    fn = scope.get("return_book")

    first = fn("X")
    x_ok = (
        first == "Ali"
        and list(scope["waiting_lists"]["X"]) == ["Ece"]
        and scope["books_by_id"]["X"]["available"] is False
    )

    second = fn("Y")
    y_ok = second is None and scope["books_by_id"]["Y"]["available"] is True
    missing_ok = fn("NOPE") is None

    correct = callable(fn) and x_ok and y_ok and missing_ok
except Exception:
    correct = False

feedback = (
    {"correct": True, "message": "İade akışı bekleme kuyruğu ve kitap durumu açısından doğru çalışıyor."}
    if correct
    else {"correct": False, "message": "Önce kitabı get() ile bulun. Bekleyen varsa popleft() ile ilk öğrenciyi döndürün; yoksa available=True yapın."}
)
feedback

book = books_by_id.get(book_id) ile başlayın. if book is None: return None. Sonra waiting_lists[book_id] kuyruğunu kontrol edin.

Solution.

def return_book(book_id):
    book = books_by_id.get(book_id)
    if book is None:
        return None

    queue = waiting_lists[book_id]

    if queue:
        book["available"] = False
        return queue.popleft()

    book["available"] = True
    return None

11 8. Davranışı doğrulama

Yeni özelliği yalnızca bir örnekle denemek yeterli değildir. Sınır durumlarını da kontrol edin:

#| edit: false
#| completion: false
from collections import deque

books_by_id = {
    "B1": {"id": "B1", "available": False},
    "B2": {"id": "B2", "available": False},
}
waiting_lists = {
    "B1": deque(["Ayşe", "Bora"]),
    "B2": deque(),
}


def return_book(book_id):
    book = books_by_id.get(book_id)
    if book is None:
        return None

    queue = waiting_lists[book_id]
    if queue:
        book["available"] = False
        return queue.popleft()

    book["available"] = True
    return None

assert return_book("B1") == "Ayşe"
assert list(waiting_lists["B1"]) == ["Bora"]
assert books_by_id["B1"]["available"] is False

assert return_book("B2") is None
assert books_by_id["B2"]["available"] is True

assert return_book("B999") is None

print("Tüm kontroller geçti")

Bu testler üç önemli durumu kapsar:

  • bekleyen var,
  • bekleyen yok,
  • kitap yok.

12 9. Kısa maliyet yorumu

Bu sistem için birkaç temel işlem:

12.1 Kimliğe göre kitap bulma

books_by_id.get(book_id) sözlük erişimidir. Ortalama durumda O(1) sezgisiyle düşünürüz.

12.2 Kategoriye göre bütün kitapları bulma

Tüm books listesini dolaşmak gerekir: O(n).

12.3 Bekleme listesinden sıradaki öğrenciyi alma

deque.popleft() kuyruk için uygun ve yaklaşık O(1) davranışlı işlemdir.

12.4 Başlığa göre rapor sıralama

sorted() genel amaçlı verimli bir sıralama aracıdır; sıralamanın maliyeti veri boyutuyla birlikte doğrusalın üzerinde büyür. Bu derste yerleşik sıralamayı uygulamada tercih ederiz.

13 10. Mini karar tablosu

Aşağıdaki tabloyu kapatıp her satırın cevabını kendiniz üretmeye çalışın:

İhtiyaç Seçim Kısa gerekçe
kimliğe göre sık erişim dict anahtar tabanlı erişim
benzersiz üye numaraları set benzersizlik + üyelik
son yapılan işlemi geri al yığın LIFO
gelen talepleri sırayla işle deque FIFO
sırasız küçük veride bir kez ara doğrusal arama sıralama maliyetine gerek yok
sıralı büyük veride çok kez ara ikili arama O(log n) arama sezgisi

14 11. Transfer görevi — destek talepleri sistemi

Şimdi kütüphane örneğini bırakıyoruz. Aşağıdaki problem bu kitapta daha önce bütünleşik biçimde çözülmedi.

Bir teknik destek sistemi şu gereksinimlere sahip:

  1. Her talebin benzersiz bir ticket_id değeri var.
  2. Talepler geldikleri sırayla işlenecek.
  3. ticket_id ile belirli bir talebe çok sık erişilecek.
  4. Daha önce kapatılmış kimliklerin yeniden kullanılmasına izin verilmeyecek.
  5. Yönetici, açık talepleri öncelik puanına göre sıralı rapor olarak görmek isteyecek.
  6. Son yapılan yönetim değişikliğinin geri alınabilmesi istenecek.

14.1 Görev A — yapı seçimi

Her gereksinim için uygun yapı veya yaklaşımı seçin:

Açık taleplerin geliş sırası: ______________________
Ticket ID ile erişim: ______________________________
Kapatılmış kimlikler: ______________________________
Öncelik raporu: ____________________________________
Geri alma geçmişi: _________________________________

14.2 Görev B — üç adımlı gerekçe

  1. haftadaki yöntemi kullanarak ticket ID ile erişim için kısa gerekçe yazın:
1. Temel işlem: ____________________________________
2. Sıklık / maliyet: _______________________________
3. Seçim: __________________________________________
Gerekçe: ___________________________________________

14.3 Görev C — yanlış çözümü eleştirin

Bir AI aracı şu çözümü öneriyor:

open_tickets = []

# yeni talep
open_tickets.append(ticket)

# sıradaki talep
current = open_tickets.pop(0)

Bu kod FIFO açısından çalışabilir. Ancak on binlerce talebin bulunduğu yoğun bir sistemde hangi satırı neden sorgularsınız? Daha uygun Python yapısı nedir?

14.4 Görev D — arama kararı

Açık talepler ticket_id değerine göre sıralı tutulmuyor. Buna rağmen bir öğrenci ikili arama kullanmayı öneriyor. Bu önerideki temel ön koşul hatasını açıklayın.

Important

Transfer görevinin amacı belirli bir kodu ezberlemek değildir. Beklenen düşünme sırası şudur:

gereksinimi çıkar → temel işlemi belirle → uygun yapı/algoritmayı seç → ön koşulu kontrol et → maliyeti kısa gerekçelendir

15 Final için kendinizi kontrol edin

Aşağıdaki soruların her birine birkaç cümleyle yanıt verebiliyorsanız dersin ana omurgasını büyük ölçüde kurmuşsunuz demektir:

  1. list, dict ve set hangi gereksinimlerde birbirinden ayrılır?
  2. Aliasing ile sığ kopya arasındaki fark nedir?
  3. Yığın ile kuyruğun çıkarma sırası nasıl farklıdır?
  4. deque.popleft() neden list.pop(0) yerine tercih edilebilir?
  5. Doğrusal arama ve ikili arama hangi ön koşullarda uygundur?
  6. Insertion sort’un sıralı ve ters sıralı girdide davranışı neden farklıdır?
  7. sorted() ile .sort() arasındaki fark nedir?
  8. O(1), O(log n), O(n), O(n log n) ve O(n²) büyümelerini sezgisel olarak açıklayabilir misiniz?
  9. Bir veri yapısını seçerken hangi üç adımı izlemelisiniz?
  10. Çalışan bir çözümün neden yine de zayıf bir veri yapısı seçimi olabileceğini açıklayabilir misiniz?

16 Bölüm özeti

  • Gerçek problemler çoğu zaman birden çok veri yapısını birlikte gerektirir.
  • list, dict, set ve deque farklı işlem gereksinimlerine hizmet eder.
  • Bir indeks oluşturmak, tekrar eden aramalarda önemli avantaj sağlayabilir.
  • Referans ilişkileri, aynı kaydın birden fazla yapıdan görülmesini sağlayabilir.
  • Sınır durumlarını assert ile doğrulamak çözümün güvenilirliğini artırır.
  • Finalde yalnızca kod üretmek değil, neden bu yapı ve neden bu algoritma? sorusunu yanıtlamak önemlidir.
  • Transfer görevi, aynı düşünme modelini daha önce görülmemiş bir bağlama taşıyabilmeyi ölçer.
Back to top