İçeriğe geç
academia.sh

Ders 04 / 23

Eşlemeler ve Alan Tipleri

Alanın tipinin dizinleme kuralını belirlemesi: metin alanı ile anahtar sözcük alanının ayrımı, aynı alanın iki tiple dizinlenip terim sayısı, gönderi girişi ve dizin baytının karşılaştırılması, tam eşleme ile parça eşlemenin dönen kümeye etkisi, çok değerli alanda oluşan çapraz eşleşme, sıralama anahtarının tanımlı olup olmaması ve toplama kovalarının iki tipte ayrışması.

İçindekiler

Önceki dersin üç zinciri de aynı kuralı bütün metne uyguladı. Katalogda ise her alan metin değildir. Raf kodu TR-866.3 bir yer belirtir ve bölünmesi anlamsızdır; dil Türkçe bir değerdir; konu etiketi çocuk edebiyatı iki sözcükten oluşur ama tek bir kategoridir. Özet ise gerçekten metindir ve içindeki her sözcük ayrı bir arama girişidir.

Bir alanın hangi kurala göre dizinleneceğini bildiren tanıma eşleme denir; eşlemenin taşıdığı karara da alan tipi. Bu ders iki tipi karşılaştırıyor. Metin alanı değeri çözümleyici zincirinden geçirir; sonuçta alan birden çok terime dağılır. Anahtar sözcük alanı değeri bölmez, dönüştürmez: değerin tamamı tek bir terimdir.

İki Tip, Aynı Alan

Ölçüm üç alan üzerinde yapılıyor: çok değerli konu, tek değerli raf ve tek değerli dil. Her alan iki kez dizinleniyor ve dört şey sayılıyor: dizinin boyutu, tam değer sorgusunun döndürdüğü küme, sıralamanın tanımlı olup olmadığı ve toplamanın ürettiği kova sayısı.

DC1: derlem aynıdır — 600 kayıt, tohum 20250317. DC2: bayt sabitleri önceki derslerdeki sabitlerdir; bu derste konum tutulmuyor. DC3: çok değerli alanda anahtar sözcük tipi her değeri ayrı bir terim yapar, metin tipi bütün değerleri tek bir metin gibi çözümler. DC4: metin alanında belge başına birden çok terim olduğu için sıralama anahtarı seçilmek zorundadır; burada “en küçük terim” kuralı seçilmiştir.

// 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/alan-tipi.mjs — ayni alanlar iki tiple dizinlenir: metin alani (cozumleyiciden gecer)
// ve anahtar sozcuk alani (deger tek terim kalir). Bayt, tam esleme, siralama ve toplama.
import { derlem, BELGE, TOHUM } from "./katalog.mjs";
import { TersDizin } from "./dizin.mjs";

const AYRAC = "\u0001";                                     // deger siniri
const deger = { konu: (b) => b.konu, raf: (b) => [b.raf], dil: (b) => [b.dil] };
const METIN = (s) => s.toLocaleLowerCase("tr").split(/[^\p{L}\p{N}]+/u).filter(Boolean);
const ANAHTAR = (s) => s.split(AYRAC).filter(Boolean);       // deger bolunmez
const kur = (alan, coz) => {
  const d = new TersDizin({ coz, konum: false });
  for (const b of derlem) d.ekle(b.id, deger[alan](b).join(AYRAC));
  return d;
};
const dizin = new Map();
for (const alan of Object.keys(deger))
  for (const [tip, coz] of [["metin", METIN], ["anahtar sozcuk", ANAHTAR]])
    dizin.set(`${alan}/${tip}`, kur(alan, coz));

console.log(`tohum ${TOHUM}; derlem ${BELGE} belge; alanlar: konu (cok degerli), raf, dil`);
console.log(`\n${"alan".padEnd(6)}${"tip".padEnd(16)}${"terim".padStart(7)}${"gonderi".padStart(9)}` +
  `${"dizin bayt".padStart(12)}${"ornek terim".padStart(28)}`);
for (const [ad, d] of dizin) {
  const [alan, tip] = ad.split("/"), b = d.bayt();
  console.log(alan.padEnd(6) + tip.padEnd(16) + String(b.terim).padStart(7) + String(b.giris).padStart(9) +
    String(b.toplam).padStart(12) + [...d.sozluk.keys()].slice(0, 2).join(" | ").padStart(28));
}

const sikRaf = [...derlem.reduce((m, b) => m.set(b.raf, (m.get(b.raf) ?? 0) + 1), new Map())]
  .sort((x, y) => y[1] - x[1])[0][0];                        // derlemde en cok yinelenen raf kodu
const soru = [["konu", "çocuk edebiyatı", "tam etiket"], ["konu", "çocuk", "etiketin tek sozcugu"],
  ["konu", "çocuk tarihi", "olmayan etiket"], ["raf", sikRaf, "tam raf kodu"], ["raf", "TR", "raf oneki"],
  ["dil", "Türkçe", "deger yazildigi gibi"], ["dil", "türkçe", "deger kucuk harfle"]];
console.log(`\n${"alan".padEnd(6)}${"sorgu".padEnd(18)}${"aciklama".padEnd(24)}${"metin".padStart(8)}${"anahtar sozcuk".padStart(16)}`);
for (const [alan, s, not] of soru) {
  const m = dizin.get(`${alan}/metin`).ara(...METIN(s)).kume.length;
  const a = dizin.get(`${alan}/anahtar sozcuk`).ara(s).kume.length;
  console.log(alan.padEnd(6) + s.padEnd(18) + not.padEnd(24) + String(m).padStart(8) + String(a).padStart(16));
}

// --- siralama: anahtar sozcuk alaninda belge basina tek terim, metin alaninda birden cok ---
const terimler = (d, id) => [...d.sozluk].filter(([, l]) => l.some((g) => g.id === id)).map(([t]) => t);
const mRaf = dizin.get("raf/metin"), aRaf = dizin.get("raf/anahtar sozcuk");
const cok = derlem.filter((b) => terimler(mRaf, b.id).length > 1).length;
const enKucuk = (d, id) => terimler(d, id).sort()[0];
const sirala = (d) => derlem.map((b) => [enKucuk(d, b.id), b.id]).sort((x, y) => (x[0] < y[0] ? -1 : x[0] > y[0] ? 1 : x[1] - y[1]));
const aSira = sirala(aRaf), mSira = sirala(mRaf);
const farkli = aSira.slice(0, 10).filter(([, id], i) => mSira[i][1] !== id).length;
console.log(`\nsiralama (raf alanina gore): metin tipinde ${cok} belgenin alaninda birden cok terim var; ` +
  `${new Set(mSira.map(([t]) => t)).size} ayri siralama anahtari`);
console.log(`anahtar sozcuk tipinde ${new Set(aSira.map(([t]) => t)).size} ayri anahtar; ` +
  `ilk on belgenin ${farkli} tanesi iki siralamada farkli`);
console.log(`  anahtar sozcuk ilk uc: ${aSira.slice(0, 3).map(([t, i]) => `${t} (#${i})`).join(", ")}`);
console.log(`  metin ilk uc        : ${mSira.slice(0, 3).map(([t, i]) => `${t} (#${i})`).join(", ")}`);

// --- toplama: konu alaninda kova sayimi ---
const kova = (d) => [...d.sozluk].map(([t, l]) => [t, l.length]).sort((x, y) => y[1] - x[1] || (x[0] < y[0] ? -1 : 1));
for (const tip of ["anahtar sozcuk", "metin"]) {
  const k = kova(dizin.get(`konu/${tip}`));
  console.log(`\nkonu toplamasi (${tip}): ${k.length} kova, toplam ${k.reduce((t, [, n]) => t + n, 0)} belge sayimi`);
  console.log(`  en buyuk uc: ${k.slice(0, 3).map(([t, n]) => `${t} ${n}`).join(", ")}`);
}
tohum 20250317; derlem 600 belge; alanlar: konu (cok degerli), raf, dil

alan  tip               terim  gonderi  dizin bayt                 ornek terim
konu  metin                17     1430       11686            biyografi | kısa
konu  anahtar sozcuk       12      999        8203       biyografi | kısa öykü
raf   metin               112     1800       15610                    en | 893
raf   anahtar sozcuk      549      600       13584         EN-893.5 | FR-881.4
dil   metin                 4      600        4866         almanca | fransızca
dil   anahtar sozcuk        4      600        4867         Almanca | Fransızca

alan  sorgu             aciklama                   metin  anahtar sozcuk
konu  çocuk edebiyatı   tam etiket                    93              93
konu  çocuk             etiketin tek sozcugu          93               0
konu  çocuk tarihi      olmayan etiket                 9               0
raf   TR-866.3          tam raf kodu                   3               3
raf   TR                raf oneki                    154               0
dil   Türkçe            deger yazildigi gibi         310             310
dil   türkçe            deger kucuk harfle           310               0

siralama (raf alanina gore): metin tipinde 600 belgenin alaninda birden cok terim var; 9 ayri siralama anahtari
anahtar sozcuk tipinde 549 ayri anahtar; ilk on belgenin 10 tanesi iki siralamada farkli
  anahtar sozcuk ilk uc: DE-801.1 (#460), DE-801.8 (#94), DE-802.5 (#594)
  metin ilk uc        : 0 (#19), 0 (#31), 0 (#42)

konu toplamasi (anahtar sozcuk): 12 kova, toplam 999 belge sayimi
  en buyuk uc: coğrafya 95, çocuk edebiyatı 93, biyografi 88

konu toplamasi (metin): 17 kova, toplam 1430 belge sayimi
  en buyuk uc: coğrafya 95, edebiyatı 93, çocuk 93

Bayt Farkı Alanın Kendisine Bağlıdır

İlk tablo tek bir kural vermiyor, çünkü iki tipin hangisinin ucuz olduğu alana göre değişiyor. konu alanında metin tipi hem daha çok terim (17’ye 12) hem daha çok gönderi girişi (1.430’a 999) üretiyor ve 11.686 baytla anahtar sözcük tipinin 8.203 baytının üstüne çıkıyor: iki sözcüklü etiketler bölününce her belge alanda iki giriş bırakıyor.

raf alanında yön tersine dönüyor. Metin tipi sözlüğü küçültüyor — 549 ayrı kod yerine 112 parça, çünkü aynı sayı ve önek yüzlerce kodda tekrar ediyor — ama gönderi girişini üçe katlıyor: 600 yerine 1.800. Toplamda yine metin tipi pahalı (15.610’a 13.584), fakat pahalılık bu kez gönderi tarafından geliyor. dil alanı iki tipin ancak bir bayt ayrıldığı sınır durumdur: değer zaten tek sözcük olduğu için tip kararı boyutu değiştirmiyor, yalnız yazımı koruyor.

Tam Eşleme, Sıralama ve Toplama

İkinci tablo kararın kümeye etkisini gösteriyor. Tam etiket sorulduğunda iki tip aynı 93 belgeyi döndürüyor; ayrım öbür satırlarda. Etiketin tek sözcüğü sorulduğunda metin alanı 93 belge, anahtar sözcük alanı 0 belge veriyor: metin alanı parça eşleşmeye açıktır, anahtar sözcük alanı değilse değildir. Aynı davranış raf alanında TR önekiyle 154 belgeye karşı 0 belge biçiminde tekrarlanıyor.

Üçüncü satır asıl uyarıdır. çocuk tarihi diye bir etiket derlemde yoktur; buna karşın metin alanı 9 belge döndürüyor. Bunlar çocuk edebiyatı ve deniz tarihi etiketlerini birlikte taşıyan belgelerdir: alan çok değerli olduğu için iki ayrı değerden gelen sözcükler tek bir eşleşme gibi davranır. Anahtar sözcük alanı bu belgeleri getirmez, çünkü onun için her etiket bölünmemiş bir bütündür. Ters yöndeki bedel dil satırında görünüyor: türkçe yazımıyla sorulduğunda anahtar sözcük alanı 0 belge döndürür, çünkü değerde büyük harf vardır.

Sıralama farkı daha keskindir. Anahtar sözcük alanında her belgenin tek bir terimi vardır ve raf kodu 549 ayrı sıralama anahtarı üretir; sıralama tanımlıdır. Metin alanında 600 belgenin tamamının alanında birden çok terim vardır, dolayısıyla “hangi terime göre” sorusu yanıtsızdır. Seçilen kural en küçük terimi alınca elde 9 anahtar kalıyor ve ilk on belgenin onu da yer değiştiriyor: sıralama pratikte anlamsızlaşıyor.

Toplama tarafında da iki tip aynı veriden iki tablo çıkarıyor. Anahtar sözcük alanı 12 kova ve 999 belge sayımı verir — bu, derlemdeki etiket sayısının tamıdır. Metin alanı 17 kova ve 1.430 sayım verir; çocuk edebiyatı etiketi çocuk ve edebiyatı diye iki kovaya bölünmüş, her ikisi de 93 sayarak listenin başına yerleşmiştir. Kategori dağılımını görmek isteyen bir raporda bu iki tablo aynı soruya farklı yanıt verir.

Özet

  • Eşleme alanın hangi kurala göre dizinleneceğini bildirir; metin alanı değeri çözümleyiciden geçirip terimlere böler, anahtar sözcük alanı değeri bölünmemiş tek terim olarak tutar.
  • Boyut farkının yönü alana bağlıdır: konu alanında metin tipi 11.686 bayta karşı 8.203 bayt ile pahalıdır, raf alanında sözlüğü 549 terimden 112 terime indirir ama gönderi girişini 600’den 1.800’e çıkarır.
  • Tam eşleme yalnız anahtar sözcük alanında tam eşlemedir: çocuk sorgusu metin alanında 93, anahtar sözcük alanında 0 belge döndürür; TR öneki 154’e karşı 0 belge verir.
  • Çok değerli alanda metin tipi çapraz eşleşme üretir: derlemde bulunmayan çocuk tarihi etiketi 9 belge döndürür, çünkü sözcükler iki ayrı etiketten gelir.
  • Sıralama metin alanında tanımsızdır: 600 belgenin tamamı birden çok terim taşır, seçilen kural 9 anahtar bırakır ve ilk on belgenin onu da yer değiştirir. Toplama 12 kova yerine 17 kova üretir ve çocuk edebiyatı iki ayrı kovaya bölünür.

Sonraki Adım

Bu dersteki eşleme elle yazıldı: hangi alanın hangi tiple dizinleneceği önceden bilinmişti. Gerçek katalog aktarımında bu bilgi çoğu zaman yoktur — kayıtlar dışarıdan gelir, her kaynak kendi alanlarını taşır ve tip kararını dizin kendisi verir. Sonraki ders bu kararı ölçüyor: belge çeşitliliği arttıkça alan sayısının nasıl büyüdüğü, aynı adın iki farklı tiple gelmesi durumunda ne olduğu ve her alanın dizin üstverisinde kaç bayta mal olduğu.

İ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