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:
konualanında metin tipi 11.686 bayta karşı 8.203 bayt ile pahalıdır,rafalanı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:
çocuksorgusu 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 tarihietiketi 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.