Ders 11 / 17
Metin Normalleştirme
Devralınan 1.816 kaydın adres alanı, abone kütüğündeki 1.178 ayrı adresle ham metin olarak yalnız 325 kayıtta eşleşir. Normalleştirme merdiveni eşleşmeyi sırayla 604, 604, 937, 1.490 ve 1.816 kayda çıkarır: kırpma ve boşluk daraltma 279, dile duyarlı küçültme 333, işaret sadeleştirme 553, kısaltma açma 326 kaydı kurtarır — dile duyarsız küçültme ise sıfır. Toplam 1.491 kayıtta eşleşme kurtarılır. Bir basamak daha ileri gitmek, bütün ayraçları atmak, eşleşmeyi hiç artırmaz ve 13 anahtarda 26 ayrı adresi üst üste bindirir: 'No 5/2' ile 'No 52' aynı anahtara düşer ve 36 kayıt tek bir aboneye bağlanamaz hale gelir. Adım 1.816 satırla girip 1.816 satırla çıkar.
İçindekiler
Bir önceki ders tarife alanının yazım farklarını kaldırdı ve 15 yazımı 5 sınıfa indirdi. Adres alanına ise hiç dokunulmadı.
Bu ders adres alanını abone kütüğüyle karşılaştırır. Kütük ayrı bir kaynaktır ve her aboneyi tek bir kanonik adresle tutar; saha formundaki adres beş ayrı biçimde yazılmıştır. Normalleştirme ikisini buluşturur, ama her basamağın bedeli vardır: metinden silinen bir fark geri gelmez. Dersin iki sayısı budur — bir basamağın eşleşmeyi kaç kayıtta kurtardığı ve kaç kayıtta gerçek bir farkı sildiği.
- VZ21. Küme önceki beş dersten devralınır ve 1.816 satırdır. Abone kütüğü ikinci bir
kaynaktır. İkisi de
nodeile modellenir ve kurgudur. - VZ22. Metin normalleştirme, iki metni karşılaştırılabilir kılmak için ikisine de aynı dönüşümü uygulamaktır; veritabanı tasarımındaki normalizasyonla ilgisi yoktur.
- VZ23. Basamaklar birbirinin üzerine biner ve hem kayda hem kütük kaydına uygulanır; yalnız bir tarafa uygulanan dönüşüm eşleşmeyi kurtarmaz.
- VZ24. Bir basamağın kurtardığı kayıt sayısı ile sildiği fark sayısı ayrı ölçülür. İkincisi kümede değil, kütükte sayılır: kaç ayrı kanonik adres aynı anahtara düşüyor.
Merdiven Eşleşmeyi Kaç Kayıtta Kurtarıyor
// adres.mjs — ayni ham kume ve abone kutugu MODELIDIR. Kume kurgudur: bolgesel // olcum aginin 1400 abonesi ve saha ziyaret kayitlari; ayni tohum, ayni satirlar. // Onceki bes dersin karari uygulanir ve bu adima 1816 satir girer. export const TOHUM = 20260305, ABONE = 1400; const karistir = (x) => { x = Math.imul(x ^ (x >>> 16), 2246822507); x = Math.imul(x ^ (x >>> 13), 3266489909); return (x ^ (x >>> 16)) >>> 0; }; export const uretec = (t) => { let s = karistir(t); return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; }; export const ayrik = (u, w) => { let t = 0; for (let i = 0; i < w.length; i++) { t += w[i]; if (u < t) return i; } return w.length - 1; }; // Son alan: ziyaretin okuma uretmeme orani. Bolgeler bu oranda ayrisir. export const BOLGE = [["kuzey", "KZ", 0.28, 21, 0.06], ["guney", "GN", 0.22, 17, 0.03], ["dogu", "DG", 0.18, 26, 0.27], ["bati", "BT", 0.14, 14, 0.03], ["merkez", "MR", 0.18, 23, 0.08]] .map(([ad, kod, pay, taban, eksik]) => ({ ad, kod, pay, taban, eksik })); const SOKAK = ["Çınar", "Söğüt", "Işık", "Ihlamur", "Zeytin", "Menekşe", "Palmiye", "Ceviz"]; const TARIFE = ["Mesken", "mesken", "MESKEN", " Mesken ", "Konut", "konut", "Ticari", "ticari", "TİCARİ", "Is yeri", "İş Yeri", "isyeri", "Sanayi", "sanayi", "Tarimsal"]; export const HAM = [], KUTUK = new Map(); let bir = null; for (let i = 0; i < ABONE; i++) { const r = uretec(TOHUM + i); const b = BOLGE[ayrik(r(), BOLGE.map((x) => x.pay))]; const taban = Math.max(1.2, b.taban * Math.exp((r() + r() + r() - 1.5) * 0.62)); const sayac = `${b.kod}-${String(1000 + i).padStart(4, "0")}`; const paylas = bir && bir.b === b.ad && r() < 0.03; // ayni binada ikinci sayac const sk = paylas ? bir.sk : SOKAK[Math.floor(r() * SOKAK.length)]; const kapi = paylas ? bir.kapi : 1 + Math.floor(r() * 60); const daire = paylas ? bir.daire : r() < 0.26 ? `/${1 + Math.floor(r() * 9)}` : ""; bir = { b: b.ad, sk, kapi, daire }; // Abone kutugu: her abone icin tek bir kanonik adres. Ikinci kaynaktir. KUTUK.set(10001 + i, `${b.ad[0].toUpperCase()}${b.ad.slice(1)} Mahallesi ${sk} ` + `Sokak No ${kapi}${daire}`); const endeks0 = 8000 + Math.floor(r() * 11000); for (const d of r() < 0.34 ? ["2026-01", "2026-02"] : ["2026-01"]) { const q = uretec(TOHUM + 7000 + HAM.length), u = q(); const gercek = Math.round(taban * (d === "2026-02" ? 0.94 : 1) * 100) / 100; // Eksiklik uc bicimde gorunur: hucre bos, hucrede yalniz bosluk, "yok" yazili. const eksik = u < b.eksik ? ayrik(q(), [0.52, 0.19, 0.29]) : -1; const kayma = eksik < 0 && q() < 0.008; // ondalik kaymasi: okuma on kat yazilmis const yazilan = kayma ? Math.round(gercek * 1000) / 100 : gercek; const okuma = eksik === 0 ? "" : eksik === 1 ? " " : eksik === 2 ? "yok" : q() < 0.31 ? yazilan.toFixed(2).replace(".", ",") : yazilan.toFixed(2); const gun = 1 + Math.floor(q() * 27), ay = d.slice(5); const tb = ayrik(q(), [0.55, 0.21, 0.24]); const tarih = tb === 0 ? `${d}-${String(gun).padStart(2, "0")}` : tb === 1 ? `${String(gun).padStart(2, "0")}.${ay}.2026` : `${String(gun).padStart(2, "0")}/${ay}/2026`; const endeks = endeks0 + Math.round(gercek * (d === "2026-02" ? 2 : 1)); const eb = q() < 0.22; // Saha formunda adres bes ayri bicimde yazilmis; kutuk bicimi av === 1. const av = ayrik(q(), [0.30, 0.18, 0.18, 0.16, 0.18]); const kap = `${kapi}${daire}`; const M = `${b.ad[0].toUpperCase()}${b.ad.slice(1)}`, mUst = b.ad.toLocaleUpperCase("tr"); const alt = sk.toLocaleLowerCase("tr"), ust = sk.toLocaleUpperCase("tr"); const adres = av === 0 ? `${b.ad} mahallesi ${alt} sokak no: ${kap}` : av === 1 ? `${M} Mahallesi ${sk} Sokak No ${kap}` : av === 2 ? `${mUst} MAHALLESİ ${ust} SOKAK NO ${kap}` : av === 3 ? ` ${M} Mahallesi ${sk} Sokak No ${kap} ` : `${b.ad} mah. ${alt} sk. no:${kap}`; HAM.push({ abone_no: 10001 + i, sayac_no: q() < 0.011 ? "" : sayac, bolge: b.ad, donem: d, okuma_metni: okuma, endeks_metni: eb ? `${Math.floor(endeks / 1000)}.${String(endeks % 1000).padStart(3, "0")}` : `${endeks}`, tarih_metni: tarih, tarife_metni: TARIFE[ayrik(q(), Array(15).fill(1 / 15))], adres_metni: adres, gercek_m3: gercek, yazilan_m3: eksik < 0 ? yazilan : null }); if (q() < 0.09) { // ayni ziyaret ikinci kez girilmis const on = HAM[HAM.length - 1], bicim = ayrik(q(), [0.34, 0.28, 0.38]); HAM.push({ ...on, sayac_no: bicim === 0 ? ` ${sayac}` : bicim === 1 ? sayac.toLowerCase() : sayac, adres_metni: av === 2 ? `${b.ad} mah. ${alt} sk. no:${kap}` : `${mUst} MAHALLESİ ${ust} SOKAK NO ${kap}`, tarife_metni: TARIFE[ayrik(q(), Array(15).fill(1 / 15))] }); } } } // Okumayi sayiya cevirir. Ondalik ayraci virgul de olabilir; donusumun kendi // tuzaklari bu konunun ucuncu dersinde olculur, burada tolere edilir. export const sayiya = (t) => { const s = String(t).trim().replace(",", "."); return s === "" || !/^\d+(\.\d+)?$/.test(s) ? null : Number(s); }; export const medyan = (a) => { const l = [...a].sort((x, y) => x - y); return l.length % 2 ? l[(l.length - 1) / 2] : (l[l.length / 2 - 1] + l[l.length / 2]) / 2; }; export const y = (w) => (h) => h.map((v, j) => (j ? String(v).padStart(w[j]) : String(v).padEnd(w[0]))).join(""); // Onceki dersin karari: kimlik alani bos olan satirlar elendi, eksik okumali // satirlar isaretlenip bolge ortancasiyla dolduruldu. Bu adima 2068 satir girer. const kimlikli = HAM.filter((s) => s.sayac_no.trim() !== ""); const olculen = kimlikli.filter((s) => sayiya(s.okuma_metni) !== null); const bolgeMed = new Map(BOLGE.map((b) => [b.ad, medyan(olculen .filter((s) => s.bolge === b.ad).map((s) => sayiya(s.okuma_metni)))])); export const GIREN = kimlikli.map((s) => sayiya(s.okuma_metni) === null ? { ...s, okuma_metni: bolgeMed.get(s.bolge).toFixed(2) } : s); // Onceki dort dersin karari: eksik doldurma, tekillestirme, bicim farkinda // donusum ve ceyrekler acikligiyla isaretlenen 64 satirin ayrilmasi. const tekille = (l, k) => { const m = new Map(); for (const s of l) if (!m.has(k(s))) m.set(k(s), s); return [...m.values()]; }; const OKUMA = tekille(GIREN, (s) => `${s.abone_no}|${s.donem}|${s.sayac_no.trim().toUpperCase()}`) .map((s) => ({ ...s, okuma_m3: Number(s.okuma_metni.trim().replace(",", ".")) })); const ceyrek = (a, p) => { const l = [...a].sort((x, z) => x - z); const h = (l.length - 1) * p, i = Math.floor(h); return l[i] + (h - i) * (l[Math.min(i + 1, l.length - 1)] - l[i]); }; const dd = OKUMA.map((s) => s.okuma_m3); const q1 = ceyrek(dd, 0.25), q3 = ceyrek(dd, 0.75), ca = q3 - q1; export const GIREN4 = OKUMA.filter((s) => s.okuma_m3 >= q1 - 1.5 * ca && s.okuma_m3 <= q3 + 1.5 * ca); // Metin normallestirme merdiveni. Her basamak bir oncekinin uzerine biner ve // hem kayda hem kutuk kaydina ayni sekilde uygulanir. export const N = [ ["N0 ham metin", (t) => t], ["N1 kirpma ve bosluk daraltma", (t) => t.trim().replace(/\s+/g, " ")], ["N2 kucuk harf (dile duyarsiz)", (t) => N[1][1](t).toLowerCase()], ["N3 kucuk harf (dile duyarli)", (t) => N[1][1](t).toLocaleLowerCase("tr")], ["N4 isaret sadelestirme", (t) => N[3][1](t).replace(/[.:]/g, " ").replace(/\s+/g, " ").trim()], ["N5 kisaltma acma", (t) => N[4][1](t).replace(/\bmah\b/, "mahallesi") .replace(/\bsk\b/, "sokak")], ["N6 tum ayraclarin atilmasi", (t) => N[5][1](t).replace(/[^a-z0-9çğıöşü]/g, "")], ]; if (import.meta.url === `file://${process.argv[1]}`) { const kutuk = new Set(KUTUK.values()); console.log(`model (tohum ${TOHUM}): onceki adimdan devralinan kume ${GIREN4.length} satir, ` + `abone kutugunde ${KUTUK.size} adres, bunlarin ${kutuk.size} tanesi ayri metin`); // Ayni kutuk adresine karsilik gelen yazimlar: en cok bicim tasiyan grup. const oymak = new Map(); for (const s of GIREN4) { const k = N[6][1](s.adres_metni); oymak.set(k, (oymak.get(k) ?? new Set()).add(s.adres_metni)); } const en = [...oymak.values()].sort((a, b) => b.size - a.size)[0]; console.log(`\nayni adres saha formunda ${en.size} ayri bicimde yazilmis:`); for (const s of en) console.log(` ${JSON.stringify(s)}`); const y1 = y([32, 11, 12, 13]); console.log("\n" + y1(["normallestirme basamagi", "eslesen", "kurtarilan", "oran %"])); let onceki = null; for (const [ad, f] of N) { const anahtarlar = new Set([...kutuk].map(f)); const es = GIREN4.filter((s) => anahtarlar.has(f(s.adres_metni))).length; console.log(y1([ad, es, onceki === null ? 0 : es - onceki, (100 * es / GIREN4.length).toFixed(1)])); onceki = es; } }
model (tohum 20260305): onceki adimdan devralinan kume 1816 satir, abone kutugunde 1400 adres, bunlarin 1178 tanesi ayri metin ayni adres saha formunda 4 ayri bicimde yazilmis: "GUNEY MAHALLESİ IHLAMUR SOKAK NO 29" "guney mahallesi ıhlamur sokak no: 29" " Guney Mahallesi Ihlamur Sokak No 29 " "guney mah. ıhlamur sk. no:29" normallestirme basamagi eslesen kurtarilan oran % N0 ham metin 325 0 17.9 N1 kirpma ve bosluk daraltma 604 279 33.3 N2 kucuk harf (dile duyarsiz) 604 0 33.3 N3 kucuk harf (dile duyarli) 937 333 51.6 N4 isaret sadelestirme 1490 553 82.0 N5 kisaltma acma 1816 326 100.0 N6 tum ayraclarin atilmasi 1816 0 100.0
Ham metinle 1.816 kaydın yalnız 325’i kütükle eşleşiyor. Kalan 1.491 kayıt gerçek bir adres taşır, doğru aboneye aittir ve hiçbir eşleşme kurmaz.
Basamaklar eşit iş yapmıyor. Kırpma ve boşluk daraltma 279 kayıt kurtarır, dile duyarlı küçültme 333, işaret sadeleştirme 553, kısaltma açma son 326 kaydı getirir ve eşleşme yüzde 100’e ulaşır.
Sıfır kurtaran tek basamak dile duyarsız küçültmedir. "MAHALLESİ" dile duyarsız
küçültüldüğünde harfin üstündeki nokta ayrı bir işaret olarak kalır ve metin "mahallesi" ile
eşleşmez; "IŞIK" ise "işik" olur, oysa dile duyarlı karşılığı "ışık"tır. Basamak hiçbir kaydı
kurtarmaz ve arkasından geleninkini zorlaştırır.
Bir Basamak Fazla
// silinen.mjs — ayni kume, ters soru: normallestirme bir basamak daha ileri // gidince kac ayri adres ayirt edilemez hale geliyor ve kac kayit etkileniyor. import { GIREN4, KUTUK, N, y } from "./adres.mjs"; const kutuk = [...new Set(KUTUK.values())]; const y1 = y([32, 12, 14, 13]); console.log(y1(["normallestirme basamagi", "kutuk kaydi", "ayri anahtar", "yutulan"])); for (const [ad, f] of N.slice(4)) { const anahtar = new Set(kutuk.map(f)); console.log(y1([ad, kutuk.length, anahtar.size, kutuk.length - anahtar.size])); } // N6 hangi adresleri ayni anahtara dusuruyor. const grup = new Map(); for (const k of kutuk) { const a = N[6][1](k); grup.set(a, (grup.get(a) ?? []).concat([k])); } const cakisan = [...grup.entries()].filter(([, l]) => l.length > 1); const cakisanAnahtar = new Set(cakisan.map(([a]) => a)); const etkilenen = GIREN4.filter((s) => cakisanAnahtar.has(N[6][1](s.adres_metni))); console.log(`\nN6 ${cakisan.length} anahtarda ${cakisan.reduce((a, [, l]) => a + l.length, 0)} ` + `ayri adresi ustuste bindiriyor; kumede bu anahtarlari tasiyan ${etkilenen.length} kayit ` + `artik tek bir aboneye baglanamiyor`); const y2 = y([36, 36]); console.log("\n" + y2(["ayni anahtara dusen iki adres", "N6 anahtari"])); for (const [a, l] of cakisan.slice(0, 2)) { console.log(y2([JSON.stringify(l[0]), a])); console.log(y2([JSON.stringify(l[1]), ""])); } console.log(`\nkapi numarasindaki egik cizgi N6'da atiliyor: "No 5/2" ile "No 52" ayni ` + `anahtari veriyor. N5 ayni kumede ${new Set(kutuk.map(N[5][1])).size} ayri anahtar tutuyor ` + `ve eslesme orani ikisinde de yuzde 100`); const ham = GIREN4.filter((s) => new Set(kutuk).has(s.adres_metni)).length; console.log(`\nsatir hesabi: giren ${GIREN4.length}, cikan ${GIREN4.length}; 0 satir elendi, ` + `0 satir sessizce dustu. Merdiven N5'te durduruldu ve eslesme ${ham} kayittan ` + `${GIREN4.length} kayda cikti: ${GIREN4.length - ham} kayitta eslesme kurtarildi, ` + `0 kayit ayirt edilemez oldu. Bir basamak daha gidilseydi eslesme hic artmaz, ` + `${etkilenen.length} kayit ayirt edilemez olurdu`);
normallestirme basamagi kutuk kaydi ayri anahtar yutulan N4 isaret sadelestirme 1178 1178 0 N5 kisaltma acma 1178 1178 0 N6 tum ayraclarin atilmasi 1178 1165 13 N6 13 anahtarda 26 ayri adresi ustuste bindiriyor; kumede bu anahtarlari tasiyan 36 kayit artik tek bir aboneye baglanamiyor ayni anahtara dusen iki adres N6 anahtari "Kuzey Mahallesi Işık Sokak No 5/2" kuzeymahallesiışıksokakno52 "Kuzey Mahallesi Işık Sokak No 52" "Dogu Mahallesi Işık Sokak No 14" dogumahallesiışıksokakno14 "Dogu Mahallesi Işık Sokak No 1/4" kapi numarasindaki egik cizgi N6'da atiliyor: "No 5/2" ile "No 52" ayni anahtari veriyor. N5 ayni kumede 1178 ayri anahtar tutuyor ve eslesme orani ikisinde de yuzde 100 satir hesabi: giren 1816, cikan 1816; 0 satir elendi, 0 satir sessizce dustu. Merdiven N5'te durduruldu ve eslesme 325 kayittan 1816 kayda cikti: 1491 kayitta eslesme kurtarildi, 0 kayit ayirt edilemez oldu. Bir basamak daha gidilseydi eslesme hic artmaz, 36 kayit ayirt edilemez olurdu
Son basamak eşleşmeyi hiç artırmıyor — N5’te oran zaten yüzde 100’dür — ama bir şeyi bozuyor.
Bütün ayraçları atmak, kapı numarasındaki eğik çizgiyi de atar: "No 5/2" ile "No 52" aynı
anahtara düşer. Kütükteki 1.178 ayrı adres 1.165 anahtara iner; 13 anahtar 26 ayrı adresi üst
üste bindirir.
Bu bir eşleşme hatası değil, bir ayırt edilemezlik durumudur. O anahtarları taşıyan 36 kayıt hâlâ bir eşleşme bulur, ama bulduğu eşleşme iki adresten hangisi olduğu belli olmayan bir eşleşmedir. Satır sayısı değişmez ve tablodan bakan biri yüzde 100 eşleşme görür. Silinen şey satır değil, ayrım.
Adımın hesabı şudur: giren 1.816 satır, çıkan 1.816 satır. Hiçbir satır elenmedi ve sessizce düşmedi; 1.491 kayıtta eşleşme kurtarıldı, sıfır kayıt ayırt edilemez oldu. Merdivenin N5’te durdurulması bir karardır: bir basamak daha ileri gitmek eşleşmeye hiçbir şey katmaz, 36 kaydın ayrımını siler. Bir normalleştirme adımı, kurtardığı eşleşmeden fazlasını sildiği anda bir temizlik olmaktan çıkar.
Özet
- Adres alanı ham metinle 1.816 kaydın yalnız 325’inde kütükle eşleşir; normalleştirme merdiveni eşleşmeyi 604, 604, 937, 1.490 ve 1.816 kayda çıkarır.
- Basamaklar eşit iş yapmaz: kırpma ve boşluk daraltma 279, dile duyarlı küçültme 333, işaret sadeleştirme 553, kısaltma açma 326 kayıt kurtarır; toplam 1.491.
- Dile duyarsız küçültme sıfır kayıt kurtarır: harflerin bir bölümünü görünmeyen bir işaretle bırakır, bir bölümünü yanlış harfe çevirir.
- Bütün ayraçları atmak eşleşmeyi hiç artırmaz ve kütükteki 1.178 adresi 1.165 anahtara indirir: 13 anahtar 26 adresi üst üste bindirir, kümede 36 kayıt tek bir aboneye bağlanamaz olur.
- Adımın hesabı: giren 1.816, çıkan 1.816; 0 elendi, 0 sessizce düştü, 1.491 kayıtta eşleşme kurtarıldı ve 0 ayrım silindi.
- Altı adımın toplamı: 2.086 satır girdi, 1.816 satır çıktı; 18 elendi, 188 birleşti, 64 ayrı deftere geçti ve sessizce düşen satır sayısı sıfır kaldı.
Sonraki Adım
Konu boyunca satırların içi açıldı ve her adımın hesabı yazıldı. Küme artık temizdir — ve hâlâ yanlış biçimdedir. Satırlar saha formunun düzenine göre durur: her satır bir ziyaret, her ziyaret bir dönem, tarife ve bölge birer metin sütunu. Sorulacak sorular ise abone başına, dönem başına ve bölge başına sorulmuştur ve bu biçimde cevaplanamaz. Sonraki konu kümeyi kaynağın biçiminden çıkarıp sorunun biçimine sokar: iki kaynağı anahtar üzerinden birleştirir ve birleştirmenin 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.