Ders 13 / 22
Metin İşlemleri
3.199 satırlık adres kodu sütununun 2.943'ü tam, 160'ı boş, 96'sı üçüncü parçasını taşımıyor. Elle yazılan döngü 20. satırda hata verip duruyor ve 0 satır üretiyor; aynı işi yapan vektörleştirilmiş zincir 3.199 satırın tamamını üretiyor, hiç hata vermiyor ve 256 satırı sessizce eksiğe çeviriyor. Zincirden sonra gelen süzgeç bu 256 satırı eşitlikte dışarıda bırakıp eşitsizlikte içeri alıyor: 1.004 ile 2.195 satır, toplamı tam olarak küme. Zincirin ilk adımı kodu küçük harfe çevirdiği için büyük harfli süzgeç 0 satır döndürüyor ve bu da uyarı üretmiyor. Bir metin sütunu üç ara serisiyle satır başına 32,6 bayt gösterge tutar ve aynı yapı 20 milyon satırda 0,61 gibibayta çıkar.
İçindekiler
Sayısal sütunlar, tarihler ve anahtarlar ele alındı. Kümede kalan son alan türü metindir: bölge adı, ziyaret sonucu, adres kodu, saha notu. Metin sütunları çerçevede sayısal sütunlarla yan yana durur ve aynı biçimde işlenebilir görünür. Bütün sütuna tek çağrıyla bir dizgi yöntemi uygulanır, sonuç yeni bir seri olur, döngü yazmaya gerek kalmaz.
Bu rahatlığın bedeli bu derste ölçülüyor. Bir dizgi yöntemi bir metin üzerinde tanımlıdır; eksik bir hücrede tanımlı değildir. Elle yazılan bir döngü bunu hata vererek bildirir. Vektörleştirilmiş bir işlem ise bildirmez: eksik hücreyi işlemden geçirmez, eksik olarak bırakır ve çalışmaya devam eder. Ölçü şudur — hangisi kaç satır üretiyor, hangisi nerede duruyor ve üretilen satırların kaçı sessizce eksiğe dönmüş oluyor.
- VC30. Vektörleştirilmiş dizgi işlemi, bir dizgi yöntemini bütün sütuna tek çağrıyla uygulamaktır. Eksik hücre işlemden geçmez ve sonuçta eksik kalır; işlem hata vermez.
- VC31. Bir dizgi zinciri eksiği yayar. Zincirin herhangi bir adımında eksiğe dönen bir hücre, sonraki bütün adımlarda eksik kalır ve zincirin sonunda kaç satırın eksik olduğu ancak ayrıca sayılırsa bilinir.
- VC32. Biçimi bozuk bir değer, eksik bir değerden farklıdır ama zincirin sonunda aynı yere düşer: ikisi de eksik olur ve ikisi de aynı biçimde raporlanır.
- VC33. Zincirin bir adımı sonraki adımların girdisini değiştirir. Büyük küçük harf, boşluk ve ayırıcı kararları zincirin ilerisindeki bir eşitlik süzgecini hiçbir uyarı üretmeden boşa düşürebilir.
Metin Sütunu ve Bozuk Biçim
# 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()] # Adres kodu sahada uretilir ve BOLGE-NNNN-H bicimindedir. Bir kismi hic # atanmamistir, bir kisminda ucuncu parca eksiktir. EKSIK = None def adres_kodu(no: int, bolge: str): r = uretec(TOHUM + 61000 + no) if r() < 0.048: return EKSIK if r() < 0.037: return f"{bolge.upper()}-{no - 10000:04d}" return f"{bolge.upper()}-{no - 10000:04d}-{'ABC'[ayrik(r(), [0.42, 0.35, 0.23])]}" kod_of = {k["abone_no"]: adres_kodu(k["abone_no"], k["bolge"]) for k in abone} kayit = [{"abone_no": o["abone_no"], "tuketim_m3": o["tuketim_m3"], "adres_kodu": kod_of[o["abone_no"]]} for o in UZUN] sutun = [k["adres_kodu"] for k in kayit] eksik = sum(1 for v in sutun if v is EKSIK) bozuk = sum(1 for v in sutun if v is not EKSIK and v.count("-") < 2) print(f"{len(kayit)} satir: {len(sutun) - eksik - bozuk} tam kod, {eksik} eksik, " f"{bozuk} ucuncu parcasi olmayan") print(f"ornek kodlar: {[v for v in sutun[:6]]}")
3199 satir: 2943 tam kod, 160 eksik, 96 ucuncu parcasi olmayan ornek kodlar: ['MERKEZ-0001-A', 'MERKEZ-0001-A', 'KUZEY-0002-A', 'KUZEY-0002-A', 'KUZEY-0002-A', 'GUNEY-0003-A']
Sütunda üç ayrı durum var ve ikisi bu dersin konusudur. 160 satırın adres kodu hiç yoktur: abonenin adres kaydı bir koda bağlanmamıştır. 96 satırın kodu vardır ama eksik biçimlidir: bölge ve numara parçaları yerindedir, üçüncü parça, yani bina harfi, yazılmamıştır.
İkisi bir kümede yan yana durur ve birbirinden farklı şeylerdir. Kod taşımayan satır için hiçbir şey bilinmez. Kodu eksik biçimli olan satır için bölge de numara da bilinir, yalnız bir parça eksiktir. Bu ayrımın sonuna kadar korunup korunmadığı, bu dersin sayacağı sayılardan biridir.
Duran Döngü, Durmayan Zincir
# dizgi.py — ayni dizgi islemi iki yolla: elle dongu ve butun sutuna tek cagri. # Ikisinin farki eksik hucrede ortaya cikiyor. import sys def elle_kucult(sutun): """Elle dongu: her hucreye dogrudan dizgi yontemi uygulanir.""" return [v.lower() for v in sutun] def vektor(sutun, islem): """Vektorlestirilmis islem: eksik hucre islemden gecmez, eksik kalir.""" return [EKSIK if v is EKSIK else islem(v) for v in sutun] try: elle_kucult(sutun) except AttributeError as hata: ilk = next(i for i, v in enumerate(sutun) if v is EKSIK) print(f"elle dongu {ilk + 1}. satirda durdu: AttributeError {hata}") print(f"uretilen satir: 0 (kalan {len(sutun) - ilk - 1} satira hic bakilmadi)") kucuk = vektor(sutun, str.lower) parca = vektor(kucuk, lambda v: v.split("-")) harf = [EKSIK if p is EKSIK or len(p) < 3 else p[2] for p in parca] print(f"\nvektorlestirilmis zincir: {len(kucuk)} satir uretti, 0 hata verdi") print(f"ucuncu parca cikarildiktan sonra {sum(1 for h in harf if h is EKSIK)} satir eksik: " f"{sum(1 for v in sutun if v is EKSIK)} hic kod tasimayan, " f"{sum(1 for p in parca if p is not EKSIK and len(p) < 3)} ucuncu parcasi olmayan") print(f"bayt : uc ara seri {sys.getsizeof(kucuk) + sys.getsizeof(parca) + sys.getsizeof(harf)}, " f"kaynak sutun {sys.getsizeof(sutun)}") print(f"kod : elle guvenli dongu 9 satir, vektorlestirilmis zincir 3 cagri")
elle dongu 20. satirda durdu: AttributeError 'NoneType' object has no attribute 'lower' uretilen satir: 0 (kalan 3179 satira hic bakilmadi) vektorlestirilmis zincir: 3199 satir uretti, 0 hata verdi ucuncu parca cikarildiktan sonra 256 satir eksik: 160 hic kod tasimayan, 96 ucuncu parcasi olmayan bayt : uc ara seri 78120, kaynak sutun 26040 kod : elle guvenli dongu 9 satir, vektorlestirilmis zincir 3 cagri
İki yaklaşım aynı işi yapıyor ve tam ters biçimde başarısız oluyor. Elle döngü 20. satırda durur, sıfır satır üretir ve kalan 3.179 satıra hiç bakmaz. Bir üretim hattında bu bir kesintidir ve düzeltilmeden devam edilemez; ama kesintinin kendisi bir bilgidir, çünkü kümede kod taşımayan satırlar olduğunu ilk yirmi satırda haber verir.
Vektörleştirilmiş zincir hiç durmaz. 3.199 satırın tamamını üretir, sıfır hata verir ve zincirin sonunda 256 satır eksiktir. Bu 256 satır, çıktının satır sayısında görünmez; çıktı hâlâ 3.199 satırdır ve boyu kaynağınkiyle aynıdır. Görünmeleri için ayrıca sayılmaları gerekir ve bu dersteki sayım da o ayrı satırın kendisidir.
Kaybın hesabı iki kalemdir ve ikisi zincirin sonunda birleşmiştir: 160 satırın hiç kodu yoktu, 96 satırın kodu vardı ama üçüncü parçası yoktu. Zincirin çıktısında ikisi de aynı şeydir, yani eksiktir. Bir önceki bölümde korunan ayrım burada kaybolmuştur ve kaybolmasının sebebi bir hata değil, üçüncü parçayı çıkaran adımın eksik parça ile eksik kaydı ayırt edememesidir.
Kazanç tarafı da yazılmalıdır. Elle yazılan güvenli döngü, hata yakalamayla birlikte dokuz satır tutar; vektörleştirilmiş zincir üç çağrıdır. Bedel bellektedir: zincirin üç ara serisi 78.120 bayt gösterge tutar, kaynak sütunun kendisi 26.040 bayttır. Her adım yeni bir seri üretir ve zincir uzadıkça bu üç kat büyür.
Zincirden Sonraki Süzgeç
# suzgec.py — dizgi isleminden cikan eksik, kendisinden sonraki suzgecte # sessizce bir tarafa dusuyor. Zincirin daha onceki adimi da suzgeci etkiler. T = lambda m: round(sum(k["tuketim_m3"] for k, b in zip(kayit, m) if b), 2) buyuk = [h == "B" for h in harf] kucuk_b = [h == "b" for h in harf] esit_degil = [h != "b" for h in harf] bilinmiyor = [h is EKSIK for h in harf] EN = [30, 10, 14] 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(["suzgec", "satir", "toplam m3"])) print(y(["harf == 'B'", sum(buyuk), T(buyuk)])) print(y(["harf == 'b'", sum(kucuk_b), T(kucuk_b)])) print(y(["harf != 'b'", sum(esit_degil), T(esit_degil)])) print(y(["harf bilinmiyor", sum(bilinmiyor), T(bilinmiyor)])) print(y(["kume", len(kayit), T([True] * len(kayit))])) print(f"\nzincir kodu once kucuk harfe cevirdigi icin 'B' suzgeci {sum(buyuk)} satir " f"dondurur ve hicbir uyari uretmez") print(f"esitlik suzgeci {sum(bilinmiyor)} bilinmeyen satiri disarida birakir, " f"esitsizlik suzgeci ayni {sum(bilinmiyor)} satiri iceri alir: " f"{sum(kucuk_b)} + {sum(esit_degil)} = {sum(kucuk_b) + sum(esit_degil)}") satir_bayt = (sys.getsizeof(sutun) + sys.getsizeof(kucuk) + sys.getsizeof(parca) + sys.getsizeof(harf)) / len(kayit) print(f"\ntek metin sutunu ve uc ara serisi satir basina {round(satir_bayt, 1)} bayt " f"gosterge tutar; {len(kayit)} satirda {round(satir_bayt * len(kayit) / 1024)} kibibayt") print(f"ayni yapi 20 milyon satirda {round(satir_bayt * 20_000_000 / 1024 ** 3, 2)} " f"gibibayt gosterge ister ve dizgilerin kendisi buna dahil degil")
suzgec satir toplam m3 harf == 'B' 0 0 harf == 'b' 1004 20069.73 harf != 'b' 2195 42990.9 harf bilinmiyor 256 4980.99 kume 3199 63060.63 zincir kodu once kucuk harfe cevirdigi icin 'B' suzgeci 0 satir dondurur ve hicbir uyari uretmez esitlik suzgeci 256 bilinmeyen satiri disarida birakir, esitsizlik suzgeci ayni 256 satiri iceri alir: 1004 + 2195 = 3199 tek metin sutunu ve uc ara serisi satir basina 32.6 bayt gosterge tutar; 3199 satirda 102 kibibayt ayni yapi 20 milyon satirda 0.61 gibibayt gosterge ister ve dizgilerin kendisi buna dahil degil
İlk satır konunun en sessiz hatasıdır. Süzgeç 'B' arar, sütunda 'B' yoktur, çünkü zincirin ilk
adımı bütün kodu küçük harfe çevirmiştir. Sonuç sıfır satır, sıfır metre küp ve sıfır uyarıdır.
Boş bir sonuç kümesi hem “böyle bir kayıt yok” hem de “yanlış yerde aradın” anlamına gelir; ikisini
ayıran bir işaret çıktıda yoktur.
İkinci ve üçüncü satırlar konunun dördüncü dersindeki bulguyu metin üzerinde tekrarlıyor. Eşitlik süzgeci 256 bilinmeyen satırı dışarıda bırakır, eşitsizlik süzgeci aynı 256 satırı içeri alır ve 1.004 ile 2.195 toplanınca tam olarak 3.199 eder. Küme ikiye bölünmüş görünür, oysa üçüncü bir küme vardır ve 4.980,99 m³ tüketim taşır. İkiye bölünmüş bir tablo bu üçüncü kümeyi yazacak bir satır bile açmaz.
Son iki satır konunun kapanış ölçüsüdür. Tek bir metin sütunu, üç ara serisiyle birlikte satır başına 32,6 bayt gösterge tutar; 3.199 satırda bu 102 kibibayttır ve hiç fark edilmez. Aynı yapı 20 milyon satırda 0,61 gibibayta çıkar ve bu, dizgilerin kendi içeriği hariç, yalnız göstergelerin maliyetidir. Sekiz derste kurulan bütün rahatlık, kümenin belleğe sığması varsayımına dayanır.
Ayrımı Geri Kazanmak
# desen.py — ayirici ile bolmek yerine bicimi dogrulayan bir desen kullanmak, # eksik kayit ile bozuk bicimi zincirin sonunda ayri tutar. import re DESEN = re.compile(r"^([a-z]+)-(\d{4})-([abc])$") def durumu(v) -> str: if v is EKSIK: return "eksik" return "tam" if DESEN.match(v) else "bozuk" sayim: dict[str, int] = {} for v in kucuk: sayim[durumu(v)] = sayim.get(durumu(v), 0) + 1 print(f"desenle ayristirma: {sayim['tam']} tam, {sayim['bozuk']} bozuk bicim, " f"{sayim['eksik']} eksik kayit; toplam {sum(sayim.values())}") desen_harf = [DESEN.match(v).group(3) if v is not EKSIK and DESEN.match(v) else EKSIK for v in kucuk] print(f"ucuncu parca: bolerek {sum(1 for h in harf if h is EKSIK)} eksik, " f"desenle {sum(1 for h in desen_harf if h is EKSIK)} eksik; ayni sayi, " f"fark raporlanabilirlikte") print(f"ayni sonucu veren iki yol: bolme {len(kucuk)} gecis ve 2 ara seri, " f"desen {len(kucuk)} gecis ve 1 ara seri")
desenle ayristirma: 2943 tam, 96 bozuk bicim, 160 eksik kayit; toplam 3199 ucuncu parca: bolerek 256 eksik, desenle 256 eksik; ayni sayi, fark raporlanabilirlikte ayni sonucu veren iki yol: bolme 3199 gecis ve 2 ara seri, desen 3199 gecis ve 1 ara seri
Bölmenin kaybettiği ayrım geri kazanılabilir ve maliyeti bir geçiş bile değildir. Ayırıcıya göre bölmek yerine biçimi doğrulayan bir desen kullanıldığında sütun üç sayıya ayrılır: 2.943 tam kod, 96 bozuk biçim, 160 eksik kayıt. Üçünün toplamı 3.199’dur ve bu toplama denetimi, konu boyunca tekrarlanan tek denetimin metin üzerindeki karşılığıdır.
Çıkarılan üçüncü parça iki yolda da aynıdır: 256 satır eksiktir. Değişen tek şey, o 256’nın nereden geldiğinin yazılabilir olmasıdır. Bozuk biçimli 96 satır düzeltilebilir bir sorundur ve kaynağına geri sorulabilir; kodu hiç olmayan 160 satır ise düzeltilemez, çünkü ortada düzeltilecek bir kayıt yoktur. İkisini tek sayıda toplayan bir rapor bu ayrımı ve onunla birlikte yapılacak işi de siler.
Geçiş sayısı iki yolda da 3.199’dur, yani desen bir bedel eklemez; ara seri sayısı ikiden bire iner. Ayrımı kaybettiren şey vektörleştirmenin kendisi değildi, bölmenin doğrulama yapmamasıydı.
Özet
- Vektörleştirilmiş dizgi işlemi eksik hücrede hata vermez, eksiği yayar: elle döngü 20. satırda durup 0 satır üretirken zincir 3.199 satırın tamamını üretir ve 256’sını sessizce eksiğe çevirir.
- Eksik kayıt ile bozuk biçim kümede farklı şeylerdir, zincirin sonunda aynı yere düşer: 160 kodsuz satır ile 96 üçüncü parçasız satır çıktıda ayırt edilemez.
- Zincirin bir adımı sonraki süzgeci boşa düşürebilir: küçük harfe çevrilmiş bir sütunda
'B'araması 0 satır döndürür ve hiçbir uyarı üretmez. - Eşitlik süzgeci 256 bilinmeyen satırı dışarıda, eşitsizlik süzgeci içeride bırakır; 1.004 ile 2.195 toplamı kümeyi verir ama 4.980,99 m³’lük üçüncü küme hiçbir satırda görünmez.
- Zincirin bedeli bellektir: üç ara seri 78.120 bayt gösterge tutar, kaynak sütun 26.040 bayttır ve satır başına 32,6 bayt 20 milyon satırda 0,61 gibibayta çıkar.
Sonraki Adım
Sekiz derste veri çerçevesi arayüzü bütünüyle kuruldu ve her adımda iki sayı yan yana yazıldı: soyutlamanın kazandırdığı geçiş ve gizlediği karar. Hizalama 781.710 karşılaştırmayı 2.361 geçişe indirdi, birleştirme 2.616.782 karşılaştırmayı 4.017 geçişe indirdi, dilimleme 2.658 karşılaştırmayı 21 geçişe indirdi. Her seferinde kısalan kod, aynı oranda görünmez olan bir varsayılan bıraktı.
Bütün bu kazancın tek bir koşulu vardı ve hiçbir derste sınanmadı: kümenin belleğe sığması. Bu konunun kümesi 3.199 satırdı ve tek bir metin sütunu ara serileriyle 102 kibibayt tuttu. Ölçüm ağı bir kentin tamamına çıktığında satır sayısı milyonlarla ölçülür ve her ara seri ayrı bir kopya ister. Sonraki konu bu koşulu sınar: aynı işler kümenin tamamı belleğe sığmadığında ne kadar sürüyor, hangi işlem parçalanabiliyor, hangi tip seçimi kaç bayt kazandırıyor ve hesabın nerede yapıldığı taşınan satır sayısını nasıl değiştiriyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.