Ana içeriğe geç
Kesit Python
Temel · Akış kontrolü

Döngü kalıpları ve temel algoritmalar

Sayaç, biriktirici, en büyük/en küçük, doğrusal arama ve bayrak, komşu karşılaştırma, frekans, iç içe döngü, erken çıkış ve while ile hedefe kadar tekrar kalıplarını tanır; her yeni problemi bu kalıplarla çözmeyi öğrenirsin.

Uygulamada aç, 17 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

Döngü yazmak, kalıp tanımaktır

Döngüler dersinde for, while, range, enumerate, zip, break ve continue'yu öğrendin. Bu ders bir adım geri çekilip şu soruyu soruyor: deneyimli bir geliştirici yeni bir problemi gördüğünde döngüyü nasıl bu kadar hızlı yazıyor? Cevap: yazdığı döngülerin büyük çoğunluğu bir avuç kalıbın (pattern) tekrarıdır. "Kaç tane?" sorusu sayaçtır, "toplam ne?" biriktiricidir, "en pahalısı hangisi?" en iyi değer kalıbıdır, "var mı?" aramadır.

Her kalıbın üç parçası vardır: döngüden önce başlangıç değeri, döngünün içinde güncelleme kuralı, döngüden sonra sonucu kullanma. Hataların çoğu bu üçünden birinde yapılır; özellikle başlangıç değerinde.

SoruKalıpBaşlangıçGüncelleme
Kaç tane?Sayaçsayac = 0sayac += 1 (koşullu)
Toplam/çarpım ne?Biriktirici0 ya da 1, "", []toplam += x
En büyüğü hangisi?En iyi değerİlk elemanDaha iyisi gelince değiştir
Var mı, nerede?Doğrusal aramabulundu = FalseBulunca bayrak + break
Hepsi mi uyuyor?Doğrulamahepsi = TrueUymayan gelince False + break
Önceki güne göre?Komşu karşılaştırmaİlk eleman ya da indeks 1x ile onceki
Her değerden kaç tane?Frekans{}d[x] = d.get(x, 0) + 1
Hedefe kaç adımda?Koşula kadar tekrarDurum + adım sayacıwhile koşulu

Sayaç ve biriktirici: tek geçişte birden fazla sonuç

Bu ikisini zaten biliyorsun. Burada kazanman gereken alışkanlık: aynı veriyi birkaç kez gezmek yerine tek geçişte birden fazla sayaç ve biriktirici tutmak.

islemler = [1200, -350, 4800, -90, -1500, 600, 0, 75]   # Ilgaz Bank hesap hareketleri

giris_toplam = 0
cikis_toplam = 0
cikis_sayisi = 0
buyuk_islem = 0                      # |tutar| >= 1000
for t in islemler:
    if t > 0:
        giris_toplam += t
    elif t < 0:
        cikis_toplam += -t
        cikis_sayisi += 1
    if abs(t) >= 1000:
        buyuk_islem += 1

print("Giriş:", giris_toplam, "Çıkış:", cikis_toplam)
print("Çıkış adedi:", cikis_sayisi, "Büyük işlem:", buyuk_islem)
print("Net:", giris_toplam - cikis_toplam)

Biriktiricinin başlangıç değeri işleme göre değişir: toplamada 0, çarpmada 1 (0 ile başlarsan sonuç hep 0 olur), metin birleştirmede "", liste toplamada [].

En büyük ve en küçük: max kullanmadan

Gerçek kodda max ve min kullanırsın; ama kalıbı bilmek iki sebeple önemli. Birincisi, çoğu zaman yalnız değeri değil kaydın kendisini ya da konumunu istersin. İkincisi, "en iyi" tanımı karmaşıklaşabilir: en yüksek tutarlı ama iptal edilmemiş sipariş, eşitlikte en eski olan gibi.

bakiyeler = [-250.0, -1200.5, -40.0, -980.0]      # hepsi negatif!

# YANLIŞ başlangıç: 0 hiçbir negatif değerden küçük değil
en_buyuk_yanlis = 0
for b in bakiyeler:
    if b > en_buyuk_yanlis:
        en_buyuk_yanlis = b
print("Yanlış sonuç:", en_buyuk_yanlis)

# DOĞRU: ilk elemanla başla, konumu da tut
en_kucuk = bakiyeler[0]
en_kucuk_i = 0
for i, b in enumerate(bakiyeler):
    if b < en_kucuk:
        en_kucuk = b
        en_kucuk_i = i
print("En düşük bakiye:", en_kucuk, "konum:", en_kucuk_i)
Başlangıç değeri tuzağıEn büyüğü bulurken 0 ile başlamak, tüm değerler negatifse yanlış sonuç verir; en küçüğü bulurken 0 ile başlamak, tüm değerler pozitifse yanlış sonuç verir. Güvenli seçenekler: listenin ilk elemanı ya da float("-inf") / float("inf"). Boş liste ihtimali varsa önce onu kontrol et: if not bakiyeler: return None.

Eşitlik durumunda hangi kaydın seçileceği karşılaştırma operatörüne bağlıdır: < ilk görüleni, <= son görüleni tutar. Bu küçük fark, raporlarda "neden bu müşteri çıktı" sorusunun cevabıdır.

Doğrusal arama ve bayrak değişkeni

"Bu listede şu koşulu sağlayan var mı, varsa ilki hangisi?" Elemanları baştan sona tek tek kontrol etmeye doğrusal arama (linear search) denir. Sonucu tutmak için bir bayrak (flag) değişkeni kullanırsın; aradığını bulunca da break ile çıkarsın, çünkü geri kalanı kontrol etmek boşa iştir.

siparisler = [("AM-1", 120), ("AM-2", 4500), ("AM-3", 80), ("AM-4", 9000)]
esik = 4000

bulundu = False
for no, tutar in siparisler:
    if tutar > esik:
        bulundu = True
        ilk_buyuk = no
        break
if bulundu:
    print("İlk büyük sipariş:", ilk_buyuk)
else:
    print("Büyük sipariş yok")

# Aynı iş for-else ile: else, döngü break OLMADAN biterse çalışır
for no, tutar in siparisler:
    if tutar > 10000:
        print("Çok büyük sipariş:", no)
        break
else:
    print("10000 üstü sipariş yok")

Bayrağın tersi doğrulama kalıbıdır: "hepsi uyuyor mu?" sorusunda hepsi = True ile başlar, uymayan ilk elemanda False yapıp çıkarsın. Bu iki kalıbın hazır karşılıkları any() ve all()'dur; ama hangi elemanın uyduğunu ya da uymadığını öğrenmen gerekiyorsa döngü yazarsın.

Fonksiyon içinde daha kısaArama bir fonksiyonun içindeyse bayrağa gerek kalmaz: bulduğun anda return et, döngü bitince "bulunamadı" değerini (None ya da -1) döndür. Erken dönüş (early return), bayrak ve break'in en okunur hâlidir.

Komşu elemanları karşılaştırmak

Zaman serilerinde (günlük satış, aylık bakiye, saatlik sıcaklık) asıl soru çoğu zaman bir değerin bir öncekine göre ne olduğudur: arttı mı, azaldı mı, kaç gün üst üste arttı? Bunun için her adımda iki elemana birden bakarsın.

satislar = [120, 135, 135, 150, 90, 110, 130, 160]   # günlük satış adedi

# Yöntem 1: indeksle, 1'den başla
artis = 0
for i in range(1, len(satislar)):
    if satislar[i] > satislar[i - 1]:
        artis += 1
print("Artış günü:", artis)

# Yöntem 2: zip ile (önceki, şimdiki) çiftleri
for onceki, simdi in zip(satislar, satislar[1:]):
    if simdi < onceki:
        print(f"Düşüş: {onceki} → {simdi} ({simdi - onceki})")

# Seri (streak): en uzun üst üste artış
seri = en_uzun = 0
for onceki, simdi in zip(satislar, satislar[1:]):
    if simdi > onceki:
        seri += 1
        en_uzun = max(en_uzun, seri)
    else:
        seri = 0
print("En uzun artış serisi:", en_uzun, "gün")

zip(satislar, satislar[1:]) güzel bir hiledir: listeyi bir kaydırılmış kopyasıyla eşler ve n elemandan n-1 komşu çifti verir. Döngünün ilk elemandan değil ikinciden başladığına dikkat et; ilk günün "önceki günü" yoktur. İndeks yönteminde range(len(satislar)) yazıp satislar[i - 1] kullanırsan ilk turda satislar[-1], yani son eleman okunur ve hata vermeden yanlış sonuç alırsın.

Frekans ve gruplama

Sözlükler dersinde sayım kalıbını, İç içe veri yapıları dersinde gruplamayı gördün. Kalıp olarak hatırla: frekans, anahtar başına bir sayaç; gruplama, anahtar başına bir liste biriktiricisi. İkisi birleşince "en sık" sorusu iki adımlı olur: önce say, sonra en iyi değer kalıbıyla en büyüğü bul.

satilan = ["çay", "kahve", "çay", "şeker", "kahve", "çay", "su"]
frekans = {}
for urun in satilan:
    frekans[urun] = frekans.get(urun, 0) + 1

en_cok, en_cok_adet = None, 0
for urun, adet in frekans.items():
    if adet > en_cok_adet:
        en_cok, en_cok_adet = urun, adet
print(frekans)
print("En çok satan:", en_cok, en_cok_adet)

İç içe döngü: tablolar ve çiftler

Dış döngünün her turunda iç döngü baştan sona çalışır. İki tipik kullanım: satır-sütun tablosu üretmek ve bir listedeki tüm çiftleri denemek. Çiftlerde aynı çifti iki kez (a-b ve b-a) ve elemanı kendisiyle eşlememek için iç döngü i + 1'den başlar.

for i in range(1, 4):
    satir = ""
    for j in range(1, 6):
        satir += f"{i * j:4}"
    print(satir)

fiyatlar = [150, 350, 200, 50]
butce = 400
for i in range(len(fiyatlar)):
    for j in range(i + 1, len(fiyatlar)):
        if fiyatlar[i] + fiyatlar[j] == butce:
            print("Bütçeye tam uyan ikili:", fiyatlar[i], fiyatlar[j])

İç içe döngüde iş sayısı çarpılır: 1000 elemanlı listede tüm çiftler yaklaşık yarım milyon karşılaştırmadır. Bu, Uzmanlaş bölümündeki karmaşıklık konusunun girişidir.

while: koşula kadar tekrar

Kaç tur süreceğini önceden bilmediğin durumlarda while kullanırsın. Ilgaz Bank'ın klasik müşteri sorusu: "Hesabımda 10.000 TL var, aylık %3 faiz alırsam 15.000 TL'ye kaç ayda ulaşırım?"

bakiye = 10_000
aylik_faiz = 0.03
hedef = 15_000

ay = 0
while bakiye < hedef:
    bakiye *= 1 + aylik_faiz
    ay += 1
print(f"{ay} ayda hedefe ulaşılır, bakiye: {bakiye:.2f} TL")

# Güvenlik sınırı: faiz 0 olursa döngü sonsuza gider
bakiye, ay = 10_000, 0
while bakiye < hedef and ay < 600:
    bakiye *= 1 + 0.0
    ay += 1
if bakiye < hedef:
    print("Hedefe ulaşılamadı, döngü", ay, "ayda durduruldu")

while döngüsünün üç sorusunu her zaman cevapla: koşul ne, döngü içinde koşulu hedefe yaklaştıran satır hangisi, ve hedefe asla ulaşılamazsa ne olur? Son soru için bir üst sınır (en fazla 600 ay gibi) koymak, üretim kodunda hayat kurtarır.

Döngüde liste değiştirme tuzağı

Gezdiğin listeden eleman silmek, Python'daki en sinsi hatalardan biridir: hata vermez, sessizce eleman atlar. Sebep: for döngüsü içeride bir indeks sayacı tutar; sen bir elemanı silince sonrakiler bir sola kayar ve sayaç bir sonrakini atlar.

siparisler = ["iptal", "iptal", "aktif", "iptal", "aktif"]
for s in siparisler:
    if s == "iptal":
        siparisler.remove(s)
print("Yanlış:", siparisler)        # bir iptal kaldı!

siparisler = ["iptal", "iptal", "aktif", "iptal", "aktif"]
temiz = []
for s in siparisler:
    if s != "iptal":
        temiz.append(s)
print("Doğru (yeni liste):", temiz)

siparisler = ["iptal", "iptal", "aktif", "iptal", "aktif"]
for s in siparisler[:]:             # kopya üzerinde gez, orijinalden sil
    if s == "iptal":
        siparisler.remove(s)
print("Doğru (kopya üzerinde):", siparisler)

En temiz yol, istediğin elemanlarla yeni bir liste kurmaktır (Orta seviyede bunu tek satırlık comprehension ile yapacaksın). Orijinal listeyi mutlaka yerinde değiştirmen gerekiyorsa kopyası üzerinde gez ya da listeyi sondan başa doğru gez. Aynı kural sözlükler için daha da serttir: gezerken sözlüğe anahtar ekleyip silmek RuntimeError verir.

Gerçek işteRaporlama betiklerinin büyük kısmı bu kalıpların birleşimidir: gün sonu raporunda tek geçişte ciro, iade sayısı ve en büyük sipariş hesaplanır; dolandırıcılık izleme kuralı "aynı karttan 10 dakika içinde art arda 3 işlem" gibi komşu karşılaştırmasıdır; stok uyarısı doğrulama kalıbıdır; kredi simülatörü while döngüsüdür. Kod incelemesinde (code review) kıdemli bir geliştirici "burada en büyüğü 0 ile başlatmışsın, iade tutarları negatif" dediğinde konuştuğu şey bu dersin kendisidir.
Uzmanlaş

Döngü değişmezi (loop invariant)

Doğru döngü yazmanın en güçlü düşünme aracı, her turun sonunda doğru kalan cümledir. En büyük bulma kalıbında değişmez şudur: "en_buyuk, şimdiye kadar görülen elemanların en büyüğüdür." Başlangıçta doğru olmalı (ilk elemanla başlatmanın sebebi), her tur korumalı ve döngü bitince sonucu vermeli. Hata ayıklarken "bu cümle hangi turda bozuldu?" diye sormak, rastgele print eklemekten çok daha hızlıdır.

Karmaşıklık: kaç adım?

KalıpAdım sayısı1 milyon eleman
Tek geçiş (sayaç, toplam, en büyük, arama)nGöz açıp kapayıncaya kadar
Erken çıkışlı aramaEn iyi 1, en kötü nBulunacak yere bağlı
Tüm çiftler (iç içe döngü)n × n / 2Yarım trilyon: kullanılamaz
Sözlük/küme ile eşlemenHızlı

"Toplamı bütçeye eşit iki ürün" problemini iç içe döngü yerine tek geçişte çözebilirsin: gördüğün fiyatları bir kümede tut, her yeni fiyatta butce - fiyat kümede var mı diye bak. Bu, mülakatların en klasik sorusudur (two sum) ve kalıp birleştirmenin gücünü gösterir.

def butce_ikilisi(fiyatlar, butce):
    gorulen = set()
    for f in fiyatlar:
        if butce - f in gorulen:
            return (butce - f, f)
        gorulen.add(f)
    return None

print(butce_ikilisi([150, 350, 200, 50], 400))
print(butce_ikilisi([10, 20], 100))

Kalıptan yerleşik fonksiyona

Kalıbı tanıdıktan sonra kısaltmasını kullanmak profesyonel koddur: sayaç → sum(1 for x in v if ...), biriktirici → sum, en iyi → max(v, key=...), arama → next((x for x in v if ...), None), doğrulama → all, frekans → collections.Counter, komşu çiftleri → itertools.pairwise. Bunların çoğunu Orta seviyede göreceksin. Ama kısaltma çalışmadığında ya da kural karmaşıklaştığında açık döngüye dönebilmek, kalıbı bilmekle mümkün.

Alıştırmalara geç (17)