Ders 01 / 17
Dosya Biçimleri
Önceki kursun 2.037 satırlık sayfasını üreten dışa aktarım dosyası iki ayrıştırma kararıyla okunuyor ve iki farklı küme çıkıyor: tırnak içini koruyan okuma 2.037 kaydın hepsini çıkarır, naif bölme 2.076 fiziksel satırdan 1.934 kayıt çıkarır ve 142 satırı hiçbir uyarı vermeden düşürür — 78'i adresindeki satır sonundan bölünmüş yarım satır, 64'ü alanı kayan satır. Kalan 1.934 kaydın 33'ü hayatta kalır ama okuma alanında adresten kaymış bir kapı numarası taşır; toplam 984,2 m³ ayrışır ve 1.157 kaydın bölge adı, dosya yanlış kodlamayla çözüldüğü için listeyle eşleşmez. Aynı 2.037 kayıt sütunlu yapıda tutulduğunda toplam okumayı öğrenmek 108.219 bayt yerine 4.074 bayt okumak demektir.
İçindekiler
Önceki kurs on bir derste otuz iki karar aldı ve yalnız yedisi sayfadan geri okunabildi. Ama o sayfanın kendisi bir yerden geldi: 2.037 satır, 1.400 abone, 42.645,6 m³. Bu ders o sayfanın öncesine bakar ve tek bir soruyu sorar — dosya çözümleme ortamına girerken kaç satır kaybetti.
Cevap kararlara bağlıdır ve kararlar hata vermez. Bir ayrıştırıcı bozuk bir satırla karşılaştığında durup uyarmaz; o satırı sessizce atlar ve okumaya devam eder. Dersin ölçü birimi budur ve kurs boyunca sürer: her adımda giren satır, çıkan satır ve nereye gitti. Üçüncüsünün en önemli kalemi sessiz kayıp, yani hiçbir uyarı üretmeden düşen satırdır.
- VT1. Dosya kurgudur. Gerçek bir dışa aktarım yerine aynı yapıyı üreten bir üreteç yazıldı; tohum görünürdür ve çıktı koşumdan koşuma aynıdır. Modeldir.
- VT2. Üretilen küme önceki kursun sayfasını verir: 2.037 kayıt, 1.400 farklı abone, 1.877 farklı abone-dönem çifti, 42.645,6 m³ toplam.
- VT3. Ayraç noktalı virgül, tırnak çift tırnaktır; adres alanı her kayıtta tırnak içinde yazılır ve dosya UTF-8 yazılır.
- VT4. İki toplamın aynı sayılması için eşik 0,05 m³’tür, çünkü okumalar bir ondalıkla raporlanıyor ve altındaki bir fark yalnızca yuvarlamadan gelebilir.
- VT5. Üretecin verebileceği en büyük okuma 37,0 m³’tür; okuma alanında bundan büyük bir değer o alanın okuma taşımadığını gösterir.
Dosyanın İçinde Ne Var
// kaynak.mjs — bolgesel olcum agindan gelen kurgu bir disa aktarim. MODELDIR: // gercek bir dosya yerine ayni yapiyi ureten bir uretec yazildi; tohum gorunur // ve cikti kosumdan kosuma aynidir. Kayitlar M26/K02'nin sayfasini uretir: // 1400 abone, 1877 abone-donem cifti, 160 yinelenen satir, toplam 2037 kayit. export const TOHUM = 1246, AYRAC = ";", TIRNAK = '"'; export const BOLGE = ["kuzey", "doğu", "merkez", "güney", "batı"]; export const BASLIK = ["abone_no", "sayac_no", "donem", "adres", "okuma_m3", "bolge"]; const SOKAK = ["Cinar Sok", "Yeni Mah Bahce Cad", "Istasyon Cad", "Dere Sok", "Zeytin Sok"]; export const uretec = (t) => { let s = t >>> 0; return () => { s = (Math.imul(s, 1103515245) + 12345) >>> 0; return s / 4294967296; }; }; // Kayitlar: once 1400 abonenin ilk donemi, sonra 477 abonenin ikinci donemi, // sonra ilk 160 satirin yinelenmesi (32'si birebir kopya, 128'i duzeltilmis okuma). export function kayitlar() { const r = uretec(TOHUM), oku = () => 5 + Math.floor(r() * 321) / 10, k = []; const yap = (i, donem, okuma) => ({ abone: 10000 + i, sayac: `S-${4000 + i}`, donem, adres: `${SOKAK[i % 5]} No ${12 + (i % 88)}`, okuma, bolge: BOLGE[i % 5] }); for (let i = 0; i < 1400; i++) k.push(yap(i, "2024-01", oku())); for (let i = 0; i < 477; i++) k.push(yap(i, "2024-02", oku())); for (let i = 0; i < 160; i++) k.push(yap(i, "2024-01", i < 32 ? k[i].okuma : oku())); return k; } // Kirlilik kurali: her 21. kaydin adresi ayrac icerir (ucte biri ayractan sonra // ciplak bir kapi numarasiyla biter), geri kalanlardan 50'de biri satir sonu // icerir. Adres alani her kayitta tirnak icinde yazilir. export const AYRACLI = (i) => i % 21 === 0; export const SATIRLI = (i) => !AYRACLI(i) && i % 50 === 13; export const SAYIYLA = (i) => AYRACLI(i) && (i / 21) % 3 === 0; export function dosya() { const k = kayitlar(), s = [BASLIK.join(AYRAC)]; k.forEach((x, i) => { let adres = x.adres; if (AYRACLI(i)) adres += SAYIYLA(i) ? `${AYRAC} ${45 + (i % 30)}` : `${AYRAC} Daire ${1 + i % 8}`; else if (SATIRLI(i)) adres += `\nKat ${1 + (i % 6)}`; s.push([x.abone, x.sayac, x.donem, TIRNAK + adres + TIRNAK, x.okuma.toFixed(1), x.bolge] .join(AYRAC)); }); return Buffer.from(s.join("\n") + "\n", "utf8"); // dosya UTF-8 yazilir } if (import.meta.url === `file://${process.argv[1]}`) { const k = kayitlar(), b = dosya(), satir = b.toString("utf8").trimEnd().split("\n"); const say = (f) => k.reduce((s, _, i) => s + (f(i) ? 1 : 0), 0); console.log(`tohum ${TOHUM}; mantiksal kayit ${k.length}, farkli abone ` + `${new Set(k.map((x) => x.abone)).size}, farkli abone-donem ` + `${new Set(k.map((x) => x.abone + x.donem)).size}`); console.log(`dosya ${b.length} bayt, ${satir.length} fiziksel satir (1 baslik + ` + `${satir.length - 1} veri satiri)`); console.log(`adresinde ayrac olan kayit ${say(AYRACLI)} (${say(SAYIYLA)} tanesi ` + `ayractan sonra sayiyla bitiyor), adresinde satir sonu olan kayit ${say(SATIRLI)}`); console.log(`dogru okunmus toplam ${k.reduce((s, x) => s + x.okuma, 0).toFixed(1)} m3`); const oz = (ad) => Buffer.from(ad, "utf8").toString("hex").match(/../g).join(" "); for (const ad of ["kuzey", "güney"]) console.log(`bolge "${ad}" UTF-8 baytlari: ${oz(ad)} (${Buffer.byteLength(ad)} bayt, ` + `${ad.length} karakter)`); console.log(satir[0]); console.log(satir[1]); console.log(satir[14]); }
tohum 1246; mantiksal kayit 2037, farkli abone 1400, farkli abone-donem 1877 dosya 108219 bayt, 2077 fiziksel satir (1 baslik + 2076 veri satiri) adresinde ayrac olan kayit 97 (33 tanesi ayractan sonra sayiyla bitiyor), adresinde satir sonu olan kayit 39 dogru okunmus toplam 42645.6 m3 bolge "kuzey" UTF-8 baytlari: 6b 75 7a 65 79 (5 bayt, 5 karakter) bolge "güney" UTF-8 baytlari: 67 c3 bc 6e 65 79 (6 bayt, 5 karakter) abone_no;sayac_no;donem;adres;okuma_m3;bolge 10000;S-4000;2024-01;"Cinar Sok No 12; 45";9.4;kuzey 10013;S-4013;2024-01;"Dere Sok No 25
İlk çelişki son üç satırda duruyor. Dosyada 2.076 veri satırı var ama 2.037 mantıksal kayıt; aradaki 39 fark adresinde satır sonu taşıyan 39 kayıttan geliyor. Bir kaydın kaç satır kapladığı sabit değildir; dosyayı satır sayarak ölçmek yanlış bir sayı verir.
İkinci çelişki ilk veri satırındadır. Adres alanı "Cinar Sok No 12; 45" yazıyor: alanın içinde
ayracın kendisi geçiyor. Ayraç, alanları ayırmak için seçilmiş bir karakterdir ve veride de
geçebilir; tırnak tam bu yüzden vardır. Tırnağı okumayan bir ayrıştırıcı burada altı yerine yedi
alan görür ve okuma alanı olarak adresin kuyruğunu okur.
Üçüncüsü bayt satırlarındadır. kuzey beş karakter ve beş bayttır, güney beş karakter ama altı
bayt. Dosyanın hangi kodlamayla yazıldığı dosyanın içinde yazmaz; okuyan taraf bir karar verir ve
karar yanlışsa hiçbir hata çıkmaz, yalnız harfler değişir.
Giren, Çıkan, Sessizce Düşen
// oku.mjs — ayni dosya iki ayristirma karariyla okunur. K1 satiri satir sonundan, // alani ayractan boler, tirnaklari siler ve dosyayi tek baytlik bir kodlamayla // cozer. K2 tirnak icini korur, dosyayi yazildigi kodlamayla cozer. Ikisi de // hata vermez; fark yalnizca sayilarda gorunur. import { dosya, BASLIK, BOLGE, AYRAC, TIRNAK, kayitlar } from "./kaynak.mjs"; const b = dosya(), OK = BASLIK.indexOf("okuma_m3"), BL = BASLIK.indexOf("bolge"); const ayikla = (satirlar) => { const cikan = [], dusen = []; for (const alan of satirlar) { const okuma = Number(alan[OK]); if (alan[OK] !== "" && alan[OK] !== undefined && Number.isFinite(okuma)) cikan.push({ okuma, bolge: alan[BL] }); else dusen.push(alan.length); } return { giren: satirlar.length, cikan, dusen }; }; // K1: naif okuyucu. Tirnak diye bir sey bilmez; alan basina tirnaklari siler. const k1 = (bayt) => ayikla(bayt.toString("latin1").trimEnd().split("\n").slice(1) .map((s) => s.split(AYRAC).map((a) => a.replaceAll(TIRNAK, "")))); // K2: tirnak icini koruyan durum makinesi. Tirnak icinde ayrac da satir sonu da // alanin parcasidir; yan yana iki tirnak tek tirnak demektir. function k2(bayt) { const m = bayt.toString("utf8"), kayit = []; let alan = [""], ic = false; for (let i = 0; i < m.length; i++) { const c = m[i]; if (ic) { if (c === TIRNAK && m[i + 1] === TIRNAK) { alan[alan.length - 1] += c; i++; } else if (c === TIRNAK) ic = false; else alan[alan.length - 1] += c; } else if (c === TIRNAK) ic = true; else if (c === AYRAC) alan.push(""); else if (c === "\n") { kayit.push(alan); alan = [""]; } else alan[alan.length - 1] += c; } if (alan.length > 1 || alan[0] !== "") kayit.push(alan); return ayikla(kayit.slice(1)); } // Iki toplamin ayni sayilmasi icin esik 0.05 m3'tur: okumalar bir ondalikla // raporlaniyor, altindaki fark yalnizca yuvarlamadan gelebilir. const ESIK = 0.05, ENBUYUK = 37.0; // uretecin verebilecegi en buyuk okuma const hedef = kayitlar().reduce((s, x) => s + x.okuma, 0); const y = (w) => (h) => h.map((v, j) => (j ? String(v).padStart(w[j]) : String(v).padEnd(w[0]))).join(""); const y1 = y([22, 8, 8, 8, 12, 11, 10]); console.log(y1(["ayristirma karari", "giren", "cikan", "dusen", "toplam m3", "eslesmeyen", "kapi no"])); const sonuc = {}; for (const [ad, f] of [["K1 naif bolme", k1], ["K2 tirnagi koruyan", k2]]) { const r = sonuc[ad] = f(b), t = r.cikan.reduce((s, x) => s + x.okuma, 0); console.log(y1([ad, r.giren, r.cikan.length, r.dusen.length, t.toFixed(1), r.cikan.filter((x) => !BOLGE.includes(x.bolge)).length, r.cikan.filter((x) => x.okuma > ENBUYUK).length])); } const r1 = sonuc["K1 naif bolme"], t1 = r1.cikan.reduce((s, x) => s + x.okuma, 0); const yarim = r1.dusen.filter((n) => n < 6).length, kayik = r1.dusen.filter((n) => n > 6).length; const kapi = r1.cikan.filter((x) => x.okuma > ENBUYUK); console.log(`\ndosyada ${r1.giren} veri satiri var ama ${kayitlar().length} mantiksal kayit: ` + `adresinde satir sonu tasiyan 39 kayit ${r1.giren - 2037} fazladan satir uretti`); console.log(`K1 ${r1.dusen.length} satiri hicbir uyari vermeden dusurdu: ${yarim} bolunmus ` + `yarim satir, ${kayik} alani kaymis satir`); console.log(`K1'in cikardigi ${r1.cikan.length} kaydin ${kapi.length} tanesi hayatta kaldi ` + `ama okuma alaninda adresten kayan bir kapi numarasi tasiyor (${kapi[0].okuma} m3 gibi)`); console.log(`iki toplam ${Math.abs(hedef - t1).toFixed(1)} m3 ayrisiyor, esik ${ESIK} m3'un ` + `${Math.round(Math.abs(hedef - t1) / ESIK)} kati; oran yuzde ` + `${(100 * Math.abs(hedef - t1) / hedef).toFixed(1)}`); console.log(`K1'in ${r1.cikan.filter((x) => !BOLGE.includes(x.bolge)).length} kaydinda bolge ` + `adi listeyle eslesmiyor; bolge kirilimi bu kayitlari sessizce disarida birakir`);
ayristirma karari giren cikan dusen toplam m3 eslesmeyen kapi no K1 naif bolme 2076 1934 142 41661.4 1157 33 K2 tirnagi koruyan 2037 2037 0 42645.6 0 0 dosyada 2076 veri satiri var ama 2037 mantiksal kayit: adresinde satir sonu tasiyan 39 kayit 39 fazladan satir uretti K1 142 satiri hicbir uyari vermeden dusurdu: 78 bolunmus yarim satir, 64 alani kaymis satir K1'in cikardigi 1934 kaydin 33 tanesi hayatta kaldi ama okuma alaninda adresten kayan bir kapi numarasi tasiyor (45 m3 gibi) iki toplam 984.2 m3 ayrisiyor, esik 0.05 m3'un 19684 kati; oran yuzde 2.3 K1'in 1157 kaydinda bolge adi listeyle eslesmiyor; bolge kirilimi bu kayitlari sessizce disarida birakir
Tablonun iki satırı aynı dosyayı okuyor ve iki farklı küme veriyor. Tırnak içini koruyan okuma 2.037 satırın hepsini çıkarır, hiçbirini düşürmez ve toplamı 42.645,6 m³ bulur. Naif bölme 2.076 satırdan 1.934 kayıt çıkarır: 142 satır kayboldu ve hiçbiri hata vermedi. İkisi de sıfır çıkış koduyla bitti.
Kaybın dökümü iki kalemdir. 78 satır bölünmüş yarımdır: adresinde satır sonu taşıyan 39 kayıt dosyada iki satıra yayılmıştı, naif okuyucu ikisini de ayrı satır sandı, ikisinde de okuma alanı eksik kaldı ve ikisini de attı. Bir kayıt kaybolmak için iki satır harcadı. 64 satır alanı kaymış satırdır: adresinde ayraç taşıyan 97 kaydın 64’ünde okuma alanına adres kuyruğu geldi, sayıya dönüşmedi ve satır düştü. Mantıksal kayıt cinsinden hesap kapanır: 2.037 kayıttan 103’ü kümeden tamamen çıktı.
Kalan 33 kayıt dersin asıl bulgusudur. Adresinde ayraç taşıyan kayıtların üçte birinde ayraçtan sonra çıplak bir kapı numarası vardı; kayan alan sayıya dönüştü ve kayıt hayatta kaldı. Okuma alanında artık 45 m³ gibi bir değer duruyor ve VT5’e göre bu değer okuma olamaz. Bu 33 kayıt hiçbir sayımda eksik görünmez, yalnız toplamı şişirir. Düşen 103 kayıt toplamı azaltır, şişen 33 kayıt artırır; net fark 984,2 m³, eşiğin 19.684 katı ve toplamın yüzde 2,3’ü olur.
Son satır dördüncü bir kanalı ölçüyor. Dosya UTF-8 yazıldı, naif okuyucu tek baytlık bir kodlamayla çözdü ve çok baytlı harf taşıyan bölge adları başka dizgelere dönüştü. 1.934 kaydın 1.157’sinde bölge adı listeyle eşleşmiyor; bölge kırılımı alan bir özet bu kayıtları sessizce dışarıda bırakır ve kırılımın toplamı genel toplamı tutmaz. Kodlama kararı tek satır düşürmedi, yalnız kayıtların yarıdan fazlasını sınıflandırılamaz hale getirdi.
Ayraç, Belge, Sütun
// bicim.mjs — ayni 2037 kayit uc yapida saklanir. MODELDIR: bicimlerin adlari // degil yapilari onemli. Sutunlu model: okuma 2 baytlik tamsayi (desi-m3), // abone 4 baytlik tamsayi, donem ve bolge birer bayt kod, metin uzunluk onekli. import { dosya, kayitlar, BOLGE } from "./kaynak.mjs"; const k = kayitlar(), belge = Buffer.from(JSON.stringify(k), "utf8"); const metin = (a) => Buffer.concat(a.map((s) => Buffer.from(s, "utf8")) .flatMap((x) => [Buffer.from([x.length]), x])); const SUTUN = { abone: Buffer.from(new Int32Array(k.map((x) => x.abone)).buffer), okuma: Buffer.from(new Uint16Array(k.map((x) => Math.round(x.okuma * 10))).buffer), donem: Buffer.from(k.map((x) => (x.donem === "2024-01" ? 0 : 1))), bolge: Buffer.from(k.map((x) => BOLGE.indexOf(x.bolge))), sayac: metin(k.map((x) => x.sayac)), adres: metin(k.map((x) => x.adres)), }; const toplam = () => { let t = 0; for (let i = 0; i < k.length; i++) t += SUTUN.okuma.readUInt16LE(i * 2) / 10; return t.toFixed(1); }; const y1 = (h) => h.map((v, j) => (j ? String(v).padStart([0, 13, 15, 12][j]) : String(v).padEnd(22))).join(""); console.log(y1(["yapi", "toplam bayt", "soru icin bayt", "toplam m3"])); for (const [ad, tam, soru] of [["ayracli metin", dosya().length, dosya().length], ["ic ice belge", belge.length, belge.length], ["sutunlu", Object.values(SUTUN).reduce((s, x) => s + x.length, 0), SUTUN.okuma.length]]) console.log(y1([ad, tam, soru, toplam()])); console.log(`toplam okumayi ogrenmek sutunlu yapida ${SUTUN.okuma.length} bayt, ayracli ` + `metinde ${dosya().length} bayt okumak demek: ${(dosya().length / SUTUN.okuma.length) .toFixed(0)} kat`);
yapi toplam bayt soru icin bayt toplam m3 ayracli metin 108219 108219 42645.6 ic ice belge 220921 220921 42645.6 sutunlu 68040 4074 42645.6 toplam okumayi ogrenmek sutunlu yapida 4074 bayt, ayracli metinde 108219 bayt okumak demek: 27 kat
Üç yapı aynı 2.037 kaydı taşıyor ve aynı toplamı veriyor. Fark, kaydın sınırının nerede yazıldığındadır. Ayraçlı metinde sınır bir karakterdir ve o karakter veride de geçebilir; 142 satırlık kayıp bunun bedelidir. İç içe belge yapısında alan adı her kayıtta tekrarlandığı için dosya iki kat büyür, ama kaydın nerede bittiği metnin yapısından okunur. Sütunlu yapıda her sütun ayrı bir bayt dizisidir ve tipi ilan edilmiştir; okuma alanına metin kayması yapısal olarak mümkün değildir.
Son satır seçimin ikinci yüzünü ölçüyor. Toplam okuma sorusu sütunlu yapıda 4.074 bayt, ayraçlı metinde 108.219 bayt okumak ister — 27 kat. Sütunlu yapı bunu ucuza yapar, çünkü adres sütununu hiç açmaz; buna karşılık tek bir kaydın bütün alanlarını görmek altı ayrı diziye gitmek demektir. Biçim seçimi bir kolaylık tercihi değil, hangi işin ucuza yapılacağının kararıdır.
Özet
- Dosyada 2.076 veri satırı, kümede 2.037 mantıksal kayıt vardır; satır saymak kayıt saymak değildir.
- Tırnak içini koruyan okuma 2.037 kaydın hepsini çıkarır ve toplamı 42.645,6 m³ bulur; naif bölme 1.934 kayıt çıkarır ve 142 satırı hiçbir uyarı vermeden düşürür.
- Düşenin dökümü 78 bölünmüş yarım satır ile 64 alanı kaymış satırdır; 2.037 kayıttan 103’ü kümeden çıkmış, kayan alanların 33’ü sayıya dönüşüp okuma alanında kapı numarasıyla hayatta kalmıştır. Net fark 984,2 m³, eşiğin 19.684 katıdır.
- Yanlış kodlama tek satır düşürmez ama 1.157 kaydın bölge adını listeyle eşleşmez hale getirir.
- Aynı küme sütunlu yapıda 68.040 bayt tutar ve toplam okuma sorusu 4.074 bayt okunarak cevaplanır; ayraçlı metinde aynı soru 108.219 bayt ister.
Sonraki Adım
Bu ders veriyi bir dosyadan aldı ve satırlarını saydı. Ama verinin çoğu bir veritabanında durur ve oradan çıkarken dosyanın taşımadığı bir soru getirir: sorgu satır döndürür, fakat o satırın neyin bir kaydı olduğu sorgunun yazımına bağlıdır. Sonraki ders sorgunun döndürdüğü satırın çözümlemenin sorduğu birimle uyuşup uyuşmadığını ölçer ve iki tablonun birleştirilmesinin satır sayısını nasıl çoğalttığını sayar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.