Ders 08 / 22
İndeksleme ve Dilimleme
Aynı iki uç, 10.100 ve 10.200, dört ayrı yazımda dört farklı satır sayısı döndürüyor: etiketle dilimlenince 95 satır çünkü etiket dilimi son ucu içerir, aynı iki konumla dilimlenince 94 satır çünkü konum dilimi son ucu dışarıda bırakır, uçlar konum sanılıp yazılınca 100 satır, tekil olmayan dizinli uzun tabloda ise 230 satır. Çerçeve toplam tüketime göre sıralanınca etiket dizini sıralı olmaktan çıkar ve aynı dilim 617 satır döner; dizinde bulunmayan bir uç sıralı dizinde 22 satırlık bir dilim verirken sırası bozuk dizinde hata üretir. Dizinsiz doğrusal tarama 2.658 geçiş tutarken etiket dizininde ikili arama 21 geçişle aynı 95 satıra varır.
İçindekiler
Küme çerçeveye girdi ve sütunlarının tipi bilinerek okundu. Sıradaki soru satıra nasıl erişileceği. Bir çerçeveden “10.100 ile 10.200 arasındaki aboneler” istendiğinde iki ayrı yol vardır: satırı etiketiyle istemek ve satırı sırasındaki yeriyle istemek. İkisi de bir aralık alır, ikisi de bir alt tablo döndürür ve kodda neredeyse aynı görünürler.
Aynı görünmelerine rağmen aynı şeyi yapmazlar ve fark tek bir kararda toplanır: aralığın son ucu sonuca dahil mi. Bu dersin ölçüsü budur — aynı iki uç, farklı yazımlarda kaç satır döndürüyor. İkinci bir soru da bunun yanında durur: etiket dilimi, dizinin sıralı olduğunu varsayar ve bu varsayım hiçbir yerde yazılmaz.
- VC10. Etiket tabanlı erişim satırı dizindeki adıyla bulur; konum tabanlı erişim satırı
tablodaki sırasıyla bulur. İkisi ancak dizin
0, 1, 2, …diye giden bir tam sayı dizisiyse çakışır. - VC11. Etiket dilimi iki ucu da içerir, konum dilimi son ucu dışarıda bırakır. Bu bir kütüphane tercihi değil, iki farklı adresleme mantığının sonucudur: bir ad aralığında son ad da bir addır, bir konum aralığında ise son konum bir sınırdır.
- VC12. Dizin tekil olmayabilir. Aynı etiket birden çok satırda geçiyorsa etiket dilimi o etiketin bütün satırlarını getirir ve dönen satır sayısı etiket sayısından büyük olur.
- VC13. Etiket dilimi dizinin sıralı olmasını varsayar. Sıralıysa iki uç ikili aramayla bulunur; sıralı değilse dilim “iki ucun ilk geçtiği konum arası” anlamına döner ve bu bambaşka bir kümedir.
İki Erişim Biçimi
# kume.py — MODELDIR: bir onceki dersin uzun bicimli okuma tablosu ayni tohumla # yeniden uretilir. Kume kurgudur; uretecin ayrintisi konunun ilk dersindedir. import math M32 = 0xFFFFFFFF TOHUM, DONEM = 20260218, ["2026-01", "2026-02", "2026-03"] BOLGE = [("kuzey", 0.28, 21), ("guney", 0.22, 17), ("dogu", 0.18, 26), ("bati", 0.14, 14), ("merkez", 0.18, 23)] def uretec(tohum: int): s = ((tohum ^ (tohum >> 16)) * 2246822507) & M32 s = ((s ^ (s >> 13)) * 3266489909) & M32 s ^= s >> 16 def sonraki() -> float: nonlocal s s = (s * 1664525 + 1013904223) & M32 return s / 4294967296 return sonraki def ayrik(u: float, agirlik: list[float]) -> int: t = 0.0 for i, p in enumerate(agirlik): t += p if u < t: return i return len(agirlik) - 1 abone, elenen = [], [] for i in range(1400): r = uretec(TOHUM + i) ad, _, taban = BOLGE[ayrik(r(), [b[1] for b in BOLGE])] r(); r() # hane ve memnuniyet cekilisleri bu derste kullanilmiyor (elenen if r() < 0.046 else abone).append( {"abone_no": 10001 + i, "bolge": ad, "taban": taban}) gecerli = {k["abone_no"] for k in abone} cift: dict[tuple[int, str], float] = {} for k in abone + elenen: r = uretec(TOHUM + 7000 + k["abone_no"]) for d in range(ayrik(r(), [0.05, 0.12, 0.21, 0.62])): t = 0.0 if r() < 0.038 else math.floor(k["taban"] * math.exp( (r() + r() + r() - 1.5) * 0.62) * (1 - d * 0.05) * 100 + 0.5) / 100 if k["abone_no"] in gecerli: cift.setdefault((k["abone_no"], DONEM[d]), t) r() UZUN = [{"abone_no": a, "donem": d, "tuketim_m3": t} for (a, d), t in cift.items()] GECIS = {"sayi": 0} class EtiketDizini: """Bir etiket listesi ve etiket -> konum sozlugu. Sirali olup olmadigi dizinin kendi ozelligidir ve dilimleme davranisini belirler.""" def __init__(self, etiket): self.etiket = list(etiket) self.yer = {e: i for i, e in enumerate(self.etiket)} self.sirali = all(self.etiket[i] <= self.etiket[i + 1] for i in range(len(self.etiket) - 1)) def __len__(self): return len(self.etiket) toplam = {} for o in UZUN: toplam[o["abone_no"]] = round(toplam.get(o["abone_no"], 0.0) + o["tuketim_m3"], 2) ozet = [{"abone_no": k["abone_no"], "bolge": k["bolge"], "toplam_m3": toplam.get(k["abone_no"], 0.0)} for k in abone] dizin = EtiketDizini([s["abone_no"] for s in ozet]) uzun_dizin = EtiketDizini([o["abone_no"] for o in UZUN]) print(f"ozet cercevesi {len(dizin)} satir, etiketler {dizin.etiket[0]}..{dizin.etiket[-1]}, " f"arada {dizin.etiket[-1] - dizin.etiket[0] + 1 - len(dizin)} bosluk") print(f"uzun tablo {len(uzun_dizin)} satir, {len(set(uzun_dizin.etiket))} tekil etiket; " f"dizin sirali mi: {'evet' if uzun_dizin.sirali else 'hayir'}")
ozet cercevesi 1329 satir, etiketler 10001..11399, arada 70 bosluk uzun tablo 3199 satir, 1260 tekil etiket; dizin sirali mi: evet
İki çerçeve de aynı kümeden çıkıyor ve dizinleri birbirinden iki yönde ayrılıyor. Özet çerçevesinde bir satır bir abonedir, dizin tekildir, ama sürekli değildir: 10.001 ile 11.399 arasında 1.399 sayı vardır ve bunların 70 tanesi bir abone karşılığı taşımaz. Bunlar M26/K03’ün temizleme adımında elenen kayıtlardır ve dizinde bir boşluk olarak kalmışlardır.
Uzun tabloda ise dizin tekil değildir: 3.199 satır 1.260 farklı abone etiketi taşır, çünkü bir abonenin üç dönemi üç satırdır. Bu iki özellik, boşluklu dizin ve tekil olmayan dizin, dilimlemenin davranışını doğrudan belirler. İkisi de dizinin kendi özelliğidir, çerçeveyi kuran kod bunları hiçbir yerde bildirmez.
Aynı Dilim, Dört Sayı
# dilim.py — ayni iki uc, farkli yazimlar. Etiket dilimi iki ucu da icerir, # konum dilimi son ucu disarida birakir. def ikili_ara(etiket, hedef, sag=False) -> int: """Sirali listede konum arar; karsilastirma sayisi GECIS'e eklenir.""" alt, ust = 0, len(etiket) while alt < ust: GECIS["sayi"] += 1 orta = (alt + ust) // 2 if (etiket[orta] <= hedef) if sag else (etiket[orta] < hedef): alt = orta + 1 else: ust = orta return alt def dilim_etiket(dizin: EtiketDizini, bas, son): """Etikete gore dilim: iki uc da dahildir. Dizin sirali degilse iki ucun kendi konumlari alinir ve arada kalan satirlar donulur.""" if dizin.sirali: return ikili_ara(dizin.etiket, bas), ikili_ara(dizin.etiket, son, sag=True) return dizin.yer[bas], dizin.yer[son] + 1 def dogrusal_dilim(etiket, bas, son): """Dizin kurulmadan ayni dilim: her etiket iki kez denetlenir.""" secilen, gecis = [], 0 for e in etiket: gecis += 2 if bas <= e <= son: secilen.append(e) return secilen, gecis BAS, SON = 10100, 10200 GECIS["sayi"] = 0 i, j = dilim_etiket(dizin, BAS, SON) etiket_satir, etiket_gecis = j - i, GECIS["sayi"] EN = [40, 10, 10] y = lambda h: "".join(str(v).rjust(EN[k]) if k else str(v).ljust(EN[0]) for k, v in enumerate(h)) print(y(["erisim bicimi", "satir", "gecis"])) print(y([f"etiketle {BAS}..{SON} (uc dahil)", etiket_satir, etiket_gecis])) print(y([f"konumla [{i}:{j - 1}] (uc haric)", (j - 1) - i, 0])) print(y([f"konumla [{BAS - 10001}:{SON - 10001}]", len(ozet[BAS - 10001:SON - 10001]), 0])) GECIS["sayi"] = 0 ui, uj = dilim_etiket(uzun_dizin, BAS, SON) print(y([f"uzun tabloda etiketle {BAS}..{SON}", uj - ui, GECIS["sayi"]])) secilen, dg = dogrusal_dilim(dizin.etiket, BAS, SON) print(y(["dizinsiz dogrusal tarama", len(secilen), dg]))
erisim bicimi satir gecis etiketle 10100..10200 (uc dahil) 95 21 konumla [95:189] (uc haric) 94 0 konumla [99:199] 100 0 uzun tabloda etiketle 10100..10200 230 24 dizinsiz dogrusal tarama 95 2658
Beş satır, aynı iki uç ve dört farklı sayı. İlk ikisi arasındaki fark tam olarak bir satırdır ve o satır dilimin son satırıdır: etiket dilimi 10.200 numaralı aboneyi içerir, aynı iki konumla yazılan konum dilimi içermez. Tek satırlık bir fark bir raporda görünmez, ama bir eşik hesabında sınırdaki kaydı dışarıda bırakır ve sınırdaki kayıt genellikle hesabın konusudur.
Üçüncü satır daha sık rastlanan bir karışıklıktır. Etiketler tam sayı olduğu için konum diye
yazılmışlardır ve dizin sürekli olsaydı bu doğru olurdu. Değildir: aradaki 70 boşluk yüzünden
10100 etiketi 95. konumdadır, 99. konumda değildir. Sonuç 100 satır döner, ilk satırı 10.100
değildir ve hiçbir hata üretilmemiştir.
Dördüncü satır dizinin tekil olmamasının bedelini gösteriyor. Uzun tabloda aynı etiket dilimi 230 satır döndürür, çünkü aralıktaki her abone bir ile üç arasında satır taşır. Sayının 95’ten büyük olması bir yanlışlık değildir; yanlış olan, “95 abone seçtim” diye okunmasıdır. Etiket dilimi abone seçmez, satır seçer.
Son satır kazancı veriyor. Aynı 95 satıra dizin kurulmadan varmak 2.658 karşılaştırma tutar, çünkü 1.329 etiketin her biri iki kez denetlenir. Etiket dizininde iki ikili arama bu işi 21 geçişle bitirir: 126 kat daha az karşılaştırma ve altı satırlık döngü yerine tek satırlık bir dilim.
Satır ve Sütun Birlikte
# eksen.py — etiket tabanli erisim iki eksende calisir: once satir, sonra sutun. # Olcu, sonuca giren hucre sayisidir. SUTUN = ["abone_no", "bolge", "toplam_m3"] istenen = ["bolge", "toplam_m3"] dilim = ozet[i:j] print(f"cerceve {len(ozet)} satir x {len(SUTUN)} sutun = {len(ozet) * len(SUTUN)} hucre") print(f"once satir dilimi, sonra sutun secimi: {len(dilim)} x {len(istenen)} = " f"{len(dilim) * len(istenen)} hucre") print(f"once sutun secimi, sonra satir dilimi: {len(ozet)} x {len(istenen)} = " f"{len(ozet) * len(istenen)} hucre ara tabloda, sonra {len(dilim)} satir") tek = [s["toplam_m3"] for s in dilim] print(f"tek sutun secilirse sonuc seri olur: {len(tek)} deger, " f"toplam {round(sum(tek), 2)} m3")
cerceve 1329 satir x 3 sutun = 3987 hucre once satir dilimi, sonra sutun secimi: 95 x 2 = 190 hucre once sutun secimi, sonra satir dilimi: 1329 x 2 = 2658 hucre ara tabloda, sonra 95 satir tek sutun secilirse sonuc seri olur: 95 deger, toplam 4348.3 m3
Etiket tabanlı erişim tek eksende çalışmaz: aynı ifade hem satırı hem sütunu adlandırır. Bu, iki
ayrı seçimi tek adımda birleştirdiği için kısaltır, ama araya bir sıra kararı sokar. Satır dilimi
önce uygulanırsa sonuca 190 hücre girer; sütun seçimi önce uygulanırsa 2.658 hücrelik bir ara
tablo kurulur ve satır dilimi ondan alınır. Sonuç aynıdır, kurulan tablo aynı değildir. Küçük bir
çerçevede bu fark önemsizdir; ölçek büyüdüğünde analiz-basarimi konusunun ölçtüğü kalem tam
olarak budur.
Bir ayrıntı da sonucun türünü değiştirir. İki sütun istenirse dönen şey bir çerçevedir; tek sütun istenirse dönen şey bir seridir. Aynı yazımın bir sütun adı yerine tek elemanlı bir sütun listesi alması sonucun tipini değiştirir ve bunu izleyen kod ya seri arayüzüne ya çerçeve arayüzüne göre yazılmıştır. Seçimin kaç sütun döndürdüğü, bu yüzden yalnız bir sayı değil bir tip kararıdır.
Sıra Bozulunca
# bozulan_sira.py — ayni etiket dilimi, sirasi bozulmus bir dizinde. Siralamayi # bozan sey bir hata degil, mesru bir siralama kararidir. sirali_toplam = sorted(ozet, key=lambda s: -s["toplam_m3"]) bozuk = EtiketDizini([s["abone_no"] for s in sirali_toplam]) print(f"toplama gore siralanan cerceve: etiket dizini sirali mi: " f"{'evet' if bozuk.sirali else 'hayir'}") bi, bj = dilim_etiket(bozuk, BAS, SON) print(f"ayni dilim {BAS}..{SON}: {BAS} {bi}. konumda, {SON} {bj - 1}. konumda, " f"dilim {max(bj - bi, 0)} satir") yok_olan = next(e for e in range(BAS, SON) if e not in dizin.yer) gi, gj = dilim_etiket(dizin, BAS, yok_olan) print(f"\ndizinde bulunmayan uc {yok_olan}: sirali dizinde dilim {gj - gi} satir doner") try: dilim_etiket(bozuk, BAS, yok_olan) except KeyError as hata: print(f"ayni uc, sirasi bozuk dizinde: KeyError {hata}") print(f"\ngecis : dizinsiz tarama {dg}, etiket dizininde ikili arama {etiket_gecis}") print(f"kod : dogrusal dilim 6 satir, etiket dilimi 1 satir") print(f"satir : ayni iki uc dort yazimda {etiket_satir}, {(j - 1) - i}, " f"{len(ozet[BAS - 10001:SON - 10001])} ve {max(bj - bi, 0)} satir doner")
toplama gore siralanan cerceve: etiket dizini sirali mi: hayir ayni dilim 10100..10200: 10100 492. konumda, 10200 1108. konumda, dilim 617 satir dizinde bulunmayan uc 10122: sirali dizinde dilim 22 satir doner ayni uc, sirasi bozuk dizinde: KeyError 10122 gecis : dizinsiz tarama 2658, etiket dizininde ikili arama 21 kod : dogrusal dilim 6 satir, etiket dilimi 1 satir satir : ayni iki uc dort yazimda 95, 94, 100 ve 617 satir doner
Çerçeveyi toplam tüketime göre sıralamak meşru bir istektir ve en çok tüketen aboneyi başa getirir. Yaptığı yan etki, etiket dizininin sıralı olmaktan çıkmasıdır. Sıralı olmayan bir dizinde etiket dilimi artık “10.100 ile 10.200 arasındaki etiketler” anlamına gelemez, çünkü böyle bir aralık tabloda bir yerde toplu durmuyordur. Anlamı zorunlu olarak şuna kayar: iki ucun bulunduğu konumlar arasındaki satırlar. Sonuç 617 satır olur ve bu satırların büyük kısmının etiketi aralığın dışındadır.
Aynı ifade, aynı iki uç, 95 yerine 617 satır. İkisi arasında bir hata mesajı yoktur, çünkü ikisi de dilimin tanımına uyar; değişen şey dizindir. Bu, kursun kuralının en açık örneklerinden biridir: soyutlama işlemi kısaltır ama üzerinde çalıştığı varsayımı görünür kılmaz.
Bulunmayan uç ise iki dizinde iki farklı davranış üretiyor ve buradaki fark yönü tersine çeviriyor. 10.122 numaralı abone dizinde yoktur. Sıralı dizinde bu bir sorun değildir: ikili arama o etiketin olacağı konumu bulur ve 22 satırlık bir dilim döner. Sırası bozuk dizinde ise o etiketin bir konumu yoktur ve dilim hata verir. Yani soyutlama burada sessiz kalmıyor, konuşuyor; ama gürültülü davrandığı durum yanlış sonucun değil, eksik bilginin olduğu durumdur. Sessiz kaldığı 617 satırlık durumda ise bir cevap üretmiştir.
Özet
- Etiket tabanlı erişim satırı adıyla, konum tabanlı erişim sırasıyla bulur; etiket dilimi son ucu içerir, konum dilimi dışarıda bırakır.
- Aynı iki uç dört yazımda 95, 94, 100 ve 617 satır döndürür; ilk ikisi arasındaki tek satırlık fark dilimin sınırdaki kaydıdır.
- Dizin sürekli değilse etiketi konum sanmak sessizce kayar: 70 boşluk yüzünden 10.100 etiketi 95. konumdadır ve konumla yazılan dilim 100 satırın yanlış 100’ünü döndürür.
- Tekil olmayan dizinde etiket dilimi abone değil satır seçer: 3.199 satırlık uzun tabloda aynı aralık 230 satır getirir.
- Dizinin kazancı geçiş sayısında okunur: doğrusal tarama 2.658 karşılaştırma tutarken ikili arama 21 geçişle aynı 95 satıra varır; gizlenen karar, etiket diliminin dizinin sıralı olduğunu varsaymasıdır.
Sonraki Adım
Dilimleme aralık verir, ama analizde asıl istenen aralık değil koşuldur: “tüketimi 25 m³’ün üstündeki okumalar”. Koşullu seçim bir karşılaştırmadan doğar ve karşılaştırma, kümedeki her değerin karşılaştırılabilir olduğunu varsayar. Endeks sütununda 111 boş ve 80 “yok” yazılı hücre olduğu görülmüştü; bir eşiğin altında mı üstünde mi olduğu bilinmeyen bir değer, süzgeçten hangi tarafa düşer. Sonraki ders aynı koşulu üç ayrı yazımla çalıştırır ve üçünün kaç satır döndürdüğünü sayar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.