Ders 05 / 23
Dinamik ve Açık Eşleme
Eşlemenin kimin kararı olduğu: gelen her alanı dizine ekleyen dinamik eşleme ile alan listesi önceden sabitlenen açık eşlemenin aynı katalog aktarımında karşılaştırılması, alan sayısının belge çeşitliliğiyle büyümesi, alan başına düşen eşleme üstverisinin bayt olarak sayılması, aynı adın iki tiple gelmesi durumunda reddedilen belgelerin ve bu belgelerin sorgu kümesinden düşmesinin ölçülmesi.
İçindekiler
Önceki derste hangi alanın hangi tiple dizinleneceği önceden biliniyordu. Gerçek katalog aktarımında bu bilgi çoğu zaman yoktur: kayıtlar bağış listelerinden, süreli yayın kütüklerinden ve dijital kopya kayıtlarından gelir, her kaynak kendi alanlarını taşır ve hiçbiri önceden bildirilmemiştir.
Bu durumda iki yol vardır. Dinamik eşleme gelen her yeni alan adını görür görmez eşlemeye ekler ve tipini o alanın ilk değerinden çıkarır. Açık eşleme ise alan listesini önceden sabitler; listede olmayan alan dizine girmez. İkisi de aynı kayıtları alır, ama biri esnekliği öbürü denetimi seçer. Bu ders ikisinin farkını dört sayıyla ölçüyor: alan sayısı, üstveri baytı, kabul edilen belge ve dönen küme.
Aktarımın Alanları
Ölçüm için aynı 600 kayıt üç ek kaynaktan gelmiş gibi zenginleştiriliyor. Bağış kayıtları
bagisci alanının yanında bağış yılına ve bağışçı soyadına göre adlandırılmış alanlar getiriyor:
bagis_1994, not_kaya. Süreli yayın kayıtları cilt, sayi ve periyot taşıyor; sayi çoğu
kayıtta bir tam sayıdır, birleşik sayılarda 3-7 gibi bir metindir. Dijital kopya kayıtları
dosya_bicimi, boyut_bayt ve sayfa ekliyor. Her kayda ayrıca sayım yılına göre adlandırılmış
bir alan düşüyor: sayim_2019.
DC1: derlem aynıdır — 600 kayıt, tohum 20250317; ek alanlar aynı tohumdan türetilir. DC2:
alan başına eşleme üstverisi alan adının baytı artı 24 bayttır (tip, çözümleyici göstergesi ve
seçenekler). DC3: açık eşleme yedi alandan oluşur ve listede olmayan alan dizinlenmez.
DC4: dinamik eşlemede bir alanın tipi ilk değerden belirlenir; sonradan gelen aykırı tip
belgenin tamamını reddettirir. DC5: terimler alan adıyla nitelenir, yani konu:roman tek bir
terimdir.
// arama/katalog.mjs — uc konunun paylastigi derlem: kutuphane katalogundan uretilmis // 600 kitap kaydi, tohum 20250317. Alanlar: ad, ozet, konu, yazar, yil, dil, raf. export const TOHUM = 20250317, BELGE = 600; let cekirdek = TOHUM; const rast = () => (cekirdek = (cekirdek * 1103515245 + 12345) % 2147483648) / 2147483648; const sec = (d) => d[Math.floor(rast() * d.length)]; const secZ = (d) => d[Math.floor(rast() ** 2 * d.length)]; // gercek metinde siklik carpiktir const ayir = (s) => s.split("|"); // govde sozcugu: yalin, tamlayan, cogul, yonelme, ayrilma, bulunma export const KOK = ayir("deniz denizin denizler denize denizden denizde|kitap kitabın kitaplar kitaba \ kitaptan kitapta|çocuk çocuğun çocuklar çocuğa çocuktan çocukta|şehir şehrin şehirler şehre \ şehirden şehirde|yol yolun yollar yola yoldan yolda|ada adanın adalar adaya adadan adada|bahçe \ bahçenin bahçeler bahçeye bahçeden bahçede|mektup mektubun mektuplar mektuba mektuptan mektupta|\ gemi geminin gemiler gemiye gemiden gemide|köprü köprünün köprüler köprüye köprüden köprüde|\ okul okulun okullar okula okuldan okulda|kuş kuşun kuşlar kuşa kuştan kuşta").map((s) => s.split(" ")); const KALIP = ayir("0 {} ve gündelik hayat üzerine notlar sunar|0 {} bu derlemenin ana izleğidir|\ 1 {} tarihine geniş yer ayırır|1 {} çevresinde gelişen olayları anlatır|2 {} üzerine derlenmiş \ yazılar içerir|2 {} hakkında kısa öyküler toplar|3 {} açılan bir yolculuğu izler|4 {} toplanmış \ belgeleri sıralar|5 {} tutulan günlüklerden seçmeler verir|5 {} geçen bölümleri İstanbul'un eski \ mahallelerine bağlar").map((s) => [Number(s[0]), s.slice(2)]); const KALIP2 = ayir("{Y} kütüphanesinde tutulan {N} üzerine kuruludur|{N} arasından seçilmiş \ örnekler taşır|{Y} ve çevresindeki {N} listesini verir|{Y} basımı bir {N} derlemesine dayanır"); const EK = ayir("denizci gelenekleri üzerine bir ek bölüm bulunur|Karadeniz kıyısındaki kasabaları \ anlatır|çocukluk anılarına yer verir|kitapçı raflarındaki dağılımı tartışır|yolculuk notlarıyla \ kapanır|adacıklardaki kuş türlerini sayar"); const YER = ayir("Ankara|İzmir|Trabzon|Kars|Bursa|Edirne|Sinop|Antakya"); const NESNE = ayir("harita|fotoğraf|söyleşi|günlük|arşiv belgesi|liman kaydı|kasaba adı|el yazması|gazete kupürü|şarkı sözü"); const ONEK = ayir("Uzak|Kayıp|Sessiz|Eski|Kısa|Büyük|Küçük|Unutulmuş|Beyaz|Yedi"); const SONEK = ayir("Günleri|Öyküleri|Üzerine Notlar|Anıları|Sözlüğü|Rehberi|Yılları|Defteri"); const KONU = ayir("çocuk edebiyatı|roman|kısa öykü|şiir|deniz tarihi|coğrafya|biyografi|gezi yazısı|halk bilimi|mimarlık|müzik|felsefe"); const AD = ayir("Ahmet|Ayşe|Zeynep|Cemal|Nuran|Selim|Elif|Kerem|Hatice|Bedri|Sevgi|Nazlı"); const SOYAD = ayir("Yılmaz|Kaya|Demir|Şahin|Çelik|Aydın|Doğan|Arslan|Koç|Ertem"); const DIL = ayir("Türkçe|Türkçe|Türkçe|İngilizce|Almanca|Fransızca"); const buyut = (s) => s[0].toLocaleUpperCase("tr") + s.slice(1); function ozetUret() { const parca = []; for (let i = 0; i < 3; i += 1) { const [d, k] = secZ(KALIP); parca.push(k.replace("{}", secZ(KOK)[d])); } parca.push(sec(KALIP2).replace("{Y}", sec(YER)).replace("{N}", sec(NESNE))); if (rast() < 0.45) parca.push(sec(EK)); return buyut(parca.join(", ")) + "."; } function adUret() { const k = secZ(KOK), o = sec(ONEK), s = sec(SONEK), t = rast(); if (t < 0.25) return `${o} ${buyut(k[2])}`; if (t < 0.5) return `${buyut(k[0])} ${s}`; if (t < 0.75) return `${o} ${buyut(k[0])} ${s}`; return `${buyut(k[1])} ${s}`; } export const derlem = []; for (let i = 1; i <= BELGE; i += 1) { const konu = [sec(KONU)]; if (rast() < 0.55) konu.push(sec(KONU)); if (rast() < 0.2) konu.push(sec(KONU)); derlem.push({ id: i, ad: adUret(), ozet: ozetUret(), konu: [...new Set(konu)], yazar: `${sec(AD)} ${sec(SOYAD)}`, yil: 1968 + Math.floor(rast() * 57), dil: sec(DIL), raf: `${sec(ayir("TR|EN|DE|FR"))}-${800 + Math.floor(rast() * 99)}.${Math.floor(rast() * 9)}`, }); }
// arama/dizin.mjs — kendi yazilan ters dizin: sozluk, gonderi listesi (belge kimligi, terim // sikligi, konum) ve bayt sayimi. Cozumleyici disaridan verilir; sonraki dersler bunu kullanir. export const BASIT = (s) => s.toLocaleLowerCase("tr").split(/[^\p{L}\p{N}]+/u).filter(Boolean); export const KIMLIK = 4, SIKLIK = 4, KONUM = 4, SOZLUK_EK = 8; // DC2: bayt sabitleri export class TersDizin { sozluk = new Map(); // terim -> gonderi listesi belge = 0; constructor({ coz = BASIT, siklik = true, konum = true } = {}) { Object.assign(this, { coz, siklik, konum }); } ekle(id, metin) { const yerel = new Map(); this.coz(metin).forEach((t, i) => (yerel.get(t) ?? yerel.set(t, []).get(t)).push(i)); for (const [t, k] of yerel) { if (!this.sozluk.has(t)) this.sozluk.set(t, []); this.sozluk.get(t).push({ id, tf: k.length, konum: this.konum ? k : [] }); } this.belge += 1; } liste(t) { return this.sozluk.get(t) ?? []; } ara(...terim) { // kesisim: listeler kimlik sirali oldugu icin tek gecis const l = terim.map((t) => this.liste(t)), p = l.map(() => 0), kume = []; let kars = 0; while (l.every((x, i) => p[i] < x.length)) { const en = Math.max(...l.map((x, i) => x[p[i]].id)); let ayni = true; for (let i = 0; i < l.length; i += 1) { while (p[i] < l[i].length && l[i][p[i]].id < en) { p[i] += 1; kars += 1; } kars += 1; if (p[i] >= l[i].length || l[i][p[i]].id !== en) { ayni = false; break; } } if (ayni) { kume.push(en); p.forEach((_, i) => (p[i] += 1)); } } return { kume, giris: l.reduce((t, x) => t + x.length, 0), kars }; } bayt() { // sozluk + gonderi + konum let s = 0, g = 0, k = 0, giris = 0, konum = 0; for (const [t, liste] of this.sozluk) { s += Buffer.byteLength(t) + SOZLUK_EK; for (const gr of liste) { g += KIMLIK + (this.siklik ? SIKLIK : 0); k += gr.konum.length * KONUM; giris += 1; konum += gr.konum.length; } } return { terim: this.sozluk.size, giris, konum, sozluk: s, gonderi: g, konumBayt: k, toplam: s + g + k }; } }
// arama/esleme.mjs — ayni kayitlar iki eslemeyle alinir: dinamik (gelen her alan dizine girer) // ve acik (alan listesi onceden sabit). Alan sayisi, ustveri bayti, reddedilen belge ve kume farki. import { derlem, BELGE, TOHUM } from "./katalog.mjs"; import { TersDizin } from "./dizin.mjs"; const ALAN_USTVERI = 24; // DC2: alan basina esleme kaydi let c = TOHUM; const rast = () => (c = (c * 1103515245 + 12345) % 2147483648) / 2147483648; const SOYAD = ["Yılmaz", "Kaya", "Demir", "Şahin", "Çelik", "Aydın", "Doğan", "Arslan", "Koç", "Ertem"]; // katalog aktarimi: her kaynak kendi alanlarini getirir (bagis, sureli yayin, dijital kopya) const kayitlar = derlem.map((b) => { const k = { ad: b.ad, ozet: b.ozet, konu: b.konu.join(" "), yazar: b.yazar, yil: b.yil, dil: b.dil, raf: b.raf }; const t = rast(); if (t < 0.35) { const yil = 1968 + Math.floor(rast() * 57), s = SOYAD[Math.floor(rast() * 10)]; Object.assign(k, { bagisci: `${s} ailesi`, [`bagis_${yil}`]: "kabul", [`not_${s.toLocaleLowerCase("tr")}`]: "arsivde" }); } else if (t < 0.6) { k.cilt = 1 + Math.floor(rast() * 12); k.sayi = rast() < 0.25 ? `${1 + Math.floor(rast() * 4)}-${5 + Math.floor(rast() * 4)}` : 1 + Math.floor(rast() * 12); k.periyot = "aylık"; } else if (t < 0.8) { Object.assign(k, { dosya_bicimi: "tarama", boyut_bayt: 1 << (18 + Math.floor(rast() * 4)), sayfa: 40 + Math.floor(rast() * 400) }); } k[`sayim_${2015 + Math.floor(rast() * 10)}`] = 1 + Math.floor(rast() * 3); return k; }); const ACIK = new Map([["ad", "metin"], ["ozet", "metin"], ["konu", "metin"], ["yazar", "metin"], ["yil", "sayi"], ["dil", "metin"], ["raf", "metin"]]); // DC3: acik esleme yedi alandir const tipBul = (v) => (typeof v === "number" ? "sayi" : "metin"); // alan adiyla nitelenmis terim: "konu:roman" tek bir terimdir const terimle = (a, v) => String(v).toLocaleLowerCase("tr").split(/[^\p{L}\p{N}]+/u) .filter(Boolean).map((t) => `${a}:${t}`).join(" "); function al(kayit, acik) { // eslemeyi kur, belgeyi kabul et ya da reddet const alan = acik ? new Map(acik) : new Map(); const dizin = new TersDizin({ coz: (s) => s.split(" ").filter(Boolean), konum: false }); const red = [], atlanan = new Map(); let kabul = 0; for (const [i, k] of kayit.entries()) { const cakisma = []; for (const [a, v] of Object.entries(k)) { const tip = tipBul(v); if (acik && !alan.has(a)) { atlanan.set(a, (atlanan.get(a) ?? 0) + 1); continue; } if (!alan.has(a)) alan.set(a, tip); else if (alan.get(a) !== tip) cakisma.push(`${a} (${alan.get(a)} -> ${tip})`); } if (cakisma.length) { red.push([i + 1, cakisma[0]]); continue; } dizin.ekle(i + 1, Object.entries(k).filter(([a]) => alan.has(a)).map(([a, v]) => terimle(a, v)).join(" ")); kabul += 1; } const ustveri = [...alan.keys()].reduce((t, a) => t + Buffer.byteLength(a) + ALAN_USTVERI, 0); return { alan, dizin, red, atlanan, kabul, ustveri }; } const dinamik = al(kayitlar, null), acik = al(kayitlar, ACIK); console.log(`tohum ${TOHUM}; ${BELGE} katalog kaydi, uc ek kaynak (bagis, sureli yayin, dijital kopya)`); console.log(`\nalan sayisinin belge sayisiyla buyumesi (dinamik esleme)`); console.log(`${"islenen belge".padStart(14)}${"alan".padStart(7)}${"ustveri bayt".padStart(14)}`); for (const n of [50, 150, 300, 600]) { const d = al(kayitlar.slice(0, n), null); console.log(String(n).padStart(14) + String(d.alan.size).padStart(7) + String(d.ustveri).padStart(14)); } console.log(`\n${"esleme".padEnd(9)}${"alan".padStart(6)}${"ustveri bayt".padStart(14)}${"terim".padStart(7)}` + `${"dizin bayt".padStart(12)}${"kabul".padStart(7)}${"red".padStart(5)}${"dizinlenmeyen alan".padStart(20)}`); for (const [ad, r] of [["dinamik", dinamik], ["acik", acik]]) console.log(ad.padEnd(9) + String(r.alan.size).padStart(6) + String(r.ustveri).padStart(14) + String(r.dizin.bayt().terim).padStart(7) + String(r.dizin.bayt().toplam).padStart(12) + String(r.kabul).padStart(7) + String(r.red.length).padStart(5) + String(r.atlanan.size).padStart(20)); console.log(`\ntip catismasi: ${dinamik.red.length} belge reddedildi; ilk ucu ` + `${dinamik.red.slice(0, 3).map(([i, a]) => `#${i} ${a}`).join(", ")}`); const sorgu = ["konu:roman", "dil:türkçe", "bagisci:kaya", "periyot:aylık"]; console.log(`\n${"sorgu".padEnd(18)}${"dinamik".padStart(9)}${"acik".padStart(7)}${"fark".padStart(7)}`); for (const s of sorgu) { const d = dinamik.dizin.ara(s).kume.length, a = acik.dizin.ara(s).kume.length; console.log(s.padEnd(18) + String(d).padStart(9) + String(a).padStart(7) + String(d - a).padStart(7)); }
tohum 20250317; 600 katalog kaydi, uc ek kaynak (bagis, sureli yayin, dijital kopya)
alan sayisinin belge sayisiyla buyumesi (dinamik esleme)
islenen belge alan ustveri bayt
50 49 1595
150 77 2546
300 85 2818
600 91 3022
esleme alan ustveri bayt terim dizin bayt kabul red dizinlenmeyen alan
dinamik 91 3022 660 159951 489 111 0
acik 7 192 437 170796 600 0 84
tip catismasi: 111 belge reddedildi; ilk ucu #2 sayi (metin -> sayi), #12 sayi (metin -> sayi), #22 sayi (metin -> sayi)
sorgu dinamik acik fark
konu:roman 70 82 -12
dil:türkçe 253 310 -57
bagisci:kaya 18 0 18
periyot:aylık 40 0 40
Alan Sayısı Belgeyle Büyür
Birinci tablo eşleme patlamasının biçimini gösteriyor. İlk 50 belge işlendiğinde eşlemede zaten
49 alan vardır: kayıt başına neredeyse bir yeni alan. Alan adları veriden türediği için —
bagis_1994, not_kaya, sayim_2019 — her yeni bağış yılı, her yeni bağışçı ve her yeni sayım
yılı eşlemeye bir satır ekler. Büyüme sonra yavaşlar (150 belgede 77, 600 belgede 91) çünkü yıl ve
soyadı havuzu tükenir; havuzun sınırlı olması derlemin özelliğidir, gerçek bir aktarımda tükenecek
bir havuz yoktur.
Üstveri bu büyümeyi doğrudan izliyor: 1.595 bayttan 3.022 bayta. Rakam küçük görünebilir, ama ölçekle birlikte okunmalıdır — üstveri belge sayısıyla değil alan sayısıyla büyür ve dizinin her parçasında yeniden tutulur. Açık eşlemede aynı kalem 192 bayttır: on beş kat küçük ve belge sayısından bağımsız olarak sabit.
Tip Çatışması Belgeyi Düşürür
İkinci tablonun en sert sayısı red sütunudur. Dinamik eşleme 600 kaydın 489’unu kabul
etmiş, 111’ini reddetmiştir. Nedeni tek bir alandır: sayi. İlk süreli yayın kaydında bu alan
3-7 biçiminde bir metin olarak geldiği için eşleme onu metin olarak sabitlemiş; sonraki
kayıtlarda aynı alan sayı olarak geldiğinde belge tümüyle reddedilmiştir. Kayıp alanla sınırlı
değildir — belgenin ad, ozet, konu alanları da dizine hiç girmez.
Sonuç sorgu tablosunda görünüyor. konu:roman sorusu dinamik eşlemede 70, açık eşlemede 82 belge
döndürüyor; dil:türkçe sorusunda fark 253’e karşı 310’dur. Yani sayi alanının tipi yüzünden
57 Türkçe kitap katalogda aranamaz durumdadır ve bunun hiçbir belirtisi sorgu sonucunda yoktur:
eksik belgeler sessizce yoktur.
Ters yön de ölçülü. bagisci:kaya sorusu dinamik eşlemede 18, açık eşlemede 0 belge döndürüyor;
periyot:aylık sorusunda 40’a karşı 0. Açık eşleme 84 ayrı alanı dizinlemediği için bu sorular
karşılıksızdır. Açık eşlemenin denetimi bedava değildir: aktarımın getirdiği her yeni soru,
eşlemeye elle bir satır eklenene kadar yanıtsız kalır.
Dizin boyutu bu tabloda yanıltıcı bir sıradadır: dinamik eşleme 660 terimle 159.951 bayt, açık eşleme 437 terimle 170.796 bayt tutuyor. Açık eşlemenin daha büyük olmasının nedeni alan sayısı değil, 111 belgeyi fazladan dizinlemesidir. Aynı sayıyı kabul edilen belge başına okumak gerekir: dinamik eşlemede belge başına 327 bayt, açık eşlemede 285 bayt.
Özet
- Dinamik eşleme gelen her alanı eşlemeye ekler ve tipini ilk değerden çıkarır; açık eşleme alan listesini önceden sabitler ve listede olmayanı dizinlemez.
- Alan adları veriden türediğinde eşleme belgeyle birlikte büyür: ilk 50 belgede 49 alan, 600 belgede 91 alan; üstveri 1.595 bayttan 3.022 bayta çıkar. Açık eşlemede aynı kalem 192 bayttır ve sabit kalır.
- Tip çatışması alanı değil belgeyi düşürür:
sayialanı bir kayıtta metin, öbüründe sayı geldiği için 600 kaydın 111’i reddedilir ve dizine hiç girmez. - Kayıp sorguda görünmez ama ölçülür:
konu:roman82 yerine 70,dil:türkçe310 yerine 253 belge döndürür. Eksik belgelerin varlığına dair bir işaret sonuçta yoktur. - Açık eşlemenin bedeli karşılıksız sorulardır: 84 alan dizinlenmediği için
bagisci:kayaveperiyot:aylıksoruları 0 belge döndürür.
Sonraki Adım
Bu dersin iki eşlemesi de belgeleri bir kez alıp bıraktı: kayıt geldi, dizine girdi ya da girmedi. Katalog ise durmuyor — bir kitabın özeti düzeltiliyor, konu etiketi değiştiriliyor, kayıp bir kitap kayıttan çıkarılıyor. Sonraki ders bu üç işlemin dizinde ne yaptığını ölçüyor: güncellemenin neden silme ile ekleme olarak gerçekleştiği, silinen belgenin gönderi listelerinde ne kadar yer tuttuğu, silme işaretinin sorgu sonucuna ve taranan girişe etkisi ve temizlik turuna kadar geçen sürede dizinin ne kadar şiştiği.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.