Ders 08 / 23
Kayan Pencere
Bitişik alt dizide artımlı hesap; negatif değerin küçültme kuralını bozduğu 10 girdi ve ön koşulun pencereye değil kurala ait olduğu.
İçindekiler
Önceki ders iki işaretçiyi dizinin iki ucuna koyuyor ve ortada buluşturuyordu; ön koşulu sıraydı. Bu dersin kalıbı işaretçileri aynı yönde tutar. Aralarındaki bölge bir penceredir: sağ işaretçi ilerledikçe pencere büyür, bir koşul sağlandığında sol işaretçi ilerleyerek pencereyi küçültür.
Kazanç, pencere her kaydığında toplamın sıfırdan hesaplanmamasından gelir; giren değer eklenir, çıkan değer düşülür. Ön koşulu ise sıraya hiç bakmaz: hiçbir değer negatif olmamalıdır. Bu ders o ön koşulun bozulduğu girdileri kâhinle sayar ve ön koşulun aslında pencerenin değil küçültme kuralının ön koşulu olduğunu gösterir.
Problem, Kâhin ve Kalıp
Problem şudur: toplamı hedeften küçük olmayan en kısa bitişik alt dizinin uzunluğu nedir. Kâhin her başlangıç konumundan başlayıp sağa doğru genişler ve hedefi ilk aştığı yerde durur; bütün başlangıçları dener. Kalıp tek geçişte ilerler: sağ uç değeri toplama katar, toplam hedefi aştığı sürece sol uçtan değer düşerek pencereyi daraltır.
PK10. Dağarcık, önceki dersin dağarcığıdır: tohum 20260218, 40 dizi, her biri 12
değer, değerler −9 ile 20 arasında. Kırk dizinin kırkı negatif değer içerir.
PK11. Ön koşulu sağlayan öbek, aynı dizilerin değerlerinin mutlak değeridir. İki
öbeğin tek farkı işarettir; uzunluk, konum ve üreteç aynıdır.
PK12. Hedef 25’tir ve bir alt dizi hedefi tam olarak tutturmak zorunda değildir;
“hedeften küçük olmamak” yeterlidir.
PK13. Kâhin kaba kuvvettir ve her zaman doğru sayılır. Yanıt bir uzunluktur; hiçbir
alt dizi hedefi tutturamıyorsa yanıt tanımsızdır.
TOHUM = 20260218 def uretec(tohum): d = tohum def sonraki(n): nonlocal d d = (d * 1103515245 + 12345) % 2147483648 return d % n return sonraki def dagarcik(tohum=TOHUM, n=40, uzunluk=12): r = uretec(tohum) return [{"no": i + 1, "dizi": [r(30) - 9 for _ in range(uzunluk)]} for i in range(n)] class Sayac: def __init__(self): self.adim = 0 def say(self): self.adim += 1 def kahin_en_kisa(dizi, hedef, s): """Butun bitisik alt dizileri dener. Her zaman dogru.""" en_iyi = None for i in range(len(dizi)): toplam = 0 for j in range(i, len(dizi)): s.say() toplam += dizi[j] if toplam >= hedef and (en_iyi is None or j - i + 1 < en_iyi): en_iyi = j - i + 1 break return en_iyi def kalip_kayan_pencere(dizi, hedef, s): """ONKOSUL: butun degerler negatif olmamali.""" sol, toplam, en_iyi = 0, 0, None for sag in range(len(dizi)): s.say() toplam += dizi[sag] while toplam >= hedef: if en_iyi is None or sag - sol + 1 < en_iyi: en_iyi = sag - sol + 1 toplam -= dizi[sol] sol += 1 s.say() return en_iyi def olc(kume, hedef): ayrilan, ak, ah = [], 0, 0 for k in kume: s1, s2 = Sayac(), Sayac() a = kalip_kayan_pencere(k["dizi"], hedef, s1) b = kahin_en_kisa(k["dizi"], hedef, s2) ak, ah = ak + s1.adim, ah + s2.adim if a != b: ayrilan.append(k["no"]) return {"ayrilan": len(ayrilan), "ilk_ayrilan": ayrilan[:6], "kalip_adim": ak, "kahin_adim": ah, "oran": round(ah / ak, 2)} K = dagarcik() P = [dict(k, dizi=[abs(x) for x in k["dizi"]]) for k in K] print("dagarcik:", len(K), "dizi | negatif iceren:", sum(1 for k in K if any(x < 0 for x in k["dizi"]))) for ad, kume in (("on_kosul saglaniyor", P), ("on_kosul bozuk ", K)): print(f" {ad}", olc(kume, 25))
dagarcik: 40 dizi | negatif iceren: 40
on_kosul saglaniyor {'ayrilan': 0, 'ilk_ayrilan': [], 'kalip_adim': 867, 'kahin_adim': 2396, 'oran': 2.76}
on_kosul bozuk {'ayrilan': 10, 'ilk_ayrilan': [2, 12, 13, 15, 24, 26], 'kalip_adim': 761, 'kahin_adim': 2462, 'oran': 3.24}
Ön koşul sağlandığında kalıp 867 adımda 40 girdinin 40’ında kâhinle aynı yanıtı veriyor; kâhin 2396 adım harcıyor, oran 2,76. Negatif değer içeren dağarcıkta ayrılan girdi 10 oluyor.
Buradaki sayı, önceki dersin 25’inden düşük ve bu düşüklük dersin en tehlikeli tarafıdır. Kalıp girdilerin dörtte üçünde hâlâ doğru yanıt veriyor. Bir sınama kümesi rastgele seçilseydi temiz sonuç verme olasılığı yüksekti; kusur, gözlemle değil kâhinle ortaya çıkar.
İkinci satırın oranı da dikkat çekicidir: 3,24, yani ön koşul sağlandığındaki 2,76’dan yüksek. Kalıp bozuk girdide daha az adım harcıyor (761’e karşı 867), çünkü negatif değerler toplamı düşürüyor ve küçültme döngüsü daha seyrek çalışıyor. Daha az adım daha iyi yanıt demek değildir.
Küçültme Kuralı Neye Dayanıyor
Kalıbın tek riskli satırı while toplam >= hedef döngüsüdür. Bu döngü, soldan bir değer
düşüldüğünde toplamın azalacağını varsayar. Negatif olmayan değerlerde bu doğrudur;
toplam, sol uç ilerledikçe tekdüze azalır ve döngü ilk kez koşulu bozduğunda o
başlangıç için en kısa pencere bulunmuş olur.
Negatif bir değer düşüldüğünde toplam artar. O anda pencere daralmış ama toplam büyümüştür; kalıp bunu bir ilerleme sayar ve sol ucu bir daha geri almaz. Atlanan başlangıçlar arasında daha kısa bir çözüm varsa görülmez.
PK14. Kalıp sol ucu geri almaz; her konum en çok bir kez sol uçtan çıkar. Kalıbın doğrusal adım sayısı bu geri almama kuralından gelir, dolayısıyla kural gevşetilemez.
def uretec(tohum): d = tohum def sonraki(n): nonlocal d d = (d * 1103515245 + 12345) % 2147483648 return d % n return sonraki def dagarcik(tohum): r = uretec(tohum) return [[r(30) - 9 for _ in range(12)] for _ in range(40)] def kahin_en_kisa(dizi, hedef): en_iyi = None for i in range(len(dizi)): toplam = 0 for j in range(i, len(dizi)): toplam += dizi[j] if toplam >= hedef and (en_iyi is None or j - i + 1 < en_iyi): en_iyi = j - i + 1 break return en_iyi def kalip_kayan_pencere(dizi, hedef): sol, toplam, en_iyi = 0, 0, None for sag in range(len(dizi)): toplam += dizi[sag] while toplam >= hedef: if en_iyi is None or sag - sol + 1 < en_iyi: en_iyi = sag - sol + 1 toplam -= dizi[sol] sol += 1 return en_iyi ikinci = dagarcik(20260218)[1] print("girdi 2:", ikinci) print(" kalip:", kalip_kayan_pencere(ikinci, 25), "| kahin:", kahin_en_kisa(ikinci, 25)) print(" mutlak degerle:", [abs(x) for x in ikinci]) print(" kalip:", kalip_kayan_pencere([abs(x) for x in ikinci], 25), "| kahin:", kahin_en_kisa([abs(x) for x in ikinci], 25)) print() print("tohum hedef on_kosul ayrilan/40 oran") for tohum in (20260218, 20260219): for hedef in (25, 15): K = dagarcik(tohum) obek = (("saglaniyor", [[abs(x) for x in d] for d in K]), ("bozuk ", K)) for ad, kume in obek: ayrilan = sum(1 for d in kume if kalip_kayan_pencere(d, hedef) != kahin_en_kisa(d, hedef)) print(f"{tohum} {hedef:5d} {ad} {ayrilan:8d} {ayrilan / 40:.4f}")
girdi 2: [-6, 15, 10, 11, 6, -5, -4, 3, 2, 17, -8, 5] kalip: 3 | kahin: 2 mutlak degerle: [6, 15, 10, 11, 6, 5, 4, 3, 2, 17, 8, 5] kalip: 2 | kahin: 2 tohum hedef on_kosul ayrilan/40 oran 20260218 25 saglaniyor 0 0.0000 20260218 25 bozuk 10 0.2500 20260218 15 saglaniyor 0 0.0000 20260218 15 bozuk 6 0.1500 20260219 25 saglaniyor 0 0.0000 20260219 25 bozuk 8 0.2000 20260219 15 saglaniyor 0 0.0000 20260219 15 bozuk 6 0.1500
İkinci girdide kâhin 2 diyor, kalıp 3. Doğru yanıt 15 + 10 çiftidir; kalıp bu
çifti göremiyor, çünkü sol uç -6 değerini düşürdüğünde toplam artmış ve pencere yanlış
yerde sabitlenmiş. Kalıbın çıktısı yine bir uzunluk, yine akla yatkın, yine yanlış.
PK15. İkinci dağarcık 20260219 tohumundan gelir. Ayrılan girdi oranı aynı büyüklük
düzeninde kalmazsa sonuç dağarcığa bağlıdır ve öyle yazılır.
İkinci dağarcıkta hedef 25 için ayrılan girdi 8, birincide 10; hedef 15 için ikisi de 6. Dört ölçümün dördü de 0,15 ile 0,25 arasında, yani aynı büyüklük düzeninde. Sonuç dağarcığa bağlı değildir. Ön koşulun sağlandığı dört satırda ayrılan girdi sıfırdır.
Ön koşul Pencerenin Değil, Kuralın
Kayan pencere adı iki ayrı kalıbı birden anar ve ikisinin ön koşulu aynı değildir. Yukarıdaki biçim değişken boyutludur: pencere bir koşul sağlanana kadar büyür, sağlandığında küçülür. Bir de sabit boyutlu biçim vardır: pencere hep k geniştir, sağdan bir değer girer, soldan bir değer çıkar.
Sabit boyutlu biçimde küçültme kuralı yoktur; pencere bir koşula bakarak daralmaz, yalnızca kayar. O hâlde toplamın tekdüze azalması da gerekmez.
PK16. Sabit boyutlu pencere yalnız artımlı toplam kullanır: bir toplama, bir çıkarma. Kalıbın adımı k’dan bağımsızdır; kâhinin adımı k ile büyür.
def uretec(tohum): d = tohum def sonraki(n): nonlocal d d = (d * 1103515245 + 12345) % 2147483648 return d % n return sonraki def dagarcik(tohum=20260218): r = uretec(tohum) return [[r(30) - 9 for _ in range(12)] for _ in range(40)] class Sayac: def __init__(self): self.adim = 0 def say(self): self.adim += 1 def kahin_sabit(dizi, k, s): """Her pencerenin toplamini sifirdan hesaplar.""" en_iyi = None for i in range(len(dizi) - k + 1): toplam = 0 for j in range(i, i + k): s.say() toplam += dizi[j] if en_iyi is None or toplam > en_iyi: en_iyi = toplam return en_iyi def kalip_sabit(dizi, k, s): """Pencere sabit boyutlu: bir deger girer, bir deger cikar. Isaret on_kosulu yok.""" toplam, en_iyi = 0, None for sag in range(len(dizi)): s.say() toplam += dizi[sag] if sag >= k: toplam -= dizi[sag - k] if sag >= k - 1 and (en_iyi is None or toplam > en_iyi): en_iyi = toplam return en_iyi print("k girdi ayrilan/40 kalip kahin oran") for k in (3, 4, 6): for ad, hazirla in (("negatifsiz", lambda d: [abs(x) for x in d]), ("negatifli ", list)): ayrilan, ak, ah = 0, 0, 0 for dizi in dagarcik(): d = hazirla(dizi) s1, s2 = Sayac(), Sayac() ayrilan += (kalip_sabit(d, k, s1) != kahin_sabit(d, k, s2)) ak, ah = ak + s1.adim, ah + s2.adim print(f"{k} {ad} {ayrilan:12d} {ak:5d} {ah:5d} {ah / ak:5.2f}")
k girdi ayrilan/40 kalip kahin oran 3 negatifsiz 0 480 1200 2.50 3 negatifli 0 480 1200 2.50 4 negatifsiz 0 480 1440 3.00 4 negatifli 0 480 1440 3.00 6 negatifsiz 0 480 1680 3.50 6 negatifli 0 480 1680 3.50
Altı satırın altısında ayrılan girdi sıfır. Negatif değerler sabit boyutlu pencereyi hiç etkilemiyor; kalıbın adımı üç k değerinde de 480, kâhinin adımı k ile büyüyor ve oran 2,50’den 3,50’ye çıkıyor.
Bu, dersin yapısal sonucudur: ön koşul kalıbın adına değil, kalıbın içindeki tek bir kurala aittir. “Kayan pencere negatif değerle çalışmaz” cümlesi yanlıştır; doğru cümle, “toplamın tekdüze azaldığı varsayımına dayanan küçültme kuralı negatif değerle çalışmaz” cümlesidir. Bir kalıbı ön koşuluyla birlikte öğrenmek, kalıbın adını değil o kuralı bilmek demektir.
Üç Sayı
| Ölçüt | Kâhin | Kalıp | Ayrılan girdi |
|---|---|---|---|
| Değişken pencere, ön koşul sağlanıyor | 2396 adım | 867 adım | 0/40 |
| Değişken pencere, ön koşul bozuk | 2462 adım | 761 adım | 10/40 |
| Sabit pencere (k=4), negatifli girdi | 1440 adım | 480 adım | 0/40 |
İkinci satır kalıbın en az adımı harcadığı satırdır ve tek bozuk satırdır. Üçüncü satır aynı girdiyle sıfır ayrılma veriyor, çünkü kalıp değişti.
Denetim Doğruluğu Geri Alır, Hızlanmayı Almaz
Ön koşulu denetlemek ucuzdur: bir dizide negatif değer aramak en çok n karşılaştırmadır ve ilk negatifte durur. Denetim başarısızsa iş kâhine bırakılır. Bu düzenlemenin doğruluğu tamdır; sorulması gereken, geriye ne kadar hızlanma kaldığıdır.
PK17. Denetimli kalıp, denetimin adımlarını da kendi hanesine yazar; kâhine devredildiğinde kâhinin adımı da kalıbın adımına eklenir.
def uretec(tohum): d = tohum def sonraki(n): nonlocal d d = (d * 1103515245 + 12345) % 2147483648 return d % n return sonraki def dagarcik(tohum=20260218): r = uretec(tohum) return [[r(30) - 9 for _ in range(12)] for _ in range(40)] class Sayac: def __init__(self): self.adim = 0 def say(self): self.adim += 1 def kahin_en_kisa(dizi, hedef, s): en_iyi = None for i in range(len(dizi)): toplam = 0 for j in range(i, len(dizi)): s.say() toplam += dizi[j] if toplam >= hedef and (en_iyi is None or j - i + 1 < en_iyi): en_iyi = j - i + 1 break return en_iyi def kalip_kayan_pencere(dizi, hedef, s): sol, toplam, en_iyi = 0, 0, None for sag in range(len(dizi)): s.say() toplam += dizi[sag] while toplam >= hedef: if en_iyi is None or sag - sol + 1 < en_iyi: en_iyi = sag - sol + 1 toplam -= dizi[sol] sol += 1 s.say() return en_iyi def denetimli(dizi, hedef, s): """Onkosulu sinar; saglanmiyorsa kahine birakir.""" for x in dizi: s.say() if x < 0: return kahin_en_kisa(dizi, hedef, s) return kalip_kayan_pencere(dizi, hedef, s) for ad, hazirla in (("negatifsiz", lambda d: [abs(x) for x in d]), ("negatifli ", list)): ayrilan, ad_denetimli, ad_kahin = 0, 0, 0 for dizi in dagarcik(): d = hazirla(dizi) s1, s2 = Sayac(), Sayac() ayrilan += (denetimli(d, 25, s1) != kahin_en_kisa(d, 25, s2)) ad_denetimli += s1.adim ad_kahin += s2.adim print(f"{ad} ayrilan {ayrilan}/40 denetimli kalip {ad_denetimli:5d}" f" kahin {ad_kahin:5d} oran {ad_kahin / ad_denetimli:.2f}")
negatifsiz ayrilan 0/40 denetimli kalip 1347 kahin 2396 oran 1.78 negatifli ayrilan 0/40 denetimli kalip 2568 kahin 2462 oran 0.96
Ayrılan girdi iki satırda da sıfır. Ama ikinci satırın oranı 0,96: negatifli dağarcıkta denetimli kalıp, kâhinden daha çok adım harcıyor. Bunun nedeni açıktır — kırk dizinin kırkı denetimden geçemiyor, iş kırk kez kâhine düşüyor ve denetimin 106 adımı üstüne biniyor. Ön koşulun sağlandığı dağarcıkta oran 1,78’de kalıyor, yani denetim orada bile 2,76’dan 1,78’e bir kayıp yazdırıyor.
Buradan çıkan okuma şudur: ön koşul denetimi bir doğruluk aracıdır, bir başarım aracı değildir. Girdilerin hangi oranda ön koşulu sağladığı bilinmeden denetimli kalıbın kazandıracağı söylenemez; bu dağarcıkta o oran sıfırdır ve kalıp bütünüyle kâhine dönüşmüştür.
Özet
- Kayan pencere iki işaretçiyi aynı yönde tutar ve toplamı sıfırdan hesaplamak yerine artımlı günceller.
- Değişken boyutlu biçimde küçültme kuralı, soldan değer düşüldüğünde toplamın azalacağını varsayar; bu varsayım yalnız negatif olmayan değerlerde doğrudur.
- Negatif içeren dağarcıkta kalıp 10 girdide kâhinden ayrılıyor ve bunu daha az adımla yapıyor; az adım doğruluk göstergesi değildir.
- İkinci dağarcıkta ayrılan girdi 8; oran aynı büyüklük düzeninde kaldığı için sonuç dağarcığa bağlı değildir.
- Sabit boyutlu pencere küçültme kuralı taşımadığı için negatif değerden etkilenmez: altı ölçümün altısında ayrılan girdi sıfırdır.
Sonraki Adım
İki kalıp da tek bir dizide, konum sayısı bilinerek çalıştı. Sonraki kalıp işaretçileri yine aynı yönde ilerletir ama farklı hızlarda ve uzunluğun bilinmediği bir yapıda: her düğümün bir ardılı vardır, sonu olup olmadığı bilinmez. Sonraki ders bu yapıda döngü tespitini ve orta elemanı ölçer; ön koşulu, ilerlemenin gerçekten tek yönlü olmasıdır ve o ön koşul bozulduğunda kalıp yalnız yanlış yanıt vermez, hiç durmayabilir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.