İçeriğe geç
academia.sh

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: sayi alanı 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:roman 82 yerine 70, dil:türkçe 310 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:kaya ve periyot:aylık soruları 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat