Ders 01 / 23
Arama Motoru ve Veritabanı Farkı
Metnin içinden belgeye gitmenin ayrı bir yapı istemesi: anahtarı bilinen erişimle sözcüğü bilinen erişimin ayrımı, aynı katalog sorusunun ilişkisel LIKE taramasıyla ve terimden belgeye eşleyen ters dizinle yanıtlanması, iki yolun taradığı kayıt ile giriş sayısının, döndürdüğü belge kümesinin ve kaçırdığı belgelerin sayılması, büyük küçük harf ile çekim ekinin küme üzerindeki etkisi.
İçindekiler
Bellek İçi Depolar kursu, ele aldığı bütün erişimlerin ortak bir varsayımıyla kapandı: aranan şeyin anahtarı biliniyordu. Bir oturum kimliği, bir sayaç adı, bir sıralı kümenin adı verildi; depo o adı bulup değeri getirdi. İlişkisel tarafta da durum benzerdi — dizin bir anahtar üzerinde kuruldu, sorgu o anahtara eşitlik, aralık ya da önek koşuluyla gitti.
Kütüphane katalogunda sorular bu biçimde gelmiyor. Okur kitabın kimliğini değil, özetinde geçen iki sözcüğü hatırlıyor: “özetinde deniz ve çocuk geçen kitaplar”. Bu soru bir anahtara değil, metnin içine gidiyor. Bu kurs o soruyu kuruyor ve her dersinde aynı iki şeyi sayıyor: bir karar hangi belgelerin döndüğünü nasıl değiştirdi, hangi sırayla döndüklerini nasıl değiştirdi ve bunun bedeli nedir. “Daha alakalı sonuç” bu kursta bir karar sayılmaz.
Anahtardan Değil, Sözcükten Gitmek
İlişkisel dizin bir değerin tamamı üzerinde sıralanmış bir yapıdır. Eşitlik, aralık ve önek
sorularını bu sıralamayla yanıtlar; aranan parça değerin başında olmadığında sıralamanın söyleyeceği
bir şey kalmaz. LIKE '%deniz%' tam olarak bu durumdur: eşleşme metnin herhangi bir yerinde
olabileceği için dizin atlanır ve her kayıt okunur. Dizin kuramı M17/K04’te kurulmuştu; burada
önemli olan tek nokta, o dizinin bu soruya yardım edememesidir.
Ters dizin bunun tersini yapar. Belgeden alanlarına değil, terimden belgelere gider: metin sözcüklere ayrılır, her sözcük için o sözcüğün geçtiği belgelerin kimlikleri saklanır. Soru geldiğinde tablo değil, iki terimin listesi okunur. Yapının adı ve bayt hesabı sonraki derste kuruluyor; bu ders yalnız iki yolun aynı soruya verdiği yanıtı karşılaştırıyor.
Aynı Soru İki Yolda
Ölçüm için kütüphane katalogundan üretilmiş bir derlem kullanılıyor ve bu derlem kursun üç
konusunda da aynı kalıyor. DC1: derlem 600 kitap kaydı taşır, tohum 20250317’dir; her kayıtta
ad, özet, konu etiketleri, yazar, yıl, dil ve raf kodu bulunur; özet ortalama 25 sözcüktür ve
sözcük havuzu sınırlıdır — bu, terim sayısını gerçek bir katalogtan küçük tutar, oranları
değiştirmez. DC2: ilişkisel yolda taranan kayıt sayısı tablonun satır sayısıdır; sorgu planının
SCAN satırı bunu doğrular, okunan bayt özet alanının toplam baytıdır. DC3: dizin girişi
4 baytlık bir belge kimliği sayılır. DC4: çekim ekli biçimlerin listesi elle verilmiştir;
sınıflandırma bu on biçimlik tabloya dayanır.
// 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/tarama.mjs — ayni soru iki yapida: iliskisel LIKE taramasi ile terimden belgeye // esleyen ters dizin. Taranan birim, donen kume, fazladan gelen ve kacirilan belge sayilir. import { DatabaseSync } from "node:sqlite"; import { derlem, BELGE, TOHUM } from "./katalog.mjs"; const db = new DatabaseSync(":memory:"); db.exec("CREATE TABLE kitap(id INTEGER PRIMARY KEY, ad TEXT, ozet TEXT)"); const ekle = db.prepare("INSERT INTO kitap VALUES (?, ?, ?)"); for (const b of derlem) ekle.run(b.id, b.ad, b.ozet); db.exec("CREATE INDEX kitap_ozet ON kitap(ozet)"); // B-agaci dizini: ozet alani uzerinde const plan = db.prepare("EXPLAIN QUERY PLAN SELECT id FROM kitap WHERE ozet LIKE ?").all("%deniz%"); const like = (...s) => db.prepare(`SELECT id FROM kitap WHERE ${s.map(() => "ozet LIKE ?").join(" AND ")}`) .all(...s.map((x) => `%${x}%`)).map((r) => r.id); // --- ters dizin: belirtecleme ve Turkce kucuk harf; kok bulma bu derste yok --- const belirtecle = (s) => s.toLocaleLowerCase("tr").split(/[^\p{L}\p{N}]+/u).filter(Boolean); const dizin = new Map(); for (const b of derlem) { for (const t of new Set(belirtecle(b.ozet))) { if (!dizin.has(t)) dizin.set(t, []); dizin.get(t).push(b.id); } } const ara = (...terim) => { const liste = terim.map((t) => dizin.get(t) ?? []); const en = liste.reduce((a, l) => (l.length < a.length ? l : a)); return { kume: en.filter((id) => liste.every((l) => l.includes(id))), giris: liste.reduce((t, l) => t + l.length, 0) }; }; const bayt = derlem.reduce((t, b) => t + Buffer.byteLength(b.ozet), 0); console.log(`tohum ${TOHUM}; derlem ${BELGE} belge, ozet alani ${bayt} bayt, dizinde ${dizin.size} terim`); console.log(`sorgu plani: ${plan[0].detail} — B-agaci dizini varken de her kayit okunuyor`); const soru = ["deniz", "çocuk"]; const l1 = like(...soru), d1 = ara(...soru); const CEKIM = new Set(["denizin", "denizler", "denize", "denizden", "denizde", "çocuğun", "çocuklar", "çocuğa", "çocuktan", "çocukta"]); // DC4: bilinen cekim tablosu const fazla = l1.filter((id) => !d1.kume.includes(id)); const cekimli = fazla.filter((id) => belirtecle(derlem[id - 1].ozet).some((t) => CEKIM.has(t))); console.log(`\nsoru: ozetinde "${soru.join('" ve "')}" gecen kitaplar`); console.log(`${"yol".padEnd(16)}${"taranan birim".padStart(15)}${"okunan bayt".padStart(13)}` + `${"donen belge".padStart(13)}${"ilk dort kimlik".padStart(20)}`); console.log("iliskisel LIKE".padEnd(16) + `${BELGE} kayit`.padStart(15) + String(bayt).padStart(13) + String(l1.length).padStart(13) + l1.slice(0, 4).join(",").padStart(20)); console.log("ters dizin".padEnd(16) + `${d1.giris} giris`.padStart(15) + String(d1.giris * 4).padStart(13) + String(d1.kume.length).padStart(13) + d1.kume.slice(0, 4).join(",").padStart(20)); console.log(`\nLIKE'in fazladan getirdigi ${fazla.length} belge iki turdur:`); const ornek = (kosul) => fazla.find((id) => kosul(belirtecle(derlem[id - 1].ozet))); for (const [ad, id] of [["cekim ekli bicim (dizinin kacirdigi)", ornek((t) => t.some((x) => CEKIM.has(x)))], ["govde ici baska sozcuk", ornek((t) => !t.some((x) => CEKIM.has(x)))]]) { const gecen = belirtecle(derlem[id - 1].ozet).filter((t) => t.includes("deniz") || t.includes("çocuk")); console.log(` ${ad}: #${id} -> ${[...new Set(gecen)].join(", ")}`); } console.log(` ${cekimli.length} belge birinci turden, ${fazla.length - cekimli.length} belge ikinci turden`); for (const y of ["istanbul", "İstanbul"]) { const l = like(y), d = ara(y.toLocaleLowerCase("tr")); console.log(`\nsoru "${y}": LIKE ${l.length} belge (${BELGE} kayit tarandi), ` + `ters dizin ${d.kume.length} belge (${d.giris} giris tarandi)`); }
tohum 20250317; derlem 600 belge, ozet alani 124863 bayt, dizinde 170 terim sorgu plani: SCAN kitap USING COVERING INDEX kitap_ozet — B-agaci dizini varken de her kayit okunuyor soru: ozetinde "deniz" ve "çocuk" gecen kitaplar yol taranan birim okunan bayt donen belge ilk dort kimlik iliskisel LIKE 600 kayit 124863 68 237,333,364,490 ters dizin 269 giris 1076 12 8,77,107,118 LIKE'in fazladan getirdigi 59 belge iki turdur: cekim ekli bicim (dizinin kacirdigi): #237 -> denizler, çocuklar govde ici baska sozcuk: #14 -> deniz, çocukluk 43 belge birinci turden, 16 belge ikinci turden soru "istanbul": LIKE 0 belge (600 kayit tarandi), ters dizin 73 belge (73 giris tarandi) soru "İstanbul": LIKE 73 belge (600 kayit tarandi), ters dizin 73 belge (73 giris tarandi)
Küme Farkı Nereden Geliyor
İki yol aynı soruya farklı yanıt veriyor: LIKE 68 belge, ters dizin 12 belge. Aradaki 59 belge tek bir nedene indirgenemez, iki ayrı kaynağı vardır ve çıktı ikisini ayrı sayıyor.
Birinci kaynak çekim ekidir ve 43 belgeyi kapsar. #237 numaralı kayıtta özet “denizler” ve
“çocuklar” biçimlerini taşıyor. Okurun sorusu bakımından bu belge ilgilidir; ters dizin onu
kaçırıyor, çünkü bu dersteki dizin sözcüğü olduğu gibi saklıyor ve denizler ile deniz iki ayrı
terim oluyor. Bu, dizinin bir eksiği değil bir ayarıdır — sözcüğün hangi biçimde saklanacağı
üçüncü derste ayrı bir kararla ele alınıyor.
İkinci kaynak gövde içi eşleşmedir ve 16 belgeyi kapsar. #14 numaralı kayıtta “çocuk” geçiyor
ama ikinci koşulu karşılayan şey “çocukluk” sözcüğüdür; LIKE '%çocuk%' metnin ortasındaki harf
dizisine bakar, sözcük sınırı tanımaz. Aynı kaynaktan “denizci”, “Karadeniz” ve “kitapçı” gibi
sözcükler de eşleşir. Bu belgeler soruya yanlış getirilmiştir ve okuru yanıltır.
Son iki satır kümenin öbür ucunu gösteriyor. İstanbul sorusu LIKE ile 73 belge döndürüyor, aynı
sorunun küçük harfle yazılmış hâli sıfır belge döndürüyor. Neden Türkçeye özgüdür: İ harfinin
küçüğü i değil, ASCII dışı bir harftir ve harf katlaması bu çifti tanımaz. Ters dizin iki yazımda
da 73 belge veriyor, çünkü hem dizinlenirken hem sorulurken metin aynı kurala sokuluyor. Arama
motorunun ilişkisel taramadan asıl ayrıldığı yer burasıdır: yazma ve sorgu zamanında aynı
dönüşümün uygulanması.
Sıra ve Bedel
Sıra sütunu üçüncü bir farkı görünür kılıyor. LIKE 237, 333, 364, 490 kimlikleriyle başlıyor;
bu sıra kimlik sırası değildir, sorgu planının seçtiği kapsayan dizinin kendi sırasıdır. Ters dizin
8, 77, 107, 118 ile başlıyor, yani belge kimliği sırasıyla. İki sıranın ortak yanı şudur:
hiçbiri seçilmiş bir sıra değildir. İkisi de yapının depolama düzeninden düşmüştür. Hangi
belgenin daha ilgili olduğu bu kursta ilk kez ikinci konuda soruluyor.
Bedel tarafı da simetrik değildir. LIKE hiçbir hazırlık istemez: kayıt yazıldığı anda aranabilir, ama her soru 600 kaydı ve 124.863 baytı okur. Ters dizin soruyu 269 giriş okuyarak, yaklaşık 1.076 bayt dokunarak yanıtlar — okunan bayt bakımından yüz on altı katlık bir fark. Karşılığında dizinin kurulması ve saklanması gerekir; her yeni belge dizine işlenmelidir ve bu yapı yer kaplar. Bu dersin ölçtüğü şey yalnız sorgu tarafıdır; yazma tarafının hesabı sonraki derste bayt bayt çıkarılıyor.
Özet
- İlişkisel dizin değerin tamamı üzerinde sıralıdır ve
LIKE '%söz%'sorusuna yardım edemez; sorgu planı dizin varken bileSCANgösterir ve 600 kaydın tamamı okunur. - Ters dizin terimden belgeye gider: aynı soru 269 giriş okunarak yanıtlanır, dokunulan bayt 124.863’ten 1.076’ya iner.
- İki yolun kümesi aynı değildir: LIKE 68, ters dizin 12 belge döndürür. Aradaki 59 belgenin 43’ü çekim ekli biçimlerdir (dizinin kaçırdığı), 16’sı gövde içi harf eşleşmesidir (LIKE’ın yanlış getirdiği).
- Büyük küçük harf farkı Türkçede kümeyi tamamen çevirir:
istanbulsorusu LIKE ile 0,İstanbulsorusu 73 belge döndürür; ters dizin metni yazarken ve sorarken aynı kurala soktuğu için iki yazımda da 73 belge verir. - İki yolun döndürdüğü sıra da seçilmiş bir sıra değildir: biri sorgu planının kullandığı dizinin düzeni, öbürü belge kimliği. İlgililik sorusu bu konuda hiç sorulmuyor.
Sonraki Adım
Bu ders ters dizini bir kara kutu olarak kullandı: terim verildi, belge kimlikleri geldi. Yapının içinde ne olduğu, kaç bayt tuttuğu ve bir belgenin aynı terimi kaç kez içerdiğinin nerede saklandığı sorulmadı. Sonraki ders dizini adım adım kuruyor: terim listesi, her terimin altındaki belge listesi, belge başına terim sayısı ve sözcüğün metindeki konumu. Ölçülen şey bu dört bileşenin her birinin bayt karşılığı ve konum bilgisinin dizini kaç kat büyüttüğü olacak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.