---
title: 'Arama Motorları ve Metin Erişimi'
source: 'https://academia.sh/tr/kurslar/arama-motorlari'
language: tr
updated: '2026-08-17T04:40:27+00:00'
license: 'CC BY-SA 4.0'
lessons: 23
outcomes:
    - 'Ters dizin ve çözümleyici zincirinin arama sonucuna etkisini açıklar'
    - 'Sorgu dili ile ilgililik ve süzme davranışını ayarlar'
    - 'Küme, parça ve dizin yaşam döngüsü kararlarını verir'
---

# Arama Motorları ve Metin Erişimi

Anahtarı bilinmeyen veriye metnin içinden gitmek: ters dizinin terimden belgeye eşlemesi, çözümleyici zincirinin belirteçleme, normalleştirme ve süzgeçlerle sonucu belirlemesi, eşleme ile alan tiplerinin metin ve anahtar sözcük ayrımı, sorgu dilinin yaprak ve bileşik yapısı, terim ile ifade sorguları, bağlam koşulları, süzme ve sorgu ayrımı, terim sıklığı temelli ilgililik puanlaması ve ayarlanması, toplama sorguları, vurgulama ile derin sayfalama, anlamsal arama ve düğüm rolleri, parça ile kopya ayarı, bölüt birleştirme, dizin yaşam döngüsü, anlık görüntü ve erişim denetimi.

## Dizin ve Çözümleme

Metnin aranabilir hâle gelmesi: arama motorunun ilişkisel veritabanından hangi soruda ayrıldığı, terimden belgeye eşleyen ters dizinin kuruluşu ve maliyeti, belirteçleme, normalleştirme ile süzgeçlerden oluşan çözümleyici zincirinin sonucu belirlemesi, metin ile anahtar sözcük alan tiplerinin ayrımı, dinamik eşlemenin şema patlaması riski ve belgenin eklenme, güncellenme ile silinme mekaniği.

### Arama Motoru ve Veritabanı Farkı

Source: https://academia.sh/tr/kurslar/arama-motorlari/arama-motoru-ve-veritabani-farki

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.

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.

```js
// 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)}`,
  });
}
```

```js
// 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, ortakları 9.
LIKE'ın fazladan getirdiği 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 bile `SCAN` gö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, ortakları 9'dur.
  LIKE'ın fazladan getirdiği 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: `istanbul` sorusu LIKE ile 0, `İstanbul`
  sorusu 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.

### Ters Dizin

Source: https://academia.sh/tr/kurslar/arama-motorlari/ters-dizin

Terimden belgeye eşleyen yapının kuruluşu ve bayt hesabı: sözlük ile gönderi listesinin ayrımı, gönderi girişinde belge kimliği, terim sıklığı ve konum bilgisinin ayrı ayrı fiyatlanması, dizinin kaynak metne oranının üç biçimde ölçülmesi, gönderi listelerinin belge kimliği sırasında tutulmasının kesişim maliyetine etkisi ve konum bilgisinin satın aldığı ardışıklık ayrımı.

Önceki ders ters dizini bir kara kutu olarak kullandı: terim verildi, belge kimlikleri geldi. Bu
ders kutuyu açıyor. Yapı iki parçadan oluşur. **Sözlük** derlemdeki her ayrı terimi bir kez tutar
ve her terimin karşısına o terimin listesini gösteren bir bağ koyar. **Gönderi listesi** ise o
terimin geçtiği belgelerin kaydıdır: her giriş bir belgeyi bildirir.

Bu kursta **gönderi**, bir terimin belirli bir belgede geçtiğini bildiren dizin kaydıdır. Sözcük
başka bir müfredatta yayımlanan bir iletiyi de adlandırıyor; buradaki kullanımla ilgisi yoktur.
Bir gönderi girişi en az belge kimliğini taşır, isteğe bağlı olarak terimin o belgede **kaç kez**
geçtiğini (terim sıklığı) ve **nerelerde** geçtiğini (konum) da taşıyabilir. Bu üç bileşenin
üçünün de bir fiyatı vardır ve ders bu fiyatı sayıyor.

#### Sözlük, Gönderi ve Konum

Sıralı bir dizin değerin tamamı üzerinde kuruludur ve bir kayıttan bir değere gider; ters dizin
bunun tersine bir terimden bir belge kümesine gider, bu yüzden aynı belge onlarca gönderi
listesinde birden görünür. Fark tek cümlede budur ve dizin boyutunu belirleyen de budur: dizinin
büyüklüğü belge sayısıyla değil, **belge başına düşen ayrı terim sayısıyla** ölçeklenir.

**DC1:** derlem önceki dersteki derlemdir — 600 kitap kaydı, tohum 20250317. **DC2:** bayt hesabı
şu sabitlerle yapılır — sözlük girişi terimin UTF-8 baytı artı 8 bayt (belge sıklığı ve liste
göstergesi), gönderi girişi 4 bayt belge kimliği, terim sıklığı tutuluyorsa 4 bayt daha, her konum
4 bayt. **DC3:** dizinlenen metin `ad` ve `ozet` alanlarının birleşimidir; önceki ders yalnız
`ozet` alanını dizinlediği için terim sayısı orada farklıdır.

```js
// 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)}`,
  });
}
```

```js
// 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 };
  }
}
```

```js
// arama/dizin-olcum.mjs — ayni derlem uc dizin bicimiyle kurulur: yalniz kimlik, kimlik+siklik,
// kimlik+siklik+konum. Terim, gonderi girisi, bayt, taranan giris ve karsilastirma sayilir.
import { derlem, BELGE, TOHUM } from "./katalog.mjs";
import { TersDizin } from "./dizin.mjs";

const metin = (b) => `${b.ad} ${b.ozet}`;                  // DC3: dizinlenen alanlar
const kaynak = derlem.reduce((t, b) => t + Buffer.byteLength(metin(b)), 0);
const kur = (ayar) => { const d = new TersDizin(ayar); for (const b of derlem) d.ekle(b.id, metin(b)); return d; };
const bicim = [["yalniz kimlik", { siklik: false, konum: false }],
  ["kimlik + siklik", { siklik: true, konum: false }], ["kimlik + siklik + konum", {}]];

console.log(`tohum ${TOHUM}; derlem ${BELGE} belge, dizinlenen metin ${kaynak} bayt`);
console.log(`${"dizin bicimi".padEnd(24)}${"terim".padStart(7)}${"gonderi".padStart(9)}${"konum".padStart(7)}` +
  `${"sozluk".padStart(8)}${"gonderi bayt".padStart(14)}${"konum bayt".padStart(12)}${"toplam".padStart(9)}${"metne oran".padStart(12)}`);
let tam;
for (const [ad, ayar] of bicim) {
  const d = kur(ayar), b = d.bayt();
  tam = d;
  console.log(ad.padEnd(24) + String(b.terim).padStart(7) + String(b.giris).padStart(9) + String(b.konum).padStart(7) +
    String(b.sozluk).padStart(8) + String(b.gonderi).padStart(14) + String(b.konumBayt).padStart(12) +
    String(b.toplam).padStart(9) + `%${(b.toplam * 100 / kaynak).toFixed(1)}`.padStart(12));
}

const ilk = tam.liste("deniz").slice(0, 3).map((g) => `(#${g.id}, tf ${g.tf}, konum ${g.konum.join("-")})`);
console.log(`\n"deniz" gonderi listesi ${tam.liste("deniz").length} giris; ilk ucu: ${ilk.join(" ")}`);
const tfDagilim = new Map();
for (const g of tam.liste("deniz")) tfDagilim.set(g.tf, (tfDagilim.get(g.tf) ?? 0) + 1);
console.log(`terim sikligi dagilimi: ${[...tfDagilim].sort().map(([t, n]) => `tf ${t} -> ${n} belge`).join(", ")}`);

// sirasiz liste ile karsilastirma: her giris icin obur listede dogrusal arama
const sirasiz = (...t) => {
  const l = t.map((x) => tam.liste(x)), en = l.reduce((a, x) => (x.length < a.length ? x : a));
  let k = 0;
  const kume = en.filter((g) => l.every((x) => { for (const y of x) { k += 1; if (y.id === g.id) return true; } return false; }));
  return { n: kume.length, k };
};
console.log(`\n${"sorgu".padEnd(20)}${"taranan giris".padStart(14)}${"kimlik sirali".padStart(14)}` +
  `${"sirasiz".padStart(9)}${"donen belge".padStart(12)}${"ilk uc kimlik".padStart(15)}`);
for (const s of [["deniz"], ["çocuk"], ["deniz", "çocuk"], ["deniz", "çocuk", "kitap"]]) {
  const r = tam.ara(...s), y = s.length > 1 ? String(sirasiz(...s).k) : "-";
  console.log(s.join("+").padEnd(20) + String(r.giris).padStart(14) + String(r.kars).padStart(14) +
    y.padStart(9) + String(r.kume.length).padStart(12) + r.kume.slice(0, 3).join(",").padStart(15));
}

// konum bilgisi ne satin aliyor: ayni belgede gecmek ile yan yana gecmek
const [a, b] = ["deniz", "günleri"];
const ikinci = new Map(tam.liste(b).map((g) => [g.id, g.konum]));
const ardisik = tam.liste(a).filter((g) => ikinci.has(g.id) &&
  g.konum.some((x) => ikinci.get(g.id).some((y) => y - x === 1))).map((g) => g.id);
console.log(`\n"${a} ${b}": ayni belgede ${tam.ara(a, b).kume.length}, ardisik ${ardisik.length} belge ` +
  `(${ardisik.slice(0, 3).map((i) => `#${i} ${derlem[i - 1].ad}`).join(", ")})`);
```

```
tohum 20250317; derlem 600 belge, dizinlenen metin 135824 bayt
dizin bicimi              terim  gonderi  konum  sozluk  gonderi bayt  konum bayt   toplam  metne oran
yalniz kimlik               185    14609      0    2850         58436           0    61286       %45.1
kimlik + siklik             185    14609      0    2850        116872           0   119722       %88.1
kimlik + siklik + konum     185    14609  16565    2850        116872       66260   185982      %136.9

"deniz" gonderi listesi 261 giris; ilk ucu: (#3, tf 1, konum 0) (#4, tf 1, konum 14) (#7, tf 1, konum 14)
terim sikligi dagilimi: tf 1 -> 206 belge, tf 2 -> 52 belge, tf 3 -> 3 belge

sorgu                taranan giris kimlik sirali  sirasiz donen belge  ilk uc kimlik
deniz                          261           261        -         261          3,4,7
çocuk                           84            84        -          84          5,6,8
deniz+çocuk                    345           470    19622          25        8,67,77
deniz+çocuk+kitap              452           630    22135           3    158,309,491

"deniz günleri": ayni belgede 23, ardisik 10 belge (#3 Deniz Günleri, #123 Büyük Deniz Günleri, #154 Beyaz Deniz Günleri)
```

#### Üç Biçim, Üç Fiyat

İlk tablo aynı 600 belgeyi üç kez dizinliyor ve tek değişen şey gönderi girişinin ne taşıdığı.
Sözlük üçünde de aynıdır: 185 terim, 2.850 bayt. Dizinin ağırlığı sözlükte değil, **14.609 gönderi
girişindedir**. Yalnız belge kimliği tutulduğunda dizin 61.286 bayt, kaynak metnin %45,1'i kadardır.
Terim sıklığı eklendiğinde gönderi bölümü tam iki katına çıkar ve dizin 119.722 bayta, metnin
%88,1'ine ulaşır. Konum eklendiğinde 185.982 bayta, yani **metnin kendisinden büyüğe** çıkar.

Bu son satır dersin en somut sonucudur: konum tutan bir ters dizin, dizinlediği metinden daha çok
yer kaplayabilir. Nedeni ikinci ve üçüncü sütunda görünüyor — 14.609 gönderi girişine karşılık
16.565 konum kaydı var, çünkü bir terim aynı belgede birden çok kez geçebiliyor. `deniz` teriminin
sıklık dağılımı bunu açıkça sayıyor: 206 belgede bir kez, 52 belgede iki kez, 3 belgede üç kez.

#### Sıra Bir Karardır

Gönderi listeleri belge kimliği sırasında tutuluyor ve bu bir tercih değil, kesişimin çalışma
biçimini belirleyen bir karardır. `deniz+çocuk` sorgusu 261 ve 84 girişlik iki listeyi okuyor,
toplam 345 giriş. İki liste de sıralı olduğu için kesişim tek geçişte yapılıyor ve **470
karşılaştırma** yetiyor. Aynı kesişim listeler sırasız olsaydı, kısa listenin her girişi için uzun
listede baştan arama gerekirdi: **19.622 karşılaştırma**, kırk kat fazla. Üç terimli sorguda oran
630'a karşı 22.135 olur.

Sıranın ikinci sonucu dönen kümededir. `deniz` tek başına 261 belge, `çocuk` 84 belge döndürüyor;
ikisinin kesişimi 25 belgeye, üçüncü terim eklendiğinde 3 belgeye iniyor. Her koşul kümeyi
daraltıyor ve daraltmanın bedeli okunan giriş sayısının artmasıdır: 345'ten 452'ye. Dönen belgeler
`8, 67, 77` sırasıyla geliyor; bu sıra yine belge kimliğidir, yani belgelerin dizine giriş
sırasıdır. Kimin daha ilgili olduğu hâlâ sorulmuyor.

#### Konumun Satın Aldığı

Konum bilgisi dizinin en pahalı bileşenidir — tek başına 66.260 bayt, dizinin %35,6'sı. Karşılığında
tek bir şey satın alır: iki terimin belgede **yan yana** geçip geçmediğini bilmek. Son satır bunu
sayıyor. `deniz` ve `günleri` terimleri 23 belgede birlikte geçiyor; bu belgelerin yalnız 10'unda
iki terim ardışıktır ve o 10 belgenin adı gerçekten `Deniz Günleri` kalıbındadır. Konum tutmayan
bir dizin bu iki kümeyi ayırt edemez ve 23 belgenin tamamını döndürür.

Karar şu biçimde konur: 66.260 bayt karşılığında 13 belgelik bir daralma satın alınıyor. Katalog
aramasında bu daralmanın değerli olup olmadığı sorunun türüne bağlıdır — tam adı bilinmeyen bir
kitabı iki sözcüğüyle arayan okur için ardışıklık ayrımı doğrudan doğru sonucu verir. Bu ayrımı
kullanan sorgu biçimi ikinci konuda kuruluyor; bu ders yalnız verinin dizinde durduğunu ve ne
tuttuğunu gösteriyor.

#### Özet

- Ters dizin iki parçadır: sözlük (185 terim, 2.850 bayt) ve gönderi listeleri (14.609 giriş).
  Ağırlık gönderi tarafındadır; sözlük dizinin %1,5'i kadardır.
- Gönderi girişinin içeriği fiyatı belirler: yalnız kimlik 61.286 bayt (metnin %45,1'i), terim
  sıklığı eklenince 119.722 bayt (%88,1), konum eklenince 185.982 bayt (%136,9) — dizin kaynak
  metinden büyük olur.
- 14.609 gönderi girişine 16.565 konum kaydı düşer; `deniz` terimi 206 belgede bir, 52 belgede
  iki, 3 belgede üç kez geçer.
- Gönderi listelerinin belge kimliği sırasında tutulması kesişimi tek geçişe indirir:
  `deniz+çocuk` 470 karşılaştırmayla, sırasız listede 19.622 karşılaştırmayla biter.
- Konum bilgisi 66.260 bayta mal olur ve `deniz günleri` sorusunda kümeyi 23 belgeden 10 belgeye
  indirme yeteneğini satın alır.

#### Sonraki Adım

Bu dersin dizini metni tek bir kuralla sözcüklere ayırdı: harf ve rakam dışındaki her karakterde
böl, Türkçe kurallarıyla küçük harfe indir. Bu kuralın kendisi hiç sorgulanmadı. Oysa `denizler`
ile `deniz` bu kural yüzünden iki ayrı terim ve ilk dersteki 43 belgelik kayıp buradan geliyordu.
Sonraki ders aynı derlemi üç ayrı çözümleyici zinciriyle dizinliyor — ham, küçük harfe indiren,
kök bulan ve durak sözcük atan — ve aynı sorguyu üçünde koşturuyor: dönen küme, sıralama ve dizin
boyutu üç yapılandırmada yan yana ölçülüyor.

### Çözümleyici Zinciri

Source: https://academia.sh/tr/kurslar/arama-motorlari/cozumleyici-zinciri

Metnin terime dönüşme kuralının kararı belirlemesi: belirteçleme, normalleştirme ve süzgeçlerden oluşan zincirin aynı derlem üzerinde üç yapılandırmayla kurulması, ham, küçük harfe indiren ve kök bulup durak sözcük atan zincirlerin dizin boyutu, dönen küme ve ilk beş sıra düzleminde karşılaştırılması, Türkçe harf katlaması, kesme işareti ve çekim ekinin yarattığı farkın sayılması.

Önceki dersin dizini metni tek bir kuralla sözcüklere ayırdı ve bu kural hiç sorgulanmadı. Oysa
`denizler` ile `deniz`in iki ayrı terim olması bir doğa yasası değil, o kuralın sonucudur. Metni
terime çeviren kural dizisine **çözümleyici zinciri** denir ve üç adımdan oluşur: **belirteçleme**
metni parçalara böler, **normalleştirme** parçaları tek bir yazıma indirger, **süzgeçler** kalan
belirteçleri değiştirir ya da atar.

Bu zincirin iki yerde birden çalışması esastır: belge dizinlenirken ve sorgu değerlendirilirken.
İkisi ayrı kural kullanırsa sorgu, dizinde bulunmayan bir terimi arar. Ders bu zinciri üç ayrı
yapılandırmada kuruyor ve aynı soruları üçünde de koşturuyor.

#### Zincirin Halkaları

**Belirteçleme** burada metnin arama birimlerine bölünmesidir; M06/K01'in `sözcük birimi` kavramı
bir dilbilgisi ayrıştırıcısına giren simgeyi adlandırır ve bölme ölçütü orada dilbilgisi
kuralıdır — buradaki ölçüt ise aramanın sözleşmesidir. **Normalleştirme** de aynı biçimde ayrı bir
şeydir: M17/K01'in ilişkisel normalleştirmesi verinin tekrarını tablolara bölerek giderirken,
buradaki normalleştirme aynı sözcüğün farklı yazımlarını tek terime indirir.

Üç yapılandırma karşılaştırılıyor. **Ham** zincir yalnız boşluktan böler; noktalama belirtece
yapışık kalır, büyük harf korunur. **Küçük harf** zinciri harf ve rakam dışındaki her karakterde
böler ve Türkçe yerel ayarıyla küçük harfe indirir — önceki iki dersin zinciri budur. **Kök +
durak** zinciri buna iki süzgeç ekler: derlemin %60'ından çok belgede geçen terimler atılır, kalan
belirteçlerden en uzun çekim eki bir kez çıkarılır.

**DC1:** derlem değişmedi — 600 kayıt, tohum 20250317, dizinlenen alanlar `ad` ve `ozet`. **DC2:**
bayt sabitleri önceki dersteki sabitlerdir ve üç zincirde de aynıdır. **DC3:** durak sözcük listesi
elle yazılmaz, ölçülür: belge sıklığı eşiği %60'tır. **DC4:** kök bulucu en uzun eki bir kez atar
ve gövdeyi üç harften kısa bırakmaz; ünsüz yumuşaması ve ünlü düşmesi bilinmez.

```js
// 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)}`,
  });
}
```

```js
// 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 };
  }
}
```

```js
// arama/cozumleyici.mjs — ayni derlem uc cozumleyici zinciriyle dizinlenir: ham, kucuk harf,
// kok bulan + durak sozcuk atan. Dizin boyutu, donen kume, ilk bes sira ve Turkce olculeri.
import { derlem, BELGE, TOHUM, KOK as GOVDE } from "./katalog.mjs";
import { TersDizin } from "./dizin.mjs";

const metin = (b) => `${b.ad} ${b.ozet}`;
const BOL = (s) => s.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
const HAM = (s) => s.split(/\s+/).filter(Boolean);            // bolme yok, donusum yok
const KUCUK = (s) => BOL(s.toLocaleLowerCase("tr"));
const EKLER = ["ları", "leri", "lerin", "ların", "nin", "nın", "nun", "nün", "dan", "den", "tan",
  "ten", "ler", "lar", "ya", "ye", "da", "de", "ta", "te", "in", "ın", "un", "ün", "a", "e"]
  .sort((x, y) => y.length - x.length);
const kokBul = (t) => {                                       // en uzun eki bir kez atar, govde >= 3
  for (const e of EKLER) if (t.length - e.length >= 3 && t.endsWith(e)) return t.slice(0, -e.length);
  return t;
};
// durak sozcuk listesi olculerek secilir: belgelerin %60'indan cogunda gecen terim
const on = new TersDizin({ coz: KUCUK, konum: false });
for (const b of derlem) on.ekle(b.id, metin(b));
const DURAK = new Set([...on.sozluk].filter(([, l]) => l.length > BELGE * 0.6).map(([t]) => t));
const KOKLU = (s) => KUCUK(s).filter((t) => !DURAK.has(t)).map(kokBul);

const zincir = [["ham", HAM], ["kucuk harf", KUCUK], ["kok + durak", KOKLU]];
const dizin = new Map(zincir.map(([ad, coz]) => {
  const d = new TersDizin({ coz });
  for (const b of derlem) d.ekle(b.id, metin(b));
  return [ad, d];
}));
const kaynak = derlem.reduce((t, b) => t + Buffer.byteLength(metin(b)), 0);

console.log(`tohum ${TOHUM}; derlem ${BELGE} belge, ${kaynak} bayt metin`);
console.log(`durak sozcuk (df > %60): ${[...DURAK].join(", ")}`);
console.log(`\n${"cozumleyici".padEnd(14)}${"terim".padStart(7)}${"gonderi".padStart(9)}${"konum".padStart(7)}` +
  `${"dizin bayt".padStart(12)}${"metne oran".padStart(12)}`);
for (const [ad, d] of dizin) {
  const b = d.bayt();
  console.log(ad.padEnd(14) + String(b.terim).padStart(7) + String(b.giris).padStart(9) +
    String(b.konum).padStart(7) + String(b.toplam).padStart(12) + `%${(b.toplam * 100 / kaynak).toFixed(1)}`.padStart(12));
}

console.log(`\n${"sorgu".padEnd(11)}${"cozumleyici".padEnd(13)}${"sorgu terimi".padEnd(15)}` +
  `${"donen belge".padStart(12)}${"ilk bes kimlik".padStart(22)}${"ilk beste degisen".padStart(19)}`);
for (const soru of ["Denizler", "kitap", "İstanbul"]) {
  const satir = zincir.map(([ad, coz]) => {
    const terim = coz(soru), r = terim.length ? dizin.get(ad).ara(...terim) : { kume: [] };
    return [ad, terim.join("+") || "-", r.kume];
  });
  const olcut = satir.find(([ad]) => ad === "kucuk harf")[2].slice(0, 5);
  for (const [ad, terim, kume] of satir) {
    const bes = kume.slice(0, 5);
    const degisen = ad === "kucuk harf" ? "-"
      : String(olcut.filter((x, i) => bes[i] !== x).length + Math.max(0, bes.length - olcut.length));
    console.log(soru.padEnd(11) + ad.padEnd(13) + terim.padEnd(15) + String(kume.length).padStart(12) +
      (bes.join(",") || "-").padStart(22) + degisen.padStart(19));
  }
}

// --- Turkce olcusu 1: harf katlamasi (Turkce yerel ayar ile genel kural) ---
const genel = new TersDizin({ coz: (s) => BOL(s.toLowerCase()), konum: false });
for (const b of derlem) genel.ekle(b.id, metin(b));
const sapan = [...genel.sozluk.keys()].filter((t) => !on.sozluk.has(t));
console.log(`\nharf katlamasi: Turkce kural ${on.sozluk.size} terim, genel kural ${genel.sozluk.size} ` +
  `terim; ayrisan terim ${JSON.stringify(sapan)}`);
console.log(`  "istanbul" sorgusu: Turkce kuralla ${on.ara("istanbul").kume.length} belge, ` +
  `genel kuralla ${genel.ara("istanbul").kume.length} belge`);
console.log(`  kesme isareti: "İstanbul'un" -> ${JSON.stringify(KUCUK("İstanbul'un"))}`);

// --- Turkce olcusu 2: kok bulucu 12 govde x 6 bicim uzerinde ---
let tek = 0; const bolunen = [];
for (const satir of GOVDE) {
  const k = new Set(satir.map(kokBul));
  if (k.size === 1) tek += 1; else bolunen.push(`${satir[0]}: ${[...k].join("/")}`);
}
console.log(`\nkok bulma: ${GOVDE.length} govde x 6 bicim; ${tek} govde tek terime indi, ` +
  `${bolunen.length} govde bolundu -> ${bolunen.join(", ")}`);
const kokKume = new Map();
for (const t of on.sozluk.keys()) (kokKume.get(kokBul(t)) ?? kokKume.set(kokBul(t), []).get(kokBul(t))).push(t);
const birlesen = [...kokKume].filter(([, l]) => l.length > 1);
console.log(`${on.sozluk.size} terim ${kokKume.size} koke indi; ${birlesen.length} kok birden cok terim topladi`);
console.log(`ornek: ${birlesen.slice(0, 3).map(([k, l]) => `${k} <- ${l.join(", ")}`).join(" | ")}`);
```

```
tohum 20250317; derlem 600 belge, 135824 bayt metin
durak sozcuk (df > %60): ve, gündelik, hayat, üzerine, notlar, sunar

cozumleyici     terim  gonderi  konum  dizin bayt  metne oran
ham               261    14727  16489      187780      %138.3
kucuk harf        185    14609  16565      185982      %136.9
kok + durak       122    11722  12627      146080      %107.6

sorgu      cozumleyici  sorgu terimi    donen belge        ilk bes kimlik  ilk beste degisen
Denizler   ham          Denizler                 80        15,16,18,20,22                  5
Denizler   kucuk harf   denizler                122         3,15,16,18,20                  -
Denizler   kok + durak  deniz                   463             2,3,4,7,8                  5
kitap      ham          kitap                    54        13,44,53,80,92                  5
kitap      kucuk harf   kitap                   107         7,10,13,28,30                  -
kitap      kok + durak  kitap                   178            2,5,6,7,10                  5
İstanbul   ham          İstanbul                  0                     -                  5
İstanbul   kucuk harf   istanbul                 73         2,21,30,32,35                  -
İstanbul   kok + durak  istanbul                 73         2,21,30,32,35                  0

harf katlamasi: Turkce kural 185 terim, genel kural 186 terim; ayrisan terim ["i","stanbul","zmir"]
  "istanbul" sorgusu: Turkce kuralla 73 belge, genel kuralla 0 belge
  kesme isareti: "İstanbul'un" -> ["istanbul","un"]

kok bulma: 12 govde x 6 bicim; 7 govde tek terime indi, 5 govde bolundu -> kitap: kitap/kitab, çocuk: çocuk/çocuğ, şehir: şehir/şehr, bahçe: bahç/bahçe, mektup: mektup/mektub
185 terim 128 koke indi; 18 kok birden cok terim topladi
ornek: gemi <- gemi, gemide, gemiye, gemiler, geminin, gemiden | öykü <- öyküleri, öyküler | mektup <- mektup, mektuptan, mektupta, mektuplar
```

#### Üç Zincir, Üç Küme

Birinci tablo zincirin dizine ne yaptığını gösteriyor. Ham zincir en çok terimi üretiyor: 261.
Bunun nedeni zenginlik değil, aynı sözcüğün noktalamayla ve büyük harfle birden çok kez sayılması.
Küçük harf zinciri terim sayısını 185'e indiriyor; kök ve durak süzgeçleri 122'ye. Gönderi girişi
14.727'den 11.722'ye, dizin 187.780 bayttan 146.080 bayta iniyor — süzgeçler dizinin %22'sini
siliyor.

İkinci tablo asıl kararı gösteriyor. `Denizler` sorusu ham zincirde 80, küçük harf zincirinde 122,
kök zincirinde 463 belge döndürüyor. Aynı derlem, aynı soru, **yaklaşık altı kat fark**. Ham zincir
yalnız büyük harfle başlayan `Denizler` belirtecini bulur; küçük harf zinciri yazımdan bağımsız
olarak `denizler` biçimini bulur; kök zinciri sorguyu `deniz` gövdesine indirdiği için altı çekimli
biçimin geçtiği bütün belgeleri getirir. İlk derste sayılan 43 belgelik kaybın kaynağı buydu;
kök zinciri kaybın `deniz` gövdesindeki kısmını kapatıyor, karşılığında küme dörde katlanıyor.

Sıra sütunu bunun sonucunu ayrıca sayıyor. Küçük harf zinciri ölçüt alındığında `Denizler` ve
`kitap` sorularında hem ham hem kök zincirinde **ilk beş kimliğin beşi de** değişiyor. Sıra burada
bir ilgililik sıralaması değil, gönderi listesinin kimlik düzenidir; yine de kullanıcının gördüğü
ilk sonuçlar zincir değiştiğinde tamamen başkalaşır. `İstanbul` sorusu bunun uç örneğidir: ham
zincir sıfır belge döndürür, çünkü metindeki belirteç `İstanbul'un` biçiminde ve kesme işaretiyle
birlikte tek parçadır.

#### Türkçenin Ölçüsü

Üçüncü bölüm Türkçenin üç somut zorluğunu sayıyor. **Harf katlaması:** aynı metin Türkçe yerel
ayarıyla 185, genel kuralla 186 terim üretiyor ve ayrışan terimler `["i", "stanbul", "zmir"]`.
Genel kural `İ` harfini `i` ile birleşik bir noktaya çevirdiği, bölücü de o noktayı harf saymadığı
için `İstanbul` sözcüğü `i` ve `stanbul` diye ikiye ayrılıyor. Sonuç ölçülü: `istanbul` sorusu
Türkçe kuralla 73 belge, genel kuralla **0 belge** döndürüyor.

**Kesme işareti:** `İstanbul'un` belirteci `["istanbul", "un"]` olarak ikiye bölünüyor. Bölme
sorguyu kurtarıyor, ama anlamı olmayan bir `un` terimi de üretiyor.

**Kök bulma:** ölçü 12 gövdenin 6'şar çekimli biçimi üzerinde alınıyor. Yedi gövde tek bir terime
iniyor, beş gövde bölünüyor: `kitap/kitab`, `çocuk/çocuğ`, `şehir/şehr`, `bahç/bahçe`,
`mektup/mektub`. Dördü ünsüz yumuşaması ve ünlü düşmesi, biri (`bahç/bahçe`) ise gövdenin
kendisinden ek sanılan bir harfin atılması yüzündendir. Ters yönde de bir maliyet var: 185 terim
128 köke inerken 18 kök birden çok terimi topluyor ve bunların hepsi doğru birleşme değildir. Bu
ders Türkçe kök bulmayı **çözmüyor**, kuralın nerede tuttuğunu ve nerede tutmadığını sayıyor;
sorgu `kitap` iken `kitabın` geçen belgeler hâlâ dışarıda kalıyor.

Durak sözcük süzgeci de ölçülerek kuruldu: altı terim belgelerin %60'ından çoğunda geçtiği için
atıldı. Listede bağlaç olmayan sözcüklerin bulunması, listenin elle değil sıklıkla belirlendiğini
gösterir.

#### Özet

- Çözümleyici zinciri belirteçleme, normalleştirme ve süzgeçlerden oluşur ve hem dizinleme hem
  sorgu tarafında aynı biçimde çalışmak zorundadır.
- Üç zincir aynı derlemde 261, 185 ve 122 terim üretir; dizin 187.780, 185.982 ve 146.080 bayt
  tutar — süzgeçler dizinin yaklaşık %22'sini siler.
- `Denizler` sorusu üç zincirde 80, 122 ve 463 belge döndürür; ölçüt zincire göre ilk beş kimliğin
  beşi de değişir. Zincir seçimi doğrudan kümeyi ve sırayı belirler.
- Türkçe harf katlaması kararı çevirir: `istanbul` sorusu Türkçe yerel ayarıyla 73, genel kuralla
  0 belge döndürür; kesme işareti `İstanbul'un` belirtecini ikiye böler.
- Kök bulucu 12 gövdenin 7'sini tek terime indirir, 5'ini böler — dördü ünsüz yumuşaması ve
  ünlü düşmesi, biri gövdeden ek sanılan bir harfin atılması yüzünden; 185 terim 128 köke
  inerken 18 kök birden çok terim toplar.

#### Sonraki Adım

Bu dersin üç zinciri de bütün metne aynı kuralı uyguladı. Oysa katalogda her alan metin değildir:
raf kodu `TR-879.4`, dil `Türkçe`, konu etiketi `çocuk edebiyatı` — bunlar sözcüklere bölünecek
metinler mi, yoksa tek parça kalması gereken değerler mi. Sonraki ders aynı alanı iki tiple
dizinliyor ve farkı ölçüyor: tam eşleme hangi tipte çalışıyor, sıralama hangisinde tanımlı,
toplama kaç kova üretiyor ve iki tipin dizin boyutu ne kadar ayrışıyor.

### Eşlemeler ve Alan Tipleri

Source: https://academia.sh/tr/kurslar/arama-motorlari/eslemeler-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ı.

Ö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.

```js
// 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)}`,
  });
}
```

```js
// 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 };
  }
}
```

```js
// 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.

### Dinamik ve Açık Eşleme

Source: https://academia.sh/tr/kurslar/arama-motorlari/dinamik-ve-acik-esleme

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.

Ö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.

```js
// 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)}`,
  });
}
```

```js
// 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 };
  }
}
```

```js
// 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
`1-8` 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.

### Belge Yaşam Döngüsü

Source: https://academia.sh/tr/kurslar/arama-motorlari/belge-yasam-dongusu

Belgenin dizine girmesi, değişmesi ve çıkması: güncellemenin silme ile ekleme olarak gerçekleşmesi, eski gönderi girişlerinin yerinde kalması, silme işaretinin iç kimlik başına tuttuğu yer, ölü girişlerin sorguda elenmesi ve taranan girişe eklenmesi, güncellenen belgenin sonuç sırasında sona taşınması ve temizlik turuna kadar dizinin ne kadar şiştiği.

Önceki iki ders belgeleri bir kez alıp bıraktı: kayıt geldi, eşleme kuruldu, dizin yazıldı.
Katalog ise durmuyor. Bir kitabın özeti düzeltiliyor, konu etiketi değiştiriliyor, kayıp bir kitap
kayıttan çıkarılıyor. Bu ders o üç işlemin dizinde ne yaptığını ölçüyor.

Ters dizinin yapısı bir kısıtlama getirir. Gönderi listeleri terim başına tutulur ve belge
kimliğine göre sıralıdır; bir belgenin metni değiştiğinde o belgenin girişleri **onlarca ayrı
listede** dağınık durur. Bu girişleri yerinde düzeltmek, her listeyi bulup içinden bir kaydı
çıkarmak demektir. Bunun yerine dizin daha ucuz olanı yapar: eski kaydı **ölü** işaretler, yeni
metni yeni bir kayıt olarak sona ekler. Güncelleme bu yüzden ayrı bir işlem değil, **silme ile
eklemedir**.

#### İç Kimlik ve Silme İşareti

Bu mekanizma iki kimlik gerektirir. **Dış kimlik** katalogun kitap numarasıdır ve değişmez. **İç
kimlik** belgenin dizine giriş sırasıdır ve her yazmada yenisi verilir. Gönderi listeleri iç
kimliği taşır; dış kimlik ile iç kimlik arasındaki eşleştirme ayrı tutulur. Bir belge güncellendiğinde
eşleştirme yeni iç kimliğe döner, eskisi **silme işaretine** girer.

**DC1:** derlem aynıdır — 600 kayıt, tohum 20250317; işlem dizisi 20250318 tohumundan üretilir.
**DC2:** bayt sabitleri önceki derslerdekiyle aynıdır ve konum tutulmuyor. **DC3:** silme işareti
iç kimlik başına 1 bit sayılır. **DC4:** her tur 60 güncelleme, 20 silme ve 20 ekleme uygular;
ölü giriş oranı %20'yi aştığında dizin canlı belgelerden yeniden yazılır. **DC5:** ölü girişler
sorgu sonucundan elenir, ama taranan girişe dahildir.

```js
// 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)}`,
  });
}
```

```js
// 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 };
  }
}
```

```js
// arama/yasam.mjs — belge yasam dongusu: ekleme, guncelleme (sil + ekle) ve silme. Olu gonderi
// girisi, silme isaretinin bayti, sorguda elenen giris ve temizlik turu sayilir.
import { derlem, BELGE, TOHUM } from "./katalog.mjs";
import { TersDizin, BASIT, KIMLIK, SIKLIK } from "./dizin.mjs";

let c = TOHUM + 1;
const rast = () => (c = (c * 1103515245 + 12345) % 2147483648) / 2147483648;

class Depo {                                  // ters dizinin uzerinde belge yasam dongusu
  #dizin = new TersDizin({ konum: false });
  #ic = new Map();                            // dis kimlik -> ic kimlik (dizindeki sira)
  #metin = new Map();
  #silinen = new Set();                       // silme isareti: olu ic kimlikler
  sayac = 0;
  ekle(dis, metin) {                          // guncelleme de budur: eski ic kimlik olu isaretlenir
    this.sayac += 1;
    if (this.#ic.has(dis)) this.#silinen.add(this.#ic.get(dis));
    this.#ic.set(dis, this.sayac); this.#metin.set(dis, metin);
    this.#dizin.ekle(this.sayac, metin);
  }
  sil(dis) { this.#silinen.add(this.#ic.get(dis)); this.#ic.delete(dis); this.#metin.delete(dis); }
  ara(terim) {                                // olu ic kimlikler sonucdan elenir
    const l = this.#dizin.liste(terim), geri = new Map([...this.#ic].map(([d, i]) => [i, d]));
    const canli = l.filter((g) => geri.has(g.id));
    return { kume: canli.map((g) => geri.get(g.id)), giris: l.length, elenen: l.length - canli.length };
  }
  sira(dis, terim) { const k = this.ara(terim).kume.indexOf(dis); return k < 0 ? "-" : `${k + 1}`; }
  olcu() {
    const b = this.#dizin.bayt();
    const olu = [...this.#dizin.sozluk.values()].flat().filter((g) => this.#silinen.has(g.id)).length;
    return { canli: this.#ic.size, terim: b.terim, giris: b.giris, olu, dizin: b.toplam,
      isaret: Math.ceil(this.sayac / 8), oran: Math.round(olu * 100 / b.giris) };
  }
  temizle() {                                 // dizin yalniz canli belgelerden yeniden yazilir
    const canli = [...this.#metin];
    this.#dizin = new TersDizin({ konum: false }); this.#ic = new Map(); this.#silinen = new Set();
    this.sayac = 0;
    for (const [d, m] of canli) this.ekle(d, m);
  }
}

const metin = (b) => `${b.ad} ${b.ozet}`;
const depo = new Depo();
for (const b of derlem) depo.ekle(b.id, metin(b));
const ilk = depo.olcu();
console.log(`tohum ${TOHUM}; ${BELGE} belge dizine alindi: ${ilk.terim} terim, ${ilk.giris} gonderi ` +
  `girisi, ${ilk.dizin} bayt, silme isareti ${ilk.isaret} bayt (ic kimlik basina 1 bit)`);

// --- tek belgede guncellemenin mekanigi ---
const hedef = derlem.find((b) => BASIT(metin(b)).includes("kuş"));
const df = (t) => depo.ara(t).kume.length;
const ortak = [...new Set(BASIT(metin(hedef)))].filter((t) => t !== "kuş").sort((a, b) => df(b) - df(a))[0];
console.log(`\n#${hedef.id} "${hedef.ad}": ozetteki "kuş" belirteci "martı" ile degistiriliyor`);
console.log(`  once : "kuş" ${df("kuş")} belge (bu belge ${depo.sira(hedef.id, "kuş")}. sirada), ` +
  `"martı" ${df("martı")} belge, "${ortak}" sorgusunda ${depo.sira(hedef.id, ortak)}. sirada`);
depo.ekle(hedef.id, BASIT(metin(hedef)).map((t) => (t === "kuş" ? "martı" : t)).join(" "));
console.log(`  sonra: "kuş" ${df("kuş")} belge (bu belge ${depo.sira(hedef.id, "kuş")}. sirada), ` +
  `"martı" ${df("martı")} belge, "${ortak}" sorgusunda ${depo.sira(hedef.id, ortak)}. sirada`);
const k = depo.ara("kuş"), o = depo.ara(ortak);
console.log(`  "kuş" gonderi listesi ${k.giris} giris tasiyor, ${k.elenen} tanesi elenerek atiliyor; ` +
  `"${ortak}" listesinde ${o.elenen} elenen giris var`);

// --- turlar: her tur 60 guncelleme, 20 silme, 20 ekleme; olu oran %20'yi asinca temizlik ---
console.log(`\n${"tur".padStart(4)}${"islem".padStart(7)}${"canli belge".padStart(13)}${"gonderi".padStart(9)}` +
  `${"olu giris".padStart(11)}${"olu %".padStart(7)}${"dizin bayt".padStart(12)}${"isaret".padStart(8)}${"temizlikten sonra".padStart(19)}`);
let sonraki = BELGE + 1, islem = 0;
for (let tur = 1; tur <= 6; tur += 1) {
  for (let i = 0; i < 60; i += 1) {
    const b = derlem[Math.floor(rast() * BELGE)];
    depo.ekle(b.id, `${metin(b)} düzeltme ${tur}`);
  }
  for (let i = 0; i < 20; i += 1) depo.sil(1 + Math.floor(rast() * BELGE));
  for (let i = 0; i < 20; i += 1) { const b = derlem[Math.floor(rast() * BELGE)]; depo.ekle(sonraki, metin(b)); sonraki += 1; }
  islem += 100;
  const s = depo.olcu(), temiz = s.oran > 20;
  if (temiz) depo.temizle();
  console.log(String(tur).padStart(4) + String(islem).padStart(7) + String(s.canli).padStart(13) +
    String(s.giris).padStart(9) + String(s.olu).padStart(11) + `%${s.oran}`.padStart(7) +
    String(s.dizin).padStart(12) + String(s.isaret).padStart(8) +
    (temiz ? `${depo.olcu().dizin} bayt` : "-").padStart(19));
}
const son = depo.olcu();
console.log(`\nson durum: ${son.canli} canli belge, ${son.giris} gonderi girisi, ${son.olu} olu giris, ` +
  `${son.dizin} bayt; baslangicta ${BELGE} belge ${ilk.dizin} bayt tutuyordu`);
```

```
tohum 20250317; 600 belge dizine alindi: 185 terim, 14609 gonderi girisi, 119722 bayt, silme isareti 75 bayt (ic kimlik basina 1 bit)

#10 "Denizin Günleri": ozetteki "kuş" belirteci "martı" ile degistiriliyor
  once : "kuş" 92 belge (bu belge 1. sirada), "martı" 0 belge, "üzerine" sorgusunda 7. sirada
  sonra: "kuş" 91 belge (bu belge -. sirada), "martı" 1 belge, "üzerine" sorgusunda 509. sirada
  "kuş" gonderi listesi 92 giris tasiyor, 1 tanesi elenerek atiliyor; "üzerine" listesinde 1 elenen giris var

 tur  islem  canli belge  gonderi  olu giris  olu %  dizin bayt  isaret  temizlikten sonra
   1    100          600    16679       1996    %12      136322      86                  -
   2    200          603    18729       3856    %21      152731      96        121883 bayt
   3    300          607    16961       1926    %11      138596      86                  -
   4    400          616    19029       3699    %19      155149      96                  -
   5    500          625    21052       5451    %26      171342     106        127720 bayt
   6    600          635    17733       1758    %10      144785      89                  -

son durum: 635 canli belge, 17733 gonderi girisi, 1758 olu giris, 144785 bayt; baslangicta 600 belge 119722 bayt tutuyordu
```

#### Tek Güncellemenin Bıraktığı İz

İkinci bölüm tek bir belgeyi izliyor. `#10` numaralı kaydın özetindeki `kuş` belirteci `martı`
ile değiştiriliyor ve üç sayı birden oynuyor. `kuş` sorgusu 92 belgeden 91 belgeye iniyor; bu
belge artık kümede yok. `martı` sorgusu 0 belgeden 1 belgeye çıkıyor. Buraya kadar beklenen
davranış budur.

Üçüncü sayı beklenmeyendir. Belgenin **değişmeyen** bir terimi olan `üzerine` sorgusunda kayıt
7. sıradan **509. sıraya** düşüyor. Metninde o terim bakımından hiçbir şey değişmediği hâlde
sıranın sonuna gitmesinin nedeni iç kimliktir: güncelleme yeni bir iç kimlik verir, yeni kimlik
en büyüktür ve gönderi listeleri kimlik sırasında tutulduğu için yeni giriş listenin sonuna
eklenir. Bir yazım hatasının düzeltilmesi, o belgenin bütün sonuç listelerindeki yerini değiştirir.

Son satır ölü girişin fiyatını gösteriyor. `kuş` gönderi listesi hâlâ **92 giriş** taşıyor;
sorgu bunların 92'sini de okuyor, 1 tanesini eleyip 91 belge döndürüyor. Silinen belge dizinden
gitmiyor, yalnız sonuçtan eleniyor. Silme işareti bunun karşılığında çok ucuzdur: 600 belgede
75 bayt, iç kimlik başına bir bit.

#### Turlar ve Temizlik

Üçüncü bölüm aynı deponun altı tur boyunca ne yaptığını izliyor. Her tur 60 güncelleme, 20 silme
ve 20 ekleme uyguluyor; yani turun 80 işlemi dizine ölü giriş bırakıyor. Birinci turun sonunda
dizin 16.679 girişin 1.996'sını (%12) ölü taşıyor ve 119.722 bayttan 136.322 bayta çıkmış oluyor.
Canlı belge sayısı hâlâ 600'dür — büyümenin tamamı israftır.

İkinci turda oran %21'e çıkıyor ve eşik aşıldığı için dizin canlı belgelerden yeniden yazılıyor:
152.731 bayt **121.883 bayta** iniyor, 30.848 bayt geri alınıyor. Aynı döngü beşinci turda
tekrarlanıyor; orada oran %26'ya, dizin 171.342 bayta kadar çıkmış oluyor ve temizlik onu 127.720
bayta indiriyor. Dördüncü turda oran %19'da kaldığı için temizlik yapılmıyor ve 3.699 ölü giriş
bir tur daha taşınıyor: eşik, israfın ne kadar biriktirileceğine dair bir karardır.

Altı turun sonunda depo 635 canlı belge tutuyor ve 144.785 bayt yer kaplıyor. Başlangıçtaki 600
belge 119.722 bayt tutuyordu; belge sayısı %6 artarken dizin %21 büyümüştür. Aradaki fark bir
sonraki temizliğe kadar taşınacak olan 1.758 ölü giriştir. Silme ve güncelleme dizinde ücretsiz
değildir: bedelleri anında değil, **turlar boyunca birikerek** ödenir.

#### Özet

- Ters dizinde güncelleme yerinde düzeltme değildir: eski iç kimlik ölü işaretlenir, yeni metin
  yeni bir iç kimlikle sona eklenir. Silme ise yalnız işaret koyar.
- Silme işareti ucuzdur — 600 belge için 75 bayt, iç kimlik başına bir bit — ama işaretlediği
  girişler dizinde durmaya devam eder: `kuş` sorgusu 92 girişi okuyup 91 belge döndürür.
- Güncelleme kümeyi ve sırayı birlikte değiştirir: `#10` numaralı belge `kuş` kümesinden çıkar,
  `martı` kümesine girer ve hiç değişmeyen `üzerine` sorgusunda 7. sıradan 509. sıraya düşer.
- Ölü girişler tur tur birikir: 100 işlemde dizin 119.722 bayttan 136.322 bayta çıkar ve
  girişlerin %12'si ölüdür; eşik aşıldığında yeniden yazma 152.731 baytı 121.883 bayta indirir.
- Altı turun sonunda belge sayısı %6 artarken dizin %21 büyümüştür; aradaki fark bir sonraki
  temizliğe kadar taşınan 1.758 ölü giriştir.

#### Sonraki Adım

Bu konu dizini baştan sona kurdu: metin terime çevrildi, terimden belgeye eşleyen yapı yazıldı,
alanların tipi kararlaştırıldı ve belge dizine girip çıkabilir duruma geldi. Sorgu tarafında ise
tek bir kalıp kullanıldı. Bu konuda sorulan her soru çıplak terimlerden oluştu ve terimler tek bir
kuralla — hepsinin birden bulunması koşuluyla — birleştirildi. Bir koşulun isteğe bağlı olması,
bir koşulun belgeyi dışlaması, iki sözcüğün yan yana aranması ya da bir yıl aralığının verilmesi
hiç sorulmadı. Sonucun hangi sırayla döneceği de hiç seçilmedi: sıra her seferinde gönderi
listesinin kendi düzeniydi ve bu dersin son ölçüsü o düzenin ne kadar rastlantısal olduğunu
gösterdi. Sonraki konu buradan başlıyor — koşulların nasıl birleştiği, sonucun hangi ölçüye göre
sıralandığı ve her iki kararın kümeyle sırayı nasıl değiştirdiği.

## Sorgulama ve İlgililik

Hangi belgelerin hangi sırayla döneceği: yaprak ve bileşik sorguların yapısı, tam eşleme ile sıra duyarlı ifade sorguları, zorunlu, olumsuz ve isteğe bağlı bağlam koşulları, puanlamaya giren sorgu ile girmeyen süzme ayrımı, terim sıklığı ve ters belge sıklığı temelli ilgililik puanlaması, alan ağırlığı ile işlev tabanlı puan ayarlama, ölçüt ile kova toplamaları ve sonuç sunumunda vurgulama ile derin sayfalama sorunu, gömme temelli anlamsal erişim ve karma yaklaşım.

### Sorgu Dili Yapısı

Source: https://academia.sh/tr/kurslar/arama-motorlari/sorgu-dili-yapisi

Aynı katalog sorusu düz metin ve yapılandırılmış biçimde sorulur: koşulların nasıl birleştiği söylenmediğinde dönen küme 4.211 ile 71 belge arasında değişir, bileşik sorgu bir ağaç olarak değerlendirilir ve ağacın değerlendirme sırası kümeyi değiştirmeden taranan gönderi girişini düşürür.

Önceki konu ters dizini kurdu ve belgenin dizine girip çıkmasını ele aldı. Oraya kadar
sorulan her soru tek bir terimdi: bir sözcük verildi, gönderi listesi okundu, belgeler
döndü. Kütüphane katalogunda sorulan sorular böyle değildir. "Çocuk edebiyatı üzerine
kısa öyküler" üç ayrı koşul taşır ve bu koşulların nasıl birleştiği söylenmemiştir:
üçü de zorunlu mudur, biri yeter mi, "kısa" sözcüğü kitabın adında mı özetinde mi
aranır? Daha sessiz ikinci soru şudur: eşleşen belgeler hangi sırayla dönecek?

Bu ders iki katmanı ayırır. **Yaprak sorgu** (leaf query) tek bir alanda tek bir terimi
arar ve karşılığı bir gönderi listesidir. **Bileşik sorgu** (compound query) yaprakları
bir ağaçta birleştirir; ağacın kök düğümü bir bağlaçtır, yaprakları terimlerdir. Aynı
katalog sorusu düz metin olarak da yazılabilir, ağaç olarak da — ikisi farklı sayıda
belge döndürür ve farklı sayıda gönderi girişi okur.

#### Derlem

**SI1 (varsayım):** derlem, kütüphane katalogundan üretilmiş 6.000 kitap kaydıdır; tohum
271828'dir ve üretim belirlenimlidir. Her kayıt bir ad, bir özet, iki etiket, bir yazar,
bir yıl ve bir dil taşır. `ad` ile `ozet` metin alanı, `etiket` anahtar sözcük alanıdır.
**SI2 (varsayım):** sorgu terimleri dizindeki biçimle yazılır; ekler ve büyük harf
çözümleyici zincirinin işidir, bu konuda çözümleyici sabittir.

```js
// derlem.mjs — kutuphane katalogu derlemi ve konum bilgili ters dizin.
// Tohum 271828, 6000 belge; konudaki butun olcumler bu modulu paylasir.
export const TOHUM = 271828, N = 6000;
let c = TOHUM;                                       // gorunur tohum, belirlenimli uretec
const r = () => { c = (c + 0x6d2b79f5) | 0; let t = Math.imul(c ^ (c >>> 15), 1 | c);
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };
const sec = (d, e = 1) => d[Math.floor(r() ** e * d.length)];   // e>1: bas ogeler sik, son ogeler seyrek

const SIFAT = ["eleştirel", "kısa", "resimli", "seçme", "karşılaştırmalı"];
const TUR = ["deneme", "öykü", "roman", "inceleme", "antoloji"];
const ALAN = ["çocuk", "tarih", "bilim", "felsefe", "toplum"];
const SON = ["seçkisi", "kitabı", "derlemesi", "dizisi"];
const NIT = ["kapsamlı", "özlü", "tanıtıcı", "tartışmalı"];
const KONU = ["eğitim", "göç", "kent", "bellek", "doğa", "müzik", "hukuk", "kimlik", "emek", "aile",
  "savaş", "gelenek", "sağlık", "yolculuk", "mimarlık", "arkeoloji", "denizcilik", "gökbilim"];
const YAZAR = ["Ahmet Yıldız", "Zeynep Kaya", "Kemal Demir", "Elif Şahin", "Necati Aydın"];
const DIL = ["türkçe", "ingilizce", "almanca"];

export const KATALOG = [];
for (let i = 1; i <= N; i += 1) {
  const s = sec(SIFAT, 1.5), t = sec(TUR, 1.5), a = sec(ALAN, 2), k = r();
  const desen = k < 0.18 ? `${s} ${t}` : k < 0.30 ? `${s} bir ${t}`
    : k < 0.40 ? `${s} ve kuramsal ${t}` : k < 0.50 ? `${t} ve ${s} inceleme`
    : k < 0.70 ? `${s} bir anlatı` : k < 0.88 ? `çağdaş bir ${t}` : "alan yazını taraması";
  KATALOG.push({ id: `K-${String(i).padStart(4, "0")}`, ad: `${a} ${t} ${sec(SON)}`,
    ozet: `${a} alanında ${desen}; ${sec(KONU, 3)} ve ${sec(KONU, 3)} üzerine ` +
      `${sec(NIT)} bir ${sec(TUR, 1.5)} olarak okunabilir.`,
    etiket: [a, t], yazar: sec(YAZAR), yil: 1990 + Math.floor(r() * 35), dil: sec(DIL, 2) });
}

export const belirtecle = (m) => m.toLocaleLowerCase("tr").split(/[^\p{L}\p{N}]+/u).filter(Boolean);

// Ters dizin: "alan|terim" -> Map(belge sirasi -> konumlar); uzunluk: alan basina belirtec sayisi.
export function tersDizin(alanlar) {
  const g = new Map(), uzunluk = new Map();
  KATALOG.forEach((b, i) => {
    for (const alan of alanlar) {
      const tk = belirtecle(String(b[alan]));
      uzunluk.set(`${alan}|${i}`, tk.length);
      tk.forEach((t, p) => {
        let l = g.get(`${alan}|${t}`); if (!l) g.set(`${alan}|${t}`, (l = new Map()));
        let k2 = l.get(i); if (!k2) l.set(i, (k2 = [])); k2.push(p);
      });
    }
  });
  return { g, uzunluk };
}
```

#### Ağaç ve Değerlendirme Sırası

Değerlendirici üç düğüm türü tanır: yaprak, `ve`, `veya`. Kesişim sıçramalı çalışır —
soldaki listenin her girişi için sağdaki listede üstel sıçrama ve ikili arama yapılır.
**SI3 (varsayım):** gönderi listesinin uzunluğu dizin sözlüğünden okunur ve girişleri
okumadan bilinir; **taranan giriş** yalnız kesişim ve birleşim yürüyüşünde sayılır. Bu,
ağacın çocuklarının hangi sırayla değerlendirileceğini gerçek bir karar yapar: kısa
listeyle başlamak, uzun listede yalnız birkaç sıçrama yapmak demektir.

```js
// sorgu.mjs — yaprak ve bilesik sorgu degerlendirici. Ayni dizinde derlem.mjs bulunur.
// Taranan giris: gonderi listelerinde okunan giris sayisi. Liste uzunlugu sozlukten bilinir.
import { KATALOG, TOHUM, tersDizin } from "./derlem.mjs";
const { g } = tersDizin(["ad", "ozet", "etiket"]);
const yaprak = (alan, terim) => [...(g.get(`${alan}|${terim}`)?.keys() ?? [])];

function atla(l, hedef, bas, s) {                    // ustel sicrama, sonra ikili arama
  let adim = 1, i = bas;
  while (i + adim < l.length && l[i + adim] < hedef) { adim *= 2; s.taranan += 1; }
  let alt = i, ust = Math.min(i + adim, l.length - 1);
  while (alt <= ust) { const o = (alt + ust) >> 1; s.taranan += 1;
    if (l[o] === hedef) return o; if (l[o] < hedef) alt = o + 1; else ust = o - 1; }
  return -alt - 1;
}
function kesis(a, b, s) {                            // a taranir, b'de sicranir
  const c = []; let j = 0;
  for (const d of a) {
    s.taranan += 1;
    const k = atla(b, d, j, s);
    if (k >= 0) { c.push(d); j = k + 1; } else j = -k - 1;
    if (j >= b.length) break;
  }
  return c;
}
function birles(listeler, s) {
  const k = new Set();
  for (const l of listeler) { s.taranan += l.length; for (const d of l) k.add(d); }
  return [...k].sort((x, y) => x - y);
}
export function calistir(dugum, s, eniyile = false) {
  if (dugum.terim) return yaprak(dugum.alan, dugum.terim);
  if (dugum.veya) return birles(dugum.veya.map((d) => calistir(d, s, eniyile)), s);
  const cocuk = dugum.ve.map((d) => calistir(d, s, eniyile));
  if (eniyile) cocuk.sort((a, b) => a.length - b.length);
  return cocuk.reduce((acc, l) => kesis(acc, l, s));
}
const olc = (ad, dugum, eniyile = false) => {
  const s = { taranan: 0 };
  const k = calistir(dugum, s, eniyile);
  console.log(`${ad.padEnd(38)} ${String(k.length).padStart(4)} belge   taranan giris ${String(s.taranan).padStart(6)}`);
  return k;
};

console.log(`derlem ${KATALOG.length} belge, tohum ${TOHUM}`);
const T = (alan, terim) => ({ alan, terim });
const ALANLAR = ["ad", "ozet", "etiket"];
const duz = (terim) => ({ veya: ALANLAR.map((a) => T(a, terim)) });
olc("duz metin, herhangi biri", { veya: ["çocuk", "kısa", "öykü"].map(duz) });
olc("duz metin, tumu zorunlu", { ve: ["çocuk", "kısa", "öykü"].map(duz) });
olc("yapilandirilmis", { ve: [T("etiket", "çocuk"), T("ozet", "kısa"), T("ad", "öykü")] });
const agac = { ve: [{ veya: [T("ad", "öykü"), T("ad", "roman")] }, T("ozet", "kısa"), T("etiket", "çocuk")] };
const a1 = olc("agac, yazilis sirasi", agac);
const a2 = olc("agac, kisa liste once", agac, true);
console.log("ayni kume mi:", JSON.stringify(a1) === JSON.stringify(a2));
for (const [a, t] of [["etiket", "çocuk"], ["ozet", "kısa"], ["ad", "öykü"], ["ad", "roman"]])
  console.log(`  gonderi listesi ${a}|${t}`.padEnd(34), String(yaprak(a, t).length).padStart(5));

// Sira: ayni kume iki bicimde siralanir. Kac kosulun karsilandigi sayilir.
const s2 = { taranan: 0 }, sayim = new Map();
for (const k of ["çocuk", "kısa", "öykü"].map(duz))
  for (const d of calistir(k, s2)) sayim.set(d, (sayim.get(d) ?? 0) + 1);
const dizinSira = [...sayim.keys()].sort((a, b) => a - b);
const kosulSira = [...dizinSira].sort((a, b) => sayim.get(b) - sayim.get(a) || a - b);
const ucu = (l) => l.slice(0, 10).filter((d) => sayim.get(d) === 3).length;
console.log(`ilk on sonucta uc kosulu da karsilayan: dizin sirasinda ${ucu(dizinSira)}, kosul sayisinda ${ucu(kosulSira)}`);
const tam = kosulSira.filter((d) => sayim.get(d) === 3);
const bas = tam.map((d) => dizinSira.indexOf(d) + 1);
console.log(`uc kosulu karsilayan ${tam.length} belge dizin sirasinda ortalama ` +
  `${Math.round(bas.reduce((x, y) => x + y, 0) / bas.length)}. basamakta, en gerisi ${Math.max(...bas)}.`);
```

```
derlem 6000 belge, tohum 271828
duz metin, herhangi biri               4211 belge   taranan giris  19102
duz metin, tumu zorunlu                 132 belge   taranan giris  21303
yapilandirilmis                          71 belge   taranan giris   7576
agac, yazilis sirasi                    138 belge   taranan giris   9501
agac, kisa liste once                   138 belge   taranan giris   7876
ayni kume mi: true
  gonderi listesi etiket|çocuk      2678
  gonderi listesi ozet|kısa          895
  gonderi listesi ad|öykü           1271
  gonderi listesi ad|roman          1007
ilk on sonucta uc kosulu da karsilayan: dizin sirasinda 1, kosul sayisinda 10
uc kosulu karsilayan 132 belge dizin sirasinda ortalama 2023. basamakta, en gerisi 4185.
```

#### Küme: Aynı Sorunun Üç Okunuşu

Üç satır aynı üç sözcükten gelir ve üç ayrı küme döndürür. "Herhangi biri" okuması 4.211
belge verir — derlemin %70,2'si. Bu okuma katalogda pratikte kullanışsızdır: sonuç
listesi kütüphanenin kendisi kadar büyüktür. "Tümü zorunlu" okuması 132 belgeye iner;
sözcüklerin hangi alanda geçtiği hâlâ serbesttir, "öykü" ister kitabın adında ister
özetinde bulunabilir. Yapılandırılmış okuma üç koşulu üç ayrı alana bağlar — çocuk
etiketli, özetinde "kısa" geçen, adında "öykü" geçen kitap — ve 71 belge döndürür.

Aradaki 61 belge kaybolmuş değildir; onlar "öykü" sözcüğünü yalnız özetinde taşıyan
kitaplardır, yani bir öykü kitabı değil, öyküden söz eden bir kitaptır. Hangi kümenin
doğru olduğu sorunun kendisine bağlıdır ve düz metin sorgu bunu söylemez. Düz metin bir
sorgu değildir; **bir sorgunun okunuş adayıdır** ve okumayı seçen taraf motordur. Bir
katalog arayüzü bu seçimi yapar, kullanıcı yapmaz — bu yüzden aynı üç sözcüğü yazan iki
kullanıcı iki farklı arayüzde 4.211 ile 71 arasında herhangi bir sayı görebilir.

#### Bedel: Ağacın Değerlendirme Sırası

Alt üç satır kümeyi sabit tutup bedeli ölçer. Bileşik ağaç şudur: adında "öykü" ya da
"roman" geçen, özetinde "kısa" geçen, çocuk etiketli kitaplar. Yazılış sırasında
değerlendirildiğinde önce iki uzun listenin birleşimi alınır (1.271 ve 1.007 giriş),
sonuç 2.678 girişlik etiket listesine kadar taşınır ve toplam 9.501 giriş okunur. Aynı
ağaçta çocuklar uzunluğa göre sıralandığında kesişim 895 girişlik en kısa listeyle
başlar ve toplam 7.876 girişe düşer — %17,1 daha az okuma. Dönen küme her iki koşumda da
138 belgedir; karşılaştırma bunu doğrular.

Aynı etki düz metin okumalarında daha büyüktür: "tümü zorunlu" 21.303 giriş okur, çünkü
her terim için önce üç alanın birleşimi kurulur ve bu birleşimler uzundur.
Yapılandırılmış biçim 7.576 girişle sonuçlanır — %64,4 daha az. Burada küme de bedel de
birlikte düşer, çünkü alan ayrımı hem gereksiz belgeleri hem gereksiz gönderi
listelerini eler. Kesişimin sıçramalı olması bu farkı doğuran şeydir: liste yürüyüşü
doğrusal olsaydı, iki sıra da uzun listenin tamamını okumak zorunda kalırdı.

#### Sıra: Küme Sırayı Vermez

Değerlendiricinin döndürdüğü dizi belge numarasına göre artan sıradadır ve bu bir
ilgililik sırası değildir — dizine giriş sırasıdır. Son iki satır bunu ölçer. "Herhangi
biri" okumasının 4.211 belgesi içinde üç koşulu da karşılayan 132 belge vardır; dizin
sırasında ilk on sonucun yalnız 1 tanesi bu gruptandır. Karşılanan koşul sayısına göre
sıralandığında ilk onun 10 tanesi bu gruptan gelir. Küme değişmemiştir, yalnız sıra
değişmiştir.

Basamak oynaması bu farkı büyütür: 132 belge dizin sırasında ortalama 2.023. basamakta
durur, en gerideki 4.185. basamaktadır. Bir kullanıcı ilk sayfayı okuyup vazgeçtiğinde,
küme doğru olsa bile aradığı kitabı görmemiş olur. Karşılanan koşul sayısı en kaba
sıralama ölçütüdür; terimlerin ne kadar seyrek olduğunu, hangi alanda geçtiğini ve alanın
uzunluğunu hesaba katmaz. Bunların hepsi bir ilgililik puanı hesabına girer ve o hesap
bu konunun beşinci dersinde kurulur. Bu dersin bıraktığı sonuç daha önceliklidir: küme
kararıyla sıra kararı ayrı kararlardır ve bir sorgu biçimi ikisini birden belirlemez.

#### Özet

- Yaprak sorgu tek alanda tek terimi arar; bileşik sorgu yaprakları bir ağaçta birleştirir
  ve ağacın kökü bağlacı belirler.
- 6.000 belgelik derlemde (tohum 271828) aynı üç sözcük üç okumada 4.211, 132 ve 71 belge
  döndürür; düz metin sorgu okumayı söylemez, okumayı motor seçer.
- Alan ayrımı hem kümeyi daraltır hem bedeli düşürür: yapılandırılmış biçim 7.576 giriş
  okur, düz metin "tümü zorunlu" 21.303 giriş.
- Ağacın çocukları uzunluğa göre sıralandığında küme değişmez (138 belge), taranan giriş
  9.501'den 7.876'ya iner.
- Eşleşen küme kendi içinde sırasızdır: üç koşulu karşılayan 132 belge dizin sırasında
  ortalama 2.023. basamaktadır, koşul sayısına göre sıralandığında ilk ondadır.

#### Sonraki Adım

Yapılandırılmış sorgu terimleri alanlara bağladı, ama terimler arasındaki ilişkiyi hâlâ
"aynı belgede geçsin" düzeyinde bıraktı. Katalogda sorulan soruların bir bölümü bundan
fazlasını ister: tam adı bilinmeyen bir kitabın adının içindeki iki sözcük yan yana
geçiyordur ve arada başka sözcük yoktur. "Eleştirel deneme" ile "eleştirel bir deneme"
aynı iki terimi taşır; ilkinde terimler bitişiktir, ikincisinde araya bir sözcük
girmiştir. Ters dizinin gönderi listelerinde tutulan konum bilgisi tam olarak bu ayrımı
yapmak içindir ve şimdiye kadar hiç kullanılmadı. Sonraki ders ifade sorgusunu kurar:
terimlerin sırasını ve aralarındaki uzaklığı koşula çevirir, kayma payının kümeyi ne
kadar genişlettiğini sayar ve konum bilgisinin dizine ne kadara mal olduğunu ölçer.

### Terim ve İfade Sorguları

Source: https://academia.sh/tr/kurslar/arama-motorlari/terim-ve-ifade-sorgulari

Aynı iki terim önce belge düzeyinde, sonra konum düzeyinde sorulur: terim sorgusu 757 belge döndürürken bitişik ifade 144 belgeye iner, kayma payı 3'e çıkınca 377'ye yükselir ve bu ayrımı olanaklı kılan konum bilgisi dizini %52,7 büyütür.

Önceki ders bileşik sorguyu bir ağaç olarak kurdu ve terimler arasındaki ilişkiyi "aynı
belgede geçsin" düzeyinde bıraktı. Katalog sorularının bir bölümü bundan fazlasını
ister. Bir okur kitabın tam adını hatırlamıyor, ama iki sözcüğün yan yana geçtiğini
biliyor. "Eleştirel deneme" ile "eleştirel bir deneme" aynı iki terimi taşır; ilkinde
terimler bitişiktir, ikincisinde araya bir sözcük girmiştir. Belge düzeyinde bakan bir
sorgu ikisini ayırt edemez — üstelik terimlerin cümlenin iki ucunda durduğu, aralarında
hiçbir ilgi bulunmayan belgeleri de aynı kümeye koyar.

**İfade sorgusu** (phrase query) bu ayrımı yapar: terimlerin yalnız varlığını değil,
konumlarını da koşula çevirir. Ters dizinin gönderi listelerinde tutulan konum bilgisi
tam olarak bunun içindir ve şimdiye kadar hiç kullanılmadı. **Kayma payı** (slop) ise
koşulu gevşetir: aradaki fazladan sözcüklere izin verir. Bu ders üç sayıyı birlikte
ölçer — ifadenin daralttığı küme, kayma payının geri getirdiği belgeler ve konum
bilgisinin dizine ödettiği bedel.

#### Derlem ve Kayma Payı Tanımı

Derlem önceki dersteki modüldür: 6.000 kitap kaydı, tohum 271828, belirlenimli üretim.
**SI4 (varsayım):** kayma payı, iki terim arasındaki fazladan sözcük sayısıdır; terimler
sorgudaki sırayla eşleşiyorsa aradaki sözcük sayısı, ters sırada eşleşiyorsa bu sayıya
iki eklenir — bir terimin ötekinin diğer yanına geçmesi iki adım sayılır. Ölçülen ifade
iki terimlidir; daha uzun ifadeler aynı kuralın ardışık uygulanmasıdır.

```js
// derlem.mjs — kutuphane katalogu derlemi ve konum bilgili ters dizin.
// Tohum 271828, 6000 belge; konudaki butun olcumler bu modulu paylasir.
export const TOHUM = 271828, N = 6000;
let c = TOHUM;                                       // gorunur tohum, belirlenimli uretec
const r = () => { c = (c + 0x6d2b79f5) | 0; let t = Math.imul(c ^ (c >>> 15), 1 | c);
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };
const sec = (d, e = 1) => d[Math.floor(r() ** e * d.length)];   // e>1: bas ogeler sik, son ogeler seyrek

const SIFAT = ["eleştirel", "kısa", "resimli", "seçme", "karşılaştırmalı"];
const TUR = ["deneme", "öykü", "roman", "inceleme", "antoloji"];
const ALAN = ["çocuk", "tarih", "bilim", "felsefe", "toplum"];
const SON = ["seçkisi", "kitabı", "derlemesi", "dizisi"];
const NIT = ["kapsamlı", "özlü", "tanıtıcı", "tartışmalı"];
const KONU = ["eğitim", "göç", "kent", "bellek", "doğa", "müzik", "hukuk", "kimlik", "emek", "aile",
  "savaş", "gelenek", "sağlık", "yolculuk", "mimarlık", "arkeoloji", "denizcilik", "gökbilim"];
const YAZAR = ["Ahmet Yıldız", "Zeynep Kaya", "Kemal Demir", "Elif Şahin", "Necati Aydın"];
const DIL = ["türkçe", "ingilizce", "almanca"];

export const KATALOG = [];
for (let i = 1; i <= N; i += 1) {
  const s = sec(SIFAT, 1.5), t = sec(TUR, 1.5), a = sec(ALAN, 2), k = r();
  const desen = k < 0.18 ? `${s} ${t}` : k < 0.30 ? `${s} bir ${t}`
    : k < 0.40 ? `${s} ve kuramsal ${t}` : k < 0.50 ? `${t} ve ${s} inceleme`
    : k < 0.70 ? `${s} bir anlatı` : k < 0.88 ? `çağdaş bir ${t}` : "alan yazını taraması";
  KATALOG.push({ id: `K-${String(i).padStart(4, "0")}`, ad: `${a} ${t} ${sec(SON)}`,
    ozet: `${a} alanında ${desen}; ${sec(KONU, 3)} ve ${sec(KONU, 3)} üzerine ` +
      `${sec(NIT)} bir ${sec(TUR, 1.5)} olarak okunabilir.`,
    etiket: [a, t], yazar: sec(YAZAR), yil: 1990 + Math.floor(r() * 35), dil: sec(DIL, 2) });
}

export const belirtecle = (m) => m.toLocaleLowerCase("tr").split(/[^\p{L}\p{N}]+/u).filter(Boolean);

// Ters dizin: "alan|terim" -> Map(belge sirasi -> konumlar); uzunluk: alan basina belirtec sayisi.
export function tersDizin(alanlar) {
  const g = new Map(), uzunluk = new Map();
  KATALOG.forEach((b, i) => {
    for (const alan of alanlar) {
      const tk = belirtecle(String(b[alan]));
      uzunluk.set(`${alan}|${i}`, tk.length);
      tk.forEach((t, p) => {
        let l = g.get(`${alan}|${t}`); if (!l) g.set(`${alan}|${t}`, (l = new Map()));
        let k2 = l.get(i); if (!k2) l.set(i, (k2 = [])); k2.push(p);
      });
    }
  });
  return { g, uzunluk };
}
```

#### Ölçüm

**SI5 (varsayım):** dizin boyutu, gönderi listelerinin değişken uzunluklu tamsayı
kodlamasıyla hesaplanır: belge numaraları ve konumlar farklarıyla yazılır, her sayı
büyüklüğüne göre 1–4 bayt tutar, sözlükte terim adı artı bir bayt sayılır. Konumsuz
dizinde belge farkı ve terim sıklığı, konumlu dizinde ayrıca konum farkları yazılır.

```js
// ifade.mjs — terim sorgusu ile ifade sorgusu. Ayni dizinde derlem.mjs bulunur.
// Kayma payi: iki terim arasindaki fazladan sozcuk sayisi; terimler ters sirada
// eslesiyorsa bu sayiya iki eklenir. Olculen ifade iki terimlidir.
import { KATALOG, TOHUM, tersDizin } from "./derlem.mjs";
const { g } = tersDizin(["ad", "ozet", "etiket"]);
const liste = (alan, terim) => g.get(`${alan}|${terim}`) ?? new Map();

function enYakin(k1, k2, s) {                      // iki konum dizisi arasindaki en kucuk maliyet
  let en = Infinity;
  for (const a of k1) for (const b of k2) { s.konum += 1; en = Math.min(en, b > a ? b - a - 1 : a - b + 1); }
  return en;
}
function ifadeSorgusu(alan, t1, t2, kayma, s) {
  const l2 = liste(alan, t2), sonuc = [];
  for (const [d, k1] of liste(alan, t1)) {
    const k2 = l2.get(d);
    if (k2 && enYakin(k1, k2, s) <= kayma) sonuc.push(d);
  }
  return sonuc;
}
const vb = (n) => (n < 128 ? 1 : n < 16384 ? 2 : n < 2097152 ? 3 : 4);   // degisken uzunluklu tamsayi
function dizinBayt(konumlu) {
  let b = 0;
  for (const [anahtar, l] of g) {
    b += Buffer.byteLength(anahtar) + 1;
    let onceki = 0;
    for (const [d, konum] of l) {
      b += vb(d - onceki) + vb(konum.length); onceki = d;
      if (konumlu) { let p = 0; for (const k of konum) { b += vb(k - p); p = k; } }
    }
  }
  return b;
}
const yaz = (ad, n, ek) => console.log(`${ad.padEnd(30)} ${String(n).padStart(4)} belge   ${ek}`);

console.log(`derlem ${KATALOG.length} belge, tohum ${TOHUM}`);
console.log(`ozet|eleştirel ${liste("ozet", "eleştirel").size} belge, ozet|deneme ${liste("ozet", "deneme").size} belge`);
const terim = [...liste("ozet", "eleştirel").keys()].filter((d) => liste("ozet", "deneme").has(d));
yaz("terim sorgusu, ikisi de gecsin", terim.length, `${" ".repeat(28)}okunan konum     0`);
let onceki = 0;
for (const kayma of [0, 1, 2, 3, 4]) {
  const s = { konum: 0 };
  const k = ifadeSorgusu("ozet", "eleştirel", "deneme", kayma, s);
  yaz(`ifade sorgusu, kayma payi ${kayma}`, k.length,
    `giren ${String(k.length - onceki).padStart(3)}   disarida ${String(terim.length - k.length).padStart(4)}   ` +
    `okunan konum ${String(s.konum).padStart(5)}`);
  onceki = k.length;
}
const konumsuz = dizinBayt(false), konumlu = dizinBayt(true);
console.log(`dizin bayt   konumsuz ${konumsuz}   konumlu ${konumlu}   artis %${((konumlu / konumsuz - 1) * 100).toFixed(1)}`);

// Sira: ayni kume, iki olcut. Terim kumesi bitisiklik uzakligina gore siralanir.
const s1 = { konum: 0 }, uzaklik = new Map();
for (const d of terim) uzaklik.set(d, enYakin(liste("ozet", "eleştirel").get(d), liste("ozet", "deneme").get(d), s1));
const yakinSira = [...terim].sort((a, b) => uzaklik.get(a) - uzaklik.get(b) || a - b);
const bitisik = (l) => l.slice(0, 10).filter((d) => uzaklik.get(d) === 0).length;
console.log(`ilk on sonucta bitisik eslesme: dizin sirasinda ${bitisik(terim)}, uzakliga gore ${bitisik(yakinSira)}`);
const uzak = terim.find((d) => uzaklik.get(d) > 4);
console.log(`bitisik  ${KATALOG[yakinSira[0]].id}: ${KATALOG[yakinSira[0]].ozet}`);
console.log(`uzak     ${KATALOG[uzak].id} (uzaklik ${uzaklik.get(uzak)}): ${KATALOG[uzak].ozet}`);
```

```
derlem 6000 belge, tohum 271828
ozet|eleştirel 1487 belge, ozet|deneme 2983 belge
terim sorgusu, ikisi de gecsin  757 belge                               okunan konum     0
ifade sorgusu, kayma payi 0     144 belge   giren 144   disarida  613   okunan konum   869
ifade sorgusu, kayma payi 1     230 belge   giren  86   disarida  527   okunan konum   869
ifade sorgusu, kayma payi 2     303 belge   giren  73   disarida  454   okunan konum   869
ifade sorgusu, kayma payi 3     377 belge   giren  74   disarida  380   okunan konum   869
ifade sorgusu, kayma payi 4     377 belge   giren   0   disarida  380   okunan konum   869
dizin bayt   konumsuz 216480   konumlu 330578   artis %52.7
ilk on sonucta bitisik eslesme: dizin sirasinda 1, uzakliga gore 10
bitisik  K-0069: tarih alanında eleştirel deneme; bellek ve eğitim üzerine tartışmalı bir inceleme olarak okunabilir.
uzak     K-0002 (uzaklik 8): bilim alanında eleştirel bir anlatı; müzik ve savaş üzerine kapsamlı bir deneme olarak okunabilir.
```

#### Küme: İfade Terim Sorgusunun Beşte Birini Bırakır

"Eleştirel" 1.487 özette, "deneme" 2.983 özette geçer; ikisinin birden geçtiği belge
sayısı 757'dir. Bitişik ifade koşulu bu kümeyi 144 belgeye indirir — terim sorgusunun
%19,0'ı. Dışarıda kalan 613 belge terimlerin ikisini de taşır, ama yan yana taşımaz.
Çıktının son satırı bunlardan birini gösteriyor: K-0002'nin özetinde "eleştirel" bir
anlatıyı niteler, "deneme" ise cümlenin öbür ucundaki bir başka türü adlandırır; iki
terimin arasındaki uzaklık 8'dir ve aralarında anlamca hiçbir bağ yoktur. Terim sorgusu
bu belgeyi yanlış getirmiştir, ifade sorgusu getirmez.

Kayma payı bu koşulu adım adım gevşetir. Bir birim kayma "eleştirel bir deneme" biçimini
kümeye sokar ve 86 belge girer; iki birim "eleştirel ve kuramsal deneme" biçimini sokar,
73 belge girer; üç birim ters sıradaki eşleşmeyi — "deneme ve eleştirel inceleme" — sokar
ve 74 belge daha girer. Dördüncü adımda kümeye hiçbir belge girmez. Bu, kayma payını
büyütmenin sürekli bir etkisi olmadığını gösterir: hangi uzaklıkların var olduğunu
derlemin cümle yapısı belirler, kayma payı yalnız o basamakları açar. Bir katalog
arayüzünde kayma payını 0'dan 3'e çıkarmak burada kümeyi 144'ten 377'ye, yani 2,6 katına
büyütür; 4'e çıkarmak hiçbir şey değiştirmez.

#### Bedel: Konum Bilgisi Her Belgeden Alınır

Sorgu anındaki bedel küçüktür. İfade koşulu yalnız iki gönderi listesinin ortak
belgelerinde konum okur: 757 aday belge için 869 konum karşılaştırması yapılır, çünkü
belgelerin çoğunda her terim bir kez geçer ve tek çift karşılaştırılır. Kayma payı
değiştiğinde bu sayı değişmez — kayma payı bir eşiktir, okunan konum sayısını değil,
eşiği geçen belge sayısını belirler.

Asıl bedel dizindedir. Konumsuz dizin 216.480 bayt, konumlu dizin 330.578 bayttır: %52,7
artış, 114.098 bayt fark, belge başına yaklaşık 19 bayt. Bu bedelin niteliği önemlidir —
sorgu başına değil, **belge başına** ödenir. Konum bilgisi hiç ifade sorgusu sorulmasa
da her belgede tutulur, her dizinleme sırasında yazılır, her bölüt birleştirmesinde
taşınır. Karar şudur: ifade sorgusuna gerek duyulmayan bir alanda konum bilgisi
kapatılabilir ve dizin üçte bir küçülür; buna karşılık o alanda ifade sorgusu artık
sorulamaz, yalnız terim sorgusuyla yaklaşık bir sonuç alınır — ve o yaklaşımın hatası
burada ölçülmüştür: 757'ye karşı 144.

#### Sıra: Uzaklık Bir Sıralama Ölçütüdür

İfade koşulu bir eşik olarak kullanıldığında küme keskin biçimde bölünür: içeride 144,
dışarıda 613. Aynı uzaklık bir eşik yerine sıralama ölçütü olarak da kullanılabilir ve o
zaman küme hiç daralmaz. Son ölçüm bunu gösterir: 757 belgelik terim kümesi dizin
sırasında verildiğinde ilk on sonucun yalnız 1 tanesi bitişik eşleşmedir; aynı küme
terimler arası uzaklığa göre sıralandığında ilk onun tamamı bitişik eşleşmedir.

İki yaklaşım arasındaki fark, bu kursun temel ayrımının en yalın örneğidir. Eşik olarak
kullanılan ifade koşulu **kümeyi** değiştirir ve 613 belgeyi tamamen görünmez kılar;
sıralama ölçütü olarak kullanılan uzaklık **sırayı** değiştirir ve o 613 belgeyi
listenin altında bırakır. İlkinde bir belge kaçırılırsa geri dönüşü yoktur, ikincisinde
kullanıcı aşağı inerse bulur. Hangisinin doğru olduğu sorunun kesinliğine bağlıdır: adı
tam bilinen bir kitapta eşik, konu araması olduğunda sıralama ölçütü daha uygundur.

#### Özet

- İfade sorgusu terimlerin varlığını değil konumlarını koşula çevirir; 6.000 belgelik
  derlemde (tohum 271828) terim sorgusunun 757 belgesini 144'e indirir.
- Kayma payı koşulu basamak basamak gevşetir: 1 birim 86, 2 birim 73, 3 birim 74 belge
  ekler, 4 birim hiçbir belge eklemez.
- Ters sıradaki eşleşme iki adım sayılır; bu yüzden "deneme ve eleştirel inceleme"
  biçimi ancak kayma payı 3 olduğunda kümeye girer.
- Konum bilgisi sorgu anında ucuzdur (757 aday için 869 konum karşılaştırması), dizinde
  pahalıdır: 216.480 bayttan 330.578 bayta, %52,7 artış.
- Aynı uzaklık ölçüsü eşik olarak kullanıldığında kümeyi böler, sıralama ölçütü olarak
  kullanıldığında yalnız sırayı değiştirir: ilk ondaki bitişik eşleşme 1'den 10'a çıkar.

#### Sonraki Adım

Şimdiye kadar kurulan koşulların hepsi aynı türdendi: sağlanmayan belge kümeden düşer.
Katalog soruları bu kadar tek biçimli değildir. "Çocuk etiketli olsun" zorunlu bir
koşuldur; "almanca olmasın" kümeyi daraltan ama tersinden çalışan bir koşuldur;
"özetinde 'resimli' geçenler üstte olsun" ise hiçbir belgeyi elemeyen, yalnız sırayı
etkileyen bir istektir. Üçü de tek bir bileşik sorgunun içinde yan yana durur ve
üçünün kümeye etkisi birbirinden tamamen farklıdır. Sonraki ders bu üç koşul türünü
ayırır ve isteğe bağlı koşulun kümeyi bir belge bile değiştirmeden ilk on sonucu nasıl
baştan yazdığını sayar.

### Bağlam Sorguları

Source: https://academia.sh/tr/kurslar/arama-motorlari/baglam-sorgulari

Zorunlu, olumsuz ve isteğe bağlı koşullar aynı bileşik sorguda ölçülür: olumsuz koşul kümeden 512 belge çıkarır, isteğe bağlı koşul bir belge bile çıkarmadan ilk on sonucun dokuzunu değiştirir, ama zorunlu koşul yokken aynı isteğe bağlı koşul kümeyi belirleyen koşula dönüşür.

Önceki iki derste kurulan koşulların hepsi aynı türdendi: koşulu sağlamayan belge
kümeden düşerdi. Katalog soruları bu kadar tek biçimli değildir. Bir okur şunu söyler:
"Çocuk kitabı olsun, almanca olmasın, resimli olanlar üstte olsun." Üç istek de tek bir
cümlede geçer, ama üçü aynı işi yapmaz. Birincisi kümeyi belirler, ikincisi kümeden
çıkarır, üçüncüsü kümeden hiç kimseyi çıkarmaz — yalnız listenin başını değiştirir.

Bileşik sorgu bu ayrımı **koşul bağlamı** ile yapar. **Zorunlu koşul** (must)
sağlanmayan belgeyi eler. **Olumsuz koşul** (must not) sağlayan belgeyi eler. **İsteğe
bağlı koşul** (should) hiçbir belgeyi elemez; sağlayan belgeye puan ekler. Bu ders üç
bağlamın kümeye ve sıraya etkisini ayrı ayrı sayar ve isteğe bağlı koşulun bir yerde
kural değiştirdiğini gösterir.

#### Derlem ve Puan Kuralı

Derlem önceki derslerin modülüdür: 6.000 kitap kaydı, tohum 271828, belirlenimli üretim.
**SI6 (varsayım):** bu derste puan, karşılanan isteğe bağlı koşul sayısıdır; eşitlik
belge numarasıyla bozulur. Terim sıklığına ve seyrekliğe dayanan gerçek puan modeli bu
konunun beşinci dersinde kurulur — burada ölçülen şey puanın büyüklüğü değil, isteğe
bağlı koşulun sıraya dokunup kümeye dokunmaması.

```js
// derlem.mjs — kutuphane katalogu derlemi ve konum bilgili ters dizin.
// Tohum 271828, 6000 belge; konudaki butun olcumler bu modulu paylasir.
export const TOHUM = 271828, N = 6000;
let c = TOHUM;                                       // gorunur tohum, belirlenimli uretec
const r = () => { c = (c + 0x6d2b79f5) | 0; let t = Math.imul(c ^ (c >>> 15), 1 | c);
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };
const sec = (d, e = 1) => d[Math.floor(r() ** e * d.length)];   // e>1: bas ogeler sik, son ogeler seyrek

const SIFAT = ["eleştirel", "kısa", "resimli", "seçme", "karşılaştırmalı"];
const TUR = ["deneme", "öykü", "roman", "inceleme", "antoloji"];
const ALAN = ["çocuk", "tarih", "bilim", "felsefe", "toplum"];
const SON = ["seçkisi", "kitabı", "derlemesi", "dizisi"];
const NIT = ["kapsamlı", "özlü", "tanıtıcı", "tartışmalı"];
const KONU = ["eğitim", "göç", "kent", "bellek", "doğa", "müzik", "hukuk", "kimlik", "emek", "aile",
  "savaş", "gelenek", "sağlık", "yolculuk", "mimarlık", "arkeoloji", "denizcilik", "gökbilim"];
const YAZAR = ["Ahmet Yıldız", "Zeynep Kaya", "Kemal Demir", "Elif Şahin", "Necati Aydın"];
const DIL = ["türkçe", "ingilizce", "almanca"];

export const KATALOG = [];
for (let i = 1; i <= N; i += 1) {
  const s = sec(SIFAT, 1.5), t = sec(TUR, 1.5), a = sec(ALAN, 2), k = r();
  const desen = k < 0.18 ? `${s} ${t}` : k < 0.30 ? `${s} bir ${t}`
    : k < 0.40 ? `${s} ve kuramsal ${t}` : k < 0.50 ? `${t} ve ${s} inceleme`
    : k < 0.70 ? `${s} bir anlatı` : k < 0.88 ? `çağdaş bir ${t}` : "alan yazını taraması";
  KATALOG.push({ id: `K-${String(i).padStart(4, "0")}`, ad: `${a} ${t} ${sec(SON)}`,
    ozet: `${a} alanında ${desen}; ${sec(KONU, 3)} ve ${sec(KONU, 3)} üzerine ` +
      `${sec(NIT)} bir ${sec(TUR, 1.5)} olarak okunabilir.`,
    etiket: [a, t], yazar: sec(YAZAR), yil: 1990 + Math.floor(r() * 35), dil: sec(DIL, 2) });
}

export const belirtecle = (m) => m.toLocaleLowerCase("tr").split(/[^\p{L}\p{N}]+/u).filter(Boolean);

// Ters dizin: "alan|terim" -> Map(belge sirasi -> konumlar); uzunluk: alan basina belirtec sayisi.
export function tersDizin(alanlar) {
  const g = new Map(), uzunluk = new Map();
  KATALOG.forEach((b, i) => {
    for (const alan of alanlar) {
      const tk = belirtecle(String(b[alan]));
      uzunluk.set(`${alan}|${i}`, tk.length);
      tk.forEach((t, p) => {
        let l = g.get(`${alan}|${t}`); if (!l) g.set(`${alan}|${t}`, (l = new Map()));
        let k2 = l.get(i); if (!k2) l.set(i, (k2 = [])); k2.push(p);
      });
    }
  });
  return { g, uzunluk };
}
```

#### Üç Bağlamın Ölçümü

```js
// baglam.mjs — zorunlu, olumsuz ve istege bagli kosullar. Ayni dizinde derlem.mjs bulunur.
// Istege bagli kosul kumeden belge elemez; yalniz puan uretir. Puan burada karsilanan
// istege bagli kosul sayisidir; esitlik belge numarasiyla bozulur.
import { KATALOG, TOHUM, tersDizin } from "./derlem.mjs";
const { g } = tersDizin(["ad", "ozet", "etiket", "dil"]);
const kume = (alan, terim) => new Set(g.get(`${alan}|${terim}`)?.keys() ?? []);

function calistir(sorgu, s) {
  let aday = null;
  for (const [alan, terim] of sorgu.zorunlu ?? []) {
    const k = kume(alan, terim); s.taranan += k.size;
    aday = aday === null ? [...k] : aday.filter((d) => k.has(d));
  }
  const istek = (sorgu.istege ?? []).map(([a, t]) => { const k = kume(a, t); s.taranan += k.size; return k; });
  if (aday === null) {                       // zorunlu kosul yoksa kumeyi istege bagli kosullar belirler
    const b = new Set();
    for (const k of istek) for (const d of k) b.add(d);
    aday = [...b];
  }
  for (const [alan, terim] of sorgu.olumsuz ?? []) {
    const k = kume(alan, terim); s.taranan += k.size;
    aday = aday.filter((d) => !k.has(d));
  }
  aday.sort((x, y) => x - y);
  const puan = new Map(aday.map((d) => [d, istek.filter((k) => k.has(d)).length]));
  const sirali = [...aday].sort((x, y) => puan.get(y) - puan.get(x) || x - y);
  return { kume: aday, puan, sirali };
}
const olc = (ad, sorgu) => {
  const s = { taranan: 0 }, c = calistir(sorgu, s);
  console.log(`${ad.padEnd(38)} ${String(c.kume.length).padStart(4)} belge   taranan giris ${String(s.taranan).padStart(5)}`);
  return c;
};

console.log(`derlem ${KATALOG.length} belge, tohum ${TOHUM}`);
const Z = [["etiket", "çocuk"]], I = [["ozet", "resimli"], ["ad", "öykü"]], O = [["dil", "almanca"]];
const a = olc("zorunlu[etiket:çocuk]", { zorunlu: Z });
const b = olc("zorunlu + olumsuz[dil:almanca]", { zorunlu: Z, olumsuz: O });
const c = olc("zorunlu + istege bagli[resimli, öykü]", { zorunlu: Z, istege: I });
const d = olc("zorunlu + istege bagli + olumsuz", { zorunlu: Z, istege: I, olumsuz: O });
const e = olc("yalniz istege bagli[resimli, öykü]", { istege: I });
console.log(`olumsuz kosulun cikardigi belge ${a.kume.length - b.kume.length}`);
console.log(`istege bagli kosulun kumeye etkisi ${c.kume.length - a.kume.length} belge`);

const ilkOn = (l) => l.slice(0, 10).map((x) => KATALOG[x].id);
const degisen = ilkOn(c.sirali).filter((x) => !ilkOn(a.sirali).includes(x)).length;
console.log(`ilk on: zorunlu ${ilkOn(a.sirali).join(" ")}`);
console.log(`ilk on: +istege ${ilkOn(c.sirali).join(" ")}   degisen ${degisen}/10`);
const ikiPuan = c.sirali.filter((x) => c.puan.get(x) === 2);
console.log(`iki istege bagli kosulu da karsilayan ${ikiPuan.length} belge; ` +
  `ilki ${KATALOG[ikiPuan[0]].id} zorunlu sirada ${a.kume.indexOf(ikiPuan[0]) + 1}. basamaktaydi`);
const oynama = ikiPuan.map((x) => a.kume.indexOf(x) + 1 - (c.sirali.indexOf(x) + 1));
console.log(`bu belgelerin basamak kazanci ortalama ${Math.round(oynama.reduce((x, y) => x + y, 0) / oynama.length)}, ` +
  `en buyugu ${Math.max(...oynama)}`);
```

```
derlem 6000 belge, tohum 271828
zorunlu[etiket:çocuk]                  2678 belge   taranan giris  2678
zorunlu + olumsuz[dil:almanca]         2166 belge   taranan giris  3800
zorunlu + istege bagli[resimli, öykü]  2678 belge   taranan giris  4638
zorunlu + istege bagli + olumsuz       2166 belge   taranan giris  5760
yalniz istege bagli[resimli, öykü]     1802 belge   taranan giris  1960
olumsuz kosulun cikardigi belge 512
istege bagli kosulun kumeye etkisi 0 belge
ilk on: zorunlu K-0004 K-0008 K-0016 K-0018 K-0020 K-0023 K-0024 K-0026 K-0033 K-0037
ilk on: +istege K-0037 K-0072 K-0078 K-0080 K-0216 K-0345 K-0371 K-0518 K-0615 K-0712   degisen 9/10
iki istege bagli kosulu da karsilayan 82 belge; ilki K-0037 zorunlu sirada 10. basamaktaydi
bu belgelerin basamak kazanci ortalama 1263, en buyugu 2574
```

#### Küme: İki Koşul Türü Eler, Biri Elemez

Zorunlu koşul tek başına 2.678 belge döndürür — çocuk etiketli kitaplar. Olumsuz koşul
eklendiğinde küme 2.166'ya iner: 512 belge çıkar, hepsi almanca kayıtlardır. Olumsuz
koşulun ürettiği sayı hep bir çıkarmadır; kümeye asla belge eklemez ve tek başına
kullanıldığında anlamı belirsizdir, çünkü "almanca olmayan" ifadesi hangi kümeden
çıkarılacağını söylemez.

Üçüncü satır dersin çekirdeğidir. Aynı zorunlu koşula iki isteğe bağlı koşul eklendi ve
küme **2.678 belge olarak kaldı**: giren yok, çıkan yok, fark sıfır. Resimli olmayan ve
adında "öykü" geçmeyen bir kitap sonuç listesinden düşmedi; yalnız puanı sıfır kaldı.
Dördüncü satır aynı sorguya olumsuz koşulu geri koyar ve küme yine 2.166'ya iner — çünkü
kümeyi değiştiren şey isteğe bağlı koşul değil, olumsuz koşuldur.

Beşinci satır kuralın sınırını gösterir ve pratikte en çok yanlış anlaşılan noktadır.
Zorunlu koşul kaldırıldığında, aynı iki isteğe bağlı koşul kümeyi 1.802 belgeye indirir.
İsteğe bağlı koşul burada birden elemeye başlamış değildir; olan şey şudur: kümeyi
belirleyen hiçbir koşul kalmayınca **en az bir isteğe bağlı koşulun sağlanması** gerekir,
yoksa sorgu bütün derlemi döndürürdü. Yani "isteğe bağlı koşul kümeyi değiştirmez" cümlesi
koşulsuz doğru değildir; doğrusu şudur: **kümeyi belirleyen başka bir koşul varsa** isteğe
bağlı koşul kümeye dokunmaz. Bir arayüzde ağırlıklandırma amacıyla eklenen bir koşulun,
zorunlu koşulun kaldırıldığı bir durumda sonucu 6.000'den 1.802'ye düşürmesi bu kuraldan
gelir.

#### Sıra ve Bedel: Elemeyen Koşulun Ödettiği

Küme değişmediği hâlde liste tanınmaz hâle gelir. Zorunlu koşulun tek başına verdiği ilk
on sonuç K-0004 ile başlar; isteğe bağlı koşullar eklendiğinde ilk onun **dokuzu**
değişir. Ayakta kalan tek kayıt K-0037'dir ve o da onuncu basamaktan birinci basamağa
çıkmıştır. İki isteğe bağlı koşulu birden karşılayan 82 belge vardır; bu belgeler
ortalama 1.263 basamak yukarı çıkar, en çok yükselen 2.574 basamak kazanır. 2.678
belgelik bir listede 2.574 basamaklık bir sıçrama, belgenin listenin sonundan başına
gelmesi demektir.

Bedel tarafı da elemeyen koşulun bedava olmadığını gösterir. Zorunlu koşul tek başına
2.678 giriş okur. İsteğe bağlı iki koşul eklendiğinde okuma 4.638 girişe çıkar: %73,2
artış, tek bir belge bile elenmeden. Bunun nedeni açıktır — isteğe bağlı koşulun puana
katkısını hesaplamak için gönderi listesinin tamamı okunmalıdır, oysa zorunlu koşul aday
kümesi küçüldükçe daha az iş yapar. Üç bağlamın hepsi bir aradayken okuma 5.760 girişe
ulaşır; bunun 1.122'si yalnız 512 belgeyi çıkarmak için okunan olumsuz koşul listesidir.

Buradan çıkan işletim kuralı şudur: bir koşulun ucuz olup olmadığı, kaç belge elediğine
değil, kaç giriş okuttuğuna bakarak söylenir. Kümeyi hiç değiştirmeyen bir koşul en
pahalı koşul olabilir.

#### Özet

- Zorunlu koşul kümeyi belirler, olumsuz koşul kümeden çıkarır, isteğe bağlı koşul
  yalnız puan üretir; 6.000 belgelik derlemde (tohum 271828) olumsuz koşul 512 belge
  çıkarır, isteğe bağlı koşul 0 belge çıkarır.
- İsteğe bağlı koşulun kümeye etkisizliği koşulludur: kümeyi belirleyen başka bir koşul
  yoksa en az biri sağlanmalıdır ve küme 6.000 yerine 1.802 belge olur.
- Küme sabitken sıra baştan yazılır: ilk on sonucun dokuzu değişir, iki isteğe bağlı
  koşulu karşılayan 82 belge ortalama 1.263 basamak yükselir, en çok yükselen 2.574
  basamak kazanır.
- Elemeyen koşul bedavaya çalışmaz: isteğe bağlı iki koşul taranan girişi 2.678'den
  4.638'e çıkarır, hiçbir belgeyi elemeden.
- Bir koşulun bedeli elediği belge sayısıyla değil, okuttuğu gönderi girişiyle ölçülür.

#### Sonraki Adım

Üç bağlam da bir noktada birleşiyor: hepsi puanlayıcının önünden geçiyor. Zorunlu koşul
kümeyi belirlerken aynı zamanda puana katkı veriyor, isteğe bağlı koşul zaten yalnız
bunun için var. Oysa katalog koşullarının bir bölümü puana hiç katılmamalıdır. "Yayın
yılı 2010 ve sonrası" ya da "dili türkçe" gibi bir koşul ya sağlanır ya sağlanmaz; bir
kitabın 2015 basımı olması onu 2011 basımı bir kitaptan daha ilgili yapmaz. Bu koşullar
ayrı bir bağlamda değerlendirilir ve o bağlamın iki sonucu vardır: puan hesabı
kısalır ve sonuç yeniden kullanılabilir hâle gelir, çünkü aynı koşul her sorguda aynı
belgeleri verir. Sonraki ders aynı koşulu iki bağlamda çalıştırır, kümenin aynı kalıp
sıranın değiştiğini gösterir ve önbelleklenen koşulun ikinci koşumda kaç giriş
okuduğunu sayar.

### Süzme ve Sorgu Ayrımı

Source: https://academia.sh/tr/kurslar/arama-motorlari/suzme-ve-sorgu-ayrimi

Aynı koşul önce sorgu bağlamında, sonra süzme bağlamında çalıştırılır: dönen küme 1.138 belge olarak sabit kalırken ilk on sonucun dokuzu değişir, 1.138 belgenin yalnız dördü yerinde kalır, puan hesabı yarıya iner ve süzgecin sonucu 750 baytlık bir bit kümesi olarak yeniden kullanılabilir.

Önceki ders üç koşul bağlamını ayırdı, ama üçünün de ortak bir yanı vardı: hepsi
puanlayıcının önünden geçiyordu. Zorunlu koşul kümeyi belirlerken puana da katkı
veriyor, isteğe bağlı koşul zaten yalnız bunun için bulunuyordu. Katalog koşullarının bir
bölümü için bu yanlıştır. "Yayın yılı 2010 ve sonrası", "dili türkçe", "konu başlığında
eğitim geçsin" gibi koşullar ya sağlanır ya sağlanmaz; bir kitabın 2015 basımı olması
onu 2011 basımı bir kitaptan daha ilgili yapmaz.

Bu ayrım **süzme bağlamı** (filter context) ile **sorgu bağlamı** (query context) olarak
adlandırılır. Aynı koşul her iki bağlamda da aynı belgeleri geçirir — küme değişmez.
Değişen şey, koşulun puana katkı verip vermemesidir ve bunun iki sonucu vardır: sıra
başka çıkar, bedel başka olur. Bu ders aynı koşulu iki bağlamda çalıştırıp üç sayıyı
karşılaştırır: dönen belge, ilk on sonuç ve okunan giriş.

#### Derlem ve Puan Kuralı

Derlem önceki derslerin modülüdür: 6.000 kitap kaydı, tohum 271828, belirlenimli üretim.
**SI7 (varsayım):** bu derste puan, puanlanan her koşulun terim sıklığının o alandaki
belirteç sayısına bölümüdür ve koşullar üzerinden toplanır. Seyreklik çarpanı henüz
yoktur; tam model bu konunun beşinci dersinde kurulur. Ölçülen şey puanın büyüklüğü
değil, bir koşulun puana girip girmemesinin sıraya etkisidir.

```js
// derlem.mjs — kutuphane katalogu derlemi ve konum bilgili ters dizin.
// Tohum 271828, 6000 belge; konudaki butun olcumler bu modulu paylasir.
export const TOHUM = 271828, N = 6000;
let c = TOHUM;                                       // gorunur tohum, belirlenimli uretec
const r = () => { c = (c + 0x6d2b79f5) | 0; let t = Math.imul(c ^ (c >>> 15), 1 | c);
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };
const sec = (d, e = 1) => d[Math.floor(r() ** e * d.length)];   // e>1: bas ogeler sik, son ogeler seyrek

const SIFAT = ["eleştirel", "kısa", "resimli", "seçme", "karşılaştırmalı"];
const TUR = ["deneme", "öykü", "roman", "inceleme", "antoloji"];
const ALAN = ["çocuk", "tarih", "bilim", "felsefe", "toplum"];
const SON = ["seçkisi", "kitabı", "derlemesi", "dizisi"];
const NIT = ["kapsamlı", "özlü", "tanıtıcı", "tartışmalı"];
const KONU = ["eğitim", "göç", "kent", "bellek", "doğa", "müzik", "hukuk", "kimlik", "emek", "aile",
  "savaş", "gelenek", "sağlık", "yolculuk", "mimarlık", "arkeoloji", "denizcilik", "gökbilim"];
const YAZAR = ["Ahmet Yıldız", "Zeynep Kaya", "Kemal Demir", "Elif Şahin", "Necati Aydın"];
const DIL = ["türkçe", "ingilizce", "almanca"];

export const KATALOG = [];
for (let i = 1; i <= N; i += 1) {
  const s = sec(SIFAT, 1.5), t = sec(TUR, 1.5), a = sec(ALAN, 2), k = r();
  const desen = k < 0.18 ? `${s} ${t}` : k < 0.30 ? `${s} bir ${t}`
    : k < 0.40 ? `${s} ve kuramsal ${t}` : k < 0.50 ? `${t} ve ${s} inceleme`
    : k < 0.70 ? `${s} bir anlatı` : k < 0.88 ? `çağdaş bir ${t}` : "alan yazını taraması";
  KATALOG.push({ id: `K-${String(i).padStart(4, "0")}`, ad: `${a} ${t} ${sec(SON)}`,
    ozet: `${a} alanında ${desen}; ${sec(KONU, 3)} ve ${sec(KONU, 3)} üzerine ` +
      `${sec(NIT)} bir ${sec(TUR, 1.5)} olarak okunabilir.`,
    etiket: [a, t], yazar: sec(YAZAR), yil: 1990 + Math.floor(r() * 35), dil: sec(DIL, 2) });
}

export const belirtecle = (m) => m.toLocaleLowerCase("tr").split(/[^\p{L}\p{N}]+/u).filter(Boolean);

// Ters dizin: "alan|terim" -> Map(belge sirasi -> konumlar); uzunluk: alan basina belirtec sayisi.
export function tersDizin(alanlar) {
  const g = new Map(), uzunluk = new Map();
  KATALOG.forEach((b, i) => {
    for (const alan of alanlar) {
      const tk = belirtecle(String(b[alan]));
      uzunluk.set(`${alan}|${i}`, tk.length);
      tk.forEach((t, p) => {
        let l = g.get(`${alan}|${t}`); if (!l) g.set(`${alan}|${t}`, (l = new Map()));
        let k2 = l.get(i); if (!k2) l.set(i, (k2 = [])); k2.push(p);
      });
    }
  });
  return { g, uzunluk };
}
```

#### İki Bağlamın Ölçümü

```js
// suzme.mjs — ayni kosul once sorgu baglaminda, sonra suzme baglaminda. Derlem: derlem.mjs.
// Puan burada terim sikliginin alan uzunluguna bolumudur; tam model sonraki derste kurulur.
import { KATALOG, TOHUM, tersDizin } from "./derlem.mjs";
const { g, uzunluk } = tersDizin(["ad", "ozet", "etiket", "dil"]);
const liste = (alan, terim) => g.get(`${alan}|${terim}`) ?? new Map();

function sorgula(puanlanan, suzgecler, s) {
  let aday = null;
  for (const [alan, terim] of [...puanlanan, ...suzgecler]) {
    const l = liste(alan, terim); s.taranan += l.size;
    aday = aday === null ? [...l.keys()] : aday.filter((d) => l.has(d));
  }
  const puan = new Map();
  for (const d of aday) {
    let p = 0;
    for (const [alan, terim] of puanlanan) { s.hesap += 1; p += liste(alan, terim).get(d).length / uzunluk.get(`${alan}|${d}`); }
    puan.set(d, p);
  }
  return { kume: aday, puan, sirali: [...aday].sort((x, y) => puan.get(y) - puan.get(x) || x - y) };
}
const olc = (ad, puanlanan, suzgecler) => {
  const s = { taranan: 0, hesap: 0 }, c = sorgula(puanlanan, suzgecler, s);
  console.log(`${ad.padEnd(40)} ${String(c.kume.length).padStart(4)} belge   puan hesabi ${String(s.hesap).padStart(4)}` +
    `   taranan giris ${String(s.taranan).padStart(5)}`);
  return c;
};

console.log(`derlem ${KATALOG.length} belge, tohum ${TOHUM}`);
const SORGU = [["ozet", "öykü"]], KOSUL = [["ozet", "eğitim"]];
const a = olc("sorgu baglami: öykü + eğitim puanlanir", [...SORGU, ...KOSUL], []);
const b = olc("suzme baglami: eğitim yalniz suzer", SORGU, KOSUL);
console.log(`ayni kume mi: ${JSON.stringify(a.kume) === JSON.stringify(b.kume)}`);
const on = (c) => c.sirali.slice(0, 10).map((d) => KATALOG[d].id);
console.log(`ilk on, sorgu baglami ${on(a).join(" ")}`);
console.log(`ilk on, suzme baglami ${on(b).join(" ")}`);
console.log(`ilk onda degisen ${on(a).filter((x) => !on(b).includes(x)).length}/10`);
console.log(`farkli puan degeri: sorgu baglaminda ${new Set(a.puan.values()).size}, ` +
  `suzme baglaminda ${new Set(b.puan.values()).size}`);
const oynama = a.kume.map((d) => Math.abs(a.sirali.indexOf(d) - b.sirali.indexOf(d)));
console.log(`basamak oynamasi: ortalama ${Math.round(oynama.reduce((x, y) => x + y, 0) / oynama.length)}, ` +
  `en buyugu ${Math.max(...oynama)}, yerinde kalan ${oynama.filter((o) => o === 0).length} belge`);

// Onbellek: suzgecin sonucu sorgudan bagimsizdir, bit kumesi olarak saklanir.
const onbellek = new Map();
function suzgecKumesi(alan, terim, s) {
  const anahtar = `${alan}|${terim}`;
  if (onbellek.has(anahtar)) { s.isabet += 1; return onbellek.get(anahtar); }
  const l = liste(alan, terim); s.taranan += l.size;
  const bit = new Uint8Array(Math.ceil(KATALOG.length / 8));
  for (const d of l.keys()) bit[d >> 3] |= 1 << (d & 7);
  onbellek.set(anahtar, bit);
  return bit;
}
const BES = ["öykü", "roman", "deneme", "inceleme", "antoloji"];
for (const onbellekli of [false, true]) {
  onbellek.clear();
  const s = { taranan: 0, isabet: 0 };
  for (const t of BES) {
    liste("ozet", t); s.taranan += liste("ozet", t).size;
    if (onbellekli) suzgecKumesi("ozet", "eğitim", s);
    else s.taranan += liste("ozet", "eğitim").size;
  }
  console.log(`bes sorgu, ayni suzgec  ${onbellekli ? "onbellekli " : "onbelleksiz"}   ` +
    `taranan giris ${String(s.taranan).padStart(6)}   onbellek isabeti ${s.isabet}`);
}
console.log(`suzgec bit kumesi ${Math.ceil(KATALOG.length / 8)} bayt; ` +
  `ayni kosulun puan dizisi ${liste("ozet", "eğitim").size * 8} bayt`);
```

```
derlem 6000 belge, tohum 271828
sorgu baglami: öykü + eğitim puanlanir   1138 belge   puan hesabi 2276   taranan giris  5513
suzme baglami: eğitim yalniz suzer       1138 belge   puan hesabi 1138   taranan giris  5513
ayni kume mi: true
ilk on, sorgu baglami K-0836 K-2188 K-4924 K-5913 K-1025 K-1207 K-2800 K-3095 K-3253 K-3683
ilk on, suzme baglami K-0171 K-0360 K-0417 K-0709 K-0836 K-0958 K-1185 K-1325 K-1520 K-1670
ilk onda degisen 9/10
farkli puan degeri: sorgu baglaminda 9, suzme baglaminda 6
basamak oynamasi: ortalama 173, en buyugu 791, yerinde kalan 4 belge
bes sorgu, ayni suzgec  onbelleksiz   taranan giris  27836   onbellek isabeti 0
bes sorgu, ayni suzgec  onbellekli    taranan giris  13260   onbellek isabeti 4
suzgec bit kumesi 750 bayt; ayni kosulun puan dizisi 29152 bayt
```

#### Küme Aynı, Sıra Değil

İki koşum da 1.138 belge döndürür ve karşılaştırma kümelerin birebir aynı olduğunu
doğrular. Bu beklenen sonuçtur: her iki bağlamda da koşul zorunludur, geçirdiği belgeler
aynıdır. Küme tarafında süzme bağlamının hiçbir etkisi yoktur.

Sıra tarafında etki büyüktür. İlk on sonucun dokuzu değişir; iki listede ortak kalan tek
kayıt K-0836'dır. 1.138 belgenin **yalnız dördü** yerinde kalır, ortalama basamak
oynaması 173, en büyüğü 791 basamaktır. Farkın kaynağı puanın çözünürlüğüdür: süzme
bağlamında puan yalnız bir koşuldan gelir ve 6 ayrı değer alır, sorgu bağlamında iki
koşuldan gelir ve 9 ayrı değer alır. Değer sayısı arttıkça beraberlikler çözülür ve
liste yeniden dizilir.

Buradan çıkan karar açıktır. "Eğitim konulu olsun" isteği bir ilgililik ölçütü değil,
bir kapsam daraltmasıdır; sorgu bağlamında bırakıldığında, özeti kısa olan ya da "eğitim"
sözcüğü iki kez geçen kitaplar listenin başına çıkar. Bu sıralama kullanıcının sorduğu
soruyla ilgisizdir — kullanıcı eğitim sözcüğünün kaç kez geçtiğini değil, öykü kitabı
arıyordur. Süzme bağlamı sıralamayı kullanıcının asıl sorusuna bırakır.

#### Bedel: Hesap, Okuma ve Önbellek

Puan hesabı sayısı yarıya iner: 2.276'dan 1.138'e. Her aday belge için bir çarpma ve bir
bölme yerine yarısı yapılır ve bu tasarruf aday sayısıyla doğru orantılı büyür. Taranan
giriş sayısı ilk koşumda değişmez (5.513) — süzgeç de sonuçta bir gönderi listesi okur.

Asıl kazanç ikinci koşumda görünür. Süzgecin sonucu **sorgudan bağımsızdır**: hangi
serbest metin sorulursa sorulsun, "özetinde eğitim geçen belgeler" kümesi aynıdır. Bu
yüzden sonuç bir bit kümesi olarak saklanabilir ve sonraki sorgularda gönderi listesine
hiç dokunulmaz. Beş farklı sorgu aynı süzgeci paylaştığında önbelleksiz koşum 27.836
giriş okur, önbellekli koşum 13.260 giriş — %52,4 daha az, dört önbellek isabetiyle.

Önbelleğin boyutu bu kararı kolaylaştırır: 6.000 belgelik derlem için bit kümesi 750
bayttır. Aynı koşulun puan katkısını saklamak isteseydik 3.644 belge için sekizer baytlık
ondalık sayı, yani 29.152 bayt gerekirdi — otuz dokuz katı. Puanın saklanması ayrıca işe
yaramaz, çünkü nihai puan sorgudaki öteki koşullarla birlikte hesaplanır ve her sorguda
başkadır. Süzgeç önbelleğe alınabilir olmasını tam da puana katılmamasına borçludur:
puana katılmayan koşulun sonucu bir doğruluk değeridir, doğruluk değeri sorgudan sorguya
değişmez.

Süzgecin bedelsiz olduğu söylenemez. Önbellek bellek tüketir ve her belge eklemesinde ya
da silmesinde geçersizleşir; seyrek sorulan bir koşulu önbellekte tutmak boşa bellektir.
Ölçüt basittir: aynı koşul kaç ayrı sorguda tekrarlanıyor. Kütüphane arayüzündeki dil ve
konu daraltmaları her sorguda tekrarlanır, kullanıcının yazdığı serbest metin tekrarlanmaz.

#### Özet

- Aynı zorunlu koşul iki bağlamda da aynı kümeyi verir: 6.000 belgelik derlemde (tohum
  271828) her iki koşum da 1.138 belge döndürür.
- Sıra tamamen değişir: ilk on sonucun dokuzu farklıdır, 1.138 belgenin yalnız dördü
  yerinde kalır, ortalama basamak oynaması 173, en büyüğü 791'dir.
- Farkın kaynağı puanın çözünürlüğüdür: süzme bağlamında 6, sorgu bağlamında 9 ayrı puan
  değeri oluşur.
- Süzme bağlamı puan hesabını yarıya indirir (2.276 → 1.138) ve sonucu sorgudan bağımsız
  kıldığı için önbelleklenebilir kılar: beş sorguda 27.836 giriş yerine 13.260 giriş.
- Süzgeç önbelleği 750 bayttır, aynı koşulun puan dizisi 29.152 bayt; puanı saklamak
  ayrıca işe yaramaz, çünkü nihai puan her sorguda başka çıkar.

#### Sonraki Adım

Buraya kadar puan hep yer tutucu bir formülle hesaplandı: önce karşılanan koşul sayısı,
sonra terim sıklığının alan uzunluğuna bölümü. İkisi de sıralama üretti, ama ikisi de
sorunun asıl yarısını atladı. Bir terimin bir belgede geçmesi, o terimin derlemde ne
kadar seyrek olduğu bilinmeden anlam taşımaz: "ve" sözcüğü 6.000 belgenin hepsinde
geçer ve hiçbir şey ayırt etmez, "gökbilim" iki yüz kadar belgede geçer ve tek başına
belgeyi belirler. Sonraki ders puanı üç bileşene ayırır — terim sıklığı, ters belge
sıklığı ve alan uzunluğu — bir belgenin sırasının hangi bileşenden geldiğini gösterir ve
sonucun ne kadarının doğru olduğunu ölçmek için kesinlik ile anmayı tanımlar.

### İlgililik Puanlaması

Source: https://academia.sh/tr/kurslar/arama-motorlari/ilgililik-puanlamasi

Puanlayıcı üç bileşenden kurulur — terim sıklığı, ters belge sıklığı ve alan uzunluğu — ve bir belgenin sırasının hangi bileşenden geldiği tek tek gösterilir; kesinlik ile anma burada tanımlanır ve sıralamanın kesinliği 0,353'ten 0,640'a çıkardığı, ama modelin göremediği bir ölçütte tavana vurduğu ölçülür.

Önceki derste puan hep yer tutucu bir formülle hesaplandı: önce karşılanan koşul sayısı,
sonra terim sıklığının alan uzunluğuna bölümü. İkisi de sıralama üretti, ama ikisi de
sorunun yarısını atladı. Bir terimin bir belgede geçmesi, o terimin derlemde ne kadar
seyrek olduğu bilinmeden anlam taşımaz: "ve" sözcüğü katalogdaki 6.000 özetin hepsinde
geçer ve hiçbir şeyi ayırt etmez, "gökbilim" iki yüz kadar özette geçer ve tek başına
belgeyi neredeyse belirler.

**İlgililik puanı** (relevance score) üç bileşenden kurulur. **Terim sıklığı** (term
frequency) terimin belgede kaç kez geçtiğidir ve doyuma ulaşır — beşinci geçiş
birinciden daha az şey söyler. **Ters belge sıklığı** (inverse document frequency)
terimin derlemdeki seyrekliğidir; seyrek terim çok, yaygın terim az ağırlık taşır. **Alan
uzunluğu** düzeltmesi ise üç sözcüklük bir kitap adındaki eşleşmeyle on dört sözcüklük
bir özetteki eşleşmeyi ayırır. Bu ders puanlayıcıyı kurar, bir belgenin sırasının hangi
bileşenden geldiğini gösterir ve sonucun ne kadarının doğru olduğunu ölçmek için iki
ölçü tanımlar.

#### Derlem, Model ve Gereksinim

Derlem önceki derslerin modülüdür: 6.000 kitap kaydı, tohum 271828, belirlenimli üretim.
**SI8 (varsayım):** puanlanan alanlar `ad` ve `ozet`; alan ağırlığı yoktur, ikisi de bire
bir toplanır. Terim sıklığı doyum katsayısı 1,2, alan uzunluğu düzeltme katsayısı
0,75'tir; ters belge sıklığı, derlem boyutuyla belge sıklığının oranından logaritmayla
bulunur. **SI9 (varsayım):** okurun gereksinimi sorgudan geniştir — "çocuk ve öykü
etiketli, türkçe bir kitap" arıyordur, ama arama kutusuna yalnız "çocuk öykü" yazar.
`etiket`, `dil` ve `yil` alanları bu derste dizinlenmez; puanlayıcı gereksinimin dil
ölçütünü göremez. Bu, gerçek bir katalogda olağan durumdur.

```js
// derlem.mjs — kutuphane katalogu derlemi ve konum bilgili ters dizin.
// Tohum 271828, 6000 belge; konudaki butun olcumler bu modulu paylasir.
export const TOHUM = 271828, N = 6000;
let c = TOHUM;                                       // gorunur tohum, belirlenimli uretec
const r = () => { c = (c + 0x6d2b79f5) | 0; let t = Math.imul(c ^ (c >>> 15), 1 | c);
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; };
const sec = (d, e = 1) => d[Math.floor(r() ** e * d.length)];   // e>1: bas ogeler sik, son ogeler seyrek

const SIFAT = ["eleştirel", "kısa", "resimli", "seçme", "karşılaştırmalı"];
const TUR = ["deneme", "öykü", "roman", "inceleme", "antoloji"];
const ALAN = ["çocuk", "tarih", "bilim", "felsefe", "toplum"];
const SON = ["seçkisi", "kitabı", "derlemesi", "dizisi"];
const NIT = ["kapsamlı", "özlü", "tanıtıcı", "tartışmalı"];
const KONU = ["eğitim", "göç", "kent", "bellek", "doğa", "müzik", "hukuk", "kimlik", "emek", "aile",
  "savaş", "gelenek", "sağlık", "yolculuk", "mimarlık", "arkeoloji", "denizcilik", "gökbilim"];
const YAZAR = ["Ahmet Yıldız", "Zeynep Kaya", "Kemal Demir", "Elif Şahin", "Necati Aydın"];
const DIL = ["türkçe", "ingilizce", "almanca"];

export const KATALOG = [];
for (let i = 1; i <= N; i += 1) {
  const s = sec(SIFAT, 1.5), t = sec(TUR, 1.5), a = sec(ALAN, 2), k = r();
  const desen = k < 0.18 ? `${s} ${t}` : k < 0.30 ? `${s} bir ${t}`
    : k < 0.40 ? `${s} ve kuramsal ${t}` : k < 0.50 ? `${t} ve ${s} inceleme`
    : k < 0.70 ? `${s} bir anlatı` : k < 0.88 ? `çağdaş bir ${t}` : "alan yazını taraması";
  KATALOG.push({ id: `K-${String(i).padStart(4, "0")}`, ad: `${a} ${t} ${sec(SON)}`,
    ozet: `${a} alanında ${desen}; ${sec(KONU, 3)} ve ${sec(KONU, 3)} üzerine ` +
      `${sec(NIT)} bir ${sec(TUR, 1.5)} olarak okunabilir.`,
    etiket: [a, t], yazar: sec(YAZAR), yil: 1990 + Math.floor(r() * 35), dil: sec(DIL, 2) });
}

export const belirtecle = (m) => m.toLocaleLowerCase("tr").split(/[^\p{L}\p{N}]+/u).filter(Boolean);

// Ters dizin: "alan|terim" -> Map(belge sirasi -> konumlar); uzunluk: alan basina belirtec sayisi.
export function tersDizin(alanlar) {
  const g = new Map(), uzunluk = new Map();
  KATALOG.forEach((b, i) => {
    for (const alan of alanlar) {
      const tk = belirtecle(String(b[alan]));
      uzunluk.set(`${alan}|${i}`, tk.length);
      tk.forEach((t, p) => {
        let l = g.get(`${alan}|${t}`); if (!l) g.set(`${alan}|${t}`, (l = new Map()));
        let k2 = l.get(i); if (!k2) l.set(i, (k2 = [])); k2.push(p);
      });
    }
  });
  return { g, uzunluk };
}
```

#### Puanlayıcı

```js
// puan.mjs — ilgililik puanlayicisi. Ayni dizinde derlem.mjs bulunur.
// Puan uc bilesenden gelir: terim sikligi (doyuma ulasan), ters belge sikligi, alan uzunlugu.
import { KATALOG, TOHUM, tersDizin } from "./derlem.mjs";
const { g, uzunluk } = tersDizin(["ad", "ozet"]);          // etiket, dil ve yil dizinlenmez
const N = KATALOG.length, ALANLAR = ["ad", "ozet"], K = 1.2, B = 0.75;
const liste = (alan, terim) => g.get(`${alan}|${terim}`) ?? new Map();
const ortUzunluk = Object.fromEntries(ALANLAR.map((a) => {
  let t = 0; for (let d = 0; d < N; d += 1) t += uzunluk.get(`${a}|${d}`); return [a, t / N];
}));
const idf = (alan, terim) => { const df = liste(alan, terim).size; return Math.log(1 + (N - df + 0.5) / (df + 0.5)); };

function bilesen(alan, terim, d, kip) {
  const tf = (liste(alan, terim).get(d) ?? []).length;
  if (tf === 0) return 0;
  if (kip === "yalniz terim sikligi") return tf;
  if (kip === "terim x ters belge sikligi") return tf * idf(alan, terim);
  const uz = uzunluk.get(`${alan}|${d}`) / ortUzunluk[alan];      // alan uzunlugu duzeltmesi
  return idf(alan, terim) * (tf * (K + 1)) / (tf + K * (1 - B + B * uz));
}
const puanla = (d, kip) => TERIM.reduce((p, t) => p + ALANLAR.reduce((q, a) => q + bilesen(a, t, d, kip), 0), 0);
const TERIM = ["çocuk", "öykü"], KIPLER = ["yalniz terim sikligi", "terim x ters belge sikligi", "tam model"];

const aday = [...Array(N).keys()].filter((d) => TERIM.every((t) => ALANLAR.some((a) => liste(a, t).has(d))));
const ilgili = new Set(aday.filter((d) => KATALOG[d].etiket.includes("çocuk")
  && KATALOG[d].etiket.includes("öykü") && KATALOG[d].dil === "türkçe"));
console.log(`derlem ${N} belge, tohum ${TOHUM};  soru "çocuk öykü"`);
console.log(`aday kume ${aday.length} belge;  gereksinim: çocuk+öykü etiketli ve türkçe kitap -> ${ilgili.size} belge ilgili`);
for (const t of TERIM) for (const a of ALANLAR)
  console.log(`  ${a}|${t}`.padEnd(13) + `belge sikligi ${String(liste(a, t).size).padStart(4)}   ` +
    `ters belge sikligi ${idf(a, t).toFixed(3)}   ortalama alan uzunlugu ${ortUzunluk[a].toFixed(2)}`);

const sirali = (kip) => [...aday].sort((x, y) => puanla(y, kip) - puanla(x, kip) || x - y);
const tam = sirali("tam model");
console.log("puanin bilesenleri: birinci ve besyuzuncu basamak");
for (const d of [tam[0], tam[499]]) {
  const par = [];
  for (const t of TERIM) for (const a of ALANLAR) {
    const k = bilesen(a, t, d, "tam model");
    if (k > 0) par.push(`${a}|${t} ${k.toFixed(2)} (siklik ${(liste(a, t).get(d) ?? []).length}, uzunluk ${uzunluk.get(`${a}|${d}`)})`);
  }
  console.log(`  ${String(tam.indexOf(d) + 1).padStart(3)}. ${KATALOG[d].id} puan ${puanla(d, "tam model").toFixed(2)} = ${par.join(" + ")}`);
}

const kesinlik = (l, k) => l.slice(0, k).filter((d) => ilgili.has(d)).length / Math.min(k, l.length);
const anma = (l, k) => l.slice(0, k).filter((d) => ilgili.has(d)).length / ilgili.size;
console.log("siralama modeli".padEnd(28) + "farkli puan  ilk on ortak  en buyuk oynama  kesinlik@10  kesinlik@50");
for (const kip of KIPLER) {
  const l = sirali(kip);
  const deger = new Set(aday.map((d) => puanla(d, kip).toFixed(6))).size;
  const ortak = l.slice(0, 10).filter((d) => tam.slice(0, 10).includes(d)).length;
  const oynama = Math.max(...aday.map((d) => Math.abs(l.indexOf(d) - tam.indexOf(d))));
  console.log(kip.padEnd(28) + String(deger).padStart(11) + String(ortak).padStart(14) +
    String(oynama).padStart(17) + kesinlik(l, 10).toFixed(3).padStart(13) + kesinlik(l, 50).toFixed(3).padStart(13));
}
console.log(`siralamasiz kume: kesinlik ${(ilgili.size / aday.length).toFixed(3)}, anma 1.000`);
for (const k of [10, 50, 200, 567, 984])
  console.log(`  ilk ${String(k).padStart(3)} sonuc: kesinlik ${kesinlik(tam, k).toFixed(3)}  anma ${anma(tam, k).toFixed(3)}`);
const yanlis = tam.slice(0, 50).filter((d) => !ilgili.has(d));
console.log(`ilk 50 sonuctaki ${yanlis.length} yanlis belgenin ilki ${KATALOG[yanlis[0]].id}: ` +
  `${KATALOG[yanlis[0]].ad}, dil ${KATALOG[yanlis[0]].dil}, etiket ${KATALOG[yanlis[0]].etiket.join("+")}`);
```

```
derlem 6000 belge, tohum 271828;  soru "çocuk öykü"
aday kume 984 belge;  gereksinim: çocuk+öykü etiketli ve türkçe kitap -> 347 belge ilgili
  ad|çocuk   belge sikligi 2678   ters belge sikligi 0.807   ortalama alan uzunlugu 3.00
  ozet|çocuk belge sikligi 2678   ters belge sikligi 0.807   ortalama alan uzunlugu 14.02
  ad|öykü    belge sikligi 1271   ters belge sikligi 1.552   ortalama alan uzunlugu 3.00
  ozet|öykü  belge sikligi 1869   ters belge sikligi 1.166   ortalama alan uzunlugu 14.02
puanin bilesenleri: birinci ve besyuzuncu basamak
    1. K-0078 puan 4.83 = ad|çocuk 0.81 (siklik 1, uzunluk 3) + ozet|çocuk 0.83 (siklik 1, uzunluk 13) + ad|öykü 1.55 (siklik 1, uzunluk 3) + ozet|öykü 1.64 (siklik 2, uzunluk 13)
  500. K-3736 puan 3.17 = ad|çocuk 0.81 (siklik 1, uzunluk 3) + ozet|çocuk 0.81 (siklik 1, uzunluk 14) + ad|öykü 1.55 (siklik 1, uzunluk 3)
siralama modeli             farkli puan  ilk on ortak  en buyuk oynama  kesinlik@10  kesinlik@50
yalniz terim sikligi                  3             3              468        0.700        0.640
terim x ters belge sikligi            4             3              342        0.700        0.640
tam model                            10            10                0        0.600        0.640
siralamasiz kume: kesinlik 0.353, anma 1.000
  ilk  10 sonuc: kesinlik 0.600  anma 0.017
  ilk  50 sonuc: kesinlik 0.640  anma 0.092
  ilk 200 sonuc: kesinlik 0.615  anma 0.354
  ilk 567 sonuc: kesinlik 0.612  anma 1.000
  ilk 984 sonuc: kesinlik 0.353  anma 1.000
ilk 50 sonuctaki 18 yanlis belgenin ilki K-0171: çocuk öykü seçkisi, dil almanca, etiket çocuk+öykü
```

#### Bir Sıra Nereden Geliyor

İlk tablo bileşenlerin kaynağını verir. "Çocuk" 2.678 belgede geçer ve ters belge sıklığı
0,807'dir; "öykü" kitap adlarında 1.271 belgede geçer ve 1,552 taşır. Aynı terim özette
1.869 belgede geçtiği için orada yalnız 1,166 eder — **aynı sözcüğün ağırlığı alandan
alana değişir**, çünkü seyreklik alan içinde ölçülür.

Birinci basamaktaki K-0078'in puanı 4,83'tür ve dördü de sıfırdan büyük dört bileşenin
toplamıdır. Bu puanın 3,19'u, yani %66'sı "öykü" teriminden gelir; "çocuk" terimi 1,64
katkı yapar. Sıralamayı belirleyen şey, sorgunun **seyrek** terimidir. Beş yüzüncü
basamaktaki K-3736 aynı üç bileşene sahiptir ve tek farkı dördüncü bileşenin
bulunmamasıdır: özetinde "öykü" hiç geçmez. Bir bileşenin yokluğu 1,66 puan ve 499
basamak eder. Katalogda bu iki kitabın ikisi de çocuk öykü kitabıdır; aralarındaki tek
fark, birinin özetinin türü iki kez anmasıdır.

Alan uzunluğunun payı daha küçük ama görünürdür. K-0078'in özeti 13 belirteçtir,
K-3736'nınki 14; ortalama 14,02'dir. Bu bir belirteçlik fark `ozet|çocuk` bileşenini
0,83'ten 0,81'e indirir. Kitap adı alanında ortalama uzunluk tam 3,00 olduğu için
düzeltme etkisizdir — her adın üç sözcük olduğu bir derlemde alan uzunluğu ayırt edici
değildir. Düzeltme, uzunluğu değişen alanlarda çalışır.

#### Üç Model, Üç Sıra

Model tablosu bileşenlerin sıraya ne yaptığını ölçer. Yalnız terim sıklığı kullanıldığında
984 aday belge yalnız **3 ayrı puan değeri** alır: liste üç yığına ayrılır ve yığın içindeki
sıra belge numarasına kalır. Ters belge sıklığı eklendiğinde değer sayısı 4'e, alan
uzunluğu düzeltmesi de eklendiğinde 10'a çıkar. Çözünürlük arttıkça beraberlikler çözülür.
Tam modele göre en büyük basamak oynaması, yalnız terim sıklığı modelinde 468, ara modelde
342 basamaktır — 984 belgelik bir listede bu, bir belgenin ortasından başına gitmesidir.

İlk on sonuçta üç modelin ortak belgesi yalnız 3 tanedir. Kesinlik değerlerine bakıldığında
tam model ilk onda 0,600 ile öteki iki modelin 0,700'ünün altında kalır. Bu, tam modelin
kötü olduğunu göstermez: ilk elli sonuçta üç model de 0,640 verir ve fark, aşağıda
görüleceği gibi modelin hiç göremediği bir ölçütten doğan gürültüdür. On sonuçluk bir
örneklem üzerinden model seçmek, ölçünün kendisini yanlış kullanmaktır.

#### Kesinlik ve Anma

İki ölçü sonucun ne kadar doğru olduğunu ayrı yönlerden sorar. **Kesinlik** (precision)
dönen sonuçların ne kadarının ilgili olduğudur: yanlış getirilen belgeyi cezalandırır.
**Anma** (recall) ilgili belgelerin ne kadarının döndüğüdür: kaçırılan belgeyi
cezalandırır. Aynı ikili yazılım testinde başka adlarla anılır — İşlevsel Olmayan Test
kursundaki statik güvenlik taraması dersinde bir bulgunun yanlış getirilmesi **yanlış
alarm**, gerçek bir açığın hiç bildirilmemesi **kaçırma** olarak geçer; kesinlik yanlış
alarmın, anma kaçırmanın ölçüsüdür.

Sıralamasız küme 984 belgedir ve içinde 347 ilgili belge vardır: kesinlik 0,353, anma
1,000. Boole sorgusunun tipik dengesi budur — hiçbir ilgili belge kaçmaz, ama üçte ikisi
gereksizdir. Sıralama bu dengeyi kesme noktasına bağlar. İlk 50 sonuçta kesinlik 0,640'a
çıkar, anma 0,092'ye düşer. İlk 200'de kesinlik 0,615, anma 0,354'tür. İlk 567'de anma
1,000'e ulaşır: 347 ilgili belgenin tamamı listenin ilk %58'indedir, yani puanlayıcı hiç
ilgili belgeyi listenin dibine atmamıştır.

Kesinliğin 0,64 dolayında takılması dersin son bulgusudur. İlk 50 sonuçtaki 18 yanlış
belgenin ilki K-0171'dir ve adı "çocuk öykü seçkisi"dir — okurun aradığı türden bir
kitaptır, yalnız almancadır. Puanlayıcı bu belgeyi haklı olarak üste koymuştur, çünkü
metinsel olarak kusursuz eşleşmedir; gereksinimi karşılamayan yanı puanlayıcının hiç
görmediği bir alandadır. Buradan çıkan kural şudur: **bir ölçüt puanlayıcının göremediği
bir alandaysa, hiçbir ağırlık ayarı kesinliği artırmaz.** Doğru araç önceki dersin
süzgecidir; puan ayarı yalnız modelin gördüğü alanlarda iş görür.

#### Özet

- İlgililik puanı üç bileşenden gelir: doyuma ulaşan terim sıklığı, ters belge sıklığı ve
  alan uzunluğu düzeltmesi; aynı terim kitap adında 1,552, özette 1,166 ağırlık taşır.
- Birinci basamaktaki belgenin 4,83 puanının %66'sı sorgunun seyrek teriminden gelir; bir
  bileşenin yokluğu 1,66 puan ve 499 basamak eder.
- Bileşen eklendikçe puanın çözünürlüğü artar: 984 aday belge 3, 4 ve 10 ayrı puan değeri
  alır; en büyük basamak oynaması 468'dir.
- Kesinlik dönen sonuçların ne kadarının ilgili olduğu, anma ilgili belgelerin ne kadarının
  döndüğüdür; sıralamasız kümede kesinlik 0,353 ve anma 1,000'dir.
- Sıralama kesinliği ilk 50 sonuçta 0,640'a çıkarır ve ilk 567 sonuçta anmayı 1,000'e
  taşır; ama gereksinimin dil ölçütü dizinlenmediği için kesinlik 0,64 dolayında tavana
  vurur.

#### Sonraki Adım

Puanlayıcı çalışıyor ve ürettiği sıranın hangi bileşenden geldiği artık görülebiliyor.
Bu, sıranın istenen sıra olduğu anlamına gelmez. Kütüphane için kitap adındaki eşleşme
özetteki eşleşmeden daha güçlü bir işarettir, oysa şu anki modelde ikisi bire bir
toplanıyor. Yeni basımların eski basımlara göre öne çıkması istenebilir, ama yayın yılı
puana hiç girmiyor. Ödünç alınma sayısı gibi metinle ilgisi olmayan bir büyüklük de
sıraya karışabilir. Bunların hepsi puanın bileşenlerine dışarıdan yapılan
müdahalelerdir ve her biri kümeyi değiştirmeden sırayı değiştirir — tıpkı isteğe bağlı
koşulun yaptığı gibi, ama bu kez ölçülü bir katsayıyla. Sonraki ders bu ayarları kurar
ve her ayarın ilk on sonuçta kaç belgeyi yer değiştirdiğini, kesinliği kaç puan
oynattığını sayar.

### Puan Ayarlama

Source: https://academia.sh/tr/kurslar/arama-motorlari/puan-ayarlama

Aynı sorgunun sırasını yeniden yazan iki ayar: alan ağırlığının ilk on sonuçta kaç belgeyi değiştirdiği ve bir belgeyi kaç basamak oynattığı, alan seçiminin ağırlıktan farklı olarak eşleşen kümeyi daraltması, işlev tabanlı puanda yenilik ile popülerliğin ilgililiği hangi ağırlıkta bastırdığı ve iki birleştirme kipinin farkı.

Bir önceki ders ilgililik puanını bileşenlerine ayırdı: bir terimin belgede kaç kez geçtiği, kaç
belgede geçtiği ve belgenin ne kadar uzun olduğu. Ayrıştırma yapıldı, ama bileşenlerin birbirine
göre **ağırlığı** verili kabul edildi; her alan aynı sayıldı ve bu bir karar olarak hiç
sorgulanmadı. Katalogda soru somuttur: "deniz gemi" araması yapan okur için bir sözcüğün kitabın
**adında** geçmesiyle **özetinde** geçmesi aynı şey midir? Yazar adında geçmesi ne anlama gelir?

Bu ders iki ayarı ölçer. **Alan ağırlığı**, aynı terimin hangi alanda bulunduğuna göre puana giren
katsayıdır. **İşlev tabanlı puan**, metinle ilgisi olmayan bir niceliğin — yayın yılı, ödünç sayısı
— puana katılmasıdır. İkisi de "daha alakalı sonuç" gerekçesiyle savunulur; burada gerekçe sayıya
çevrilir.

#### Derlem ve Dört Alan

Ölçümün tamamı tek bir derlem üzerinde yapılır: kütüphane katalogundan üretilmiş 1.200 kitap kaydı,
tohum 20260801. Her kayıt dört alanda dizinlenir.

| Kod | Varsayım | Değer | Gerekçe |
|---|---|---|---|
| SI10 | çözümleyici zinciri | önceki konuda kuruldu | belirteçler normalleştirilmiş gelir |
| SI11 | dizinlenen alan | ad, özet, konu, yazar | her biri ayrı gönderi listesiyle |
| SI12 | konu etiketi | dörtte birinde metnin temasından farklı | etiket yayınevi kararıdır |
| SI13 | ödünç sayısı ve yıl | belge alanı olarak dizinde durur | işlev tabanlı puanın girdisi |

SI12 bu dersin ayırt edici varsayımıdır: alanlar birbirinin kopyası olsaydı ağırlık hiçbir şeyi
oynatmazdı.

```js
// derlem.mjs — kutuphane katalogu: tohumlu derlem, ters dizin ve alan basina puanlayici.
export const N = 1200, TOHUM = 20260801, ALANLAR = ["ad", "ozet", "konu", "yazar"];
let d = TOHUM;                                            // 32 bit uretec, tasma yok
export const rast = () => { d = (d + 0x6D2B79F5) | 0; let t = Math.imul(d ^ (d >>> 15), 1 | d);
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 2 ** 32; };
const sec = (a) => a[Math.floor(rast() * a.length)];
const TEMA = [["öykü|hikâye", "edebiyat", "kısa seçki derleme anlatı kurgu taşra"],
  ["masal", "çocuk edebiyatı", "çocuk resimli okul genç orman uyku"],
  ["tarih", "tarih", "osmanlı cumhuriyet arşiv belge kronik vakıf"],
  ["deniz", "gezi", "kıyı balıkçı liman gemi ada fener"],
  ["matematik", "bilim", "geometri sayı kanıt kuram çözüm olasılık"],
  ["şiir", "şiir", "dize toplu divan çeviri seçme aruz"]]
  .map(([k, konu, a]) => ({ k: k.split("|"), konu, ana: a.split(" ") }));
const ORTAK = "kitap cilt baskı yayın inceleme notlar giriş sözlük".split(" ");
const AD = "Ahmet Elif Selim Nuray Kemal Deniz Ayşe Ozan Meral Barış".split(" ");
const SOY = "Yıldız Aksu Demir Karaca Toprak Şen".split(" ");

export const belgeler = Array.from({ length: N }, (_, id) => {
  const t = sec(TEMA), cek = t.k[t.k.length > 1 && rast() < 0.5 ? 1 : 0], oz = [cek, cek];
  const ad = [...new Set([cek, sec(t.ana), sec(t.ana)])];
  t.ana.forEach((s, j) => { if (rast() < 1 / (1 + j * 0.42)) oz.push(s); });
  ORTAK.forEach((s, j) => { if (rast() < 0.55 / (1 + j * 0.28)) oz.push(s); });
  if (rast() < 0.5) oz.push(sec(sec(TEMA).ana));          // baska temadan sizan sozcuk
  for (let k = oz.length - 1; k > 0; k -= 1) { const j = Math.floor(rast() * (k + 1));
    [oz[k], oz[j]] = [oz[j], oz[k]]; }
  // konu etiketi yayinevi kararidir: belgelerin dortte birinde metinle ayni temada degil
  return { id, ad: ad.join(" "), ozet: oz.join(" "), konu: (rast() < 0.25 ? sec(TEMA) : t).konu,
    yazar: `${sec(AD)} ${sec(SOY)}`, yil: 1975 + Math.floor(rast() ** 0.6 * 50),
    odunc: Math.floor(rast() ** 3 * 400) };
});

export const belirtec = (s) => s.toLocaleLowerCase("tr").match(/[\p{L}\p{N}]+/gu) ?? [];
export function dizinKur(bs) {                            // alan basina ters dizin
  const dz = {};
  for (const a of ALANLAR) {
    const gonderi = new Map(), boy = new Float64Array(bs.length);
    for (const b of bs) {
      const ts = belirtec(b[a]), say = new Map();
      boy[b.id] = ts.length;
      for (const t of ts) say.set(t, (say.get(t) ?? 0) + 1);
      for (const [t, tf] of say) {
        if (!gonderi.has(t)) gonderi.set(t, []);
        gonderi.get(t).push({ id: b.id, tf });
      }
    }
    dz[a] = { gonderi, boy, ort: boy.reduce((x, y) => x + y, 0) / bs.length };
  }
  return dz;
}
export function alanPuan(dz, a, terimler) {               // tf, ters belge sikligi, uzunluk
  const k1 = 1.2, b = 0.75, p = new Map();
  for (const t of terimler) {
    const g = dz[a].gonderi.get(t);
    if (!g) continue;
    const idf = Math.log(1 + (N - g.length + 0.5) / (g.length + 0.5));
    for (const e of g) {
      const nrm = 1 - b + (b * dz[a].boy[e.id]) / dz[a].ort;
      p.set(e.id, (p.get(e.id) ?? 0) + (idf * e.tf * (k1 + 1)) / (e.tf + k1 * nrm));
    }
  }
  return p;
}
export function ara(dz, sorgu, agirlik) {                 // alan agirliklariyla toplam puan
  const terimler = belirtec(sorgu), toplam = new Map();
  for (const a of ALANLAR) {
    if (!agirlik[a]) continue;
    for (const [id, v] of alanPuan(dz, a, terimler))
      toplam.set(id, (toplam.get(id) ?? 0) + agirlik[a] * v);
  }
  return [...toplam].sort((x, y) => y[1] - x[1] || x[0] - y[0]);
}
```

Puanlayıcı alan başına ayrı çalışır: her alanın kendi gönderi listesi, kendi ortalama uzunluğu ve
kendi ters belge sıklığı vardır; toplam puan alan puanlarının ağırlıklı toplamıdır. Ağırlık sıfırsa
o alan hiç taranmaz.

#### Ağırlık Sırayı Değiştirir, Alan Seçimi Kümeyi

Altı yapılandırma aynı sorguyu koşar. Dördü yalnız ağırlığı değiştirir, ikisi taranan alan kümesini
daraltır.

```js
// puan.mjs — ayni sorgu, degisen alan agirliklari: kume, ilk on, basamak ve bedel.
import { belgeler, dizinKur, ara, alanPuan, belirtec, ALANLAR, N, TOHUM } from "./derlem.mjs";
const dz = dizinKur(belgeler), SORGU = "deniz gemi", terim = belirtec(SORGU);
const giris = (a) => terim.reduce((s, t) => s + (dz[a].gonderi.get(t)?.length ?? 0), 0);
const KUR = { "eşit": { ad: 1, ozet: 1, konu: 1, yazar: 1 },
  "ad 5 kat": { ad: 5, ozet: 1, konu: 1, yazar: 1 },
  "ad 12 kat": { ad: 12, ozet: 1, konu: 1, yazar: 1 },
  "yazar 5 kat": { ad: 1, ozet: 1, konu: 1, yazar: 5 },
  "yalnız ad": { ad: 1 }, "yalnız özet": { ozet: 1 } };

const temel = ara(dz, SORGU, KUR["eşit"]);
const sira = (r) => new Map(r.map(([id], i) => [id, i + 1])), tSira = sira(temel);
const tOn = new Set(temel.slice(0, 10).map(([id]) => id));
console.log(`derlem ${N} belge, tohum ${TOHUM}; sorgu "${SORGU}"`);
console.log("alan terim sayısı / ortalama uzunluk: " +
  ALANLAR.map((a) => `${a} ${dz[a].gonderi.size}/${dz[a].ort.toFixed(1)}`).join(", "));
console.log("dokunulan gönderi girişi: " + ALANLAR.map((a) => `${a} ${giris(a)}`).join(", ") + "\n");
console.log("yapılandırma".padEnd(14) + "eşleşen".padStart(9) + "giriş".padStart(7) +
  "ilk10 değişen".padStart(15) + "en büyük sıçrama".padStart(18) + "  ilk sıradaki");
for (const [ad, w] of Object.entries(KUR)) {
  const r = ara(dz, SORGU, w), y = sira(r);
  const degisen = r.slice(0, 10).filter(([id]) => !tOn.has(id)).length;
  let en = 0, kim = -1;
  for (const [id] of temel.slice(0, 50))
    if (y.has(id) && Math.abs(tSira.get(id) - y.get(id)) > en) {
      en = Math.abs(tSira.get(id) - y.get(id)); kim = id;
    }
  const b = belgeler[r[0][0]];
  console.log(ad.padEnd(14) + `${r.length}`.padStart(9) +
    `${Object.keys(w).reduce((s, a) => s + giris(a), 0)}`.padStart(7) +
    `${degisen}`.padStart(15) + (kim < 0 ? "temel" : `${en} (belge ${kim})`).padStart(18) +
    `  ${b.ad} / ${b.yazar}`);
}

const bilesen = (id) => ALANLAR
  .map((a) => `${a} ${(alanPuan(dz, a, terim).get(id) ?? 0).toFixed(2)}`).join(", ");
const yz = ara(dz, SORGU, KUR["yazar 5 kat"])[0][0];
console.log(`\nyazar 5 kat ile ilk sıraya gelen belge ${yz}: ${belgeler[yz].ad} / ` +
  `${belgeler[yz].yazar}\n  eşit ağırlıkta ${tSira.get(yz)}. sırada; bileşen: ${bilesen(yz)}`);
console.log(`eşit ağırlıkta ilk sıradaki belge ${temel[0][0]}: ${belgeler[temel[0][0]].ad} / ` +
  `${belgeler[temel[0][0]].yazar}\n  bileşen: ${bilesen(temel[0][0])}`);
```

```
derlem 1200 belge, tohum 20260801; sorgu "deniz gemi"
alan terim sayısı / ortalama uzunluk: ad 43/2.9, ozet 51/8.4, konu 7/1.1, yazar 16/2.0
dokunulan gönderi girişi: ad 265, ozet 302, konu 0, yazar 131

yapılandırma    eşleşen  giriş  ilk10 değişen  en büyük sıçrama  ilk sıradaki
eşit                325    698              0             temel  deniz kıyı gemi / Ozan Demir
ad 5 kat            325    698              4    44 (belge 365)  deniz gemi / Barış Toprak
ad 12 kat           325    698              4    44 (belge 365)  deniz gemi / Barış Toprak
yazar 5 kat         325    698             10   131 (belge 143)  deniz gemi kıyı / Deniz Aksu
yalnız ad           199    265              9  144 (belge 1122)  deniz gemi / Selim Demir
yalnız özet         211    302              8   159 (belge 852)  deniz fener balıkçı / Ozan Toprak

yazar 5 kat ile ilk sıraya gelen belge 622: deniz gemi kıyı / Deniz Aksu
  eşit ağırlıkta 17. sırada; bileşen: ad 4.59, ozet 2.69, konu 0.00, yazar 2.21
eşit ağırlıkta ilk sıradaki belge 143: deniz kıyı gemi / Ozan Demir
  bileşen: ad 4.59, ozet 5.46, konu 0.00, yazar 0.00
```

Belge içerikleri tohuma bağlıdır; dokunulan gönderi girişi sayısı değildir, o sayı doğrudan gönderi
listelerinin uzunluğudur.

İlk sütun dört satırda aynı: **325**. Ağırlık değiştirmek eşleşen kümeye dokunmaz, çünkü ağırlık
puana giren bir çarpandır, eşleşme koşulu değildir. Bir belge sorgu terimlerinden en az birini
taşıyorsa kümededir; ağırlık yalnız nerede duracağını belirler. Son iki satırda küme 199 ve 211'e
iniyor, çünkü orada değişen şey ağırlık değil **hangi alanların tarandığıdır**. Yalnız ad alanında
arandığında adında hiçbir sorgu terimi geçmeyen 126 belge kümeden çıkar. Ağırlık ile alan seçimi aynı ayar gibi
konuşulur, oysa biri sırayı, öteki kümeyi değiştirir.

Sıra tarafında ilk sürpriz `ad 5 kat` ile `ad 12 kat` satırlarının aynı olmasıdır: ilk onda dört
belge değişiyor, en büyük sıçrama kırk dört basamak, katsayıyı iki buçuk kat daha artırmak hiçbir
şey eklemiyor. Ağırlık büyüdükçe sıralama ad alanının kendi iç sırasına yakınsar ve orada durur;
"biraz daha yukarı çekelim" cümlesinin karşılığı bir noktadan sonra boştur.

İkinci sürpriz `yazar 5 kat` satırındadır: ilk onun **onu birden** değişiyor. Nedeni çıktının alt
bölümünde görünüyor. Yazar alanında "Deniz" bir addır; 622 numaralı belge eşit ağırlıkta on yedinci
sıradayken yazar bileşeninin beşle çarpılması onu ilk sıraya taşıyor, eşit ağırlıkta birinci olan
143 numaralı belgenin yazar bileşeni sıfır olduğu için o belge 132. sıraya düşüyor. Tek bir katsayı,
yüz otuz bir basamak. Alan ağırlığı burada ilgililiği artırmıyor, **alan karışmasını** büyütüyor.

Konu alanı satır boyunca sessiz: dokunulan gönderi girişi sıfır, çünkü "deniz" ve "gemi" hiçbir konu
etiketinde geçmiyor. Konu ağırlığını yüze çıkarmak bu sorguda hiçbir şeyi değiştirmez; bir ağırlığın
etkisi o alanda sorgu teriminin bulunmasına bağlıdır.

#### İşlev Tabanlı Puan ve Bastırma Noktası

İkinci ayar metnin dışından gelir. Kütüphane "yeni ve çok ödünç alınan kitaplar yukarıda çıksın"
ister. Bu istek puana iki biçimde katılabilir: metin puanıyla **çarpılarak** ya da metin puanına
**eklenerek**. İkisi aynı isteğin gerçekleştirimidir ve sonuçları aynı değildir.

```js
// islev.mjs — islev tabanli puan: iki birlestirme kipi ve ilgililigin bastirilma noktasi.
import { belgeler, dizinKur, ara } from "./derlem.mjs";
const dz = dizinKur(belgeler), SORGU = "deniz gemi";
const metin = ara(dz, SORGU, { ad: 1, ozet: 1, konu: 1, yazar: 1 }), PM = metin[0][1];
const mSira = new Map(metin.map(([id], i) => [id, i + 1]));
const mOn = new Set(metin.slice(0, 10).map(([id]) => id));
const kat = (b) => 0.6 * (Math.log1p(b.odunc) / Math.log1p(400)) + 0.4 * ((b.yil - 1975) / 49);
const on = (f) => metin.map(([id, p]) => [id, f(p, belgeler[id])])
  .sort((x, y) => y[1] - x[1] || x[0] - y[0]).slice(0, 10);
const say = (r) => `${r.filter(([id]) => !mOn.has(id)).length}/` +
  `${r.filter(([id]) => mSira.get(id) > 50).length}`;
const ort = (r, f) => (r.reduce((s, [id]) => s + f(belgeler[id]), 0) / r.length).toFixed(0);

console.log(`sorgu "${SORGU}", ${metin.length} eşleşen belge; küme her ağırlıkta aynı`);
console.log(`en yüksek metin puanı ${PM.toFixed(2)}; ilk on ödünç ortalaması ` +
  `${ort(metin.slice(0, 10), (b) => b.odunc)}, yıl ortalaması ` +
  `${ort(metin.slice(0, 10), (b) => b.yil)}\n`);
console.log("w".padEnd(6) + "çarpımsal".padStart(11) + "toplamsal".padStart(11) +
  "ort ödünç".padStart(11) + "ort yıl".padStart(9) + "  toplamsal kipte ilk sıradaki");
for (const w of [0.25, 0.5, 1, 2, 4, 8]) {
  const c = on((p, b) => p * (1 + w * kat(b))), t = on((p, b) => p + w * PM * kat(b));
  const b = belgeler[t[0][0]];
  console.log(`${w}`.padEnd(6) + say(c).padStart(11) + say(t).padStart(11) +
    ort(t, (x) => x.odunc).padStart(11) + ort(t, (x) => x.yil).padStart(9) +
    `  ${b.ad} (${b.yil}, ${b.odunc} ödünç, metinde ${mSira.get(b.id)}.)`);
}
```

```
sorgu "deniz gemi", 325 eşleşen belge; küme her ağırlıkta aynı
en yüksek metin puanı 10.05; ilk on ödünç ortalaması 100, yıl ortalaması 2006

w       çarpımsal  toplamsal  ort ödünç  ort yıl  toplamsal kipte ilk sıradaki
0.25          4/0        5/0        185     2013  deniz kıyı gemi (2008, 235 ödünç, metinde 1.)
0.5           5/0        6/0        191     2013  deniz gemi balıkçı (2013, 398 ödünç, metinde 14.)
1             6/0        6/0        191     2013  deniz gemi balıkçı (2013, 398 ödünç, metinde 14.)
2             6/0        7/2        198     2017  deniz gemi balıkçı (2013, 398 ödünç, metinde 14.)
4             6/0        8/5        257     2018  deniz gemi balıkçı (2013, 398 ödünç, metinde 14.)
8             6/0       10/8        324     2021  deniz kıyı balıkçı (2024, 301 ödünç, metinde 123.)
```

Sütunlardaki iki sayı şöyle okunur: ilk on sonuçtan kaçı metin sıralamasının ilk onunda değildi,
bunların kaçı metin sıralamasında ellinci sıradan **sonra** geliyordu.

Çarpımsal kip w bir olduktan sonra donuyor: dört, beş, altı ve orada kalıyor; ikinci sayı hiçbir
ağırlıkta sıfırdan çıkmıyor. Nedeni çarpanın yapısıdır: metin puanı çarpan olarak kaldığı için
sıralama, metin puanı ile katsayının çarpımına yakınsar ve metinde çok zayıf bir belge bu çarpımı
kazanamaz. Çarpımsal kip ilk onu **yeniden düzenler**, doldurmaz.

Toplamsal kipte katkı, metin puanından bağımsız bir büyüklük olarak eklenir ve w büyüdükçe metin
puanı toplamın içinde erir. w dörtte ilk onun beşi metin sıralamasının elli sonrasından geliyor;
sayılabilir bir ölçüt gerekirse **bastırma noktası buradadır**: ilk onun yarısı artık sorguyla
değil, ödünç sayısıyla seçilmiştir. w sekizde ilk on tamamen değişiyor, ilk sıradaki belge metin
sıralamasında yüz yirmi üçüncü, ilk onun ödünç ortalaması 100'den 324'e ve yıl ortalaması 2006'dan
2021'e çıkıyor. Liste hâlâ "deniz gemi" sorgusunun yanıtıdır ve her belgesi sorgu terimlerinden
birini taşır; ama sıra artık kütüphanenin en çok ödünç verilen yeni kitaplarının sırasıdır.

Küme bütün bu ağırlıklarda 325'te sabit: işlev tabanlı puan hiçbir belgeyi eklemez ya da çıkarmaz.
Kullanıcının gördüğü şeyin ilk on sonuç olduğu düşünülürse ayrım incelir — küme değişmese de
**görülen küme** tamamen değişmiştir.

#### Özet

- Alan ağırlığı eşleşen kümeye dokunmaz: dört ağırlık yapılandırmasında da 325 belge döndü. Kümeyi
  değiştiren şey ağırlık değil alan seçimidir; yalnız ad alanında arandığında küme 199'a indi.
- Ağırlığın etkisi doyar: ad ağırlığını 5 kattan 12 kata çıkarmak ilk ondaki değişimi dört belgede
  ve en büyük sıçramayı kırk dört basamakta bıraktı. Ağırlık tarama bedelini de değiştirmez;
  698 gönderi girişini 265'e indiren tek karar bir alanı kapatmaktır.
- Yanlış alana verilen ağırlık ilgililiği değil alan karışmasını büyütür: yazar ağırlığı beşe
  çıkınca ilk onun onu birden değişti, eşit ağırlıkta birinci olan belge 132. sıraya düştü. Konu
  alanı ise bu sorguda hiç iş görmez, çünkü dokunulan gönderi girişi sıfırdır.
- İşlev tabanlı puanda çarpımsal kip ilk onu yeniden düzenlemekle kalır, toplamsal kip w dörtte ilk
  onun yarısını metin sıralamasının elli sonrasından doldurur; w sekizde ilk sıradaki belge metinde
  yüz yirmi üçüncüdür.

#### Sonraki Adım

Buraya kadar sorulan her soru bir belge listesi istedi: hangi kitaplar, hangi sırayla. Ödünç
masasının sorduğu şey çoğu zaman belge değil sayıdır: bu aramanın sonuçlarından kaçı 2015 sonrası
basılmış, hangi konu başlığında kaç kitap düşüyor, eşleşen kümenin ortalama yayın yılı nedir. Bu
sorular aynı ters dizin üzerinde çalışır ama çıktıları sıralanmış bir liste değildir, üstelik
yanıtın eşleşen küme üzerinde mi bütün derlem üzerinde mi hesaplandığı sonucu tümüyle değiştirir.
Sonraki ders belge yerine sayı üreten sorguları ve bunların bellek maliyetini ele alır.

### Toplama Sorguları

Source: https://academia.sh/tr/kurslar/arama-motorlari/toplama-sorgulari

Belge yerine sayı üreten sorgular: ölçüt, kova ve boru hattı toplamalarının aynı ödünç kaydı üzerinde ölçülmesi, kova sayısının bayt maliyeti, yüksek kardinaliteli tekil sayımda kesin kümeyle kestirimin karşılaştırılması ve toplamanın bütün derlem, eşleşen küme ya da süzülmüş küme üzerinde çalışmasının sonucu nasıl değiştirdiği.

Bir önceki ders sıralı bir belge listesi üretti ve o listenin ilk onunu tartıştı. Ödünç masasının
panosunda duran sorular ise belge istemez: hangi konu başlığında kaç kitap var, bu aramanın
sonuçları kitap başına kaç kez ödünç verilmiş, yıllara göre ödünç eğrisi nasıl gidiyor. Bu sorular
aynı ters dizin üzerinde çalışır ama çıktıları sıralanmış bir liste değildir.

Üç biçim ayrılır. **Ölçüt toplaması** bir belge kümesinden tek bir sayı üretir: sayı, toplam,
ortalama, en büyük. **Kova toplaması** kümeyi bir anahtara göre parçalara ayırır ve her parçada bir
ölçüt hesaplar. **Boru hattı toplaması** girdisini belgelerden değil, başka bir toplamanın
kovalarından alır: kümülatif pay, hareketli ortalama, kovalar arası fark.

Toplamanın sıraya bakmaması bu dersin ilk gözlemidir. Sorgu değerlendiricisi sıralı bir liste
üretir; toplama o listenin **sırasını** değil **kümesini** okur. Kendi sırası vardır ama o sıra kova
sırasıdır. Ders küme ile sırayı bu düzlemde ölçer, bedeli bayt olarak sayar.

#### Derlem, Ödünç Kaydı ve Üç Toplama

| Kod | Varsayım | Değer | Gerekçe |
|---|---|---|---|
| SI14 | ödünç kaydı | her kitap kendi ödünç sayısı kadar olay | kayıt derlemden türetilir, ayrı tohum gerekmez |
| SI15 | okur havuzu | 40.000 okur, çarpık seçim | tekil okur sayısı kova başına yüksek kardinalite üretsin |
| SI16 | ölçüt kovası | kova başına üç sayı (sayı, toplam, en büyük) | 24 baytlık sabit kova durumu |
| SI17 | toplama girdisi | eşleşen kümenin tamamı | ilk N sonuç kısıtı toplamaya uygulanmaz |

SI17 sessiz ama belirleyicidir: kullanıcıya on sonuç gösterilse de toplama 325 belgenin hepsini
görür; maliyeti gösterilen sonuç sayısıyla değil eşleşen küme boyuyla büyür.

```js
// derlem.mjs — kutuphane katalogu: tohumlu derlem, ters dizin ve eslesen kume.
export const N = 1200, TOHUM = 20260801, ALANLAR = ["ad", "ozet", "konu", "yazar"];
let d = TOHUM;                                            // 32 bit uretec, tasma yok
export const rast = () => { d = (d + 0x6D2B79F5) | 0; let t = Math.imul(d ^ (d >>> 15), 1 | d);
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 2 ** 32; };
const sec = (a) => a[Math.floor(rast() * a.length)];
const TEMA = [["öykü|hikâye", "edebiyat", "kısa seçki derleme anlatı kurgu taşra"],
  ["masal", "çocuk edebiyatı", "çocuk resimli okul genç orman uyku"],
  ["tarih", "tarih", "osmanlı cumhuriyet arşiv belge kronik vakıf"],
  ["deniz", "gezi", "kıyı balıkçı liman gemi ada fener"],
  ["matematik", "bilim", "geometri sayı kanıt kuram çözüm olasılık"],
  ["şiir", "şiir", "dize toplu divan çeviri seçme aruz"]]
  .map(([k, konu, a]) => ({ k: k.split("|"), konu, ana: a.split(" ") }));
const ORTAK = "kitap cilt baskı yayın inceleme notlar giriş sözlük".split(" ");
const AD = "Ahmet Elif Selim Nuray Kemal Deniz Ayşe Ozan Meral Barış".split(" ");
const SOY = "Yıldız Aksu Demir Karaca Toprak Şen".split(" ");

export const belgeler = Array.from({ length: N }, (_, id) => {
  const t = sec(TEMA), cek = t.k[t.k.length > 1 && rast() < 0.5 ? 1 : 0], oz = [cek, cek];
  const ad = [...new Set([cek, sec(t.ana), sec(t.ana)])];
  t.ana.forEach((s, j) => { if (rast() < 1 / (1 + j * 0.42)) oz.push(s); });
  ORTAK.forEach((s, j) => { if (rast() < 0.55 / (1 + j * 0.28)) oz.push(s); });
  if (rast() < 0.5) oz.push(sec(sec(TEMA).ana));          // baska temadan sizan sozcuk
  for (let k = oz.length - 1; k > 0; k -= 1) { const j = Math.floor(rast() * (k + 1));
    [oz[k], oz[j]] = [oz[j], oz[k]]; }
  // konu etiketi yayinevi kararidir: belgelerin dortte birinde metinle ayni temada degil
  return { id, ad: ad.join(" "), ozet: oz.join(" "), konu: (rast() < 0.25 ? sec(TEMA) : t).konu,
    yazar: `${sec(AD)} ${sec(SOY)}`, yil: 1975 + Math.floor(rast() ** 0.6 * 50),
    odunc: Math.floor(rast() ** 3 * 400) };
});

export const belirtec = (s) => s.toLocaleLowerCase("tr").match(/[\p{L}\p{N}]+/gu) ?? [];
export function dizinKur(bs) {                            // alan basina ters dizin
  const dz = {};
  for (const a of ALANLAR) {
    const gonderi = new Map();
    for (const b of bs) for (const t of new Set(belirtec(b[a]))) {
      if (!gonderi.has(t)) gonderi.set(t, []);
      gonderi.get(t).push(b.id);
    }
    dz[a] = gonderi;
  }
  return dz;
}
export function eslesenler(dz, sorgu) {   // sorgu terimlerinden en az birini tasiyan belgeler
  const s = new Set();
  for (const a of ALANLAR) for (const t of belirtec(sorgu))
    for (const id of dz[a].get(t) ?? []) s.add(id);
  return s;
}
```

#### Kova Sayısı Bir Bellek Kararıdır

Kova toplaması "hangi alana göre gruplayalım" sorusuyla belirlenir ve bu bir bellek kararıdır: her
kova, ölçütünü hesaplamak için kendi durumunu tutmak zorundadır. Sayı ve toplam gibi ölçütler kova
başına sabit yer ister; **tekil sayım** istemez, çünkü kaç ayrı okur olduğunu bilmek o okurların bir
temsilini tutmayı gerektirir. Düzenek beş kova anahtarı için ikisini de ölçer: kesin küme ve
kardinalite kestirimi. Kestirimin yapısı M17/K06'da kurulup ölçülmüştü; burada araç olarak
kullanılır, tuttuğu bayt kendi tamponundan okunur.

```js
// toplama.mjs — kova sayisi ile tekil sayimin bayt maliyeti: kesin kume ve kestirim.
import { belgeler, rast, N, TOHUM } from "./derlem.mjs";
const OKUR = 40_000, olay = [];                           // her kitap kendi odunc sayisi kadar
for (const b of belgeler) for (let i = 0; i < b.odunc; i += 1)
  olay.push([b.id, 1 + Math.floor(rast() ** 2 * OKUR)]);  // okur secimi carpik

class Kume {                                              // acik adresleme, bos yuva 0
  constructor() { this.t = new Int32Array(16); this.n = 0; }
  yer(t, x) { let i = (Math.imul(x, 2654435761) >>> 0) & (t.length - 1);
    while (t[i] !== 0 && t[i] !== x) i = (i + 1) & (t.length - 1); return i; }
  ekle(x) {
    if ((this.n + 1) * 2 > this.t.length) { const y = new Int32Array(this.t.length * 2);
      for (const v of this.t) if (v !== 0) y[this.yer(y, v)] = v; this.t = y; }
    const i = this.yer(this.t, x);
    if (this.t[i] === 0) { this.t[i] = x; this.n += 1; }
  }
}
class Yaklasik {          // kardinalite kestirimi: yapisi M17/K06'da kuruldu, burada araç
  constructor(p) { this.p = p; this.R = new Uint8Array(1 << p); }
  ekle(x) { let h = Math.imul(x ^ 0x9e37, 0x85ebca6b) >>> 0;
    h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35) >>> 0; h = (h ^ (h >>> 16)) >>> 0;
    const i = h >>> (32 - this.p), w = (h << this.p) >>> 0;
    const r = w === 0 ? 33 - this.p : Math.clz32(w) + 1;
    if (r > this.R[i]) this.R[i] = r; }
  say() { const m = this.R.length; let z = 0, s = 0;
    for (const v of this.R) { z += 2 ** -v; if (v === 0) s += 1; }
    const e = (0.7213 / (1 + 1.079 / m)) * m * m / z;
    return e <= 2.5 * m && s > 0 ? m * Math.log(m / s) : e; }
}

const tr = (x) => Math.round(x).toLocaleString("tr-TR");
const ANAHTAR = { konu: (b) => b.konu, "on yıl": (b) => `${Math.floor(b.yil / 10) * 10}`,
  yıl: (b) => `${b.yil}`, yazar: (b) => b.yazar, kitap: (b) => `${b.id}` };
console.log(`derlem ${N} belge, tohum ${TOHUM}; ödünç kaydı ${tr(olay.length)} olay, ` +
  `${tr(OKUR)} okur havuzu\n`);
console.log("kova alanı".padEnd(11) + "kova".padStart(6) + "ölçüt".padStart(8) +
  "kesin tekil".padStart(13) + "kestirim p8".padStart(13) + "sapma".padStart(8) +
  "kestirim p12".padStart(14) + "sapma".padStart(8) + "en büyük kova".padStart(15));
for (const [ad, f] of Object.entries(ANAHTAR)) {
  const kesin = new Map(), yak = [new Map(), new Map()], P = [8, 12];
  for (const [kitap, okur] of olay) {
    const k = f(belgeler[kitap]);
    if (!kesin.has(k)) { kesin.set(k, new Kume()); P.forEach((p, j) => yak[j].set(k, new Yaklasik(p))); }
    kesin.get(k).ekle(okur); yak.forEach((m) => m.get(k).ekle(okur));
  }
  let kb = 0, enBuyuk = 0;
  const yb = [0, 0], sapma = [0, 0];
  for (const [k, s] of kesin) {
    kb += s.t.byteLength; enBuyuk = Math.max(enBuyuk, s.n);
    yak.forEach((m, j) => { yb[j] += m.get(k).R.byteLength;
      sapma[j] = Math.max(sapma[j], Math.abs(m.get(k).say() - s.n) / s.n); });
  }
  console.log(ad.padEnd(11) + tr(kesin.size).padStart(6) + tr(kesin.size * 3 * 8).padStart(8) +
    tr(kb).padStart(13) + tr(yb[0]).padStart(13) + `%${(sapma[0] * 100).toFixed(1)}`.padStart(8) +
    tr(yb[1]).padStart(14) + `%${(sapma[1] * 100).toFixed(1)}`.padStart(8) +
    `${tr(enBuyuk)} tekil`.padStart(15));
}
```

```
derlem 1200 belge, tohum 20260801; ödünç kaydı 120.809 olay, 40.000 okur havuzu

kova alanı   kova   ölçüt  kesin tekil  kestirim p8   sapma  kestirim p12   sapma  en büyük kova
konu            6     144      786.432        1.536   %11.1        24.576    %1.6   15.257 tekil
on yıl          6     144      819.200        1.536    %6.5        24.576    %2.1   20.949 tekil
yıl            50   1.200    1.202.176       12.800   %18.5       204.800    %2.8    4.392 tekil
yazar          60   1.440    1.318.912       15.360   %14.8       245.760    %3.3    3.908 tekil
kitap       1.045  25.080    1.401.216      267.520   %33.1     4.280.320    %8.2      394 tekil
```

Bayt sütunları yapıların kendi tamponlarından okunur; ölçüt sütunu kova sayısı çarpı yirmi dörttür.

Ölçüt sütunu bir kova toplamasının neden ucuz sanıldığını gösteriyor: 1.045 kova için 25.080 bayt.
Kova sayısı bin katına çıksa bile bu sütun yirmi beş megabayttır; "kova sayısı bellek yer" cümlesi
ölçüt toplamasında neredeyse yanlıştır.

Tekil sayım sütunu başka bir dünyadır. Aynı 1.045 kova için kesin kümeler 1.401.216 bayt tutuyor,
ölçüt durumunun elli altı katı. Kesin sütun kova sayısıyla birlikte fazla da artmıyor — altı kovada
786.432, bin kırk beş kovada 1.401.216 — çünkü maliyeti belirleyen kova sayısı değil, kovalara
dağılmış farklı okur sayısıdır.

Kestirim sütunları kararın asıl yerini gösteriyor. Altı kovada p8 kestirimi 1.536 bayt istiyor,
kesin kümenin beş yüz on ikide biri, karşılığında en kötü kovada yüzde 11,1 sapma. p12'ye çıkmak
sapmayı yüzde 1,6'ya indiriyor ve maliyeti 24.576 bayta çıkarıyor — hâlâ kesinin otuz ikide biri.
Ama son satırda işaret ters dönüyor: bin kırk beş kova için p12 kestirimi **4.280.320 bayt**
istiyor, kesin kümelerin üç katı. Kestirim kova başına **sabit** bir tampon ister; kovalar
küçüldükçe o sabit, temsil etmesi gereken tekil sayısını geçer. M17/K06 bunu tek bir yapı için
ölçmüştü; kova toplamasında aynı kırılma, kova sayısının kendisiyle gelir.

Hata payı da kova boyuyla okunur: en kötü sapma altı kovada yüzde 11,1 iken bin kırk beş kovada
yüzde 33,1. Kestirimin hata payı toplam kardinalite üzerinden değil **en küçük kova** üzerinden
değerlendirilmelidir; raporun yanlış çıkacağı yer büyük kovalar değil, küçük olanlardır.

#### Toplama Hangi Küme Üzerinde Çalışır

İkinci karar bellekle değil anlamla ilgilidir: toplama bütün derlem üzerinde mi, sorgunun eşleşen
kümesi üzerinde mi, yoksa kullanıcının uyguladığı süzgeçten sonra kalan küme üzerinde mi
hesaplanacak. Üçü üç ayrı sayı verir ve üçü de "doğru" olabilir.

```js
// kova.mjs — toplama hangi kume uzerinde: derlem, eslesen kume, suzulmus kume; ve boru hatti.
import { belgeler, dizinKur, eslesenler } from "./derlem.mjs";
const dz = dizinKur(belgeler);
const eslesen = [...eslesenler(dz, "deniz gemi")].map((id) => belgeler[id]);
const suzulmus = eslesen.filter((b) => b.konu === "gezi");
const tr = (x) => Math.round(x).toLocaleString("tr-TR");
const kovala = (bs, f) => {                               // kova: anahtar -> sayi ve odunc
  const m = new Map();
  for (const b of bs) {
    const k = f(b), v = m.get(k) ?? { n: 0, odunc: 0 };
    v.n += 1; v.odunc += b.odunc; m.set(k, v);
  }
  return [...m].sort((x, y) => y[1].n - x[1].n || (x[0] < y[0] ? -1 : 1));
};

const derlemKova = new Map(kovala(belgeler, (b) => b.konu));
console.log(`konu kovaları — derlem ${belgeler.length}, eşleşen ${eslesen.length}, ` +
  `konu süzgecinden sonra ${suzulmus.length} belge\n`);
console.log("konu".padEnd(17) + "derlem".padStart(8) + "eşleşen".padStart(9) +
  "eşleşen ödünç".padStart(15) + "kitap başına".padStart(14));
for (const [k, v] of kovala(eslesen, (b) => b.konu))
  console.log(k.padEnd(17) + tr(derlemKova.get(k).n).padStart(8) + tr(v.n).padStart(9) +
    tr(v.odunc).padStart(15) + (v.odunc / v.n).toFixed(1).padStart(14));
console.log("aynı toplama süzülmüş küme üzerinde çalıştırılırsa kova sayısı: " +
  `${kovala(suzulmus, (b) => b.konu).length}`);

const yilKova = kovala(eslesen, (b) => `${b.yil}`);
const enBuyuk = [...yilKova].sort((x, y) => y[1].odunc - x[1].odunc);
const toplam = (l) => l.reduce((s, [, v]) => s + v.odunc, 0);
console.log(`\nboru hattı: yıl kovalarının ödünç toplamı üzerinde kümülatif pay`);
console.log("kova kümesi".padEnd(16) + "kova".padStart(6) + "ödünç".padStart(9) +
  "ilk üç kovanın payı".padStart(21) + "en yüksek kovanın payı".padStart(24));
for (const [ad, l] of [["tam", enBuyuk], ["ilk 10 kova", enBuyuk.slice(0, 10)],
  ["ilk 3 kova", enBuyuk.slice(0, 3)]]) {
  const t = toplam(l);
  console.log(ad.padEnd(16) + `${l.length}`.padStart(6) + tr(t).padStart(9) +
    `%${((toplam(l.slice(0, 3)) / t) * 100).toFixed(1)}`.padStart(21) +
    `%${((l[0][1].odunc / t) * 100).toFixed(1)}`.padStart(24));
}
```

```
konu kovaları — derlem 1200, eşleşen 325, konu süzgecinden sonra 168 belge

konu               derlem  eşleşen  eşleşen ödünç  kitap başına
gezi                  208      168         14.842          88.3
şiir                  208       35          3.761         107.5
bilim                 185       34          2.886          84.9
tarih                 206       33          2.937          89.0
edebiyat              220       30          3.875         129.2
çocuk edebiyatı       173       25          3.895         155.8
aynı toplama süzülmüş küme üzerinde çalıştırılırsa kova sayısı: 1

boru hattı: yıl kovalarının ödünç toplamı üzerinde kümülatif pay
kova kümesi       kova    ödünç  ilk üç kovanın payı  en yüksek kovanın payı
tam                 49   32.196                %15.4                    %5.4
ilk 10 kova         10   13.181                %37.6                   %13.1
ilk 3 kova           3    4.961               %100.0                   %34.8
```

İlk iki sütun aynı kovanın iki farklı sayısıdır. Derlemde 208 gezi kitabı var, "deniz gemi"
sorgusunun eşleşen kümesinde 168 tane. Kullanıcıya "gezi (208)" yazan bir yüzey sorgudan bağımsız
bir bilgi verir; "gezi (168)" sorgunun içinde gezinmeyi anlatır. İkisi karıştırıldığında toplam
tutmaz: eşleşen sütunun toplamı 325, derlem sütununun toplamı 1.200.

Kitap başına ödünç sütunu ölçütün okunma kuralını gösteriyor: en yüksek değer 155,8 ile çocuk
edebiyatı kovasında, ama o kovada yalnız 25 belge var. Yirmi beş belgeden çıkan ortalama ile yüz
altmış sekiz belgeden çıkan ortalama aynı güvende değildir.

Süzme satırı gezinme yüzeylerinin klasik hatasını sayıya çeviriyor. Kullanıcı "gezi" seçtiğinde
sonuç kümesi 168 belgeye iner ve aynı konu toplaması o küme üzerinde çalıştırılırsa **tek kova**
döner: yüzeyde başka hiçbir konu görünmez, seçim değiştirilemez. Konu toplaması kendi süzgeci
uygulanmadan önceki küme üzerinde, öteki süzgeçler uygulandıktan sonra hesaplanmalıdır. Toplama ile
süzmenin sırası bir görsel tercih değil, kümeyi belirleyen bir karardır.

Boru hattı tablosu son sorunu gösteriyor. "İlk üç yılın toplam ödünç içindeki payı" sorusunun yanıtı
kırk dokuz kovanın hepsi görüldüğünde yüzde 15,4, yalnız en yüksek on kova görüldüğünde yüzde
37,6, üç kova görüldüğünde yüzde 100. Boru hattı toplaması girdisini belgelerden değil kovalardan
aldığı için, girdisi kırpılmışsa çıktısı da yanlıştır ve yanlışlığı çıktıya bakarak anlaşılmaz —
üç sayı da kendi içinde tutarlıdır. Kova kırpma bir sunum kararı gibi görünür, oysa boru hattının
girdisini değiştirir.

#### Özet

- Toplama belge sırasını kullanmaz, kümeyi okur; kendi sırası kova sırasıdır ve kovalar sayıya,
  anahtara ya da ölçüte göre dizilebilir.
- Ölçüt kovası kova başına yirmi dört bayttır: 1.045 kova için 25.080 bayt. Tekil sayım kovası aynı
  kovalarda kesin kümelerle 1.401.216 bayt ister ve maliyeti kova sayısına değil kovalardaki farklı
  okur sayısına bağlıdır.
- Kardinalite kestirimi az kovada beş yüz kat kazandırır (altı kovada 1.536 bayta karşı 786.432),
  çok kovada kaybettirir: bin kırk beş kova için p12 kestirimi 4.280.320 bayt, kesinin üç katı.
- Kestirimin hata payı en küçük kovada okunur: en kötü sapma altı kovada yüzde 11,1, bin kırk beş
  kovada yüzde 33,1.
- Aynı konu toplaması derlem üzerinde 208, eşleşen küme üzerinde 168, kendi süzgecinden sonra tek
  kova verir; boru hattının yanıtı da girdisindeki kova sayısına göre yüzde 15,4 ile yüzde 100
  arasında değişir.

#### Sonraki Adım

Toplamalar sayı üretti, sorgu değerlendiricisi sıralı bir liste üretti; ikisi de sonucun okura nasıl
görüneceğini konuşmadı. Ekranda duran şey belge kimliği değil, kitabın adından ve özetinden alınmış
bir parçadır; o parçanın hangi bölümünün seçileceği sorgunun hangi sözcüğünün nerede eşleştiğine
bağlıdır ve bu bilgi puanlama sırasında tutulmadı. İkinci soru daha ağırdır: okur yirminci sayfaya
geçtiğinde aynı sıralamanın nasıl korunacağı ve bunun kaç adayı bellekte tutmayı gerektirdiği
sorulmadı. Sonraki ders sonuç sunumunu ve derin sayfalamanın maliyetini ele alır.

### Vurgulama ve Sayfalama

Source: https://academia.sh/tr/kurslar/arama-motorlari/vurgulama-ve-sayfalama

Sonucun okura görünen yüzü: vurgulamanın belge başına yeniden çözümleme bedeli ile konumu dizinde saklamanın kalıcı bayt yükü, derin sayfalamada her parçadan çekilen aday sayısının sayfa derinliğiyle doğrusal büyümesi, imleç tabanlı gezinmenin aynı sayfayı sabit bedelle üretmesi ve dizin değişince kaydırmalı sayfalamanın ürettiği tekrar ile atlama.

Buraya kadarki bütün ölçümler belge kimlikleri ve puanlar üzerinde yapıldı. Okurun gördüğü şey ise
kimlik değil, kitabın adı ve özetinden alınmış kısa bir parçadır; parçanın içinde aradığı sözcük
işaretlidir. İkinci bir şey daha var: liste on satırda bitmiyor, okur ikinci sayfaya, sonra
yirminci sayfaya geçiyor.

Bu iki iş birbirine benzemez. **Vurgulama** yalnız gösterilen belgelere dokunur ve bedeli sayfa
derinliğinden bağımsızdır. **Sayfalama** ise derinleştikçe pahalanır ve pahalanma dizinin kaç
parçaya bölündüğüyle çarpılır. Ders ikisini de aynı derlem üzerinde ölçer.

#### Derlem, Sayfa ve Parça

| Kod | Varsayım | Değer | Gerekçe |
|---|---|---|---|
| SI18 | sayfa boyu ve gösterilen alan | 10 sonuç; ad ve özet | vurgulama yalnız bu iki alanda yapılır |
| SI19 | vurgulama penceresi | 30 karakter, ilk eşleşmenin çevresi | parça bir satıra sığmalı |
| SI20 | dizin parçası | 4 parça, her parça kendi sıralamasını üretir | birleştirme aynı süreçte yapılır |
| SI21 | aday girişi | 12 bayt (kimlik 4, puan 8) | birleştiricide tutulan adayın boyu |

SI20 bu dersin ölçüm düzeneğidir: parçalar aynı süreçte koşar, aralarındaki ağ yoktur; ölçülen şey
**parçadan çekilen aday sayısıdır**, süre değil.

```js
// derlem.mjs — kutuphane katalogu: tohumlu derlem, ters dizin ve puanlayici.
export const N = 1200, TOHUM = 20260801, ALANLAR = ["ad", "ozet", "konu", "yazar"];
let d = TOHUM;                                            // 32 bit uretec, tasma yok
export const rast = () => { d = (d + 0x6D2B79F5) | 0; let t = Math.imul(d ^ (d >>> 15), 1 | d);
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 2 ** 32; };
const sec = (a) => a[Math.floor(rast() * a.length)];
const TEMA = [["öykü|hikâye", "edebiyat", "kısa seçki derleme anlatı kurgu taşra"],
  ["masal", "çocuk edebiyatı", "çocuk resimli okul genç orman uyku"],
  ["tarih", "tarih", "osmanlı cumhuriyet arşiv belge kronik vakıf"],
  ["deniz", "gezi", "kıyı balıkçı liman gemi ada fener"],
  ["matematik", "bilim", "geometri sayı kanıt kuram çözüm olasılık"],
  ["şiir", "şiir", "dize toplu divan çeviri seçme aruz"]]
  .map(([k, konu, a]) => ({ k: k.split("|"), konu, ana: a.split(" ") }));
const ORTAK = "kitap cilt baskı yayın inceleme notlar giriş sözlük".split(" ");
const AD = "Ahmet Elif Selim Nuray Kemal Deniz Ayşe Ozan Meral Barış".split(" ");
const SOY = "Yıldız Aksu Demir Karaca Toprak Şen".split(" ");

export const belgeler = Array.from({ length: N }, (_, id) => {
  const t = sec(TEMA), cek = t.k[t.k.length > 1 && rast() < 0.5 ? 1 : 0], oz = [cek, cek];
  const ad = [...new Set([cek, sec(t.ana), sec(t.ana)])];
  t.ana.forEach((s, j) => { if (rast() < 1 / (1 + j * 0.42)) oz.push(s); });
  ORTAK.forEach((s, j) => { if (rast() < 0.55 / (1 + j * 0.28)) oz.push(s); });
  if (rast() < 0.5) oz.push(sec(sec(TEMA).ana));          // baska temadan sizan sozcuk
  for (let k = oz.length - 1; k > 0; k -= 1) { const j = Math.floor(rast() * (k + 1));
    [oz[k], oz[j]] = [oz[j], oz[k]]; }
  // konu etiketi yayinevi kararidir: belgelerin dortte birinde metinle ayni temada degil
  return { id, ad: ad.join(" "), ozet: oz.join(" "), konu: (rast() < 0.25 ? sec(TEMA) : t).konu,
    yazar: `${sec(AD)} ${sec(SOY)}`, yil: 1975 + Math.floor(rast() ** 0.6 * 50),
    odunc: Math.floor(rast() ** 3 * 400) };
});

export const belirtec = (s) => s.toLocaleLowerCase("tr").match(/[\p{L}\p{N}]+/gu) ?? [];
export function dizinKur(bs) {                            // alan basina ters dizin
  const dz = {};
  for (const a of ALANLAR) {
    const gonderi = new Map(), boy = new Float64Array(bs.length);
    for (const b of bs) {
      const ts = belirtec(b[a]), say = new Map();
      boy[b.id] = ts.length;
      for (const t of ts) say.set(t, (say.get(t) ?? 0) + 1);
      for (const [t, tf] of say) {
        if (!gonderi.has(t)) gonderi.set(t, []);
        gonderi.get(t).push({ id: b.id, tf });
      }
    }
    dz[a] = { gonderi, boy, ort: boy.reduce((x, y) => x + y, 0) / bs.length };
  }
  return dz;
}
export function ara(dz, sorgu) {          // esit alan agirligiyla puanlanmis sonuc listesi
  const terimler = belirtec(sorgu), k1 = 1.2, b = 0.75, p = new Map();
  for (const a of ALANLAR) for (const t of terimler) {
    const g = dz[a].gonderi.get(t);
    if (!g) continue;
    const idf = Math.log(1 + (N - g.length + 0.5) / (g.length + 0.5));
    for (const e of g) {
      const nrm = 1 - b + (b * dz[a].boy[e.id]) / dz[a].ort;
      p.set(e.id, (p.get(e.id) ?? 0) + (idf * e.tf * (k1 + 1)) / (e.tf + k1 * nrm));
    }
  }
  return [...p].sort((x, y) => y[1] - x[1] || x[0] - y[0]);
}
```

#### Vurgulamanın Belge Başına Bedeli

Ters dizin hangi belgede hangi terimin geçtiğini bilir, ama terimin metnin **neresinde** geçtiğini
bilmez: dizine giren şey belirteçtir, belirtecin karakter konumu değil. Vurgulama tam da bu bilgiyi
ister. İki yol var: konumu dizinde saklamak ya da sorgu anında gösterilen belgeleri yeniden
çözümlemek.

```js
// vurgula.mjs — vurgulama bedeli: sorgu aninda yeniden cozumleme mi, konumu dizinde tutmak mi.
import { belgeler, dizinKur, ara, belirtec, N, TOHUM } from "./derlem.mjs";
const dz = dizinKur(belgeler), SORGU = "deniz gemi kitap";
const terim = new Set(belirtec(SORGU)), sonuc = ara(dz, SORGU);
const PARCA = /[\p{L}\p{N}]+/gu, tr = (x) => Math.round(x).toLocaleString("tr-TR");

function coz(metin) {                       // yeniden cozumleme: belirtecle, konumlari isaretle
  const kon = [];
  let sayi = 0;
  for (const m of metin.matchAll(PARCA)) {
    sayi += 1;
    if (terim.has(m[0].toLocaleLowerCase("tr"))) kon.push([m.index, m[0].length]);
  }
  return { karakter: metin.length, belirtec: sayi, kon };
}
const PENCERE = 30;
const parca = (metin, kon, en = PENCERE) => {    // ilk eslesmenin cevresinden pencere
  if (kon.length === 0) return "";
  const b = Math.max(0, kon[0][0] - 12), son = b + en < metin.length;
  let s = metin.slice(b, b + en);
  if (son) s = s.slice(0, s.lastIndexOf(" "));
  return (b > 0 ? "…" : "") + s.replace(PARCA, (w) =>
    terim.has(w.toLocaleLowerCase("tr")) ? `[${w}]` : w) + (son ? "…" : "");
};

console.log(`derlem ${N} belge, tohum ${TOHUM}; sorgu "${SORGU}" ${sonuc.length} belge eşleştirdi`);
console.log("gösterilen".padEnd(12) + "yeniden çözümlenen karakter".padStart(28) +
  "üretilen belirteç".padStart(19) + "bulunan eşleşme".padStart(17) +
  "pencere dışında kalan".padStart(23));
for (const gosterilen of [10, 50, 100]) {
  let k = 0, b = 0, e = 0, bos = 0;
  for (const [id] of sonuc.slice(0, gosterilen)) {
    const a = coz(belgeler[id].ad), o = coz(belgeler[id].ozet);
    k += a.karakter + o.karakter; b += a.belirtec + o.belirtec;
    e += a.kon.length + o.kon.length;
    const bas = o.kon.length ? Math.max(0, o.kon[0][0] - 12) : 0;
    bos += o.kon.filter(([i]) => i < bas || i >= bas + PENCERE).length;
  }
  console.log(`${gosterilen} belge`.padEnd(12) + tr(k).padStart(28) + tr(b).padStart(19) +
    tr(e).padStart(17) + `${bos}`.padStart(23));
}

let occ = 0, kar = 0;                       // butun derlem icin konum dizini kurulsaydi
for (const b of belgeler) { occ += belirtec(b.ad).length + belirtec(b.ozet).length;
  kar += b.ad.length + b.ozet.length; }
console.log(`\nkonum dizini seçilseydi: ${tr(occ)} belirteç geçişi, geçiş başına 8 bayt ` +
  `(başlangıç ve uzunluk) = ${tr(occ * 8)} bayt kalıcı ek yük`);
console.log(`aynı bilgi sorgu anında üretilirse kalıcı yük yoktur: derlemin ${tr(kar)} ` +
  `karakterinden yalnız gösterilen belgelerinki çözümlenir`);
for (const [id] of sonuc.slice(0, 3)) {
  const o = coz(belgeler[id].ozet), a = coz(belgeler[id].ad);
  console.log(`  ${belgeler[id].ad.padEnd(24)} özet parçası: ` +
    `${parca(belgeler[id].ozet, o.kon) || "(yok, ad alanından: " + parca(belgeler[id].ad, a.kon) + ")"}`);
}
```

```
derlem 1200 belge, tohum 20260801; sorgu "deniz gemi kitap" 817 belge eşleştirdi
gösterilen   yeniden çözümlenen karakter  üretilen belirteç  bulunan eşleşme  pencere dışında kalan
10 belge                             597                107               60                     12
50 belge                           3.315                581              263                     56
100 belge                          6.302              1.103              493                     90

konum dizini seçilseydi: 13.529 belirteç geçişi, geçiş başına 8 bayt (başlangıç ve uzunluk) = 108.232 bayt kalıcı ek yük
aynı bilgi sorgu anında üretilirse kalıcı yük yoktur: derlemin 83.666 karakterinden yalnız gösterilen belgelerinki çözümlenir
  deniz gemi               özet parçası: [kitap] liman cilt kıyı [deniz]…
  deniz kıyı gemi          özet parçası: [deniz] [gemi] [kitap] liman baskı…
  deniz gemi liman         özet parçası: [deniz] notlar [gemi] balıkçı…
```

Vurgulama ne kümeyi ne sırayı değiştirir: eşleşen 817 belge ve sıraları, vurgulama açık ya da
kapalı olsun aynıdır. Değişen tek şey gösterilen satırın içeriğidir. Buna karşılık bedeli **doğrusal
ve gösterilen belge sayısına bağlıdır**: on belge için 597 karakter ve 107 belirteç, yüz belge için
6.302 karakter ve 1.103 belirteç. Sayfa derinliği bu tabloda hiç görünmez; yüzüncü sayfanın on
belgesini vurgulamak da 597 karakter civarında iş yapar.

Konum dizini satırı öteki yolun fiyatını veriyor: bütün derlemin 13.529 belirteç geçişi için geçiş
başına sekiz bayt, toplam 108.232 bayt kalıcı ek yük. Bu yük **her belge için** ödenir, oysa
vurgulama yalnız gösterilen on belgede yapılır. Dizin 1.200 belgeyi tutarken bir sorgu on tanesine
bakıyorsa, konumu saklamak yüz yirmi belgenin işini peşin ödemek demektir. Karar bu asimetriyle
verilir: konum dizini metin uzunsa ve aynı belgeler sık sık vurgulanıyorsa kazanır, katalog
kaydında olduğu gibi alan kısaysa yeniden çözümleme daha ucuza gelir.

Son sütun vurgulamanın kendi kaybını sayıyor. On belgede bulunan altmış eşleşmenin on ikisi otuz
karakterlik pencerenin dışında kalıyor; okur o eşleşmeleri hiç görmüyor. Pencere büyütülürse bu
sayı düşer ama satır uzar. Vurgulama, eşleşmenin **var olduğunu** göstermez, yalnız bir tanesinin
çevresini gösterir.

#### Derin Sayfalama

Sayfalamanın maliyeti tek bir sıralı listede tutulan bir imleçmiş gibi konuşulur. Dizin parçalara
bölündüğünde bu doğru olmaktan çıkar: hiçbir parça bütün sıralamayı bilmediği için, ikinci sayfanın
onuncu belgesinin hangi parçadan geleceği ancak her parçadan **baştan itibaren** yeterince aday
çekilerek bulunabilir.

```js
// sayfala.mjs — derin sayfalama: parca basina cekilen aday, imlecle karsilastirma ve kayma.
import { belgeler, dizinKur, ara } from "./derlem.mjs";
const dz = dizinKur(belgeler), SORGU = "deniz gemi kitap", P = 4, BOY = 10, BAYT = 12;
const tam = ara(dz, SORGU), once = (a, b) => b[1] - a[1] || a[0] - b[0];
let parcalar = Array.from({ length: P }, (_, p) => tam.filter(([id]) => id % P === p));
const TEMEL = parcalar.map((pz) => [...pz]);
const tr = (x) => Math.round(x).toLocaleString("tr-TR");

const kaydirmali = (sayfa) => {                 // her parca bastan from+size aday dondurur
  const bas = (sayfa - 1) * BOY, istenen = bas + BOY, havuz = [];
  for (const pz of parcalar) havuz.push(...pz.slice(0, istenen));
  havuz.sort(once);
  return { aday: istenen * P, sayfa: havuz.slice(bas, bas + BOY) };
};
const imlecSayfa = (imlec) => {                 // her parca imlecten sonraki size adayi dondurur
  const havuz = [];
  for (const pz of parcalar)
    havuz.push(...pz.filter((e) => imlec === null || once(e, imlec) > 0).slice(0, BOY));
  havuz.sort(once);
  return havuz.slice(0, BOY);
};

console.log(`sorgu "${SORGU}": ${tam.length} sonuç, ${P} parça, sayfa boyu ${BOY}`);
console.log("sayfa".padEnd(7) + "kaydırmalı aday".padStart(16) + "imleçli aday".padStart(14) +
  "oran".padStart(8) + "birleştirme baytı".padStart(19) + "kümülatif kaydırmalı".padStart(22) +
  "kümülatif imleçli".padStart(19) + "aynı sayfa".padStart(12));
let kum = 0, imlec = null, s = [];
for (let sayfa = 1; sayfa <= 20; sayfa += 1) {
  const k = kaydirmali(sayfa);
  kum += k.aday;
  s = imlecSayfa(imlec); imlec = s[s.length - 1];
  if (![1, 5, 10, 20].includes(sayfa)) continue;
  const ayni = k.sayfa.every(([id], i) => id === s[i][0]);
  console.log(`${sayfa}`.padEnd(7) + tr(k.aday).padStart(16) + tr(BOY * P).padStart(14) +
    `${(k.aday / (BOY * P)).toFixed(0)} kat`.padStart(8) + tr(k.aday * BAYT).padStart(19) +
    tr(kum).padStart(22) + tr(sayfa * BOY * P).padStart(19) +
    (ayni ? "evet" : "hayır").padStart(12));
}

const tumu = () => parcalar.flat().sort(once).map(([id]) => id);
const s1 = kaydirmali(1).sayfa, g1 = new Set(s1.map(([id]) => id)), i1 = s1[s1.length - 1];
console.log("\nsayfa 1 gösterildikten sonra dizin değişirse sayfa 2'de ne olur");
console.log("değişiklik".padEnd(24) + "kaydırmalı tekrar".padStart(18) + "atlanan".padStart(9) +
  "imleçli tekrar".padStart(16) + "atlanan".padStart(9));
for (const [ad, deg] of [
  ["ilk sıraya yeni belge", () => parcalar[0].unshift([-1, tam[0][1] + 1])],
  ["ilk sayfadan silme", () => { const x = s1[2][0];
    parcalar[x % P] = parcalar[x % P].filter(([id]) => id !== x); }]]) {
  parcalar = TEMEL.map((pz) => [...pz]);
  deg();
  const yeni = tumu();                          // atlanan: sayfa 2 basladigi yerin onunde kalan
  const say = (s2) => [s2.filter((id) => g1.has(id)).length,
    yeni.slice(0, yeni.indexOf(s2[0])).filter((id) => id >= 0 && !g1.has(id)).length];
  const k = say(kaydirmali(2).sayfa.map(([id]) => id)), i = say(imlecSayfa(i1).map(([id]) => id));
  console.log(ad.padEnd(24) + `${k[0]}`.padStart(18) + `${k[1]}`.padStart(9) +
    `${i[0]}`.padStart(16) + `${i[1]}`.padStart(9));
}
```

```
sorgu "deniz gemi kitap": 817 sonuç, 4 parça, sayfa boyu 10
sayfa   kaydırmalı aday  imleçli aday    oran  birleştirme baytı  kümülatif kaydırmalı  kümülatif imleçli  aynı sayfa
1                    40            40   1 kat                480                    40                 40        evet
5                   200            40   5 kat              2.400                   600                200        evet
10                  400            40  10 kat              4.800                 2.200                400        evet
20                  800            40  20 kat              9.600                 8.400                800        evet

sayfa 1 gösterildikten sonra dizin değişirse sayfa 2'de ne olur
değişiklik               kaydırmalı tekrar  atlanan  imleçli tekrar  atlanan
ilk sıraya yeni belge                    1        0               0        0
ilk sayfadan silme                       0        1               0        0
```

Son sütun bütün karşılaştırmanın dayanağıdır: iki yol da **aynı sayfayı** üretiyor. Belgeler de,
sıraları da aynı. Değişen tek şey bedeldir.

Kaydırmalı sayfalamada parça başına çekilen aday, atlanan sayı artı sayfa boyudur; dört parçayla
yirminci sayfa 800 aday demektir ve bunların 790'ı atılır. Bedel sayfa derinliğiyle **doğrusal**
büyür, parça sayısıyla **çarpılır**: aynı sayfa sekiz parçalı bir dizinde 1.600 aday isterdi. İmleçli
gezinmede sayı sabittir: her sayfada 40 aday, çünkü her parçaya "şu puandan ve şu kimlikten sonraki
ilk on belge" sorulur ve baştan sayma gerekmez.

Kümülatif sütunlar okurun bütün gezintisini gösteriyor. Yirmi sayfayı sırayla gezen bir okur
kaydırmalı yolda 8.400 aday, imleçli yolda 800 aday ürettirir. Kaydırmalının kümülatifi karesel
büyür, imleçlininki doğrusal; yirminci sayfada aradaki fark on buçuk kattır. Buna karşılık imleçli
gezinme bir şeyi yapamaz: doğrudan yirminci sayfaya atlayamaz, çünkü imleç bir sıra numarası değil,
son gösterilen belgenin puanı ile kimliğidir. Sayfa numarasıyla gezinme isteniyorsa kaydırmalı
yolun bedeli ödenecektir.

Alt tablo üçüncü farkı ölçüyor. Birinci sayfa gösterildikten sonra dizine ilk sıraya girecek bir
belge eklenirse, kaydırmalı yolda ikinci sayfada okurun **zaten gördüğü** bir belge yeniden çıkar.
Birinci sayfadaki bir belge silinirse, ikinci sayfanın başına gelecek bir belge okura hiç
gösterilmez. İmleçli yolda iki sayı da sıfırdır, çünkü imleç bir sayaç değil bir değerdir; listenin
başında olan biten imlecin sağındaki pencereyi kaydırmaz. Sayfalama biçimi burada sunum tercihi
olmaktan çıkıp **okurun gördüğü kümeyi** belirleyen bir karara dönüşür.

#### Özet

- Vurgulama kümeyi ve sırayı değiştirmez, bedeli yalnız gösterilen belge sayısıyla büyür: on belge
  için 597 karakter ve 107 belirteç, yüz belge için 6.302 karakter ve 1.103 belirteç.
- Konumu dizinde tutmak 13.529 belirteç geçişi için 108.232 bayt kalıcı yük ister ve bu yük bütün
  belgeler için ödenir; yeniden çözümleme yalnız gösterilenler için çalışır.
- Vurgulama pencereye sığmayan eşleşmeleri gizler: on belgedeki altmış eşleşmenin on ikisi otuz
  karakterlik pencerenin dışında kaldı.
- Kaydırmalı sayfalamada parça başına aday, sayfa derinliğiyle doğrusal büyür ve parça sayısıyla
  çarpılır: yirminci sayfa dört parçada 800 aday, 9.600 bayt birleştirme yükü.
- İmleçli gezinme aynı sayfayı sabit 40 adayla üretir ve dizin değişse de tekrar ile atlama vermez;
  karşılığında sayfa numarasıyla atlamayı bırakır.

#### Sonraki Adım

Sonuç sayfası artık eksiksiz: eşleşen küme belirlendi, sıralandı, ayarlandı, sayılara indirgendi ve
okuyan kişiye gösterilecek biçime getirildi. Bütün bu iş tek bir varsayıma dayanıyor: sorgudaki
sözcük ile belgedeki sözcük **aynı sözcük**. Katalogta bu varsayım sık sık tutmaz. "hikâye" arayan
okur "öykü" başlıklı kitapları göremez; ters dizin için bu iki dizgi arasında hiçbir bağ yoktur ve
şimdiye kadarki hiçbir ayar bu bağı kuramaz, çünkü ağırlık da işlev de yalnız eşleşmiş bir terimin
puanını değiştirir. Sonraki ders sözcüğün kendisini değil, sözcüğün geçtiği bağlamı karşılaştıran
bir erişim biçimini kurar ve iki yolun döndürdüğü kümeyi ve sırayı yan yana ölçer.

### Anlamsal ve Vektör Arama

Source: https://academia.sh/tr/kurslar/arama-motorlari/anlamsal-ve-vektor-arama

Sözcüğün kendisi yerine geçtiği bağlamı karşılaştıran erişim: terim sayımından üretilen gömmelerin kurulması, eşanlamlı iki terimin hiç birlikte geçmeden yakın çıkması, vektör erişiminin terim erişimine göre kazandırdığı ve kaybettirdiği belgeler, karma aramanın iki listeyi birleştirmesi ve vektör dizininin bayt ile tarama maliyeti.

Buraya kadarki bütün düzenek tek bir varsayıma dayanıyor: sorgudaki sözcük ile belgedeki sözcük
aynı dizidir. Katalogda bu varsayım sık sık tutmaz. "hikâye" arayan okur, adında "öykü" geçen
kitapları göremez. Bu bir puanlama sorunu değildir: ters dizin için o iki dizgi arasında hiçbir bağ
yoktur, dolayısıyla alan ağırlığı da işlev tabanlı puan da o belgeleri getiremez — ikisi de yalnız
**eşleşmiş** bir terimin puanını değiştirir.

Bu ders başka bir erişim biçimi kurar. Her terim ve her belge, sabit uzunlukta bir **sayı dizisiyle**
temsil edilir; buna **gömme** denir. İki belgenin ya da bir sorgu ile bir belgenin yakınlığı, bu iki
dizi arasında hesaplanan bir **ölçüdür**. Sözcük eşleşmesi aranmaz; sayılar karşılaştırılır. Terim
"gömme" bu kursta iki anlamda geçer: belge modelinde bir belgenin başka bir belgenin içine
gömülmesini anlatıyordu, burada bir metnin sayı dizisine dönüştürülmüş halidir.

#### Derlem ve Gömmenin Kurulumu

| Kod | Varsayım | Değer | Gerekçe |
|---|---|---|---|
| SI22 | gömmeye giren alan | ad ve özet | konu ve yazar alanları vektöre girmez |
| SI23 | bağlam penceresi | belgenin tamamı | aynı kayıtta geçen iki terim birlikte geçmiş sayılır |
| SI24 | gömme boyutu | 32; izdüşüm aynı tohumdan | vektörler her koşumda aynı çıkar |
| SI25 | eşanlamlı çift | "öykü" ve "hikâye" aynı bağlamda, hiçbir belgede birlikte değil | ölçülen kazanç bu yapıdan gelir, dilden çıkarılmaz |

SI25 açıkça yazılmalıdır: derlem, iki eşanlamlının aynı komşu sözcüklerle geçeceği biçimde
üretilmiştir. Ders bu yapının **ölçülebilir sonucunu** gösterir; gömmenin dildeki her eşanlamlıyı
bulacağını göstermez.

```js
// derlem.mjs — kutuphane katalogu: tohumlu derlem, ters dizin ve puanlayici.
export const N = 1200, TOHUM = 20260801, ALANLAR = ["ad", "ozet", "konu", "yazar"];
let d = TOHUM;                                            // 32 bit uretec, tasma yok
export const rast = () => { d = (d + 0x6D2B79F5) | 0; let t = Math.imul(d ^ (d >>> 15), 1 | d);
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 2 ** 32; };
const sec = (a) => a[Math.floor(rast() * a.length)];
const TEMA = [["öykü|hikâye", "edebiyat", "kısa seçki derleme anlatı kurgu taşra"],
  ["masal", "çocuk edebiyatı", "çocuk resimli okul genç orman uyku"],
  ["tarih", "tarih", "osmanlı cumhuriyet arşiv belge kronik vakıf"],
  ["deniz", "gezi", "kıyı balıkçı liman gemi ada fener"],
  ["matematik", "bilim", "geometri sayı kanıt kuram çözüm olasılık"],
  ["şiir", "şiir", "dize toplu divan çeviri seçme aruz"]]
  .map(([k, konu, a]) => ({ k: k.split("|"), konu, ana: a.split(" ") }));
const ORTAK = "kitap cilt baskı yayın inceleme notlar giriş sözlük".split(" ");
const AD = "Ahmet Elif Selim Nuray Kemal Deniz Ayşe Ozan Meral Barış".split(" ");
const SOY = "Yıldız Aksu Demir Karaca Toprak Şen".split(" ");

export const belgeler = Array.from({ length: N }, (_, id) => {
  const t = sec(TEMA), cek = t.k[t.k.length > 1 && rast() < 0.5 ? 1 : 0], oz = [cek, cek];
  const ad = [...new Set([cek, sec(t.ana), sec(t.ana)])];
  t.ana.forEach((s, j) => { if (rast() < 1 / (1 + j * 0.42)) oz.push(s); });
  ORTAK.forEach((s, j) => { if (rast() < 0.55 / (1 + j * 0.28)) oz.push(s); });
  if (rast() < 0.5) oz.push(sec(sec(TEMA).ana));          // baska temadan sizan sozcuk
  for (let k = oz.length - 1; k > 0; k -= 1) { const j = Math.floor(rast() * (k + 1));
    [oz[k], oz[j]] = [oz[j], oz[k]]; }
  // konu etiketi yayinevi kararidir: belgelerin dortte birinde metinle ayni temada degil
  return { id, ad: ad.join(" "), ozet: oz.join(" "), konu: (rast() < 0.25 ? sec(TEMA) : t).konu,
    yazar: `${sec(AD)} ${sec(SOY)}`, yil: 1975 + Math.floor(rast() ** 0.6 * 50),
    odunc: Math.floor(rast() ** 3 * 400) };
});

export const belirtec = (s) => s.toLocaleLowerCase("tr").match(/[\p{L}\p{N}]+/gu) ?? [];
export function dizinKur(bs) {                            // alan basina ters dizin
  const dz = {};
  for (const a of ALANLAR) {
    const gonderi = new Map(), boy = new Float64Array(bs.length);
    for (const b of bs) {
      const ts = belirtec(b[a]), say = new Map();
      boy[b.id] = ts.length;
      for (const t of ts) say.set(t, (say.get(t) ?? 0) + 1);
      for (const [t, tf] of say) {
        if (!gonderi.has(t)) gonderi.set(t, []);
        gonderi.get(t).push({ id: b.id, tf });
      }
    }
    dz[a] = { gonderi, boy, ort: boy.reduce((x, y) => x + y, 0) / bs.length };
  }
  return dz;
}
export function ara(dz, sorgu) {          // esit alan agirligiyla puanlanmis sonuc listesi
  const terimler = belirtec(sorgu), k1 = 1.2, b = 0.75, p = new Map();
  for (const a of ALANLAR) for (const t of terimler) {
    const g = dz[a].gonderi.get(t);
    if (!g) continue;
    const idf = Math.log(1 + (N - g.length + 0.5) / (g.length + 0.5));
    for (const e of g) {
      const nrm = 1 - b + (b * dz[a].boy[e.id]) / dz[a].ort;
      p.set(e.id, (p.get(e.id) ?? 0) + (idf * e.tf * (k1 + 1)) / (e.tf + k1 * nrm));
    }
  }
  return [...p].sort((x, y) => y[1] - x[1] || x[0] - y[0]);
}
```

#### Bağlamdan Vektör Üretmek

Gömme şöyle kurulur. Her terim için, o terimle aynı kayıtta geçen bütün terimlerin sayımı tutulur;
bu sayım o terimin bağlamıdır. Bağlam vektörü 51 sözcüklük sözlük boyundadır ve tohumlu bir izdüşümle
32 boyuta indirilir. Belgenin gömmesi, içindeki terimlerin gömmelerinin ağırlıklı ortalamasıdır.
Sorgu da aynı işlemden geçer ve benzerlik iki birim vektörün iç çarpımıdır.

```js
// gomme.mjs — terim sayimindan uretilen gomme, vektor erisimi ve karma arama.
import { belgeler, dizinKur, ara, belirtec, rast, N, TOHUM, ALANLAR } from "./derlem.mjs";
const D = 32, dz = dizinKur(belgeler), tr = (x) => Math.round(x).toLocaleString("tr-TR");
const metin = (b) => [...new Set([...belirtec(b.ad), ...belirtec(b.ozet)])];

const bs = new Map();                                     // sozluk: terim -> belge sikligi
for (const b of belgeler) for (const t of metin(b)) bs.set(t, (bs.get(t) ?? 0) + 1);
const V = [...bs.keys()], yer = new Map(V.map((t, i) => [t, i]));
const C = V.map(() => new Float64Array(V.length));        // birlikte gecis sayimi
for (const b of belgeler) { const ts = metin(b);
  for (const a of ts) for (const c of ts) if (a !== c) C[yer.get(a)][yer.get(c)] += 1; }

const idf = V.map((t) => Math.log(N / bs.get(t)));
const R = V.map(() => Float64Array.from({ length: D }, () => (rast() < 0.5 ? -1 : 1)));
const birle = (v) => { let s = 0; for (const x of v) s += x * x; s = Math.sqrt(s) || 1;
  for (let i = 0; i < v.length; i += 1) v[i] /= s; return v; };
const terimVek = C.map((satir) => {                       // baglam sayimi -> D boyutlu gomme
  const p = new Float64Array(D);
  for (let i = 0; i < satir.length; i += 1) if (satir[i]) { const w = satir[i] * idf[i];
    for (let j = 0; j < D; j += 1) p[j] += w * R[i][j]; }
  return birle(p);
});
const gomme = new Float32Array(N * D);                    // belge gommesi: terimlerin ortalamasi
for (const b of belgeler) { const p = new Float64Array(D);
  for (const t of metin(b)) { const v = terimVek[yer.get(t)], w = idf[yer.get(t)];
    for (let j = 0; j < D; j += 1) p[j] += w * v[j]; }
  gomme.set(birle(p), b.id * D); }
const sorguVek = (q) => { const p = new Float64Array(D);
  for (const t of belirtec(q)) if (yer.has(t)) { const v = terimVek[yer.get(t)];
    for (let j = 0; j < D; j += 1) p[j] += idf[yer.get(t)] * v[j]; }
  return birle(p); };
const vektorAra = (q) => { const s = sorguVek(q), r = [];
  for (let i = 0; i < N; i += 1) { let d = 0;
    for (let j = 0; j < D; j += 1) d += s[j] * gomme[i * D + j];
    r.push([i, d]); }
  return r.sort((a, b) => b[1] - a[1] || a[0] - b[0]); };
const karma = (t, v) => {                                 // sira temelli birlestirme, k = 60
  const p = new Map(), ek = (l) => l.slice(0, 100).forEach(([id], i) =>
    p.set(id, (p.get(id) ?? 0) + 1 / (60 + i + 1)));
  ek(t); ek(v);
  return [...p].sort((a, b) => b[1] - a[1] || a[0] - b[0]);
};

let giris = 0;
for (const a of ALANLAR) for (const g of dz[a].gonderi.values()) giris += g.length;
console.log(`derlem ${N} belge, tohum ${TOHUM}; sözlük ${V.length} terim, gömme boyutu ${D}`);
console.log(`vektör dizini ${tr(gomme.byteLength)} bayt; ters dizin ${tr(giris)} gönderi ` +
  `girişi x 8 bayt = ${tr(giris * 8)} bayt`);
console.log(`sorgu başına vektör taraması ${tr(N * D)} çarpma-toplama; küme sütunu terim yolunda ` +
  `eşleşen belgeyi, vektör yolunda benzerliği 0,5 ve üstü olan belgeyi sayar\n`);
const kos = (a, b) => { let s = 0; for (let i = 0; i < a.length; i += 1) s += a[i] * b[i]; return s; };
for (const t of ["hikâye", "masal"]) {
  const k = V.map((u, i) => [u, kos(terimVek[yer.get(t)], terimVek[i])])
    .sort((a, b) => b[1] - a[1]).slice(1, 5);
  console.log(`"${t}" gömmesine en yakın terimler: ` +
    k.map(([u, d]) => `${u} ${d.toFixed(3)}`).join(", "));
}
console.log(`belge 0 gömmesinin ilk dört bileşeni: ` +
  [...gomme.slice(0, 4)].map((x) => x.toFixed(3)).join(", ") + "\n");
console.log("sorgu".padEnd(11) + "yol".padEnd(8) + "küme".padStart(7) +
  "ilk10 terimli".padStart(15) + "ilk10 eşanlamlı".padStart(17) + "terim ilk10 ile ortak".padStart(23) +
  "  ilk sıradaki");
for (const [q, es] of [["hikâye", "öykü"], ["kısa öykü", "hikâye"]]) {
  const t = ara(dz, q), v = vektorAra(q), k = karma(t, v);
  const qt = new Set(belirtec(q)), tOn = new Set(t.slice(0, 10).map(([id]) => id));
  const esik = v.filter(([, d]) => d >= 0.5).length;
  for (const [ad, l, n] of [["terim", t, t.length], ["vektör", v, esik], ["karma", k, k.length]]) {
    const on = l.slice(0, 10).map(([id]) => belgeler[id]);
    const terimli = on.filter((b) => metin(b).some((x) => qt.has(x))).length;
    const esanlamli = on.filter((b) => metin(b).includes(es)).length;
    console.log(q.padEnd(11) + ad.padEnd(8) + tr(n).padStart(7) + `${terimli}`.padStart(15) +
      `${esanlamli}`.padStart(17) +
      `${on.filter((b) => tOn.has(b.id)).length}`.padStart(23) + `  ${on[0].ad}`);
  }
}
```

```
derlem 1200 belge, tohum 20260801; sözlük 51 terim, gömme boyutu 32
vektör dizini 153.600 bayt; ters dizin 16.050 gönderi girişi x 8 bayt = 128.400 bayt
sorgu başına vektör taraması 38.400 çarpma-toplama; küme sütunu terim yolunda eşleşen belgeyi, vektör yolunda benzerliği 0,5 ve üstü olan belgeyi sayar

"hikâye" gömmesine en yakın terimler: öykü 0.997, anlatı 0.891, seçki 0.854, kurgu 0.847
"masal" gömmesine en yakın terimler: çocuk 0.864, uyku 0.839, okul 0.812, orman 0.800
belge 0 gömmesinin ilk dört bileşeni: -0.175, -0.092, -0.125, 0.231

sorgu      yol        küme  ilk10 terimli  ilk10 eşanlamlı  terim ilk10 ile ortak  ilk sıradaki
hikâye     terim       108             10                0                     10  hikâye taşra
hikâye     vektör      383              4                6                      2  hikâye anlatı kısa
hikâye     karma       153             10                0                      5  hikâye anlatı kısa
kısa öykü  terim       245             10                0                     10  öykü kısa
kısa öykü  vektör      452             10                4                      1  hikâye anlatı kısa
kısa öykü  karma       156             10                0                      5  öykü taşra kısa
```

İlk satırlar gömmenin ne olduğunu somutlaştırıyor: belge 0'ın gömmesi otuz iki ondalık sayıdan
oluşur ve ilk dördü ekranda duruyor. "hikâye" ile "öykü" arasındaki yakınlık 0,997 — bu iki terim
derlemde **hiçbir belgede birlikte geçmiyor**, yakınlıkları yalnız aynı komşularla (anlatı, seçki,
kurgu) geçmelerinden geliyor. Ters dizinde bu iki dizgi arasında ölçülebilir hiçbir ilişki yoktur.

#### Kazanılan, Kaybedilen ve İkisini Birleştiren

"hikâye" sorgusunun üç satırı bu dersin özetidir. Terim yolu 108 belge eşleştiriyor ve ilk onun onu
da gerçekten "hikâye" taşıyor: kesinlik tam, ama "öykü" taşıyan tek bir belge bile yok — bunlar
kümenin dışında ve hiçbir ağırlıkla içeri giremez.

Vektör yolu ilk onun **altısını** "öykü" taşıyan belgelerden getiriyor. Kazanç budur ve terim
yolunun yapısal olarak veremeyeceği bir kazançtır. Aynı satırda kayıp da duruyor: ilk onun yalnız
dördü "hikâye" taşıyor, terim yolunun ilk onuyla ortak belge sayısı ikiye düşmüş. Kütüphaneci tam
o sözcüğü arıyorsa altı satır işine yaramaz.

Küme sütunu ikinci bir farkı gösteriyor. Terim yolunda "eşleşen belge" tanımlıdır: 108. Vektör
yolunda böyle bir şey yoktur; her belgenin bir benzerliği vardır ve küme ancak bir **eşikle**
seçilir. 0,5 eşiğinde 383 belge kalıyor; eşik 0,6 seçilseydi başka bir sayı çıkardı. Vektör aramada
kümenin sınırı veriden değil, seçilen eşikten gelir.

"kısa öykü" satırları farkın ne kadar derin olabileceğini gösteriyor: iki yolun ilk onunda **tek**
ortak belge var. İki liste de savunulabilir, biri sözcüğü tutuyor, öteki bağlamı; ve neredeyse
ayrıklar.

Karma satırları iki listeyi sıra temelli birleştiriyor: her belgenin puanı, iki listedeki
sıralarının tersinden gelir. Sonuç ilk onda tam eşlemeyi geri alıyor — onda onu "hikâye" taşıyor —
ve terim listesiyle ortaklık ikiden beşe çıkıyor. Ama eşanlamlı sütunu sıfıra düşüyor: birleştirme,
vektör yolunun kazandırdığı belgeleri ilk onun dışına itiyor. Karma arama iki listenin kesişimini
öne çıkarır; kazanç ilk onda değil, listenin daha derininde durur. Küme de daralıyor: 153 belge,
çünkü birleştirmeye her iki listeden yalnız ilk yüz belge giriyor.

Bedel tarafında iki sayı var. Vektör dizini 153.600 bayt tutuyor, ters dizin 128.400; vektör dizini
belge sayısıyla **doğrusal** büyür (belge başına 128 bayt, sabit), ters dizin ise belgelerin
uzunluğuna ve terim çeşitliliğine bağlıdır. Asıl fark sorgu anında: terim yolu yalnız sorgu
terimlerinin gönderi listelerini okur, "hikâye" için 216 giriş; vektör yolu **her belgeye** dokunur
ve 38.400 çarpma-toplama yapar. Vektör aramada eşleşmeyen belge diye bir şey olmadığı için
elenecek belge de yoktur; taramayı kısaltmak ayrı bir yapı gerektirir ve bu ders o yapıyı kurmaz.

#### Özet

- Gömme sabit uzunlukta bir sayı dizisidir, benzerlik iki dizi arasındaki bir ölçüdür: "hikâye" ile
  "öykü" hiçbir belgede birlikte geçmeden 0,997 yakınlıkta çıkar, çünkü aynı komşularla geçerler.
- "hikâye" sorgusunda terim yolu 108 belge ve ilk onda on tam eşleme verir; vektör yolu ilk onun
  altısını "öykü" taşıyan belgelerden doldurur ve yalnız dördünde sorgu terimi bulunur.
- Vektör aramada eşleşen küme yoktur; küme bir eşikle seçilir: 0,5 eşiğinde 383 belge.
- Karma arama tam eşlemeyi ilk onda geri alır (onda on) ve terim listesiyle ortaklığı ikiden beşe
  çıkarır, ama eşanlamlı belgeleri ilk ondan düşürür.
- Vektör dizini belge başına sabit 128 bayttır (toplam 153.600) ve sorgu başına bütün derlemi
  gezerek 38.400 çarpma-toplama ister; terim yolu "hikâye" için yalnız 216 gönderi girişi okur.

#### Sonraki Adım

Bu konu sorgunun bütün yüzeyini kurdu: sorgu dili, eşleşme, süzme, puanlama, ağırlık, toplama,
sunum ve son olarak sözcük yerine bağlamı karşılaştıran erişim. Ölçülen her sayının altında
söylenmemiş bir koşul var: bütün bu sorgular **tek bir süreçte ve tek bir dizin üzerinde** koştu.
Ters dizin bir bilgisayarın belleğine sığdı, puanlayıcı bütün gönderi listelerini yerel olarak
okudu, toplama bütün kovaları tek yerde topladı, sayfalama parçaları aynı süreçte birleştirdi ve
vektör taraması bütün belgelere sırayla dokundu. Katalog bir milyon kayda çıktığında ve dizin
birden çok makineye yayıldığında bu sorguların hiçbiri aynı biçimde koşamaz: ters belge sıklığı
artık kimin bildiği bir sayıdır, kova toplaması nerede birleşir, aynı sorgu iki makinede aynı
sırayı verir mi. Sonraki konu dizinin makinelere dağıtılmasını ve aynı sorgunun orada ne olduğunu
ele alır.

## Küme İşletimi

Dizinin bir küme üzerinde yaşaması: ana, veri ve eşgüdüm düğümlerinin rolleri, parça ile kopya sayısının bölümleme ve yedeklilik dengesi, çoğunluk gerekliliğiyle bölünmüş beyin sorunu, toplu dizinlemenin yazma verimine etkisi, bölüt birleştirmenin arka plan maliyeti, devretme, soğutma ve silme politikalarıyla dizin yaşam döngüsü, anlık görüntü ile geri yükleme stratejisi ve kullanıcı, rol ile API anahtarı yönetimi.

### Düğüm Rolleri

Source: https://academia.sh/tr/kurslar/arama-motorlari/dugum-rolleri

Aramanın tek süreçten kümeye taşınması: veri, eşgüdüm ve ana düğümlerin ayrılması, aynı üç yüz sorgunun iki rol yerleşiminde koşturulup her düğümün okuduğu gönderi girişi ile yaptığı birleştirme adımının sayılması, parça sayısı artarken veri işinin bölünüp eşgüdüm işinin bölünmediğinin gösterilmesi ve ana düğüm rolünün yüklü bir veri düğümüne bindirilmesinin küme durumunu geciktirerek dönen belge kümesiyle ilk beş sonucun sırasını değiştirmesinin ölçülmesi.

Önceki konu bütün sorguları tek bir süreçte ve tek bir dizin üzerinde koşturdu: sorgu
değerlendirici gönderi listelerini doğrudan okuyordu ve dönen sıra puanlayıcının hesapladığı
sıranın kendisiydi. Katalog büyüdüğünde bu düzenek iki yerinden kırılır — dizin tek bir
makinenin belleğine sığmaz olur ve tek bir süreç gelen sorgu hızına yetişemez. Dizin birden
çok makineye yayıldığında sorgunun kendisi değişmez; değişen, sorguyu kimin karşıladığı,
gönderi listelerini kimin okuduğu ve dönen adayları kimin birleştirdiğidir. Bu ders o üç işi
üç role ayırır ve rollerin küme üzerine nasıl dağıtıldığının neyi değiştirdiğini sayar.

#### Üç Rol

**Veri düğümü** bir parçayı taşır: o parçanın ters dizini onun belleğindedir. Sorgu geldiğinde
yalnızca kendi parçasını tarar ve kendi yerel sırasının ilk adaylarını döndürür.

**Eşgüdüm düğümü** sorguyu karşılar, her parçaya birer istek gönderir, dönen aday listelerini
birleştirir ve son listeyi çıkarır; kendisi gönderi listesi okumaz.

**Ana düğüm** sorgu yolunda hiç yer almaz. Taşıdığı şey küme durumudur: hangi parçanın hangi
düğümde olduğu, hangi eşlemenin geçerli olduğu, bir düğüm eklendiğinde parçaların nereye
atanacağı. Bu durumun yeni sürümünü kümeye yayabilen tek düğüm odur.

Aşağıdaki düzenek bu üç rolü **süreç içinde modeller**: düğümler aynı süreçte nesnelerdir, ağ
ve ileti gecikmesi yoktur. Model olduğu için ölçtüğü şey süre değil, her düğümün yaptığı iştir —
okunan gönderi girişi ve yapılan birleştirme adımı, ikisi de koşumdan bağımsız.

**AK1 — sorgu karışımı dört katalog sorusundan oluşur ve 300 tur boyunca eşit paylıdır.**
**AK2 — bir küme durumu değişikliğinin uygulanması 500 iş birimi tutar, dizinleme hızı 20 iş
birimi başına bir belgedir.** İki varsayım da doğrusal etkilidir: değerler değişirse aşağıdaki
gecikme ve reddedilen belge sayısı aynı oranda değişir.

```js
// kume/derlem.mjs — kutuphane katalogu derlemi ve ters dizin. Uretec kendi yazilmistir, tohum
// gorunurdur: her kosumda ayni 4000 kayit cikar. Dizin bir sozluk tutar: terim -> gonderi
// listesi; gonderi burada bir belge girisidir (belge kimligi + terim sikligi).
const ORTAK = ["kitap", "yazar", "eser", "metin", "bolum", "baski", "sayfa", "dil", "cilt", "yayin"];
const OZEL = {
  cocuk: ["masal", "resimli", "okul", "oyun", "hayvan", "cizgi"], oyku: ["kisa", "anlati", "derleme", "gunluk", "yalnizlik", "kasaba"],
  roman: ["kahraman", "kent", "kusak", "ev", "yolculuk", "mektup"], tarih: ["imparatorluk", "belge", "arsiv", "savas", "yuzyil", "vakayiname"],
  gezi: ["deniz", "yol", "harita", "sehir", "liman", "gemi"], siir: ["dize", "olcu", "imge", "ses", "sessizlik", "kafiye"],
  deneme: ["dusunce", "elestiri", "okuma", "zaman", "not", "soylesi"], bilim: ["olcum", "deney", "kuram", "veri", "gozlem", "denklem"],
};
const NADIR = ["fener", "kuyu", "ipek", "kule", "bahce", "kar", "ada", "koru", "tas", "cinar", "kirlangic", "demirci", "pusula", "kehribar"];
const KONU = Object.keys(OZEL);

export function derlem({ adet = 4000, tohum = 20260731 } = {}) {
  let s = tohum % 2147483647;
  const r = () => (s = (s * 48271) % 2147483647) / 2147483647;
  const sec = (a) => a[Math.floor(r() * a.length)], belge = [];
  for (let i = 1; i <= adet; i += 1) {
    const konu = sec(KONU), oz = OZEL[konu], soz = [sec(oz), sec(NADIR)];
    if (r() < 0.5) soz.push(sec(ORTAK));
    for (let j = 0, n = 10 + Math.floor(r() * 7); j < n; j += 1)
      soz.push(r() < 0.45 ? sec(ORTAK) : r() < 0.85 ? sec(oz) : sec(NADIR));
    belge.push({ id: i, konu, yil: 1990 + Math.floor(r() * 36), metin: [soz[0], soz[1], konu, ...soz.slice(2)].join(" ") });
  }
  return belge;
}

export function dizinle(belge) {
  const gonderi = new Map(), uzunluk = new Map();
  let bayt = 0;
  for (const d of belge) {
    const t = d.metin.split(" "), sayim = new Map();
    for (const x of t) sayim.set(x, (sayim.get(x) ?? 0) + 1);
    uzunluk.set(d.id, t.length);
    for (const [x, n] of sayim) {
      if (!gonderi.has(x)) { gonderi.set(x, []); bayt += x.length + 4; }
      gonderi.get(x).push([d.id, n]); bayt += 8;
    }
  }
  const ort = [...uzunluk.values()].reduce((a, b) => a + b, 0) / (belge.length || 1);
  return { gonderi, uzunluk, N: belge.length, ort, bayt };
}

// Puanlama: terim sikligi, ters belge sikligi ve belge uzunlugu. YEREL puanlamada df ve ortalama
// uzunluk yalniz bu dizinden okunur; kuresel verilirse butun derlemin istatistigi kullanilir.
export function ara(dz, terim, k, kuresel = null) {
  const N = kuresel ? kuresel.N : dz.N, ort = kuresel ? kuresel.ort : dz.ort, puan = new Map();
  let taranan = 0;
  for (const t of terim) {
    const g = dz.gonderi.get(t) ?? [];
    const df = kuresel ? kuresel.df.get(t) ?? 0 : g.length;
    const idf = Math.log(1 + (N - df + 0.5) / (df + 0.5));
    for (const [id, tf] of g) {
      taranan += 1;
      const norm = tf + 1.2 * (0.25 + 0.75 * dz.uzunluk.get(id) / ort);
      puan.set(id, (puan.get(id) ?? 0) + idf * tf * 2.2 / norm);
    }
  }
  const sirali = [...puan].sort((a, b) => b[1] - a[1] || a[0] - b[0]).slice(0, k);
  return { aday: sirali.map(([id, p]) => ({ id, p })), taranan, eslesen: puan.size };
}

// k-yollu birlestirme: her adimda parca listelerinin basi karsilastirilir, en yuksek puanli
// alinir. Adim sayisi gercek karsilastirma sayisidir ve makineden bagimsizdir.
export function birlestir(liste, k) {
  const konum = liste.map(() => 0), sonuc = [];
  let adim = 0, en = 0;
  while (sonuc.length < k && en >= 0) {
    en = -1;
    for (let i = 0; i < liste.length; i += 1) {
      const a = liste[i][konum[i]], b = en < 0 ? null : liste[en][konum[en]];
      if (!a) continue;
      adim += 1;
      if (!b || a.p > b.p || (a.p === b.p && a.id < b.id)) en = i;
    }
    if (en >= 0) { sonuc.push(liste[en][konum[en]]); konum[en] += 1; }
  }
  return { sonuc, adim };
}
```

```js
// kume/roller.mjs — ana, veri ve esgudum dugumlerinin sorgu basina yaptigi is. Kume SUREC ICI
// BIR MODELDIR: dugumler ayni surecte nesnelerdir, ag yoktur. Olculen sey sure degil, okunan
// gonderi girisi ile yapilan birlestirme adimidir; ikisi de is birimi sayilir.
import { derlem, dizinle, ara, birlestir } from "./derlem.mjs";

const P = 4, KP = 10, K = 10, TUR = 300, DURUM_ISI = 500, BELGE_ISI = 20, YENI = 800;
const SORGU = [["kisa", "oyku", "yalnizlik"], ["deniz", "liman", "gemi"],
  ["masal", "resimli", "cocuk"], ["arsiv", "belge", "yuzyil"]];
const s = (x, n) => String(x).padStart(n);
const parcala = (b, p) => Array.from({ length: p }, (_, i) => dizinle(b.filter((d) => d.id % p === i)));

// Bir sorgu turu: esgudum dugumu her parcaya gider, parca basina KP yerel aday toplar, birlestirir.
function sorgula(dizin, terim, esgudum, is) {
  const liste = [];
  for (let i = 0; i < dizin.length; i += 1) {
    const r = ara(dizin[i], terim, KP);
    is[`d${i}`] = (is[`d${i}`] ?? 0) + r.taranan;
    liste.push(r.aday);
  }
  const b = birlestir(liste, K);
  is[esgudum] = (is[esgudum] ?? 0) + b.adim;
  return { sonuc: b.sonuc, aday: liste.reduce((t, l) => t + l.length, 0), adim: b.adim };
}

const belge = derlem(), tam = dizinle(belge), dizin = parcala(belge, P), yuk = {};
console.log(`${belge.length} belge, ${P} parca, parca basina ${KP} aday, son liste ${K}. Tohum 20260731.`);
console.log(`tek parcali dizin: ${tam.gonderi.size} terim, ${tam.bayt} bayt. Sorgu turu: ${TUR},`);
console.log(`esgudum dugumune ulasan aday: ${TUR * P * KP}.\n`);
console.log("yerlesim        | dugum | roller           | is birimi | pay");
console.log("----------------|-------|------------------|-----------|------");
for (const ayri of [false, true]) {
  const is = {}, esgudum = ayri ? "e0" : "d0";
  for (let t = 0; t < TUR; t += 1) sorgula(dizin, SORGU[t % SORGU.length], esgudum, is);
  const dugum = ayri ? ["a0", "e0", "d0", "d1", "d2", "d3"] : ["d0", "d1", "d2", "d3"];
  const rol = (n) => n === "a0" ? "ana" : n === "e0" ? "esgudum"
    : (n === "d0" && !ayri) ? "ana+veri+esgudum" : "veri";
  const toplam = dugum.reduce((t, n) => t + (is[n] ?? 0), 0);
  yuk[ayri ? "ayri" : "birlesik"] = is;
  for (const n of dugum)
    console.log(`${(ayri ? "roller ayri" : "roller birlesik").padEnd(15)} | ${n.padEnd(5)} | ${rol(n).padEnd(16)} | ` +
      `${s(is[n] ?? 0, 9)} | ${s("%" + (100 * (is[n] ?? 0) / toplam).toFixed(1), 5)}`);
}

console.log("\nesgudum dugumunun sorgu basina isi (ayni derlem, parca sayisi degisiyor):");
console.log("parca | toplanan aday | birlestirme adimi | esgudum payi | veri dugumu basina taranan giris");
for (const p of [1, 2, 4, 8, 16]) {
  const is = {}, r = sorgula(parcala(belge, p), SORGU[0], "e0", is);
  const veri = Object.entries(is).filter(([n]) => n !== "e0").reduce((t, [, v]) => t + v, 0);
  console.log(`${s(p, 5)} | ${s(r.aday, 13)} | ${s(r.adim, 17)} | ` +
    `${s("%" + (100 * r.adim / (veri + r.adim)).toFixed(1), 12)} | ${s(Math.round(veri / p), 32)}`);
}

// Ana dugum sorgu yolunda yer almaz; kume durumunu yayar. 150. sorgudan sonra besinci parca
// istenirse, ana rolu bir veri dugumune bindirildiginde bu is o dugumun sorgu kuyrugunun
// arkasina gecer. Atama uygulanana kadar yeni parcaya yonlenecek belgeler reddedilir.
console.log(`\nana dugumun yeri: 150. sorgudan sonra besinci parca isteniyor, ${YENI} yeni kayit bekliyor`);
console.log("yerlesim        | ana dugumun kuyrugu | durum gecikmesi | reddedilen | dizinlenen belge | eslesen | ilk 5 sonuc");
const genis = derlem({ adet: 4000 + YENI });
for (const ayri of [false, true]) {
  const kuyruk = ayri ? 0 : Math.round(yuk.birlesik.d0 * (TUR - 150) / TUR);
  const gecikme = kuyruk + DURUM_ISI;
  const red = Math.min(YENI, Math.floor(gecikme / BELGE_ISI));
  const kayit = genis.filter((d) => d.id <= 4000 || d.id > 4000 + red);
  const dz = parcala(kayit, red === YENI ? P : P + 1), is = {};
  const son = sorgula(dz, SORGU[0], "e0", is);
  const eslesen = dz.reduce((t, x) => t + ara(x, SORGU[0], K).eslesen, 0);
  console.log(`${(ayri ? "roller ayri" : "roller birlesik").padEnd(15)} | ${s(kuyruk, 19)} | ${s(gecikme, 15)} | ` +
    `${s(red, 10)} | ${s(kayit.length, 16)} | ${s(eslesen, 7)} | ${son.sonuc.slice(0, 5).map((x) => x.id).join(" ")}`);
}
console.log(`\nkosumdan bagimsiz: esgudum dugumu sorgu basina P x ${KP} aday toplar. Parca eklendikce` +
  ` veri dugumu basina is bolunur, esgudum dugumunun isi bolunmez.`);
```

```
4000 belge, 4 parca, parca basina 10 aday, son liste 10. Tohum 20260731.
tek parcali dizin: 80 terim, 382752 bayt. Sorgu turu: 300,
esgudum dugumune ulasan aday: 12000.

yerlesim        | dugum | roller           | is birimi | pay
----------------|-------|------------------|-----------|------
roller birlesik | d0    | ana+veri+esgudum |     94875 | %27.4
roller birlesik | d1    | veri             |     78375 | %22.6
roller birlesik | d2    | veri             |     89100 | %25.7
roller birlesik | d3    | veri             |     84150 | %24.3
roller ayri     | a0    | ana              |         0 |  %0.0
roller ayri     | e0    | esgudum          |     12000 |  %3.5
roller ayri     | d0    | veri             |     82875 | %23.9
roller ayri     | d1    | veri             |     78375 | %22.6
roller ayri     | d2    | veri             |     89100 | %25.7
roller ayri     | d3    | veri             |     84150 | %24.3

esgudum dugumunun sorgu basina isi (ayni derlem, parca sayisi degisiyor):
parca | toplanan aday | birlestirme adimi | esgudum payi | veri dugumu basina taranan giris
    1 |            10 |                10 |         %0.8 |                             1238
    2 |            20 |                20 |         %1.6 |                              619
    4 |            40 |                40 |         %3.1 |                              310
    8 |            80 |                80 |         %6.1 |                              155
   16 |           160 |               160 |        %11.4 |                               77

ana dugumun yeri: 150. sorgudan sonra besinci parca isteniyor, 800 yeni kayit bekliyor
yerlesim        | ana dugumun kuyrugu | durum gecikmesi | reddedilen | dizinlenen belge | eslesen | ilk 5 sonuc
roller birlesik |               47438 |           47938 |        800 |             4000 |     515 | 2689 701 2181 3159 1777
roller ayri     |                   0 |             500 |         25 |             4775 |     624 | 3159 3544 2689 701 2362

kosumdan bagimsiz: esgudum dugumu sorgu basina P x 10 aday toplar. Parca eklendikce veri dugumu basina is bolunur, esgudum dugumunun isi bolunmez.
```

#### Sorgu Başına İş Nereye Düşüyor

İlk tablo aynı 300 sorguyu iki rol yerleşiminde koşturuyor ve **aynı sonucu döndürüyor**: iki
yerleşim de aynı parça dizinlerini okuduğu için dönen belge kümesi ve sıra birebir aynıdır.
Değişen, işin nereye düştüğüdür.

Roller birleşikken sorgular tek bir giriş noktasına, d0'a geliyor; d0 hem kendi parçasını
tarıyor hem de bütün birleştirmeyi yapıyor. 94.875 iş birimiyle kümenin yüzde 27,4'ünü
taşıyor, en hafif düğüm d1 ise yüzde 22,6. Aradaki 12.000 birimlik fark eşgüdüm işinin tam
kendisidir; roller ayrıldığında o birimler e0'a geçiyor ve d0 82.875 birime iniyor.

İkinci satır grubunda ana düğümün payı **yüzde 0,0** çıkıyor: 300 sorgu boyunca a0 tek bir
gönderi girişi okumadı ve tek bir karşılaştırma yapmadı. Ana düğümün yükü sorgu sayısıyla
değil, **küme durumunun değişme sıklığıyla** artar.

#### Eşgüdüm İşi Bölünmez

İkinci tablo aynı derlemi 1, 2, 4, 8 ve 16 parçaya bölüp tek bir sorguyu koşturuyor. İki sütun
ters yönde hareket ediyor. Veri düğümü başına taranan gönderi girişi 1238'den 77'ye iniyor —
parça sayısıyla tam olarak bölünüyor, çünkü aynı gönderi listeleri parçalara dağılıyor. Buna
karşılık eşgüdüm düğümünün topladığı aday ve yaptığı birleştirme adımı 10'dan 160'a çıkıyor:
her parça kendi ilk on adayını göndermek zorundadır ve birleştirici her sonuç için bütün liste
başlarını karşılaştırır.

Eşgüdüm payının yüzde 0,8'den 11,4'e çıkması bu iki eğrinin bileşkesidir: parça eklemek ucuz
olan tarafı böler, pahalı olan tarafı tek düğümde toplar.

#### Ana Düğümün Yeri Kümeyi Değiştiriyor

Üçüncü tablo 150. sorgunun ardından kümeye beşinci bir parça eklenmesini istiyor; 800 yeni
katalog kaydı bu parçaya yönlenmeyi bekliyor. Ana rolü d0'a bindirildiğinde durum değişikliği o
düğümün sorgu kuyruğunun arkasına düşüyor: 47.438 birimlik birikmiş sorgu işi, ardından 500
birimlik durum işi. AK2'nin dizinleme hızıyla bu, 800 kaydın tamamının reddedilmesi demektir ve
derlem 4000 belgede kalıyor. Ana rolü ayrı düğümdeyken gecikme 500 birim, reddedilen kayıt 25;
dizin 4775 belgeye çıkıyor.

Fark sorgu sonucunda görünüyor. Aynı sorgu — kısa öykü konusunda yalnızlık geçen kayıtlar —
birleşik yerleşimde 515, ayrık yerleşimde 624 belgeyle eşleşiyor: 109 belge fark. İlk beş
sonuç da aynı değil. Birleşik yerleşimde sıra 2689, 701, 2181, 3159, 1777; ayrık yerleşimde
3159, 3544, 2689, 701, 2362. Üç belge ortak, ama hiçbiri aynı basamakta değil ve listenin iki
üyesi tamamen değişiyor. Rol yerleşimi bir sorgu ayarı değildir; buna karşın dönen kümeyi ve
sırayı değiştirir, çünkü hangi belgelerin dizine girebildiğini belirler.

#### Özet

- Üç rol üç ayrı işe karşılık gelir: veri düğümü gönderi listesi okur, eşgüdüm düğümü aday
  toplayıp birleştirir, ana düğüm küme durumunu yayar ve sorgu yolunda yer almaz.
- Rollerin ayrılması dönen kümeyi ve sırayı değiştirmedi; 300 sorguda 12.000 iş birimini d0'dan
  eşgüdüm düğümüne taşıdı ve en yüklü düğümün payı yüzde 27,4'ten 25,7'ye indi. Aynı koşumda ana
  düğümün payı yüzde 0,0 kaldı: yükü sorgu sayısıyla değil, durum değişikliğiyle artar.
- Parça sayısı 1'den 16'ya çıktığında veri düğümü başına taranan giriş 1238'den 77'ye indi,
  eşgüdüm düğümünün adayı ve birleştirme adımı 10'dan 160'a, payı yüzde 0,8'den 11,4'e çıktı.
- Ana rolü yüklü bir veri düğümüne bindirildiğinde durum değişikliği 47.938 birim gecikti ve
  800 kaydın tamamı reddedildi; ayrı düğümde gecikme 500 birim, reddedilen 25 kayıt oldu. Bu
  gecikme sorguya geçti: eşleşen belge 515'e karşı 624 ve ilk beşin sırası tamamen değişti.

#### Sonraki Adım

Bu ders parça sayısını sabit tuttu ve her parçadan sabit sayıda aday çekti; iki sayının da
seçilmiş olduğu sorgulanmadı. Daha ağır bir soru açık kaldı: her parça kendi ilk on adayını
**kendi yerel istatistiğiyle** seçiyor ve birleştirici bu yerel puanlara güveniyor. Parça
sayısı arttıkça her parçanın ölçtüğü belge sıklığı küresel değerden uzaklaşır ve seçtiği
adaylar değişir. Sonraki ders parça sayısını 1'den 16'ya çıkarıp ilk on sonucun tek parçalı
dizinin ilk onundan ne kadar saptığını sayar, buna kopya sayısının dizin boyutuna ve düğüm
kaybına etkisini ekler.

### Parçalar ve Kopyalar

Source: https://academia.sh/tr/kurslar/arama-motorlari/parcalar-ve-kopyalar

Parça sayısının bir puanlama kararı olduğunun gösterilmesi: aynı derlem bir, iki, dört, sekiz ve on altı parçaya bölünüp her yerleşimin ilk on sonucunun tek parçalı dizinin ilk onuyla ortak belge ve basamak kayması bakımından karşılaştırılması, aday derinliğini artırmanın sapmayı düzeltmediğinin ölçülmesi, küresel belge sıklığı turunun ilk onu birebir geri getirdiğinin ve fazladan bir tura mal olduğunun sayılması, kopya sayısının dizin baytını ve düğüm kaybında dönen belge kümesini nasıl değiştirdiğinin ayrıca ölçülmesi.

Önceki ders parça sayısını dörtte sabit tuttu ve her parçadan sabit sayıda aday çekti. Bu iki
sayı sessizce bir şey varsayıyordu: her parçanın gönderdiği ilk on aday, gerçekten de o parçanın
en iyi on belgesi. Oysa bir parça puanı hesaplarken **kendi gördüğü** belge sıklığını kullanır.
Dört parçalı bir kümede her parça derlemin dörtte birini görür, on altı parçalıda on altıda
birini; terim seyreldikçe ters belge sıklığı sapar ve parçanın "en iyi" dediği belge değişir. Bu
ders parça sayısını 1'den 16'ya çıkarıp aynı dört katalog sorgusunun ilk on sonucunun ne kadar
bozulduğunu sayar, sonra kopya sayısını ayrı bir karar olarak ölçer.

#### Düzenek

Ölçüt bellidir: **tek parçalı dizinin ilk on sonucu** — bütün derlemin istatistiğini gören tek
yerleşim odur. İki doğruluk ölçüsü kullanılıyor: ortak belge sayısı ve ortak belgelerin ortalama
basamak kayması. Bedel sütunları toplanan aday, birleştirme adımı ve toplam dizin baytıdır.

Küme burada da **süreç içi bir modeldir**: parçalar aynı süreçte ayrı ters dizinlerdir, ağ
yoktur. Ölçülen şey süre değil, istek sayısı ve sayılabilir yapı büyüklükleridir.

**AK3 — belgeler parçalara kimlik kalanına göre, yani içerikten bağımsız ve dengeli dağılır.**
Bu en iyi durumdur: konuya göre dağıtılan bir derlemde her parçanın gördüğü sözcük dağılımı daha
çarpık olur ve aşağıdaki sapma büyür. **AK4 — sorgu her parçanın yalnızca bir kopyasına gider ve
kopyalar aynı belgeleri taşır**; kopya gecikmesi sıfır kabul edilir.

```js
// kume/derlem.mjs — kutuphane katalogu derlemi ve ters dizin. Uretec kendi yazilmistir, tohum
// gorunurdur: her kosumda ayni 4000 kayit cikar. Dizin bir sozluk tutar: terim -> gonderi
// listesi; gonderi burada bir belge girisidir (belge kimligi + terim sikligi).
const ORTAK = ["kitap", "yazar", "eser", "metin", "bolum", "baski", "sayfa", "dil", "cilt", "yayin"];
const OZEL = {
  cocuk: ["masal", "resimli", "okul", "oyun", "hayvan", "cizgi"], oyku: ["kisa", "anlati", "derleme", "gunluk", "yalnizlik", "kasaba"],
  roman: ["kahraman", "kent", "kusak", "ev", "yolculuk", "mektup"], tarih: ["imparatorluk", "belge", "arsiv", "savas", "yuzyil", "vakayiname"],
  gezi: ["deniz", "yol", "harita", "sehir", "liman", "gemi"], siir: ["dize", "olcu", "imge", "ses", "sessizlik", "kafiye"],
  deneme: ["dusunce", "elestiri", "okuma", "zaman", "not", "soylesi"], bilim: ["olcum", "deney", "kuram", "veri", "gozlem", "denklem"],
};
const NADIR = ["fener", "kuyu", "ipek", "kule", "bahce", "kar", "ada", "koru", "tas", "cinar", "kirlangic", "demirci", "pusula", "kehribar"];
const KONU = Object.keys(OZEL);

export function derlem({ adet = 4000, tohum = 20260731 } = {}) {
  let s = tohum % 2147483647;
  const r = () => (s = (s * 48271) % 2147483647) / 2147483647;
  const sec = (a) => a[Math.floor(r() * a.length)], belge = [];
  for (let i = 1; i <= adet; i += 1) {
    const konu = sec(KONU), oz = OZEL[konu], soz = [sec(oz), sec(NADIR)];
    if (r() < 0.5) soz.push(sec(ORTAK));
    for (let j = 0, n = 10 + Math.floor(r() * 7); j < n; j += 1)
      soz.push(r() < 0.45 ? sec(ORTAK) : r() < 0.85 ? sec(oz) : sec(NADIR));
    belge.push({ id: i, konu, yil: 1990 + Math.floor(r() * 36), metin: [soz[0], soz[1], konu, ...soz.slice(2)].join(" ") });
  }
  return belge;
}

export function dizinle(belge) {
  const gonderi = new Map(), uzunluk = new Map();
  let bayt = 0;
  for (const d of belge) {
    const t = d.metin.split(" "), sayim = new Map();
    for (const x of t) sayim.set(x, (sayim.get(x) ?? 0) + 1);
    uzunluk.set(d.id, t.length);
    for (const [x, n] of sayim) {
      if (!gonderi.has(x)) { gonderi.set(x, []); bayt += x.length + 4; }   // sozluk girisi
      gonderi.get(x).push([d.id, n]); bayt += 8;
    }
  }
  const ort = [...uzunluk.values()].reduce((a, b) => a + b, 0) / (belge.length || 1);
  return { gonderi, uzunluk, N: belge.length, ort, bayt };
}

// Puanlama: terim sikligi, ters belge sikligi ve belge uzunlugu. YEREL puanlamada df ve ortalama
// uzunluk yalniz bu dizinden okunur; kuresel verilirse butun derlemin istatistigi kullanilir.
export function ara(dz, terim, k, kuresel = null) {
  const N = kuresel ? kuresel.N : dz.N, ort = kuresel ? kuresel.ort : dz.ort, puan = new Map();
  let taranan = 0;
  for (const t of terim) {
    const g = dz.gonderi.get(t) ?? [];
    const df = kuresel ? kuresel.df.get(t) ?? 0 : g.length;
    const idf = Math.log(1 + (N - df + 0.5) / (df + 0.5));
    for (const [id, tf] of g) {
      taranan += 1;
      const norm = tf + 1.2 * (0.25 + 0.75 * dz.uzunluk.get(id) / ort);
      puan.set(id, (puan.get(id) ?? 0) + idf * tf * 2.2 / norm);
    }
  }
  const sirali = [...puan].sort((a, b) => b[1] - a[1] || a[0] - b[0]).slice(0, k);
  return { aday: sirali.map(([id, p]) => ({ id, p })), taranan, eslesen: puan.size };
}

// k-yollu birlestirme: her adimda parca listelerinin basi karsilastirilir, en yuksek puanli
// alinir. Adim sayisi gercek karsilastirma sayisidir ve makineden bagimsizdir.
export function birlestir(liste, k) {
  const konum = liste.map(() => 0), sonuc = [];
  let adim = 0, en = 0;
  while (sonuc.length < k && en >= 0) {
    en = -1;
    for (let i = 0; i < liste.length; i += 1) {
      const a = liste[i][konum[i]], b = en < 0 ? null : liste[en][konum[en]];
      if (!a) continue;
      adim += 1;
      if (!b || a.p > b.p || (a.p === b.p && a.id < b.id)) en = i;
    }
    if (en >= 0) { sonuc.push(liste[en][konum[en]]); konum[en] += 1; }
  }
  return { sonuc, adim };
}
```

```js
// kume/parca.mjs — parca sayisinin ve aday derinliginin ilk on sonuca etkisi. Kume SUREC ICI
// BIR MODELDIR: parcalar ayni surecte ayri ters dizinlerdir. Olcut, tek parcali dizinin ilk on
// sonucudur; her yerlesim ona gore ortak belge ve sira kaymasi bakimindan karsilastirilir.
import { derlem, dizinle, ara, birlestir } from "./derlem.mjs";

const K = 10, SORGU = [["kisa", "oyku", "yalnizlik"], ["deniz", "liman", "gemi"],
  ["masal", "resimli", "cocuk"], ["arsiv", "belge", "yuzyil"]];
const s = (x, n) => String(x).padStart(n);
const parcala = (b, p) => Array.from({ length: p }, (_, i) => dizinle(b.filter((d) => d.id % p === i)));

// Kuresel istatistik: butun parcalarin belge sikligi toplanir. Bunu kullanmak fazladan bir tur
// demektir — esgudum dugumu once df toplar, sonra parcalara puanlatir.
function kuresel(dz) {
  const df = new Map();
  let N = 0, uz = 0;
  for (const d of dz) {
    N += d.N; uz += d.ort * d.N;
    for (const [t, g] of d.gonderi) df.set(t, (df.get(t) ?? 0) + g.length);
  }
  return { df, N, ort: uz / N };
}

// Bir sorgu: her parca KP yerel aday dondurur, esgudum birlestirir. kur verilirse parcalar
// kuresel istatistikle puanlar (iki turlu sorgu).
function sor(dz, terim, KP, kur = null) {
  const liste = dz.map((d) => ara(d, terim, KP, kur).aday);
  const b = birlestir(liste, K);
  return { ilk: b.sonuc.map((x) => x.id), adim: b.adim, aday: liste.reduce((t, l) => t + l.length, 0) };
}

const belge = derlem(), tek = dizinle(belge);
const olcut = SORGU.map((q) => ara(tek, q, K).aday.map((x) => x.id));
const kiyas = (a, i) => {                       // olcutle ortak belge ve toplam sira kaymasi
  let ortak = 0, kayma = 0;
  a.forEach((id, j) => { const k = olcut[i].indexOf(id); if (k >= 0) { ortak += 1; kayma += Math.abs(k - j); } });
  return { ortak, kayma };
};
const ortala = (f) => { const t = SORGU.map(f); return t.reduce((a, b) => a + b, 0) / t.length; };

console.log(`${belge.length} belge, tohum 20260731, ${tek.gonderi.size} terim. Tek parcali dizin: ${tek.bayt} bayt.`);
console.log(`Olcut: tek parcali dizinin ilk ${K} sonucu, dort katalog sorgusunda ortalama.\n`);
console.log("parca | dizin bayt | aday | birlestirme | derinlik 10: ortak / kayma | derinlik 50 | kuresel df turu");
console.log("------|------------|------|-------------|----------------------------|-------------|----------------");
for (const P of [1, 2, 4, 8, 16]) {
  const dz = parcala(belge, P), kur = kuresel(dz);
  const bayt = dz.reduce((t, d) => t + d.bayt, 0);
  const d10 = ortala((q, i) => kiyas(sor(dz, q, 10).ilk, i).ortak);
  const k10 = ortala((q, i) => { const x = kiyas(sor(dz, q, 10).ilk, i); return x.kayma / x.ortak; });
  const d50 = ortala((q, i) => kiyas(sor(dz, q, 50).ilk, i).ortak);
  const dkur = ortala((q, i) => kiyas(sor(dz, q, 10, kur).ilk, i).ortak);
  const r = sor(dz, SORGU[0], 10);
  console.log(`${s(P, 5)} | ${s(bayt, 10)} | ${s(r.aday, 4)} | ${s(r.adim, 11)} | ` +
    `${s(d10.toFixed(2) + " / " + k10.toFixed(2), 26)} | ${s(d50.toFixed(2), 11)} | ${s(dkur.toFixed(2), 15)}`);
}

console.log("\nilk 10 sonuc (sorgu: kisa oyku yalnizlik):");
console.log(`${"tek parca".padEnd(21)}: ${olcut[0].join(" ")}`);
for (const P of [4, 16]) {
  const dz = parcala(belge, P);
  console.log(`${(P + " parca, derinlik 10").padEnd(21)}: ${sor(dz, SORGU[0], 10).ilk.join(" ")}`);
  console.log(`${(P + " parca, kuresel df").padEnd(21)}: ${sor(dz, SORGU[0], 10, kuresel(dz)).ilk.join(" ")}`);
}

// Kopya: her parcanin R+1 kopyasi ayri dugumlerde durur. Sorgu her parcanin BIR kopyasina
// gider; kopya sayisi sorgu genisligini degistirmez, dizin baytini ve dugum kaybini degistirir.
const P = 4, dz = parcala(belge, P), bayt = dz.reduce((t, d) => t + d.bayt, 0);
console.log("\nkopya sayisi (4 parca, bir dugum kayboluyor):");
console.log("kopya | dugum | toplam dizin bayt | dokunulan dugum | erisilemeyen parca | eslesen | ilk 10'da kalan");
for (const R of [0, 1, 2]) {
  const kayip = R === 0 ? 1 : 0;                    // R=0 iken bir dugum bir parcayi goturur
  const kalan = dz.filter((_, i) => i >= kayip);
  const eslesen = kalan.reduce((t, d) => t + ara(d, SORGU[0], K).eslesen, 0);
  const ilk = sor(kalan, SORGU[0], 10).ilk;
  console.log(`${s(R, 5)} | ${s(P * (R + 1), 5)} | ${s(bayt * (R + 1), 17)} | ${s(P, 15)} | ` +
    `${s(kayip, 18)} | ${s(eslesen, 7)} | ${s(ilk.filter((x) => olcut[0].includes(x)).length + "/10", 15)}`);
}
console.log(`\nkosumdan bagimsiz: toplanan aday P x derinlik, birlestirme adimi ${K} x P, dizin bayti` +
  ` (R+1) kati. Tek turlu sorgu P istek, kuresel df turu 2P istek eder. Sorgunun dokundugu dugum` +
  ` sayisi kopyayla degismez, parca sayisiyla degisir.`);
```

```
4000 belge, tohum 20260731, 80 terim. Tek parcali dizin: 382752 bayt.
Olcut: tek parcali dizinin ilk 10 sonucu, dort katalog sorgusunda ortalama.

parca | dizin bayt | aday | birlestirme | derinlik 10: ortak / kayma | derinlik 50 | kuresel df turu
------|------------|------|-------------|----------------------------|-------------|----------------
    1 |     382752 |   10 |          10 |               10.00 / 0.00 |       10.00 |           10.00
    2 |     383496 |   20 |          20 |                8.50 / 1.28 |        8.50 |           10.00
    4 |     384984 |   40 |          40 |                6.25 / 2.50 |        6.25 |           10.00
    8 |     387960 |   80 |          80 |                4.75 / 2.04 |        4.75 |           10.00
   16 |     393912 |  160 |         160 |                4.75 / 4.01 |        4.75 |           10.00

ilk 10 sonuc (sorgu: kisa oyku yalnizlik):
tek parca            : 3159 3544 2689 2362 3370 701 2448 502 2181 2320
4 parca, derinlik 10 : 2689 701 2181 3159 1777 549 3544 2362 3370 565
4 parca, kuresel df  : 3159 3544 2689 2362 3370 701 2448 502 2181 2320
16 parca, derinlik 10: 2689 1777 3265 2323 2180 2529 3544 502 2181 1150
16 parca, kuresel df : 3159 3544 2689 2362 3370 701 2448 502 2181 2320

kopya sayisi (4 parca, bir dugum kayboluyor):
kopya | dugum | toplam dizin bayt | dokunulan dugum | erisilemeyen parca | eslesen | ilk 10'da kalan
    0 |     4 |            384984 |               4 |                  1 |     379 |            6/10
    1 |     8 |            769968 |               4 |                  0 |     515 |            7/10
    2 |    12 |           1154952 |               4 |                  0 |     515 |            7/10

kosumdan bagimsiz: toplanan aday P x derinlik, birlestirme adimi 10 x P, dizin bayti (R+1) kati. Tek turlu sorgu P istek, kuresel df turu 2P istek eder. Sorgunun dokundugu dugum sayisi kopyayla degismez, parca sayisiyla degisir.
```

#### Parça Sayısı İlk Onu Bozuyor

Birinci tablonun bedel sütunları beklendiği gibi: toplanan aday ve birleştirme adımı parça
sayısıyla doğrusal artıyor, 10'dan 160'a. Toplam dizin baytı 382.752'den 393.912'ye, yüzde 2,9
çıkıyor; kaynağı gönderi listeleri değil sözlüktür — aynı 80 terim her parçada yineleniyor.

Asıl sayı doğruluk sütununda. İki parçada ilk onun ortalama 8,5'i ölçütle ortak; dörtte 6,25;
sekizde ve on altıda 4,75. On altı parçada ilk on sonucun **yarısından fazlası ölçütte hiç
yok** ve ortak kalanların ortalama basamak kayması 1,28'den 4,01'e çıkıyor.

Listeler bunu tek tek gösteriyor. Tek parçalı dizinde sıra 3159, 3544, 2689 diye başlıyor; dört
parçada aynı sorgu 2689, 701, 2181 sırasıyla dönüyor — ölçütün birincisi olan 3159 dördüncü
basamağa iniyor ve listeye ölçütte bulunmayan 1777, 549, 565 giriyor. On altı parçada 3159 ilk
ondan **tamamen çıkıyor** ve ölçütle ortak yalnızca dört belge kalıyor. Hiçbir sorgu değişmedi,
hiçbir belge silinmedi; değişen tek şey her parçanın kaç belge gördüğü.

#### Derinlik Düzeltmiyor, Fazladan Bir Tur Düzeltiyor

Sapmayı kapatmanın akla gelen ilk yolu her parçadan daha çok aday çekmektir. Tablo bunu
çürütüyor: aday derinliği 10'dan 50'ye çıkarıldığında ortak belge sayısı **hiç değişmiyor**
(8,50 / 6,25 / 4,75 / 4,75). Hata kırpmadan gelmiyor: her parça listesini kendi yerel puanına
göre sıralıyor ve birleştirici o puanlara güveniyor. Yanlış olan sıranın uzunluğu değil,
sıranın kendisidir.

Düzelten şey son sütunda: eşgüdüm düğümü önce bütün parçalardan terim başına belge sıklığını
toplar, sonra parçalara **küresel istatistikle** puanlatır. Ortak belge sayısı her parça
sayısında 10,00'a dönüyor; dört ve on altı parçanın listeleri tek parçalı dizininkiyle birebir
aynı. Bedeli sorgunun tek turdan iki tura, P istekten 2P isteğe çıkmasıdır.

#### Kopya Sayısı Kümeyi Korur, Sırayı Düzeltmez

İkinci tablo kopyayı ayrı bir karar olarak ölçüyor. Kopyasız dört parçalı kümede bir düğümün
kaybı bir parçayı tümüyle götürüyor: eşleşen belge 515'ten 379'a düşüyor — 136 katalog kaydı
sorguya hiç görünmüyor — ve ilk ondaki ortak belge 7'den 6'ya iniyor. Bir kopya eklendiğinde
aynı düğüm kaybı hiçbir şey değiştirmiyor.

Bedel dizin baytıdır ve doğrusaldır: 384.984'ten 769.968'e, iki kopyada 1.154.952'ye. Sorgunun
dokunduğu düğüm sayısı üç satırda da 4 — kopya eklemek sorguyu genişletmez.

Son sütunun 7/10'da kalması bu dersin ayrımıdır: kopya **kaybolan belgeleri** geri getirdi,
**yanlış sırayı** getirmedi. O üç belgelik fark yerel puanlamadan geliyor ve ancak küresel
istatistik turuyla kapanır. Yedeklilik bir dayanıklılık kararıdır; bölümleme aynı anda bir
puanlama kararıdır.

#### Özet

- Parça sayısı 1'den 16'ya çıktığında toplanan aday ve birleştirme adımı 10'dan 160'a, toplam
  dizin baytı 382.752'den 393.912'ye çıktı; artış gönderi listelerinden değil, her parçada
  yinelenen sözlükten geliyor.
- İlk onun ölçütle ortak belge sayısı dört parçada 6,25'e, on altı parçada 4,75'e düştü; ortak
  belgelerin ortalama basamak kayması 4,01'e çıktı.
- Aday derinliğini 10'dan 50'ye çıkarmak doğruluğu hiç değiştirmedi: hata kırpmadan değil,
  parça başına yerel belge sıklığından geliyor.
- Küresel belge sıklığı turu ilk onu her parça sayısında 10,00'a döndürdü ve listeleri tek
  parçalı dizinle birebir eşitledi; bedeli sorgunun P istekten 2P isteğe çıkmasıdır.
- Kopyasız kümede bir düğüm kaybı eşleşen belgeyi 515'ten 379'a indirdi; bir kopya bunu tümüyle
  önledi, dizin baytını iki katına çıkardı, dokunulan düğüm sayısını değiştirmedi. Kopya kaybolan
  kümeyi geri getirir, bozulan sırayı getirmez: ilk onun ortak belge sayısı 7/10'da kaldı.

#### Sonraki Adım

Bu ders kopyayı bir yedeklilik kararı olarak ölçtü ve kopyaların aynı belgeleri taşıdığını
varsaydı; küme sağlamken bu doğrudur. Ağ ikiye bölündüğünde ise kopyalar birbirini göremez ve
her yarı kendi başına yazma kabul edebilir: aynı dizine iki yerden belge girer, iki yarının
gönderi listeleri ayrışır. Sonraki ders beş kopyalı bir dizini üç ve iki düğümlük iki yarıya
böler, çoğunluk gerekliliğini açıp kapatır ve iki şeyi sayar — azınlıkta kabul edilen dizinleme
işlemlerinden kaçının bölünme onarıldığında kaybolduğunu ve bölünme sürerken her iki yarıya
gelen sorgunun eksik döndürdüğü belge sayısını.

### Bölünmüş Beyin Sorunu

Source: https://academia.sh/tr/kurslar/arama-motorlari/bolunmus-beyin-sorunu

Beş kopyalı bir dizinin üç ve iki düğümlük iki yarıya bölünmesi: çoğunluk gerekliliği kapalıyken azınlık yarısında kabul edilip onarımda kaybolan dizinleme işlemlerinin sayılması, aynı sorgunun iki yarıda farklı belge kümesi ve farklı ilk beş döndürmesinin ölçülmesi, çoğunluk açıkken azınlığın yazmayı reddedip sorguları eksik yanıtlamasının bedelinin yazma iletisi ve eksik belge cinsinden karşılaştırılması ve onarım sonrası dizinin bölünme hiç olmasaydı oluşacak dizinle karşılaştırılması.

Önceki ders kopyaların aynı belgeleri taşıdığını varsaydı ve bu varsayımla kopyanın kaybolan
kümeyi geri getirdiğini ölçtü. Varsayım yalnızca kopyalar birbirini görebildiği sürece
geçerlidir. Ağ ikiye ayrıldığında beş kopyalı bir dizin iki ayrı dizine dönüşür: her yarı
kendi ters dizinini büyütmeye devam eder, iki yarının gönderi listeleri ayrışır ve aynı sorgu
iki farklı sıra döndürmeye başlar. Bu duruma **bölünmüş beyin** denir. Aynı olgu lider seçimi
anlatısında **iki liderli tur** adıyla ölçülmüştü; seçim mekaniği ve otomatik devralma orada
kuruldu ve burada tekrarlanmaz. Buradaki soru arama tarafındandır: azınlıkta kabul edilen bir
dizinleme işleminden geriye ne kalır ve bölünme sürerken sorgu hangi belgeleri döndürmez.

#### Düzenek

Dizinin beş kopyası var ve ağ üç düğümlük A yarısıyla iki düğümlük B yarısına ayrılıyor.
Bölünme sürerken kataloğa 600 yeni kayıt geliyor — tek konudan gelen bir bağış partisi, yani
sorgunun tam ortasına düşen belgeler. Bir yazmanın kabul edilmesi için kaç kopyanın onaylaması
gerektiği bir ayardır: **çoğunluk gerekliliği kapalıyken** bir onay yeter, **açıkken** beş
kopyanın çoğunluğu olan üç onay gerekir.

Ölçüt, bölünme hiç olmasaydı oluşacak 4600 belgelik dizinin aynı sorguya verdiği yanıttır.
Küme burada da **süreç içi bir modeldir**: yarılar aynı süreçte ayrı ters dizinlerdir, ağ
yerine bir yönlendirme kuralı vardır.

**AK5 — bölünme sürerken gelen 600 kaydın yüzde 65'i üç düğümlük yarıya, yüzde 35'i iki
düğümlük yarıya ulaşır.** Gerekçe: istemciler düğümlere dağıtılmışsa bölünme onları düğüm
sayısına göre ayırır. Doğrusal etkilidir. **AK6 — bölünme onarıldığında çoğunluk yarısının
dizini kanonik kabul edilir.** Bir tarafın kazanması gerekir; hangi taraf kazanırsa kazansın
öbür tarafın kabul ettiği yazmalar kaybolur.

```js
// kume/derlem.mjs — kutuphane katalogu derlemi ve ters dizin (onceki derslerle ayni uretec ve
// ayni tohum; bu ders yalnizca kullandigi parcalari tasir). Dizin bir sozluk tutar: terim ->
// gonderi listesi; gonderi burada bir belge girisidir (belge kimligi + terim sikligi).
const ORTAK = ["kitap", "yazar", "eser", "metin", "bolum", "baski", "sayfa", "dil", "cilt", "yayin"];
const OZEL = {
  cocuk: ["masal", "resimli", "okul", "oyun", "hayvan", "cizgi"], oyku: ["kisa", "anlati", "derleme", "gunluk", "yalnizlik", "kasaba"],
  roman: ["kahraman", "kent", "kusak", "ev", "yolculuk", "mektup"], tarih: ["imparatorluk", "belge", "arsiv", "savas", "yuzyil", "vakayiname"],
  gezi: ["deniz", "yol", "harita", "sehir", "liman", "gemi"], siir: ["dize", "olcu", "imge", "ses", "sessizlik", "kafiye"],
  deneme: ["dusunce", "elestiri", "okuma", "zaman", "not", "soylesi"], bilim: ["olcum", "deney", "kuram", "veri", "gozlem", "denklem"],
};
const NADIR = ["fener", "kuyu", "ipek", "kule", "bahce", "kar", "ada", "koru", "tas", "cinar", "kirlangic", "demirci", "pusula", "kehribar"];
const KONU = Object.keys(OZEL);

export function derlem({ adet = 4000, tohum = 20260731 } = {}) {
  let s = tohum % 2147483647;
  const r = () => (s = (s * 48271) % 2147483647) / 2147483647;
  const sec = (a) => a[Math.floor(r() * a.length)], belge = [];
  for (let i = 1; i <= adet; i += 1) {
    const konu = sec(KONU), oz = OZEL[konu], soz = [sec(oz), sec(NADIR)];
    if (r() < 0.5) soz.push(sec(ORTAK));
    for (let j = 0, n = 10 + Math.floor(r() * 7); j < n; j += 1)
      soz.push(r() < 0.45 ? sec(ORTAK) : r() < 0.85 ? sec(oz) : sec(NADIR));
    belge.push({ id: i, konu, yil: 1990 + Math.floor(r() * 36), metin: [soz[0], soz[1], konu, ...soz.slice(2)].join(" ") });
  }
  return belge;
}

export function dizinle(belge) {
  const gonderi = new Map(), uzunluk = new Map();
  let bayt = 0;
  for (const d of belge) {
    const t = d.metin.split(" "), sayim = new Map();
    for (const x of t) sayim.set(x, (sayim.get(x) ?? 0) + 1);
    uzunluk.set(d.id, t.length);
    for (const [x, n] of sayim) {
      if (!gonderi.has(x)) { gonderi.set(x, []); bayt += x.length + 4; }
      gonderi.get(x).push([d.id, n]); bayt += 8;                        // kimlik + siklik
    }
  }
  const ort = [...uzunluk.values()].reduce((a, b) => a + b, 0) / (belge.length || 1);
  return { gonderi, uzunluk, N: belge.length, ort, bayt };
}

// Puanlama: terim sikligi, ters belge sikligi ve belge uzunlugu; istatistik bu dizinden okunur.
export function ara(dz, terim, k) {
  const N = dz.N, ort = dz.ort, puan = new Map();
  let taranan = 0;
  for (const t of terim) {
    const g = dz.gonderi.get(t) ?? [], df = g.length;
    const idf = Math.log(1 + (N - df + 0.5) / (df + 0.5));
    for (const [id, tf] of g) {
      taranan += 1;
      const norm = tf + 1.2 * (0.25 + 0.75 * dz.uzunluk.get(id) / ort);
      puan.set(id, (puan.get(id) ?? 0) + idf * tf * 2.2 / norm);
    }
  }
  const sirali = [...puan].sort((a, b) => b[1] - a[1] || a[0] - b[0]).slice(0, k);
  return { aday: sirali.map(([id, p]) => ({ id, p })), taranan, eslesen: puan.size };
}
```

```js
// kume/bolunme.mjs — bes kopyali bir dizinin uc ve iki dugumluk iki yariya bolunmesi. Kume
// SUREC ICI BIR MODELDIR: yarilar ayni surecte ayri ters dizinlerdir, ag yerine bir yonlendirme
// kurali vardir. Olcut, bolunme hic olmasaydi olusacak dizinin ilk on sonucudur.
import { derlem, dizinle, ara } from "./derlem.mjs";

const N0 = 4000, YENI = 600, KOPYA = 5, AD = 3, BD = 2, K = 10;
const SORGU = ["kisa", "oyku", "yalnizlik"];
const s = (x, n) => String(x).padStart(n);

const taban = derlem({ adet: N0 });
// Bolunme sirasinda kataloglanan yeni parti: tek konudan gelen bir bagis. Ayri bir tohumla
// uretilir, kimlikler N0 sonrasina tasinir; icerigine el degdirilmez.
const parti = derlem({ adet: 9000, tohum: 20260801 }).filter((d) => d.konu === "oyku")
  .slice(0, YENI).map((d, i) => ({ ...d, id: N0 + i + 1 }));
const aYeni = parti.filter((d) => d.id % 20 < 13);   // istemcilerin %65'i uc dugumluk yariya
const bYeni = parti.filter((d) => d.id % 20 >= 13);  // %35'i iki dugumluk yariya
const olcut = dizinle([...taban, ...parti]), olcutSon = ara(olcut, SORGU, K);
const olcutIlk = olcutSon.aday.map((x) => x.id), hedef = olcutSon.eslesen;

const bak = (belge) => {                       // bir dizinin bu sorguya verdigi kume ve sira
  const dz = dizinle(belge), r = ara(dz, SORGU, K);
  const ilk = r.aday.map((x) => x.id);
  return { adet: belge.length, eslesen: r.eslesen, ilk, ortak: ilk.filter((x) => olcutIlk.includes(x)).length };
};

console.log(`${N0} belge dizinde, bolunme sirasinda ${YENI} yeni kayit geliyor. ${KOPYA} kopya,`);
console.log(`yarilar ${AD} ve ${BD} dugum. Tohum 20260731. Olcut: bolunme olmasaydi ${N0 + YENI} belgelik dizin,`);
console.log(`sorgu "kisa oyku yalnizlik", eslesen ${hedef} belge.\n`);

console.log("cogunluk | W | A kabul | B kabul | B red | onarimda kaybolan | yazma iletisi | onarim sonrasi belge");
console.log("---------|---|---------|---------|-------|-------------------|---------------|---------------------");
const sonuc = {};
for (const cogunluk of [false, true]) {
  const W = cogunluk ? Math.floor(KOPYA / 2) + 1 : 1;
  const bKabul = cogunluk && BD < W ? 0 : bYeni.length;
  const bRed = bYeni.length - bKabul;
  const kayip = cogunluk ? 0 : bKabul;                   // onarimda cogunluk yarisinin dizini kanonik
  const ileti = aYeni.length * Math.min(W, AD) + bKabul * Math.min(W, BD) + bRed * BD;
  const sonrasi = taban.length + aYeni.length + (cogunluk ? bRed : 0);   // red edilenler yeniden gonderilir
  sonuc[cogunluk] = { W, bKabul, sonrasi };
  console.log(`${(cogunluk ? "acik" : "kapali").padEnd(8)} | ${s(W, 1)} | ${s(aYeni.length, 7)} | ${s(bKabul, 7)} | ` +
    `${s(bRed, 5)} | ${s(kayip, 17)} | ${s(ileti, 13)} | ${s(sonrasi, 20)}`);
}

console.log("\nbolunme surerken ayni sorgu (olcut: bolunme olmasaydi donecek ilk 10):");
console.log("cogunluk | yari | dugum | dizindeki belge | eslesen | eksik | ilk 10 ortak | ilk 5 sonuc");
for (const cogunluk of [false, true]) {
  for (const [ad, dugum, ek] of [["A", AD, aYeni], ["B", BD, sonuc[cogunluk].bKabul ? bYeni : []]]) {
    const r = bak([...taban, ...ek]);
    console.log(`${(cogunluk ? "acik" : "kapali").padEnd(8)} | ${ad.padEnd(4)} | ${s(dugum, 5)} | ${s(r.adet, 15)} | ` +
      `${s(r.eslesen, 7)} | ${s(hedef - r.eslesen, 5)} | ${s(r.ortak + "/10", 12)} | ${r.ilk.slice(0, 5).join(" ")}`);
  }
}

console.log("\nonarimdan sonra:");
console.log("cogunluk | dizindeki belge | eslesen | ilk 10 ortak | ilk 5 sonuc");
for (const cogunluk of [false, true]) {
  const ek = cogunluk ? [...aYeni, ...bYeni] : aYeni;
  const r = bak([...taban, ...ek]);
  console.log(`${(cogunluk ? "acik" : "kapali").padEnd(8)} | ${s(r.adet, 15)} | ${s(r.eslesen, 7)} | ` +
    `${s(r.ortak + "/10", 12)} | ${r.ilk.slice(0, 5).join(" ")}`);
}
console.log(`olcut    | ${s(olcut.N, 15)} | ${s(hedef, 7)} | ${s("10/10", 12)} | ${olcutIlk.slice(0, 5).join(" ")}`);
console.log(`\nkosumdan bagimsiz: ${KOPYA} kopyada cogunluk ${Math.floor(KOPYA / 2) + 1}'tur ve iki ayrik yari` +
  ` ayni anda cogunluk olamaz, cunku ${Math.floor(KOPYA / 2) + 1} + ${Math.floor(KOPYA / 2) + 1} > ${KOPYA}.`);
```

```
4000 belge dizinde, bolunme sirasinda 600 yeni kayit geliyor. 5 kopya,
yarilar 3 ve 2 dugum. Tohum 20260731. Olcut: bolunme olmasaydi 4600 belgelik dizin,
sorgu "kisa oyku yalnizlik", eslesen 1115 belge.

cogunluk | W | A kabul | B kabul | B red | onarimda kaybolan | yazma iletisi | onarim sonrasi belge
---------|---|---------|---------|-------|-------------------|---------------|---------------------
kapali   | 1 |     390 |     210 |     0 |               210 |           600 |                 4390
acik     | 3 |     390 |       0 |   210 |                 0 |          1590 |                 4600

bolunme surerken ayni sorgu (olcut: bolunme olmasaydi donecek ilk 10):
cogunluk | yari | dugum | dizindeki belge | eslesen | eksik | ilk 10 ortak | ilk 5 sonuc
kapali   | A    |     3 |            4390 |     905 |   210 |         8/10 | 4544 3159 3544 2689 2362
kapali   | B    |     2 |            4210 |     725 |   390 |         8/10 | 4457 4534 3159 3544 2689
acik     | A    |     3 |            4390 |     905 |   210 |         8/10 | 4544 3159 3544 2689 2362
acik     | B    |     2 |            4000 |     515 |   600 |         6/10 | 3159 3544 2689 2362 3370

onarimdan sonra:
cogunluk | dizindeki belge | eslesen | ilk 10 ortak | ilk 5 sonuc
kapali   |            4390 |     905 |         8/10 | 4544 3159 3544 2689 2362
acik     |            4600 |    1115 |        10/10 | 4457 4534 4544 3159 3544
olcut    |            4600 |    1115 |        10/10 | 4457 4534 4544 3159 3544

kosumdan bagimsiz: 5 kopyada cogunluk 3'tur ve iki ayrik yari ayni anda cogunluk olamaz, cunku 3 + 3 > 5.
```

#### Azınlıkta Kabul Edilen Yazma

Birinci tablo iki ayarın yazma tarafını gösteriyor. Çoğunluk kapalıyken her iki yarı da yazma
kabul ediyor: A 390, B 210. Kütüphaneci açısından bu 600 kaydın altı yüzü de başarıyla
kataloglanmıştır — her biri onay almıştır. Onarımda AK6 devreye giriyor ve azınlık yarısının
kabul ettiği **210 kayıt kayboluyor**. Bu kayıtlar hata döndürmedi, bir kuyrukta beklemiyor,
yeniden gönderilecek bir listede değil; onay verilmiş ve sonra yok olmuşlardır.

Çoğunluk açıkken aynı 210 istek en baştan reddediliyor. Fark, kaybın nereye düştüğüdür:
reddedilen bir istek istemcinin elinde kalır ve bölünme onarıldığında yeniden gönderilebilir.
Kaybolan sütunu 0, onarım sonrası dizin 4600 belge. İki ayar aynı ağ arızasını yaşadı ve
biri 210 kaydı sessizce yuttu, öbürü 210 kez görünür biçimde reddetti.

Bedel yazma iletisi sütununda: 600 iletiden 1590'a. Çoğunluk her kabul edilen yazma için üç
onay ister, üstelik reddedilen istekler de azınlık yarısında iki düğüme dokunduktan sonra
başarısız olur. Bu, yazma başına yaklaşık 2,65 katlık bir ileti artışıdır ve gecikmeye
doğrudan yansır.

Son satırdaki koşumdan bağımsız sayı bu ayarın neden işe yaradığını söyler: beş kopyada
çoğunluk üçtür ve iki ayrık yarı aynı anda çoğunluk olamaz, çünkü 3 + 3 beşten büyüktür. Bu
bir ayar değil, bir aritmetik.

#### Bölünme Sürerken Sorgu

İkinci tablo aynı sorgunun bölünme sürerken ne döndürdüğünü sayıyor. Bölünme hiç olmasaydı
1115 belge eşleşecekti.

Çoğunluk kapalıyken iki yarı da yanıt veriyor ve **ikisi de eksik**: A 905 belge (210 eksik),
B 725 belge (390 eksik). İlk beş sıralamaları da ayrışıyor. A yarısı 4544 ile başlıyor, B
yarısı 4457 ve 4534 ile. Bu üç kayıt da bağış partisinden geliyor ve her biri yalnızca kendi
yarısında var. Aynı katalog sorusunu iki farklı uçtan soran iki kütüphaneci, aynı anda,
birbiriyle çelişen iki liste alır. Bölünmüş beynin arama tarafındaki karşılığı budur: yanlış
bir yanıt değil, **iki ayrı doğru**.

Çoğunluk açıkken B yarısının dizini 4000 belgede donuyor: 515 eşleşme, 600 eksik belge ve ilk
onda ölçütle ortak yalnızca 6 belge. Azınlık yarısı yanıt vermeyi sürdürüyor ama bayat bir
dizinden yanıt veriyor. Bu ayarın verdiği güvence dönen kümenin eksiksizliği değildir; verdiği
güvence, azınlığın **dizini ileriye taşımamasıdır**.

#### Onarımdan Sonra

Üçüncü tablo son durumu ölçütle karşılaştırıyor. Çoğunluk kapalıyken dizin 4390 belgede
kalıyor: 905 eşleşme, ilk onda 8/10 ve ilk beşte ölçütün ilk iki kaydı (4457, 4534) hiç yok.
Bu iki kayıt geri gelmez; onları yeniden dizine sokmanın tek yolu bağış partisinin kaynağına
dönüp 210 kaydı elden yeniden kataloglamaktır.

Çoğunluk açıkken onarımdan sonraki dizin 4600 belge, 1115 eşleşme ve ilk on ölçütle birebir
aynı. Reddedilen 210 istek yeniden gönderildiği için sonuç, bölünme hiç yaşanmamış gibi.
Çoğunluk gerekliliği arızayı önlemedi — ağ yine bölündü ve azınlık yarısı 600 kayıt boyunca
eksik yanıt verdi. Önlediği şey, arızanın **kalıcı** olmasıydı.

#### Özet

- Çoğunluk kapalıyken iki yarı da yazma kabul etti (390 ve 210) ve onarımda azınlığın kabul
  ettiği 210 kayıt kayboldu; bu kayıtlar istemciye onay verilmiş kayıtlardır.
- Çoğunluk açıkken aynı 210 istek reddedildi, kaybolan 0 oldu ve istekler onarımdan sonra
  yeniden gönderilerek dizin 4600 belgeye çıktı.
- Çoğunluğun bedeli yazma iletisidir: 600 iletiden 1590'a, yazma başına yaklaşık 2,65 kat.
- Bölünme sürerken çoğunluk kapalıyken A yarısı 905, B yarısı 725 belge döndürdü ve ilk beş
  listeleri ayrıştı: A 4544 ile, B 4457 ile başladı. Aynı soru iki çelişik yanıt aldı.
- Çoğunluk açıkken azınlık yarısı 4000 belgelik bayat dizinden 515 eşleşme döndürdü: 600 eksik
  belge ve ilk onda 6/10. Güvence eksiksiz yanıt değil, azınlığın dizini ilerletmemesidir.
- Onarım sonrası çoğunluk kapalı ayarda 905 eşleşme ve 8/10 kaldı; açık ayarda sonuç bölünme
  hiç olmamış gibi 1115 eşleşme ve 10/10 oldu.

#### Sonraki Adım

Bu ders 600 kaydı tek tek, her biri ayrı bir istekmiş ve kabul edilir edilmez aranabilir
olurmuş gibi saydı. İki varsayım da ölçülmedi. Bir katalog partisi gerçekte tek tek değil
yığın hâlinde gelir ve her kaydı ayrı bir istekle göndermekle hepsini tek istekte göndermek
aynı şey değildir: istek sayısı, oluşan bölüt sayısı ve toplam süre değişir. Dizine giren bir
belgenin **aranabilir** hâle gelmesi de anlık değildir; belge önce bir tampona yazılır ve
ancak bir yenileme işlemiyle sorgulara görünür. Sonraki ders aynı 4000 belgelik derlemi tek
tek ve toplu olarak dizinler, istek ile bölüt sayısını karşılaştırır, sonra yenileme aralığını
değiştirip görünürlük gecikmesiyle yazma verimi arasındaki takası sayar.

### Toplu Dizinleme Eniyilemesi

Source: https://academia.sh/tr/kurslar/arama-motorlari/toplu-dizinleme-eniyilemesi

Aynı dört bin kayıtlık derlemin tek tek ve toplu dizinlenmesi: istek sayısı, oluşan bölüt sayısı, toplam dizin baytı ve sorgu başına sözlük araması cinsinden karşılaştırılması, parti boyu ile yenileme aralığının birbirinden bağımsız iki eksen olduğunun gösterilmesi ve yenileme aralığı değiştikçe görünürlük gecikmesiyle sorgu maliyeti arasındaki takasın altı yüz kayıtlık bir bağış partisi üzerinde eşleşen belge, eksik belge ve ilk beş sonucun sırası sayılarak ölçülmesi.

Önceki ders bölünme sürerken gelen 600 kaydı tek tek saydı ve iki şeyi sessizce varsaydı: her
kayıt ayrı bir istektir ve kabul edilir edilmez aranabilir. İkisi de ölçülmemişti. Bir katalog
partisi yığın hâlinde gelir; 600 kaydı 600 istekle göndermekle 12 istekle göndermek aynı şey
değildir. Dizine giren belgenin **aranabilir** hâle gelmesi de anlık değildir: belge önce bir
tampona yazılır ve ancak bir **yenileme** işlemi tamponu bir **bölüte** çevirdiğinde sorgulara
görünür. Bu ders iki ayarı ayrı ölçer — parti boyu ve yenileme aralığı.

#### Düzenek

Bölüt, bir tampon dolusu belgeden oluşan **değişmez** bir ters dizindir; yeni belge var olan
bir bölüte eklenmez, yeni bir bölüt doğar. Bir sorgu bütün bölütlere bakmak zorundadır: her
bölütte terim başına bir sözlük araması yapar ve gönderi listelerini okur. Puanlama istatistiği
bütün bölütlerden toplandığı için bölüt sayısı sırayı değil maliyeti değiştirir; sıra üzerindeki
tek etki **görünürlükten** gelir.

Süre de ölçülüyor ama ham süre basılmıyor: süre ortama bağlıdır ve süreç içi model ağ
taşımadığı için istek başına düşen ağ maliyetini ölçemez. Basılan şey bir eşiğin üstünde olup
olmadığıdır; yükü taşıyan sayılar istek, bölüt, bayt ve sözlük aramasıdır.

**AK7 — 600 kayıtlık bağış partisi kataloğa 50'lik isteklerle girer ve her istekten hemen
sonra aynı sorgu koşar.** Doğrusal etkilidir: istek boyu değişirse görünmeyen belge sayısı
aynı yönde değişir. **AK8 — yenileme arka planda her `aralık` belgede bir çalışır ve tamponu
tek bir bölüte çevirir.** Tamponda bekleyen belge sorguya görünmez; bu, modelin kuralıdır.

```js
// kume/derlem.mjs — kutuphane katalogu derlemi ve ters dizin (onceki derslerle ayni uretec ve
// ayni tohum). dizinle() burada TEK BIR BOLUT kurar: bir bolut, bir tampon dolusu belgeden
// olusan degismez bir ters dizindir.
const ORTAK = ["kitap", "yazar", "eser", "metin", "bolum", "baski", "sayfa", "dil", "cilt", "yayin"];
const OZEL = {
  cocuk: ["masal", "resimli", "okul", "oyun", "hayvan", "cizgi"], oyku: ["kisa", "anlati", "derleme", "gunluk", "yalnizlik", "kasaba"],
  roman: ["kahraman", "kent", "kusak", "ev", "yolculuk", "mektup"], tarih: ["imparatorluk", "belge", "arsiv", "savas", "yuzyil", "vakayiname"],
  gezi: ["deniz", "yol", "harita", "sehir", "liman", "gemi"], siir: ["dize", "olcu", "imge", "ses", "sessizlik", "kafiye"],
  deneme: ["dusunce", "elestiri", "okuma", "zaman", "not", "soylesi"], bilim: ["olcum", "deney", "kuram", "veri", "gozlem", "denklem"],
};
const NADIR = ["fener", "kuyu", "ipek", "kule", "bahce", "kar", "ada", "koru", "tas", "cinar", "kirlangic", "demirci", "pusula", "kehribar"];
const KONU = Object.keys(OZEL);

export function derlem({ adet = 4000, tohum = 20260731 } = {}) {
  let s = tohum % 2147483647;
  const r = () => (s = (s * 48271) % 2147483647) / 2147483647;
  const sec = (a) => a[Math.floor(r() * a.length)], belge = [];
  for (let i = 1; i <= adet; i += 1) {
    const konu = sec(KONU), oz = OZEL[konu], soz = [sec(oz), sec(NADIR)];
    if (r() < 0.5) soz.push(sec(ORTAK));
    for (let j = 0, n = 10 + Math.floor(r() * 7); j < n; j += 1)
      soz.push(r() < 0.45 ? sec(ORTAK) : r() < 0.85 ? sec(oz) : sec(NADIR));
    belge.push({ id: i, konu, yil: 1990 + Math.floor(r() * 36), metin: [soz[0], soz[1], konu, ...soz.slice(2)].join(" ") });
  }
  return belge;
}

export function dizinle(belge) {
  const gonderi = new Map(), uzunluk = new Map();
  let bayt = 0;
  for (const d of belge) {
    const t = d.metin.split(" "), sayim = new Map();
    for (const x of t) sayim.set(x, (sayim.get(x) ?? 0) + 1);
    uzunluk.set(d.id, t.length);
    for (const [x, n] of sayim) {
      if (!gonderi.has(x)) { gonderi.set(x, []); bayt += x.length + 4; }   // sozluk girisi
      gonderi.get(x).push([d.id, n]); bayt += 8;
    }
  }
  const ort = [...uzunluk.values()].reduce((a, b) => a + b, 0) / (belge.length || 1);
  return { gonderi, uzunluk, N: belge.length, ort, bayt };
}

// Puanlama: terim sikligi, ters belge sikligi ve belge uzunlugu. YEREL puanlamada df ve ortalama
// uzunluk yalniz bu dizinden okunur; kuresel verilirse butun derlemin istatistigi kullanilir.
export function ara(dz, terim, k, kuresel = null) {
  const N = kuresel ? kuresel.N : dz.N, ort = kuresel ? kuresel.ort : dz.ort, puan = new Map();
  let taranan = 0;
  for (const t of terim) {
    const g = dz.gonderi.get(t) ?? [];
    const df = kuresel ? kuresel.df.get(t) ?? 0 : g.length;
    const idf = Math.log(1 + (N - df + 0.5) / (df + 0.5));
    for (const [id, tf] of g) {
      taranan += 1;
      const norm = tf + 1.2 * (0.25 + 0.75 * dz.uzunluk.get(id) / ort);
      puan.set(id, (puan.get(id) ?? 0) + idf * tf * 2.2 / norm);
    }
  }
  const sirali = [...puan].sort((a, b) => b[1] - a[1] || a[0] - b[0]).slice(0, k);
  return { aday: sirali.map(([id, p]) => ({ id, p })), taranan, eslesen: puan.size };
}
```

```js
// kume/toplu.mjs — tek tek dizinleme ile toplu dizinlemenin ve yenileme araliginin olculmesi.
// Dizin SUREC ICI BIR MODELDIR: belgeler once bir tampona yazilir, YENILEME tamponu degismez
// bir bolute cevirir ve belge ancak o an sorgulara gorunur olur. Puanlama istatistigi butun
// bolutlerden toplanir, boylece bolut sayisi sirayi degil yalnizca maliyeti degistirir.
import { derlem, dizinle, ara } from "./derlem.mjs";

const N = 4000, YENI = 600, ISTEK = 50, K = 10, OLCUM = 100, SORGU = ["kisa", "oyku", "yalnizlik"];
const s = (x, n) => String(x).padStart(n);

const bos = () => ({ bolut: [], tampon: [], istek: 0, arama: 0 });
const ekle = (D, parti) => { D.istek += 1; D.tampon.push(...parti); };
const yenile = (D) => { if (D.tampon.length) { D.bolut.push(dizinle(D.tampon)); D.tampon = []; } };

function sor(D, terim, k) {
  const df = new Map();
  let n = 0, uz = 0;
  for (const b of D.bolut) {                       // birinci gecis: bolutler arasi istatistik
    n += b.N; uz += b.ort * b.N;
    for (const t of terim) { D.arama += 1; const g = b.gonderi.get(t); if (g) df.set(t, (df.get(t) ?? 0) + g.length); }
  }
  const kur = { df, N: n, ort: uz / n }, puan = [];
  for (const b of D.bolut) { D.arama += terim.length; puan.push(...ara(b, terim, Infinity, kur).aday); }
  puan.sort((a, b) => b.p - a.p || a.id - b.id);
  return { ilk: puan.slice(0, k).map((x) => x.id), eslesen: puan.length, gorunen: n };
}

// N belgeyi parti boyunda isteklerle gonder; her `aralik` belgede bir yenileme calisir.
function kur(belge, parti, aralik) {
  const D = bos();
  for (let i = 0; i < belge.length; i += parti) {
    ekle(D, belge.slice(i, i + parti));
    if ((i + parti) % aralik === 0 || i + parti >= belge.length) yenile(D);
  }
  return D;
}

// Sure olcumu: dizinleme + OLCUM sorgu. Ham sure ORTAMA BAGLIDIR, bu yuzden asagida yalnizca
// esigin ustunde mi altinda mi oldugu basilir. Uc kosumun en kucugu alinir.
function sure(parti, aralik) {
  let en = Infinity;
  for (let k = 0; k < 3; k += 1) {
    const t0 = process.hrtime.bigint();
    const D = kur(taban, parti, aralik);
    for (let q = 0; q < OLCUM; q += 1) sor(D, SORGU, K);
    en = Math.min(en, Number(process.hrtime.bigint() - t0));
  }
  return en;
}

const taban = derlem({ adet: N });
console.log(`${N} belge, tohum 20260731. Sorgu "kisa oyku yalnizlik", son liste ${K}.`);
console.log("Sure ORTAMA BAGLIDIR; ham sure yerine yalnizca esikli bir oran basilir.\n");
console.log("parti | yenileme araligi | istek | bolut | dizin bayt | sorgu basina sozluk aramasi");
console.log("------|------------------|-------|-------|------------|----------------------------");
for (const [parti, aralik] of [[1, 1], [1, 500], [500, 500], [1000, 1000], [500, N]]) {
  const D = kur(taban, parti, aralik);
  D.arama = 0;
  sor(D, SORGU, K);
  console.log(`${s(parti, 5)} | ${s(aralik, 16)} | ${s(D.istek, 5)} | ${s(D.bolut.length, 5)} | ` +
    `${s(D.bolut.reduce((t, b) => t + b.bayt, 0), 10)} | ${s(D.arama, 27)}`);
}
sure(500, 500);                                    // isinma kosumu
const oran = sure(1, 1) / sure(500, 500);
console.log(`\ndizinleme + ${OLCUM} sorgu suresi, tek tek (1/1) bolu toplu (500/500): ` +
  `${oran > 2 ? "2 kattan buyuk" : "2 kat ve altinda"} (ham sure ortama baglidir, basilmaz)`);

// Gorunurluk: 4000 belgelik dizine 600 kayitlik bagis partisi 50'lik isteklerle giriyor ve her
// istekten hemen sonra ayni sorgu kosuluyor. Yenileme arka planda her `aralik` belgede bir
// calisir; yenilenmemis belgeler tamponda bekler ve sorguya gorunmez.
const parti = derlem({ adet: 9000, tohum: 20260801 }).filter((d) => d.konu === "oyku")
  .slice(0, YENI).map((d, i) => ({ ...d, id: N + i + 1 }));
const olcut = sor(kur([...taban, ...parti], 500, 500), SORGU, K);
console.log(`\nyenileme araligi (${YENI} kayitlik parti ${ISTEK}'lik isteklerle giriyor, her istekten sonra bir sorgu):`);
console.log(`olcut: parti tumuyle gorunurken eslesen ${olcut.eslesen}, ilk 5: ${olcut.ilk.slice(0, 5).join(" ")}`);
console.log("aralik | eklenen bolut | ort. gorunmeyen | son sorguda eslesen | eksik | ilk 10 ortak | sozluk aramasi | ilk 5 sonuc");
for (const aralik of [1, 75, 250, YENI * 2]) {
  const D = kur(taban, 500, 500), tabanBolut = D.bolut.length;
  let gorunmez = 0, gorunur = 0, son = null;
  for (let g = ISTEK; g <= YENI; g += ISTEK) {
    const hedef = Math.floor(g / aralik) * aralik;
    while (gorunur < hedef) {                      // her yenileme bir bolut yaratir
      ekle(D, parti.slice(gorunur, gorunur + aralik)); yenile(D); gorunur += aralik;
    }
    D.arama = 0;
    son = sor(D, SORGU, K);
    gorunmez += g - gorunur;
  }
  console.log(`${s(aralik > YENI ? "yok" : aralik, 6)} | ${s(D.bolut.length - tabanBolut, 13)} | ` +
    `${s((gorunmez / (YENI / ISTEK)).toFixed(1), 15)} | ${s(son.eslesen, 19)} | ${s(olcut.eslesen - son.eslesen, 5)} | ` +
    `${s(son.ilk.filter((x) => olcut.ilk.includes(x)).length + "/10", 12)} | ${s(D.arama, 14)} | ${son.ilk.slice(0, 5).join(" ")}`);
}
console.log(`\nkosumdan bagimsiz: istek sayisi = belge / parti boyu, bolut sayisi = belge / yenileme` +
  ` araligi, sorgu basina sozluk aramasi = bolut x terim x 2.`);
```

```
4000 belge, tohum 20260731. Sorgu "kisa oyku yalnizlik", son liste 10.
Sure ORTAMA BAGLIDIR; ham sure yerine yalnizca esikli bir oran basilir.

parti | yenileme araligi | istek | bolut | dizin bayt | sorgu basina sozluk aramasi
------|------------------|-------|-------|------------|----------------------------
    1 |                1 |  4000 |  4000 |     813787 |                       24000
    1 |              500 |  4000 |     8 |     387960 |                          48
  500 |              500 |     8 |     8 |     387960 |                          48
 1000 |             1000 |     4 |     4 |     384984 |                          24
  500 |             4000 |     8 |     1 |     382752 |                           6

dizinleme + 100 sorgu suresi, tek tek (1/1) bolu toplu (500/500): 2 kattan buyuk (ham sure ortama baglidir, basilmaz)

yenileme araligi (600 kayitlik parti 50'lik isteklerle giriyor, her istekten sonra bir sorgu):
olcut: parti tumuyle gorunurken eslesen 1115, ilk 5: 4457 4534 4544 3159 3544
aralik | eklenen bolut | ort. gorunmeyen | son sorguda eslesen | eksik | ilk 10 ortak | sozluk aramasi | ilk 5 sonuc
     1 |           600 |             0.0 |                1115 |     0 |        10/10 |           3648 | 4457 4534 4544 3159 3544
    75 |             8 |            25.0 |                1115 |     0 |        10/10 |             96 | 4457 4534 4544 3159 3544
   250 |             2 |            95.8 |                1015 |   100 |         8/10 |             60 | 4457 3159 3544 2689 2362
   yok |             0 |           325.0 |                 515 |   600 |         6/10 |             48 | 3159 3544 2689 2362 3370

kosumdan bagimsiz: istek sayisi = belge / parti boyu, bolut sayisi = belge / yenileme araligi, sorgu basina sozluk aramasi = bolut x terim x 2.
```

#### İki Ayrı Eksen

Birinci tablonun ilk satırı en kötü durumdur: her belge ayrı bir istekle gelir ve her istekten
sonra yenileme çalışır. Sonuç 4000 istek, 4000 bölüt, 813.787 bayt dizin ve tek bir sorgu için
24.000 sözlük araması. Beşinci satır aynı derlemi 8 istekle ve tek bir bölütle dizinliyor:
382.752 bayt ve sorgu başına 6 arama. Aynı 4000 belge, aynı sıra, dört bin kat daha az sözlük
araması.

Baytın iki katına çıkmasının sebebi gönderi listeleri değil sözlüktür: derlemin 80 terimi 4000
bölütte 4000 kez yeniden yazılır. Bölüt başına sabit bir maliyet vardır ve bölüt sayısı arttıkça
bu maliyet toplamı büyütür.

İkinci satır dersin asıl ayrımını veriyor. Parti boyu 1 ama yenileme aralığı 500: istek sayısı
yine 4000, buna karşın bölüt sayısı 8, bayt 387.960 ve arama 48. **Parti boyu istek sayısını
belirler, yenileme aralığı bölüt sayısını.** İkisi ayrı ayarlardır ve ayrı şeylere mal olur.
Toplu dizinleme, istek başına düşen ağ ve ayrıştırma yükünü azaltır; süreç içi model bu yükü
taşımadığı için burada basılan süre oranı yalnızca bölüt maliyetini gösterir — dizinleme ve
yüz sorgu birlikte ölçüldüğünde tek tek yerleşim, toplu yerleşimin iki katından uzun sürüyor.

#### Yenileme Aralığı: Görünürlük ve Verim

İkinci tablo 4000 belgelik dizine 600 kayıtlık bir bağış partisini 50'lik isteklerle sokuyor ve
her istekten hemen sonra aynı sorguyu koşuyor. Ölçüt, partinin tamamı görünürken dönen yanıt:
1115 eşleşme ve ilk beşte 4457, 4534, 4544.

Aralık 1 iken hiçbir belge beklemiyor: ortalama görünmeyen 0,0 ve son sorgu ölçütle birebir
aynı. Bedeli 600 yeni bölüt ve sorgu başına 3648 sözlük aramasıdır — yenilemesiz durumun 76
katı, çünkü her sorgu birer belgelik altı yüz bölüte tek tek bakıyor.

Aralık 75 bu dersin en dengeli satırıdır: 8 bölüt, ortalama 25 belge tamponda bekliyor, ama son
sorgu yine 1115 eşleşme ve 10/10 veriyor. Görünürlük gecikmesi var, sonuç doğruluğu yok
olmuyor; sorgu maliyeti 96 aramada kalıyor.

Aralık 250'de gecikme kümeye geçiyor. Ortalama 95,8 belge görünmez durumda ve son sorgu 1015
belge döndürüyor: **100 kayıt eksik**. İlk on 8/10'a düşüyor ve ilk beş değişiyor — 4534 ile
4544 listeden çıkıyor, yerlerine daha eski kayıtlar giriyor. Yenileme kapalıyken tablo en uca
gidiyor: ortalama 325 belge görünmez, son sorgu 515 belgeyle yanıt veriyor, **600 kaydın
tamamı eksik** ve ilk beşte bağış partisinden tek bir kitap yok. Sorgu en ucuz hâlinde, 48
arama; dönen liste ise partinin hiç gelmediği bir kataloğun listesi.

Takas iki uçlu değildir. Aralığı kısaltmak görünürlük gecikmesini düşürür ama bölüt üretir;
uzatmak sorguyu ucuzlatır ama dönen kümeyi geriye çeker. Ortada, gecikmenin ölçüldüğü ama
sonuca yansımadığı bir aralık vardır: bu derlemde 75.

#### Özet

- Aynı 4000 belge tek tek dizinlendiğinde 4000 istek, 4000 bölüt, 813.787 bayt ve sorgu başına
  24.000 sözlük araması üretti; 500'lük partilerle ve tek yenilemeyle 8 istek, 1 bölüt, 382.752
  bayt ve 6 arama üretti.
- Bayt artışı gönderi listelerinden değil sözlükten geliyor: 80 terim 4000 bölütte yineleniyor.
- Parti boyu istek sayısını, yenileme aralığı bölüt sayısını belirler; parti 1 ile aralık 500
  birleştiğinde istek 4000 kalırken bölüt 8'e iniyor.
- Dizinleme ile yüz sorgu birlikte ölçüldüğünde tek tek yerleşim toplu yerleşimin iki katından
  uzun sürdü; ham süre ortama bağlı olduğu için yalnızca eşik basıldı.
- Yenileme aralığı 1 iken görünmeyen belge 0 ama sorgu başına arama 3648; aralık 75 iken
  ortalama 25 belge bekliyor ve sonuç hâlâ 10/10.
- Aralık 250'de son sorgu 100 kaydı kaçırdı ve ilk beşten 4534 ile 4544 düştü; yenileme
  kapalıyken 600 kaydın tamamı eksik kaldı ve ilk on 6/10'a indi.

#### Sonraki Adım

Bu ders bölütü ucuz bir yan ürün gibi ele aldı ve yalnızca sayısını saydı. Tablolar bunun
sürmeyeceğini gösteriyor: tek tek dizinleme 4000 bölüt bıraktı, yenileme aralığı 1 olan yerleşim
600 bölüt ekledi ve her yeni bölüt sorgu başına sabit bir maliyet daha getirdi. Bölütler
değişmez olduğu için silinen bir belge de yerinde durur, yalnızca silinmiş işaretlenir; dizin
büyüdükçe okunmayan gönderi girişleri birikir. Bu yükün geri alınmasının tek yolu bölütleri
birleştirmektir. Sonraki ders küçük bölütleri büyük bölütlere dönüştürmenin arka plan maliyetini
ölçer: birleştirme sırasında okunan ve yeniden yazılan bayt, silinmiş belgelerin geri kazandırdığı
yer ve birleştirme sürerken sorgu başına düşen bölüt sayısının nasıl değiştiği.

### Bölüt Birleştirme

Source: https://academia.sh/tr/kurslar/arama-motorlari/bolut-birlestirme

Her toplu işten geriye kalan bölütlerin sorguya ödettiği bedel: bölüt sayısının sözlük araması, taranan gönderi girişi ve dizin boyutuna etkisi, silme işaretlerinin temizlenmesi, birleştirmenin kopyaladığı bayt ve katmanlı birleştirme politikasının iki çarpan ayarının karşılaştırılması.

Toplu dizinleme yazma verimini artırdı: belgeler tek tek değil parti hâlinde dizine girdi. Bu
kazancın sessiz bir yan ürünü vardır. Her toplu iş dizine yeni bir **bölüt** bırakır: kendi
sözlüğü, kendi gönderi listeleri ve kendi belge sayısı olan, yazıldıktan sonra bir daha
değiştirilmeyen bir birim. Bölüt değişmez olduğu için silinen bir belge yerinden kalkmaz,
yalnız bir **silme işareti** alır ve orada durmayı sürdürür.

Kırk altı parti sonunda dizin kırk altı ayrı bölüttür ve her sorgu hepsini ayrı ayrı aramak
zorundadır. Bu dersin iki sorusu vardır. Bölüt sayısı sorguya tam olarak neyi ödetir ve arka
planda çalışan birleştirme bunun ne kadarını geri alır, karşılığında neyi kopyalar.

#### Bölüt Yığınının Kurulumu

Ölçüm için katalog derlemi, bölütlü ters dizin ve birleştirme kendi yazılır. Derlem 12.000
kitap kaydıdır, tohum 20260731; 300 belgelik kırk parti dizine girer, ardından altı düzeltme
partisi daha önce dizinlenmiş kayıtları yeniden yazar ve eski kopyalarına silme işareti koyar.

**AK10.** Her bölüt bağımsız arandığı için puanlama bölütün **kendi** belge sayısını ve belge
sıklığını kullanır. Bu, gerçek bir bölütlü dizinin davranışıdır: küresel istatistik ancak
bölütler birleştiğinde ortaya çıkar.

**AK11.** Bölütün diskteki boyutu, sözlüğün ve gönderi listelerinin metin olarak
serileştirilmiş hâlidir. Gerçek bir gerçekleştirim bunu sıkıştırır; oran değişir, ölçümün yönü
değişmez.

**AK12.** Bir birleştirmenin bedeli, okunan girdi baytı ile yazılan çıktı baytının toplamıdır.

```js
// derlem.mjs — tohumlu katalog derlemi, bölütlü ters dizin ve katmanlı birleştirme
export function uretec(tohum) {               // belirlenimci sözde rastgele üreteç
  let a = tohum >>> 0;
  return () => {
    a = (a + 0x6d2b79f5) >>> 0;
    let t = a;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

const SIFAT = 'sessiz uzak kırık yitik beyaz kara ince derin sarı uzun geç son eski yeni yalnız'.split(' ');
const AD = 'kapı deniz yol ev şehir bahçe ada defter ırmak kuyu saat orman köprü kule bahar'.split(' ');
const KONU = ['çocuk edebiyatı', 'kısa öykü', 'roman', 'deneme', 'şiir', 'tarih', 'coğrafya',
  'felsefe', 'psikoloji', 'ekonomi', 'mimarlık', 'müzik', 'sinema', 'gezi', 'biyografi',
  'anı', 'bilim kurgu', 'polisiye', 'halk bilimi', 'dil bilim'];
const KOK = ('kitap yazar anlatı öykü çocuk kent bellek yolculuk kuşak savaş göç aile deniz kış ' +
  'sabah mektup fotoğraf tren sokak okul kütüphane rüzgâr sessizlik umut ada yağmur orman köy ' +
  'dağ nehir sınır yüzyıl kadın asker öğretmen hekim ressam şarkı dize kapak baskı').split(' ');
const KISI = 'Ahmet Zeynep Mehmet Elif Can Ayşe Murat Selin Kemal Deniz Nazlı Orhan'.split(' ');
const SOY = 'Yılmaz Demir Kaya Çelik Şahin Aydın Öztürk Arslan Doğan Koç'.split(' ');

function cekim(kok, i) {                      // ek yüzey biçimi: ünlü uyumu ve sertleşme
  const v = [...kok].reverse().find((c) => 'aeıioöuü'.includes(c));
  const k = 'aıou'.includes(v), d = 'fhkpsştç'.includes(kok.at(-1)) ? 't' : 'd';
  return kok + ['', k ? 'lar' : 'ler', d + (k ? 'a' : 'e'), d + (k ? 'an' : 'en'), k ? 'la' : 'le',
    k ? 'lara' : 'lere', k ? 'larda' : 'lerde', k ? 'lardan' : 'lerden'][i];
}

export function derlemUret(sayi, tohum) {     // kütüphane katalogundan kitap kaydı
  const r = uretec(tohum), liste = [];
  for (let i = 0; i < sayi; i++) {
    const ozet = [];
    for (let j = 0; j < 16; j++)              // sık kökler daha sık geçer: eğik seçim
      ozet.push(cekim(KOK[Math.floor(r() ** 2 * KOK.length)], Math.floor(r() ** 2 * 8)));
    const konu = [KONU[Math.floor(r() * 20)]];
    if (r() < 0.45) konu.push(KONU[Math.floor(r() * 20)]);
    liste.push({
      id: 'k' + String(i).padStart(5, '0'), ozet: ozet.join(' '), konu,
      ad: `${SIFAT[Math.floor(r() * 15)]} ${AD[Math.floor(r() * 15)]}`,
      yazar: `${KISI[Math.floor(r() * 12)]} ${SOY[Math.floor(r() * 10)]}`,
      yil: 1990 + Math.floor(r() * 35),
      raf: 'pl' + (100 + Math.floor(r() * 900)) + '.' + (1 + Math.floor(r() * 40)),
    });
  }
  return liste;
}

export const metin = (b) => `${b.ad} ${b.ozet} ${b.konu.join(' ')} ${b.yazar} ${b.yil} ${b.raf}`;
export const belirtecle = (s) =>              // Türkçe küçük harf, harf dışı ayırıcı
  s.toLocaleLowerCase('tr').split(/[^\p{L}\p{N}]+/u).filter((t) => t.length > 1);

export class Bolut {                          // bir toplu işten doğan, değişmez bölüt
  constructor(belgeler) {
    this.sozluk = new Map();                  // terim -> [[belge kimliği, terim sıklığı], ...]
    this.belge = new Set();
    this.silinen = new Set();
    for (const b of belgeler) {
      this.belge.add(b.id);
      const sayac = new Map();
      for (const t of belirtecle(metin(b))) sayac.set(t, (sayac.get(t) || 0) + 1);
      for (const [t, tf] of sayac) {
        if (!this.sozluk.has(t)) this.sozluk.set(t, []);
        this.sozluk.get(t).push([b.id, tf]);
      }
    }
  }
  serile() {                                  // diske yazılan biçim: sözlük + gönderi listeleri
    let s = '';
    for (const [t, g] of this.sozluk) s += t + '\t' + g.map(([i, f]) => i + ':' + f).join(' ') + '\n';
    for (const id of this.silinen) s += '-\t' + id + '\n';
    return s;
  }
  get bayt() { return Buffer.byteLength(this.serile()); }
}

// bölütler üzerinde sorgu: her bölütte ayrı arama, puan bölütün kendi istatistiğinden
export function ara(bolutler, terimler, k = 10) {
  const puan = new Map();
  let sozlukAramasi = 0, taranan = 0, atilan = 0;
  for (const bl of bolutler) {
    const N = bl.belge.size;                  // silinen belge bölütün istatistiğinde durur
    for (const t of terimler) {
      sozlukAramasi++;
      const g = bl.sozluk.get(t);
      if (!g) continue;
      const idf = Math.log(1 + N / g.length);
      for (const [id, tf] of g) {
        taranan++;
        if (bl.silinen.has(id)) { atilan++; continue; }
        puan.set(id, (puan.get(id) || 0) + tf * idf);
      }
    }
  }
  const sira = [...puan].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1));
  return { ilkK: sira.slice(0, k).map(([id]) => id), eslesen: sira.length, sozlukAramasi,
    taranan, atilan };
}

export function birlestir(bolutler) {         // silme işaretlilerini atarak tek bölüte indirir
  const yeni = new Bolut([]);
  for (const bl of bolutler) {
    for (const id of bl.belge) if (!bl.silinen.has(id)) yeni.belge.add(id);
    for (const [t, g] of bl.sozluk) {
      const kalan = g.filter(([id]) => !bl.silinen.has(id));
      if (kalan.length === 0) continue;
      if (!yeni.sozluk.has(t)) yeni.sozluk.set(t, []);
      for (const p of kalan) yeni.sozluk.get(t).push(p);
    }
  }
  return yeni;
}

// katmanlı politika: aynı katmanda çarpan kadar bölüt birikince hepsi üst katmana birleşir
export function yiginKur(carpan, sayi = 12000, tohum = 20260731) {
  const derlem = derlemUret(sayi, tohum);
  const katman = [], nerede = new Map();
  let kopyalanan = 0, birlestirme = 0, toplamBolut = 0, parti = 0;
  const sayim = () => katman.reduce((n, k) => n + k.length, 0);
  const yerlestir = (b, k) => {
    while (katman.length <= k) katman.push([]);
    katman[k].push(b);
    if (katman[k].length < carpan) return;
    const grup = katman[k].splice(0, carpan), yeni = birlestir(grup);
    for (const g of grup) kopyalanan += g.bayt;         // okunan
    kopyalanan += yeni.bayt;                            // yazılan
    birlestirme++;
    for (const id of yeni.belge) nerede.set(id, yeni);
    yerlestir(yeni, k + 1);
  };
  const ekle = (belgeler) => {
    const b = new Bolut(belgeler);
    for (const id of b.belge) nerede.set(id, b);
    yerlestir(b, 0);
    toplamBolut += sayim(); parti++;
  };
  for (let i = 0; i < sayi; i += 300) ekle(derlem.slice(i, i + 300));
  const r = uretec(907);
  for (let p = 0; p < 6; p++) {               // düzeltme partisi: kayıt yeniden dizinlenir
    const duzeltme = [];
    while (duzeltme.length < 300) {
      const b = derlem[Math.floor(r() * sayi)], eski = nerede.get(b.id);
      if (!eski || eski.silinen.has(b.id)) continue;
      eski.silinen.add(b.id);                 // eski kopya silme işareti alır
      duzeltme.push(b);
    }
    ekle(duzeltme);
  }
  return { derlem, bolutler: katman.flat(), kopyalanan, birlestirme,
    ortalama: toplamBolut / parti };
}
```

#### Bölüt Sayısı Sorguya Ne Ödetir

Aynı yığın üç dizilimle aranır: hiç birleştirilmemiş kırk altı bölüt, beş bölüte indirilmiş
hâli ve tek bölüt. Beş katalog sorusu koşar; her dizilimde sözlük araması, taranan gönderi
girişi, silme işareti yüzünden atılan giriş ve eşleşen belge sayısı sayılır. Sonra ilk on
sonuç tek bölütlü dizilimle karşılaştırılır.

```js
// bolut.mjs — bölüt sayısının sorgu maliyetine, kümeye ve sıraya etkisi
import { ara, birlestir, yiginKur } from './derlem.mjs';

const SORGULAR = [
  ['çocuk', 'edebiyatı', 'kısa', 'öykü'], ['yitik', 'kapı'],
  ['kütüphane', 'bellek', 'yolculuk'], ['deniz', 'ada', 'yağmur'], ['polisiye', 'kent'],
];
const { bolutler } = yiginKur(Infinity);      // Infinity: hiç birleştirme yapılmaz
const grupla = (bl, n) => {                   // ardışık bölütleri n gruba indirger
  const adim = Math.ceil(bl.length / n), c = [];
  for (let i = 0; i < bl.length; i += adim) c.push(birlestir(bl.slice(i, i + adim)));
  return c;
};
const dizilim = [['birleştirme yok', bolutler], ['beş bölüt', grupla(bolutler, 5)],
  ['tek bölüt', grupla(bolutler, 1)]];

console.log('derlem: 12000 belge, tohum 20260731 | 300 belgelik 40 parti + 6 düzeltme partisi');
console.log('dizilim         bölüt  sözlük girişi  dizin bayt  sözlük araması  taranan  atılan  eşleşen');
const ilkOn = new Map();
for (const [ad, bl] of dizilim) {
  let sozluk = 0, bayt = 0, sa = 0, tar = 0, at = 0, es = 0;
  for (const b of bl) { sozluk += b.sozluk.size; bayt += b.bayt; }
  const ilk = [];
  for (const s of SORGULAR) {
    const c = ara(bl, s);
    sa += c.sozlukAramasi; tar += c.taranan; at += c.atilan; es += c.eslesen; ilk.push(c.ilkK);
  }
  ilkOn.set(ad, ilk);
  console.log(ad.padEnd(15), String(bl.length).padStart(5), String(sozluk).padStart(14),
    String(bayt).padStart(11), String(sa).padStart(15), String(tar).padStart(8),
    String(at).padStart(7), String(es).padStart(8));
}

console.log('\nilk on sonuç, tek bölütlü dizilime göre (5 sorgu, 50 basamak)');
console.log('dizilim         ilk ondan çıkan  basamağı oynayan  ilk sırası değişen sorgu');
const temel = ilkOn.get('tek bölüt');
for (const [ad, ilk] of ilkOn) {
  let cikan = 0, oynayan = 0, bas = 0;
  for (let q = 0; q < SORGULAR.length; q++) {
    const t = temel[q], y = ilk[q];
    for (const id of t) if (!y.includes(id)) cikan++;
    for (let i = 0; i < t.length; i++) if (y[i] !== t[i]) oynayan++;
    if (y[0] !== t[0]) bas++;
  }
  console.log(ad.padEnd(15), String(cikan).padStart(15), String(oynayan).padStart(17),
    String(bas).padStart(25));
}
```

```
derlem: 12000 belge, tohum 20260731 | 300 belgelik 40 parti + 6 düzeltme partisi
dizilim         bölüt  sözlük girişi  dizin bayt  sözlük araması  taranan  atılan  eşleşen
birleştirme yok    46          33098     3193775             644    26336    3434    18775
beş bölüt           5           6458     2587395              70    22902       0    18775
tek bölüt           1           1364     2552007              14    22902       0    18775

ilk on sonuç, tek bölütlü dizilime göre (5 sorgu, 50 basamak)
dizilim         ilk ondan çıkan  basamağı oynayan  ilk sırası değişen sorgu
birleştirme yok              12                42                         1
beş bölüt                    14                37                         2
tek bölüt                     0                 0                         0
```

Birinci sütun ikisinin de kaynağıdır. Derlemde 1.364 ayrı terim vardır; kırk altı bölüte
dağılmış hâlde sözlük **33.098 giriş** tutar. Aynı terim her bölütte yeniden yazılıdır, çünkü
her bölüt kendi başına aranabilir olmak zorundadır. Bu, dizin boyutunun 2.552.007 bayttan
3.193.775 bayta çıkmasının başlıca nedenidir: yüzde 25 fazla yer, aynı veri için.

Sorgu tarafında iki ayrı maliyet vardır. **Sözlük araması** bölüt sayısıyla doğrusal artar: on
dört terimlik sorgu kümesi tek bölütte 14 arama yaparken kırk altı bölütte 644 arama yapar.
Bu, arama başına ödenen sabit maliyettir ve gönderi listeleri boş çıksa bile ödenir. **Taranan
giriş** ise 22.902'den 26.336'ya çıkar; aradaki 3.434 giriş, silme işareti almış eski kopyaların
gönderi listelerinde durmayı sürdüren kayıtlarıdır. Bunlar taranır, puanlanmadan atılır ve
tarama işini yine de yaptırır.

Son sütun kararın en önemli tarafını gösterir: **eşleşen belge sayısı üç dizilimde de 18.775**.
Bölüt sayısı kümeyi değiştirmez; silinen kopyalar süzüldüğü için yanıt aynı yanıttır. Değişen
sıradır. Kırk altı bölütlü dizilimde ilk on sonucun elli basamağının **42'si** tek bölütlü
dizilimden farklı bir belge taşır ve on iki belge ilk ondan bütünüyle düşer. Nedeni AK10'dur:
her bölüt ters belge sıklığını yalnızca kendi 300 belgesine bakarak hesaplar, dolayısıyla aynı
terim bir bölütte nadir, ötekinde sıradan görünür. Beş bölütlü ara dizilim bu bozulmayı tam
olarak düzeltmez — bu satırda ilk ondan çıkan belge sayısı 46 bölütlü dizilimden bir tık
yüksektir. Küresel istatistik ancak bütün bölütler tek bölüte indiğinde ortaya çıkar;
birleştirme sırayı yavaşça düzeltmez, eşiği geçtiğinde düzeltir.

#### Birleştirme Politikasının İki Ayarı

Birleştirme kararı elle verilmez; bir politika sürekli çalışır. Katmanlı politika bölütleri
boyutuna göre katmanlara koyar: aynı katmanda **çarpan** kadar bölüt birikince hepsi tek bölüte
indirilir ve sonuç bir üst katmana yerleşir. Çarpan tek ayardır. Aynı kırk altı parti iki
çarpanla oynatılır.

```js
// politika.mjs — katmanlı birleştirme politikasının iki ayarı: çarpan 3 ve çarpan 10
import { ara, yiginKur } from './derlem.mjs';

const SORGULAR = [
  ['çocuk', 'edebiyatı', 'kısa', 'öykü'], ['yitik', 'kapı'],
  ['kütüphane', 'bellek', 'yolculuk'], ['deniz', 'ada', 'yağmur'], ['polisiye', 'kent'],
];
console.log('aynı 46 parti, aynı tohum: 12000 belge, tohum 20260731');
console.log('çarpan  birleştirme  kopyalanan bayt  ortalama bölüt  son bölüt  dizin bayt  sözlük araması  taranan');

for (const carpan of [3, 10]) {
  const y = yiginKur(carpan);
  let bayt = 0, sa = 0, tar = 0;
  for (const b of y.bolutler) bayt += b.bayt;
  for (const s of SORGULAR) { const c = ara(y.bolutler, s); sa += c.sozlukAramasi; tar += c.taranan; }
  console.log(String(carpan).padStart(6), String(y.birlestirme).padStart(12),
    String(y.kopyalanan).padStart(16), y.ortalama.toFixed(1).padStart(15),
    String(y.bolutler.length).padStart(10), String(bayt).padStart(11),
    String(sa).padStart(15), String(tar).padStart(8));
}
```

```
aynı 46 parti, aynı tohum: 12000 belge, tohum 20260731
çarpan  birleştirme  kopyalanan bayt  ortalama bölüt  son bölüt  dizin bayt  sözlük araması  taranan
     3           21         15355451             3.3          4     2922192              56    25934
    10            4          5342633             6.3         10     3009648             140    26336
```

İki ayar arasındaki fark sorgu tarafında ölçülüdür ama küçüktür: sözlük araması 140'tan 56'ya
iner, taranan giriş 26.336'dan 25.934'e. Dizin boyutu 3.009.648 bayttan 2.922.192 bayta düşer.
Arka planda ödenen fark ise büyüktür: çarpan 3 yirmi bir birleştirme yapıp **15.355.451 bayt**
kopyalar, çarpan 10 dört birleştirmeyle 5.342.633 bayt. Ölçüyü bir orana çevirmek ayrımı
keskinleştirir. Çarpan 3, sonunda 2,9 MB tutan bir dizin için 15,4 MB okuyup yazar: **yazma
büyütmesi 5,25 kat**. Çarpan 10 için aynı oran 1,78 kattır.

Bu, birleştirmenin gerçek ödünleşimidir. Toplu dizinlemenin kazandırdığı yazma verimi, arka
planda çalışan birleştirme tarafından geri alınabilir; parti başına yazılan bayt azken
kopyalanan bayt üç katına çıkabilir. Aynı diski ve aynı kanalı hem gelen partiler hem
birleştirme kullanır. Çarpanı düşürmek sorguyu biraz hızlandırır, dizini biraz küçültür ve
arka plan yükünü üçe katlar; sorgu maliyetinin asıl düşüşü, birinci ölçümdeki 644'ten 140'a
inişte, yani birleştirmenin **hiç yapılmaması ile yapılması** arasında gerçekleşmiştir.

#### Özet

- Her toplu iş bir bölüt bırakır; bölüt değişmez olduğu için silinen belge yerinde kalır ve
  yalnız bir silme işareti alır.
- Kırk altı bölütte sözlük 33.098 giriş tutar, tek bölütte 1.364; aynı veri için dizin
  3.193.775 bayttan 2.552.007 bayta iner.
- Bölüt sayısı kümeyi değiştirmez (üç dizilimde de 18.775 eşleşen belge), sırayı değiştirir:
  ilk on sonucun elli basamağının 42'si tek bölütlü dizilimden farklıdır, çünkü her bölüt ters
  belge sıklığını kendi 300 belgesiyle hesaplar.
- Birleştirme 3.434 silme işaretli girişi tarama işinden çıkarır ve sözlük aramasını 644'ten
  14'e indirir.
- Katmanlı politikada çarpan 3, çarpan 10'a göre sözlük aramasını 140'tan 56'ya indirir ama
  kopyalanan baytı 5,3 MB'tan 15,4 MB'a çıkarır: yazma büyütmesi 1,78 kattan 5,25 kata çıkar.

#### Sonraki Adım

Birleştirme bölüt sayısını denetler, ama dizinin **büyümesini** denetlemez. Ölçümdeki 12.000
kayıt sabit bir katalogdur; sürekli yeni kayıt akan bir dizinde birleştirme her seferinde daha
büyük bir bölütü yeniden kopyalar ve eski veriyi silmenin bedeli de aynı kopyalamaya bağlanır.
Sonraki ders dizini tek bir sonsuz büyüyen nesne olmaktan çıkarır: belirli bir eşikte yeni bir
dizine devretmek, sorgunun kaç dizine gitmesi gerektiğini ve eski veriyi silmenin kaç bayta mal
olduğunu değiştirir. Ölçülecek olan, zamana göre devretmenin sorgu kapsamına etkisi, tek büyük
dizinde belge silmek ile dizin düşürmek arasındaki maliyet farkı ve sıcak–ılık–soğuk katman
yerleşiminin bellekte tuttuğu kaynaktır.

### Dizin Yaşam Döngüsü

Source: https://academia.sh/tr/kurslar/arama-motorlari/dizin-yasam-dongusu

Sürekli büyüyen bir dizini zamana göre devretmenin ölçülmüş etkisi: devretme aralığının sorgu kapsamına ve taranan gönderi girişine etkisi, tek büyük dizinde belge silmek ile dizin düşürmenin maliyet farkı, devretme granülerliğinin saklama penceresi dışında tuttuğu veri ve sıcak, ılık, soğuk katman yerleşiminin bellekte tuttuğu kaynak.

Birleştirme bölüt sayısını denetler, dizinin büyümesini denetlemez. Kırk altı parti sonunda
elde kalan tek bölüt, dört yüz partiden sonra dört yüz partilik bir bölüt olur ve her
birleştirme onun tamamını yeniden kopyalar. Sürekli akan veride asıl soru şudur: dizin neden
tek bir nesne olmak zorunda olsun.

Bu ders dizini zamana göre bölmenin etkisini ölçer. Belirli bir aralıkta yeni bir dizin açmaya
**devretme** denir; eski dizinler yerinde kalır, sorgular gerektiğinde onlara da gider ve
saklama süresi dolduğunda dizin bütün olarak düşürülür. Üç şey sayılır: sorgunun kaç dizine
gitmek zorunda kaldığı, eski veriyi silmenin kaç bayta mal olduğu ve katmanlı bir yerleşimin
bellekte ne tuttuğu.

#### Zaman Damgalı Dizin ve Devretme

**AK13.** Yaşam döngüsü kararı zaman damgalı veride anlamlıdır; bu yüzden ders katalog
kayıtlarını değil, katalog üzerindeki **erişim günlüğünü** dizinler. Her kayıt bir günün bir
şubesinde belirli terimlerle yapılan erişimi ve o kaydın erişim sayısını taşır. Sorgunun
sırası ilgililik puanına değil bu **ölçüte** göredir: en çok erişilen kayıt başta gelir.

**AK16.** Gönderi listeleri zaman için ayrıca dizinlenmemiştir; tek dizinde bir gün
penceresini uygulamak bütün girişleri taramayı gerektirir. Gerçek bir gerçekleştirim bölüt
düzeyinde en küçük ve en büyük günü tutarak dizin içinde de atlayabilir — bu, aşağıdaki
atlamanın daha ince taneli hâlidir.

Derlem 180 gün boyunca günde 400 kayıttır: 72.000 belge, tohum 20260731.

```js
// gunluk.mjs — zaman damgalı katalog erişim günlüğü ve devretilebilir dizin
export function uretec(tohum) {               // belirlenimci sözde rastgele üreteç
  let a = tohum >>> 0;
  return () => {
    a = (a + 0x6d2b79f5) >>> 0;
    let t = a;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

const KONU = ('edebiyat öykü roman deneme şiir tarih coğrafya felsefe psikoloji ekonomi mimarlık ' +
  'müzik sinema gezi biyografi anı kurgu polisiye folklor dilbilim').split(' ');
const SIFAT = 'sessiz uzak kırık yitik beyaz kara ince derin sarı uzun'.split(' ');
const AD = 'kapı deniz yol ev şehir bahçe ada defter ırmak kuyu'.split(' ');
const SUBE = 'merkez kadıköy bornova çankaya nilüfer'.split(' ');

// her kayıt bir katalog erişimidir: hangi gün, hangi şube, hangi terimlerle, kaç görüntüleme
export function gunlukUret(gunSayisi, gunluk, tohum) {
  const r = uretec(tohum), kayit = [];
  for (let g = 0; g < gunSayisi; g++)
    for (let i = 0; i < gunluk; i++) {
      const terim = [KONU[Math.floor(r() * 20)], SIFAT[Math.floor(r() * 10)],
        AD[Math.floor(r() * 10)], SUBE[Math.floor(r() * 5)]];
      if (r() < 0.4) terim.push(KONU[Math.floor(r() * 20)]);
      kayit.push({ id: 'e' + String(kayit.length).padStart(6, '0'), gun: g, terim,
        erisim: Math.ceil(2 / Math.sqrt(r())) });   // AK14: uzun kuyruklu erişim sayısı
    }
  return kayit;
}

export class Dizin {
  constructor(ad, ilkGun) { this.ad = ad; this.ilkGun = ilkGun; this.sonGun = ilkGun;
    this.sozluk = new Map(); this.belge = new Map(); this.silinen = new Set(); }
  ekle(k) {
    this.sonGun = k.gun; this.belge.set(k.id, k);
    for (const t of new Set(k.terim)) {
      if (!this.sozluk.has(t)) this.sozluk.set(t, []);
      this.sozluk.get(t).push([k.id, k.erisim, k.gun]);   // gönderi girişi: kimlik, ölçüt, gün
    }
  }
  get giris() { let n = 0; for (const g of this.sozluk.values()) n += g.length; return n; }
  get bayt() {
    let s = '';
    for (const [t, g] of this.sozluk) s += t + '\t' + g.map((p) => p.join(':')).join(' ') + '\n';
    for (const id of this.silinen) s += '-\t' + id + '\n';
    return Buffer.byteLength(s);
  }
  yerlesikBayt(katman) {                      // AK15: sözlük girişi terim baytı + 32,
    if (katman === 'soğuk') return 128;       // gönderi girişi 16 bayt; kapalı dizin 128 bayt
    let b = 0;
    for (const t of this.sozluk.keys()) b += Buffer.byteLength(t) + 32;
    return katman === 'ılık' ? b : b + this.giris * 16;
  }
}

export function devret(kayitlar, gunAdim) {   // gunAdim gün dolunca yeni dizin açılır
  const dizinler = [];
  for (const k of kayitlar) {
    const no = Math.floor(k.gun / gunAdim);
    if (!dizinler[no]) dizinler[no] = new Dizin('gunluk-' + no, k.gun);
    dizinler[no].ekle(k);
  }
  return dizinler;
}

// sorgu: gün penceresiyle örtüşmeyen dizin hiç açılmaz; sıra erişim sayısına göredir
export function ara(dizinler, terimler, pencere = null, k = 10) {
  let dokunulan = 0, sozlukAramasi = 0, taranan = 0;
  const aday = new Map();
  for (const d of dizinler) {
    if (pencere && (d.sonGun < pencere[0] || d.ilkGun > pencere[1])) continue;
    dokunulan++;
    for (const t of terimler) {
      sozlukAramasi++;
      const g = d.sozluk.get(t);
      if (!g) continue;
      for (const [id, erisim, gun] of g) {
        taranan++;
        if (pencere && (gun < pencere[0] || gun > pencere[1])) continue;
        if (d.silinen.has(id)) continue;
        aday.set(id, [erisim, gun]);
      }
    }
  }
  const sira = [...aday].sort((a, b) => b[1][0] - a[1][0] || b[1][1] - a[1][1] ||
    (a[0] < b[0] ? -1 : 1));
  return { dokunulan, sozlukAramasi, taranan, eslesen: sira.length,
    ilkK: sira.slice(0, k).map(([id, v]) => [id, v[1]]) };
}
```

#### Devretmenin Sorgu Kapsamına Etkisi

Aynı 72.000 kayıt üç dizilime konur: tek dizin, otuz günlük devretme (6 dizin) ve yedi günlük
devretme (26 dizin). Her dizilimde aynı sorgu iki kez koşar — bir kez son on dört güne
sınırlanmış, bir kez pencere olmadan.

```js
// devretme.mjs — devretme aralığının sorgu kapsamına etkisi
import { gunlukUret, devret, ara } from './gunluk.mjs';

const KAYIT = gunlukUret(180, 400, 20260731);
const SORGU = ['polisiye', 'kadıköy'];

console.log('günlük: 180 gün x 400 kayıt = 72000 kayıt, tohum 20260731 | sorgu: polisiye kadıköy');
console.log('dizilim     dizin  sorgu        dokunulan  sözlük araması  taranan  eşleşen');
for (const [ad, adim] of [['tek dizin', 180], ['30 günlük', 30], ['7 günlük', 7]]) {
  const d = devret(KAYIT, adim);
  for (const [etiket, pencere] of [['son 14 gün', [166, 179]], ['pencere yok', null]]) {
    const c = ara(d, SORGU, pencere);
    console.log(ad.padEnd(11), String(d.length).padStart(5), '', etiket.padEnd(12),
      String(c.dokunulan).padStart(8), String(c.sozlukAramasi).padStart(15),
      String(c.taranan).padStart(8), String(c.eslesen).padStart(8));
  }
}
```

```
günlük: 180 gün x 400 kayıt = 72000 kayıt, tohum 20260731 | sorgu: polisiye kadıköy
dizilim     dizin  sorgu        dokunulan  sözlük araması  taranan  eşleşen
tek dizin       1  son 14 gün          1               2    19476     1406
tek dizin       1  pencere yok         1               2    19476    18456
30 günlük       6  son 14 gün          1               2     3188     1406
30 günlük       6  pencere yok         6              12    19476    18456
7 günlük       26  son 14 gün          3               6     1993     1406
7 günlük       26  pencere yok        26              52    19476    18456
```

Pencereli sorgu üç dizilimde de **1.406** kayıt döndürür: küme değişmez. Değişen, o kümeye
ulaşmak için yapılan iştir. Tek dizinde 19.476 gönderi girişi taranır ve bunların yüzde
93'ü gün süzgecinde elenir. Yedi günlük devretmede sorgu 26 dizinden yalnız 3'ünü açar ve
1.993 giriş tarar — onda bir. Kazanç taramanın hızlanmasından değil, **hiç açılmayan dizinden**
gelir: örtüşmeyen bir dizinin sözlüğü okunmaz, gönderi listesi belleğe alınmaz.

Aynı tablonun ikinci satırları bedeli gösterir. Pencere olmayan sorguda taranan giriş üç
dizilimde de 19.476'dır, ama sözlük araması 2'den 52'ye çıkar. Devretme, zaman penceresi
olmayan her sorguya dizin sayısı kadar sabit maliyet ekler; her dizinde terim ayrı ayrı
aranır ve her dizinden gelen kısmi sonuç ayrıca birleştirilir. Devretme aralığı bu iki sorgu
biçimi arasında bir seçimdir: pencereli sorgular sıklaştıkça kısa aralık kazanır, pencereye
sığmayan sorgular ağır bastıkça uzun aralık.

#### Eski Veriyi Silmenin İki Yolu

Saklama penceresi son 100 gündür (gün 80–179). Tek dizinde bunu uygulamanın tek yolu, 80.
günden önceki her belgeye silme işareti koymak ve dizini temizlemektir. Devretmede eski
dizinler bütün olarak düşürülür — ama yalnız **tamamı** pencere dışında kalanlar.

```js
// saklama.mjs — 100 günlük saklama penceresinin iki uygulanışı ve katman yerleşimi
import { gunlukUret, devret, ara } from './gunluk.mjs';

const KAYIT = gunlukUret(180, 400, 20260731);
const ESIK = 80;                              // son 100 gün tutulur: 80..179
const SORGU = ['polisiye', 'kadıköy'];

function temizle(adim) {
  const dz = devret(KAYIT, adim);
  if (adim === 180) {                         // tek dizin: her eski belge silme işareti alır
    const d = dz[0];
    for (const k of KAYIT) if (k.gun < ESIK) d.silinen.add(k.id);
    const okunan = d.bayt;
    const yeni = devret(KAYIT.filter((k) => k.gun >= ESIK), 180);
    return { kalan: yeni, dusen: 0, isaret: d.silinen.size, kopyalanan: okunan + yeni[0].bayt,
      fazla: 0 };
  }
  const kalan = dz.filter((d) => d.sonGun >= ESIK);   // tamamı eski olan dizin düşürülür
  return { kalan, dusen: dz.length - kalan.length, isaret: 0, kopyalanan: 0,
    fazla: kalan.reduce((n, d) => n + [...d.belge.values()].filter((k) => k.gun < ESIK).length, 0) };
}

console.log('saklama penceresi: son 100 gün (gün 80-179), 180 gün x 400 kayıt, tohum 20260731');
console.log('dizilim     düşen dizin  silme işareti  kopyalanan bayt  fazladan tutulan  eşleşen  ilk onda pencere dışı');
const yerlesim = new Map();
for (const [ad, adim] of [['tek dizin', 180], ['30 günlük', 30], ['7 günlük', 7]]) {
  const t = temizle(adim);
  yerlesim.set(ad, t.kalan);
  const c = ara(t.kalan, SORGU, null);
  console.log(ad.padEnd(11), String(t.dusen).padStart(12), String(t.isaret).padStart(14),
    String(t.kopyalanan).padStart(16), String(t.fazla).padStart(17),
    String(c.eslesen).padStart(8), String(c.ilkK.filter(([, g]) => g < ESIK).length).padStart(22));
}

const kalan = yerlesim.get('7 günlük');       // katman yerleşimi 7 günlük devretme üzerinde
const yas = (d) => 179 - d.sonGun;
console.log('\nkatman yerleşimi (7 günlük devretme, temizlik sonrası):');
console.log('yerleşim     sıcak  ılık  soğuk  yerleşik bayt  açılması gereken bayt');
for (const [ad, katmanla] of [['hepsi sıcak', () => 'sıcak'],
  ['üç katman', (d) => (yas(d) < 7 ? 'sıcak' : yas(d) < 30 ? 'ılık' : 'soğuk')]]) {
  const sayi = { sıcak: 0, ılık: 0, soğuk: 0 };
  let bayt = 0, acilacak = 0;
  for (const d of kalan) {
    const k = katmanla(d);
    sayi[k]++; bayt += d.yerlesikBayt(k);
    if (k === 'soğuk') acilacak += d.bayt;
  }
  console.log(ad.padEnd(12), String(sayi['sıcak']).padStart(5), String(sayi['ılık']).padStart(5),
    String(sayi['soğuk']).padStart(6), String(bayt).padStart(14), String(acilacak).padStart(22));
}
```

```
saklama penceresi: son 100 gün (gün 80-179), 180 gün x 400 kayıt, tohum 20260731
dizilim     düşen dizin  silme işareti  kopyalanan bayt  fazladan tutulan  eşleşen  ilk onda pencere dışı
tek dizin              0          32000          6985595                 0    10262                      0
30 günlük              2              0                0              8000    12274                      2
7 günlük              11              0                0              1200    10545                      0

katman yerleşimi (7 günlük devretme, temizlik sonrası):
yerleşim     sıcak  ılık  soğuk  yerleşik bayt  açılması gereken bayt
hepsi sıcak     15     0      0        2913702                      0
üç katman        2     3     10         346802                1685859
```

Maliyet farkı tek satırda görünür. Tek dizinde silme 32.000 belgeye işaret koyar ve
temizlik **6.985.595 bayt** okuyup yazar; silinen veriden kurtulmanın yolu, kalan verinin
tamamını yeniden yazmaktan geçer. Devretmede aynı silme **sıfır bayt** kopyalar: dizin
düşürülür, dosya bütün olarak gider. Bu, bir önceki dersteki birleştirme bedelinin doğrudan
kardeşidir — orada kopyalanan bayt bölüt başına ödendi, burada silme başına ödenir.

Devretmenin bedeli granülerliktedir. Otuz günlük aralıkta 60–89 aralığını kapsayan dizinin
bir bölümü pencerenin içinde kaldığı için dizin düşürülemez ve **8.000 kayıt** saklama
penceresi dışında tutulmayı sürdürür; yedi günlük aralıkta bu sayı 1.200'e iner. Fark,
ölçülebilir biçimde yanıta sızar. Aynı sorgu tek dizinde 10.262, yedi günlük dizilimde 10.545,
otuz günlük dizilimde **12.274** kayıt döndürür: küme yüzde 20 büyümüştür. Sıra da değişir:
otuz günlük dizilimde ilk on sonucun **ikisi** saklama penceresi dışından, silinmiş olması
gereken günlerden gelir. Beklenen büyüklük de bunu doğrular — eşleşen kümenin yaklaşık altıda
biri pencere dışıdır, ilk onda iki kayıt bu oranla uyumludur. "Yüz günlük veri tutuyoruz"
cümlesi, devretme aralığı otuz gün olduğunda 120 güne kadar veri tutmak anlamına gelir.

#### Sıcak, Ilık ve Soğuk Katmanlar

Kalan on beş dizinin hepsi eşit ilgi görmez: son bir haftanın dizini sürekli sorgulanır,
üç ay öncesinin dizini ayda birkaç kez. Katmanlı yerleşim bunu kaynak kararına çevirir. Sıcak
katmanda sözlük ve gönderi listeleri bellekte durur; ılık katmanda yalnız sözlük tutulur,
gönderi listesi her sorguda diskten okunur; soğuk katmanda dizin kapatılır ve geriye yalnız
gün aralığını taşıyan üstveri kalır.

İkinci tablo bunun bedelini verir. Hepsi sıcak yerleşimde on beş dizin **2.913.702 bayt**
yerleşik bellek tutar. Üç katmanlı yerleşimde iki dizin sıcak, üçü ılık, onu soğuk kalır ve
yerleşik bellek **346.802 bayta** iner: sekizde bir. Karşılığında soğuk katmandaki on dizine
giden bir sorgu, 1.685.859 baytı diskten açmak zorundadır. Katman kararı bu iki sayı
arasındadır ve doğrudan bir önceki bölümdeki sorgu biçimine bağlıdır: pencereli sorgular
soğuk dizinleri hiç açmaz, pencere olmayan sorgu her seferinde hepsini açar.

#### Özet

- Zamana göre devretme kümeyi değiştirmez; pencereli sorgu üç dizilimde de 1.406 kayıt
  döndürdü, ama taranan gönderi girişi 19.476'dan 1.993'e indi çünkü örtüşmeyen dizin hiç
  açılmadı.
- Devretmenin sabit bedeli pencere olmayan sorgudadır: sözlük araması 2'den 52'ye çıktı, çünkü
  her dizinde terim ayrı ayrı aranır.
- Tek dizinde 32.000 eski belgeyi silmek 6.985.595 bayt kopyalattı; devretmede aynı silme
  dizin düşürerek sıfır bayta yapıldı.
- Devretme aralığı saklama granülerliğidir: otuz günlük aralık 8.000 kaydı pencere dışında
  tuttu, küme 10.262'den 12.274'e çıktı ve ilk on sonucun ikisi silinmiş olması gereken
  günlerden geldi; yedi günlük aralıkta fazladan tutulan 1.200 kayda indi.
- Üç katmanlı yerleşim yerleşik belleği 2.913.702 bayttan 346.802 bayta indirdi; bedeli, soğuk
  katmana giden sorgunun açmak zorunda kaldığı 1.685.859 bayttır.

#### Sonraki Adım

Devretme, dizin düşürmeyi ucuzlattığı gibi bir başka işi de değiştirir. Bölütler ve tamamlanmış
dizinler bir daha yazılmaz; yedeklenecek şey durmadan değişen tek bir dosya değil, değişmez
dosyalardan oluşan bir kümedir. Sonraki ders bunu ele alır: bir dizinin anlık görüntüsü bölüt
düzeyinde artımlı çalışabilir, ama arka planda çalışan birleştirme artımlılığı bozar. Aynı
derste geri yükleme ile kaynaktan **yeniden dizinleme** karşılaştırılır; ölçülecek olan,
görüntü başına kopyalanan bayt, geri yüklemenin taşıdığı bayt ile yeniden dizinlemenin işlediği
belge sayısı ve çözümleyici değiştiğinde yeniden dizinlemenin kümeyle sırayı nasıl kaydırdığıdır.

### Anlık Görüntü ve Geri Yükleme

Source: https://academia.sh/tr/kurslar/arama-motorlari/anlik-goruntu-ve-geri-yukleme

Dizine özgü yedekleme kararı: bölütlerin değişmezliği üzerine kurulan artımlı anlık görüntünün kopyaladığı bayt, arka plan birleştirmesinin artımlılığı bozması, depoda tutulanın canlı dizinden büyük olması ve geri yükleme ile kaynaktan yeniden dizinlemenin kopyalanan bayt, işlenen belge, küme ile sıra üzerinden karşılaştırılması.

Devretme, eski veriden kurtulmayı dosya düşürmeye indirdi. Aynı yapı yedeklemeyi de değiştirir:
yazıldıktan sonra bir daha değişmeyen bölütler, yedeklenecek şeyi "durmadan değişen bir durum"
olmaktan çıkarıp **değişmez dosyalardan oluşan bir kümeye** çevirir.

Bellek içi bir deponun anlık görüntüsü daha önce ölçüldü: aralık ile kayıp penceresi arasındaki
ilişki, görüntü almanın çalışan depoya maliyeti. O ölçüm burada tekrarlanmaz. Buranın sorusu
dizine özgüdür. Bölütler değişmezse görüntü **artımlı** olabilir; peki arka planda çalışan
birleştirme buna ne yapar ve dizin geri gelmesi gerektiğinde onu depodan taşımak mı yoksa
kaynaktan **yeniden dizinlemek** mi daha ucuzdur.

#### Bölüt Düzeyinde Görüntü

**AK18.** Bölüt dosyası adıyla birlikte değişmezdir: depoda belirli bir ada sahip dosya her
zaman aynı içeriği taşır. Artımlı görüntü bunun üzerine kurulur — depoda o ad varsa dosya
yeniden kopyalanmaz.

**AK19.** Görüntünün bedeli kopyalanan bayttır; depoda tutulan, birbirinden farklı bölüt
dosyalarının toplamıdır.

**AK20.** Yeniden dizinleme kaynak kayıtların elde olduğunu, aynı günlük partilerin ve aynı
birleştirme politikasının uygulandığını varsayar. Süre koşum ortamına bağlıdır ve bu yüzden
basılmaz; onun yerine koşumdan bağımsız iki nicelik sayılır: kopyalanan bayt ile işlenen belge
ve belirteç.

Derlem 8.000 kitap kaydıdır, tohum 20260731.

```js
// yedek.mjs — katalog derlemi, çözümleyiciler, bölütlü dizin ve bölüt düzeyinde görüntü deposu
import { writeFileSync, existsSync, mkdirSync, statSync } from 'node:fs';

export function uretec(tohum) {               // belirlenimci sözde rastgele üreteç
  let a = tohum >>> 0;
  return () => {
    a = (a + 0x6d2b79f5) >>> 0;
    let t = a;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

const SIFAT = 'sessiz uzak kırık yitik beyaz kara ince derin sarı uzun'.split(' ');
const ADLAR = 'kapı deniz yol ev şehir bahçe ada defter ırmak kuyu'.split(' ');
const KONU = ('çocuk edebiyatı|kısa öykü|roman|deneme|şiir|tarih|coğrafya|felsefe|psikoloji|' +
  'ekonomi|mimarlık|müzik|sinema|gezi|biyografi|anı|bilim kurgu|polisiye|halk bilimi').split('|');
const KOK = ('kitap yazar anlatı öykü çocuk kent bellek yolculuk kuşak savaş göç aile deniz kış ' +
  'sabah mektup fotoğraf tren sokak okul kütüphane rüzgâr sessizlik umut ada yağmur orman köy ' +
  'dağ nehir sınır yüzyıl kadın asker öğretmen hekim ressam şarkı dize kapak').split(' ');
const DOLGU = 've ile bir ya'.split(' ');

function cekim(kok, i) {                      // ek yüzey biçimi: ünlü uyumu ve sertleşme
  const v = [...kok].reverse().find((c) => 'aeıioöuü'.includes(c));
  const k = 'aıou'.includes(v), d = 'fhkpsştç'.includes(kok.at(-1)) ? 't' : 'd';
  return kok + ['', k ? 'lar' : 'ler', d + (k ? 'a' : 'e'), d + (k ? 'an' : 'en'), k ? 'la' : 'le',
    k ? 'lara' : 'lere', k ? 'larda' : 'lerde', k ? 'lardan' : 'lerden'][i];
}

export function derlemUret(sayi, tohum) {
  const r = uretec(tohum), liste = [];
  for (let i = 0; i < sayi; i++) {
    const ozet = [];
    for (let j = 0; j < 16; j++)
      ozet.push(j % 4 === 3 ? DOLGU[Math.floor(r() * 4)]
        : cekim(KOK[Math.floor(r() ** 2 * KOK.length)], Math.floor(r() ** 2 * 8)));
    liste.push({ id: 'k' + String(i).padStart(5, '0'),
      ad: `${SIFAT[Math.floor(r() * 10)]} ${ADLAR[Math.floor(r() * 10)]}`,
      ozet: ozet.join(' '), konu: KONU[Math.floor(r() * 19)] });
  }
  return liste;
}

export const metin = (b) => `${b.ad} ${b.ozet} ${b.konu}`;

const DURAK = new Set(DOLGU);
const EK = ['lardan', 'lerden', 'larda', 'lerde', 'lara', 'lere', 'lar', 'ler', 'dan', 'den',
  'tan', 'ten', 'da', 'de', 'ta', 'te', 'la', 'le'];
const kok = (t) => {                          // kök bulma: en uzun eşleşen eki kırpar
  for (const e of EK) if (t.length > e.length + 2 && t.endsWith(e)) return t.slice(0, -e.length);
  return t;
};
export const temel = (s) =>                   // küçük harf + harf dışı ayırıcı
  s.toLocaleLowerCase('tr').split(/[^\p{L}\p{N}]+/u).filter((t) => t.length > 1);
export const gelismis = (s) =>                // + durak sözcük atma + kök bulma
  temel(s).filter((t) => !DURAK.has(t)).map(kok);

let sayac = 0;
export class Bolut {                          // adı içeriğiyle birlikte sabittir: bir daha yazılmaz
  constructor(belgeler = [], coz = temel) {
    this.ad = 'b' + String(++sayac).padStart(3, '0');
    this.sozluk = new Map(); this.belge = new Set();
    for (const b of belgeler) {
      this.belge.add(b.id);
      const sayim = new Map();
      for (const t of coz(metin(b))) sayim.set(t, (sayim.get(t) || 0) + 1);
      for (const [t, tf] of sayim) {
        if (!this.sozluk.has(t)) this.sozluk.set(t, []);
        this.sozluk.get(t).push([b.id, tf]);
      }
    }
  }
  serile() {
    let s = '';
    for (const [t, g] of this.sozluk) s += t + '\t' + g.map(([i, f]) => i + ':' + f).join(' ') + '\n';
    return s;
  }
  get bayt() { return Buffer.byteLength(this.serile()); }
}

export function yigin(carpan = 4) {           // katmanlı birleştirme yığını
  const katman = [];
  let birlestirme = 0;
  const yerlestir = (b, k) => {
    while (katman.length <= k) katman.push([]);
    katman[k].push(b);
    if (katman[k].length < carpan) return;
    birlestirme++;
    yerlestir(birlestir(katman[k].splice(0, carpan)), k + 1);
  };
  return {
    ekle(belgeler, coz) { birlestirme = 0; yerlestir(new Bolut(belgeler, coz), 0); },
    get bolutler() { return katman.flat(); },
    get birlestirme() { return birlestirme; },
  };
}

export function birlestir(grup) {
  const y = new Bolut();
  for (const bl of grup) {
    for (const id of bl.belge) y.belge.add(id);
    for (const [t, g] of bl.sozluk) {
      if (!y.sozluk.has(t)) y.sozluk.set(t, []);
      for (const p of g) y.sozluk.get(t).push(p);
    }
  }
  return y;
}

export function ara(bolutler, terimler, k = 10) {
  const puan = new Map();
  for (const bl of bolutler) {
    const N = bl.belge.size;
    for (const t of terimler) {
      const g = bl.sozluk.get(t);
      if (!g) continue;
      const idf = Math.log(1 + N / g.length);
      for (const [id, tf] of g) puan.set(id, (puan.get(id) || 0) + tf * idf);
    }
  }
  const sira = [...puan].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1));
  return { ilkK: sira.slice(0, k).map(([id]) => id), eslesen: sira.length };
}

// görüntü: bölüt dosyası depoda yoksa yazılır; artımlı olmayan görüntü hepsini yeniden yazar
export function goruntuAl(bolutler, depo, artimli) {
  mkdirSync(depo, { recursive: true });
  let kopyalanan = 0, dosya = 0;
  for (const b of bolutler) {
    const yol = `${depo}/${b.ad}`;
    if (artimli && existsSync(yol)) continue;
    writeFileSync(yol, b.serile());
    kopyalanan += statSync(yol).size; dosya++;
  }
  return { kopyalanan, dosya, liste: bolutler.map((b) => b.ad) };
}
```

#### Artımlı Görüntü ve Birleştirmenin Bozduğu

Katalog on altı gün boyunca günde 500 kayıt alır ve her günün sonunda bir görüntü alınır. Aynı
gün iki kez ölçülür: dizinin tamamını kopyalayan tam görüntü ve yalnız depoda bulunmayan bölüt
dosyalarını kopyalayan artımlı görüntü.

```js
// goruntu.mjs — on altı günlük dizinde tam ve artımlı anlık görüntünün kopyaladığı bayt
import { derlemUret, yigin, goruntuAl } from './yedek.mjs';

const derlem = derlemUret(8000, 20260731);
const y = yigin(4);
console.log('derlem: 8000 belge, tohum 20260731 | günde 500 belgelik bir toplu iş, çarpan 4');
console.log('gün  bölüt  birleştirme  tam görüntü bayt  artımlı bayt  artımlı dosya  depoda tutulan');
let tamToplam = 0, depoBayt = 0;
for (let gun = 1; gun <= 16; gun++) {
  y.ekle(derlem.slice((gun - 1) * 500, gun * 500));
  const tam = goruntuAl(y.bolutler, `tam/g${gun}`, false);
  const art = goruntuAl(y.bolutler, 'depo', true);
  tamToplam += tam.kopyalanan; depoBayt += art.kopyalanan;
  console.log(String(gun).padStart(3), String(y.bolutler.length).padStart(6),
    String(y.birlestirme).padStart(12), String(tam.kopyalanan).padStart(17),
    String(art.kopyalanan).padStart(13), String(art.dosya).padStart(14),
    String(depoBayt).padStart(15));
}
console.log('toplam: tam görüntüler', tamToplam, 'bayt, artımlı görüntüler', depoBayt, 'bayt');
```

```
derlem: 8000 belge, tohum 20260731 | günde 500 belgelik bir toplu iş, çarpan 4
gün  bölüt  birleştirme  tam görüntü bayt  artımlı bayt  artımlı dosya  depoda tutulan
  1      1            0             81694         81694              1           81694
  2      2            0            162974         81280              1          162974
  3      3            0            244317         81343              1          244317
  4      1            1            315289        315289              1          559606
  5      2            0            397163         81874              1          641480
  6      3            0            478662         81499              1          722979
  7      4            0            560200         81538              1          804517
  8      2            1            631586        316297              1         1120814
  9      3            0            713127         81541              1         1202355
 10      4            0            794677         81550              1         1283905
 11      5            0            876578         81901              1         1365806
 12      3            1            947640        316054              1         1681860
 13      4            0           1029181         81541              1         1763401
 14      5            0           1111100         81919              1         1845320
 15      6            0           1192704         81604              1         1926924
 16      1            2           1253638       1253638              1         3180562
toplam: tam görüntüler 10790530 bayt, artımlı görüntüler 3180562 bayt
```

Birleştirme olmayan günlerde artımlı görüntü sabit bir bedel öder: yaklaşık 81.500 bayt, yani
o gün eklenen tek bölüt. Tam görüntü ise her gün bütün dizini kopyalar ve on beşinci günde tek
bir yedek 1.192.704 bayta çıkar. On altı günün toplamında fark üç katından fazladır: 10.790.530
bayta karşılık 3.180.562 bayt.

Kritik satırlar 4, 8, 12 ve 16. numaralı günlerdir. Birleştirmenin çalıştığı günde artımlı
görüntü o gün eklenen bölütü değil, birleştirmenin ürettiği bölütün tamamını kopyalar —
81 bin bayt yerine 316 bin — çünkü birleştirmenin ürettiği bölüt **yeni bir dosyadır**:
içeriğinin tamamı zaten depoda olsa bile, girdileri değil kendisi kopyalanır. Dizinde tek
bölüt kaldığında bu, tam görüntüyle aynı işe çıkar; dördüncü günde ikisi de 315.289 bayttır.
Sekizinci ve on ikinci günlerde geriye birden çok bölüt kaldığı için artımlı görüntü tam
görüntünün yarısı ve üçte biri kadardır. On altıncı gün bunun uç hâlidir — iki birleştirme
dizini tek bölüte indirir ve artımlı görüntü 1.253.638 baytın hepsini yeniden yazar.
Artımlılık, bölütlerin değişmezliğinden gelen bir kazançtır ve birleştirme tam da o
değişmezliği kullanıp yeni dosya ürettiği için düzenli aralıklarla sıfırlanır.

Son sütun üçüncü bir sonucu verir. Depoda tutulan 3.180.562 bayttır, oysa canlı dizin
1.253.638 bayttır. Depo, canlı dizinin iki buçuk katını tutar; fazlalık, birleştirmeyle
geçersizleşen ama eski görüntüler onlara başvurduğu için silinemeyen bölüt dosyalarıdır. Eski
görüntüler düşürülmeden bu alan geri gelmez. Birleştirme çarpanını düşürmek — bir önceki
derste sorgu tarafında küçük bir kazançtı — burada hem artımlı görüntü trafiğini hem depo
boyutunu doğrudan büyütür.

#### Geri Yükleme mi, Yeniden Dizinleme mi

Dizin kaybolduğunda iki yol vardır. Depodaki son görüntünün dosyaları yerine taşınabilir, ya da
katalog kaynağındaki 8.000 kayıt yeniden dizinlenebilir. İkincisi ilk bakışta yedeğe hiç
gerek bırakmaz. Ölçüm iki yolun neyi ödediğini ve **ne getirdiğini** ayırır: üçüncü satırda
yeniden dizinleme, o sırada çözümleyici zinciri değiştirilmiş bir sistemde koşar.

```js
// kurtarma.mjs — geri yükleme ile yeniden dizinlemenin karşılaştırması
import { mkdirSync, readFileSync, writeFileSync, statSync } from 'node:fs';
import { derlemUret, yigin, goruntuAl, ara, metin, temel, gelismis } from './yedek.mjs';

const derlem = derlemUret(8000, 20260731);
const SORGU = [['çocuk', 'edebiyatı', 'öykü'], ['yitik', 'kapılar'],
  ['kütüphaneler', 'bellek'], ['denizde', 'ada'], ['polisiye', 'kent']];

function kur(coz) {                           // aynı günlük partiler, aynı birleştirme politikası
  const y = yigin(4);
  let belirtec = 0;
  for (let g = 0; g < 16; g++) {
    const parti = derlem.slice(g * 500, (g + 1) * 500);
    for (const b of parti) belirtec += coz(metin(b)).length;
    y.ekle(parti, coz);
  }
  return { bolutler: y.bolutler, belirtec };
}

const asil = kur(temel);                      // özgün dizin ve son görüntünün içeriği
const gor = goruntuAl(asil.bolutler, 'depo', true);

mkdirSync('dizin', { recursive: true });      // geri yükleme: depodaki dosyalar yerine taşınır
let geriBayt = 0;
for (const ad of gor.liste) {
  writeFileSync(`dizin/${ad}`, readFileSync(`depo/${ad}`));
  geriBayt += statSync(`dizin/${ad}`).size;
}

const olc = (bolutler, coz) => {
  let bayt = 0, sozluk = 0, eslesen = 0;
  const ilk = [];
  for (const b of bolutler) { bayt += b.bayt; sozluk += b.sozluk.size; }
  for (const s of SORGU) {
    const c = ara(bolutler, coz(s.join(' ')));
    eslesen += c.eslesen; ilk.push(c.ilkK);
  }
  return { bayt, sozluk, eslesen, ilk };
};
const temelOlcu = olc(asil.bolutler, temel);
const fark = (o) => {                         // özgün dizine göre ilk on değişimi
  let cikan = 0, oynayan = 0;
  for (let q = 0; q < SORGU.length; q++) {
    for (const id of temelOlcu.ilk[q]) if (!o.ilk[q].includes(id)) cikan++;
    for (let i = 0; i < 10; i++) if (o.ilk[q][i] !== temelOlcu.ilk[q][i]) oynayan++;
  }
  return [cikan, oynayan];
};

const yeni = kur(gelismis);
const yeniOlcu = olc(yeni.bolutler, gelismis);
console.log('derlem: 8000 belge, tohum 20260731 | 5 sorgu, ilk on: 50 basamak');
console.log('yol                              kopyalanan  işlenen belge  belirteç  dizin bayt  sözlük  eşleşen  çıkan  oynayan');
const satir = (ad, kop, belge, belirtec, o) => console.log(ad.padEnd(32),
  String(kop).padStart(10), String(belge).padStart(14), String(belirtec).padStart(9),
  String(o.bayt).padStart(11), String(o.sozluk).padStart(7), String(o.eslesen).padStart(8),
  String(fark(o)[0]).padStart(6), String(fark(o)[1]).padStart(8));
satir('geri yükleme', geriBayt, 0, 0, temelOlcu);
satir('yeniden dizinleme (aynı zincir)', 0, 8000, asil.belirtec, temelOlcu);
satir('yeniden dizinleme (yeni zincir)', 0, 8000, yeni.belirtec, yeniOlcu);
```

```
derlem: 8000 belge, tohum 20260731 | 5 sorgu, ilk on: 50 basamak
yol                              kopyalanan  işlenen belge  belirteç  dizin bayt  sözlük  eşleşen  çıkan  oynayan
geri yükleme                        1253638              0         0     1253638     363     7879      0        0
yeniden dizinleme (aynı zincir)           0           8000    153690     1253638     363     7879      0        0
yeniden dizinleme (yeni zincir)           0           8000    121690      919902      79    17387     41       49
```

İlk iki satır aynı dizini üretir: 1.253.638 bayt, 363 terim, beş sorguda 7.879 eşleşen belge ve
ilk onda tek basamak farkı yok. Aynı sonuca giden iki yolun ödediği ise bambaşkadır. Geri
yükleme 1.253.638 bayt taşır ve hiçbir belgeye dokunmaz; yeniden dizinleme tek bayt taşımaz ama
8.000 belgeyi okuyup **153.690 belirteç** üretir. Süre koşum ortamına bağlıdır ve burada
basılmaz; karar bu iki niceliğin nasıl ölçekleneceğine bakılarak verilir. Geri yüklemenin
bedeli dizin boyutuyla, yeniden dizinlemenin bedeli kaynak belge sayısı ve belge başına
belirteçle büyür. Yeniden dizinlemenin ayrıca sessiz bir koşulu vardır: kaynak kayıtların
eksiksiz durması. Dizin, katalogda artık bulunmayan bir alanı da tutuyorsa yeniden dizinleme
onu geri getiremez.

Üçüncü satır, yeniden dizinlemenin neden yedeğin yerine geçmediğini gösterir. Çözümleyici
zinciri durak sözcük atma ve kök bulma ile genişletildiğinde aynı 8.000 kayıt farklı bir dizin
üretir: 153.690 yerine 121.690 belirteç (aradaki 32.000, atılan dolgu sözcükleridir), 363 yerine
79 terim, 1.253.638 yerine 919.902 bayt. Etki yanıtta görünür. Beş sorgunun eşleşen belge
toplamı 7.879'dan **17.387'ye** çıkar, çünkü "kapılar" artık "kapı" ile aynı terime iner ve
çekimli biçimler tek gönderi listesinde toplanır — anma yükselir. Sıra da yeniden kurulur: ilk
on sonucun elli basamağının 49'u değişir ve 41 belge ilk ondan bütünüyle düşer. Bu bir arıza
değildir, kararın kendisidir; ama geri yükleme ile aynı işlem sayılamaz. Geri yükleme dizini
**olduğu gibi** getirir, yeniden dizinleme o günkü yapılandırmayla **yeniden üretir**.

#### Özet

- Bölütler değişmez olduğu için görüntü artımlı alınabilir: birleştirme olmayan günlerde
  kopyalanan bayt o günün tek bölütüdür (yaklaşık 81.500), tam görüntü ise dizinin tamamıdır.
- On altı günün toplamında tam görüntüler 10.790.530 bayt, artımlı görüntüler 3.180.562 bayt
  kopyaladı.
- Birleştirme artımlılığı bozar: birleştirmenin çalıştığı günde artımlı görüntü o gün eklenen
  bölütü değil birleşen bölütün tamamını kopyalar (81 bin yerine 316 bin bayt) ve dizin tek
  bölüte indiğinde tam görüntüyle eşitlenir; on altıncı günde 1.253.638 baytın hepsi yeniden
  kopyalandı.
- Depo 3.180.562 bayt tutar, canlı dizin 1.253.638 bayt: fark, eski görüntülerin başvurduğu
  için silinemeyen geçersiz bölüt dosyalarıdır.
- Geri yükleme 1.253.638 bayt taşıyıp sıfır belge işler, yeniden dizinleme sıfır bayt taşıyıp
  8.000 belge ve 153.690 belirteç işler; ikisi aynı dizini verir.
- Çözümleyici zinciri değişmişse yeniden dizinleme aynı dizini vermez: eşleşen belge 7.879'dan
  17.387'ye çıktı, ilk on sonucun 49 basamağı değişti ve 41 belge ilk ondan düştü.

#### Sonraki Adım

Bu ders bir dizinin bütün olarak kopyalanmasını, taşınmasını ve yeniden üretilmesini ele aldı.
Hepsinin ortak varsayımı, kümeye erişen herkesin bütün dizini görmeye yetkili olduğuydu:
görüntü alan da, geri yükleyen de, sorgulayan da aynı belgeleri görüyordu. Kütüphane
katalogunda bu doğru değildir — kişisel ödünç kayıtları, satın alma notları ve bağış
yazışmaları aynı dizinde durabilir ama her kullanıcıya açık değildir. Sonraki ders bu ayrımı
ölçer: aynı sorgu iki farklı rolde koştuğunda dönen kümenin nasıl daraldığı, belge ve alan
düzeyinde süzmenin sıraya ne yaptığı ve bu süzmenin sorgu maliyetine ne eklediği.

### Erişim Denetimi

Source: https://academia.sh/tr/kurslar/arama-motorlari/erisim-denetimi

Arama kümesinin yetki yüzeyi: aynı sorgunun okuyucu, görevli ve kapsamsız anahtar altında döndürdüğü dizin, belge ve alan kümesinin sayılması, belge düzeyi süzmenin puanlamadan önce mi sonra mı uygulandığının ilk on sonuca ve derin sıraya etkisinin ölçülmesi, süzülen belgelerin sayaç üzerinden sızdırdığı bilginin gösterilmesi, kapsamı yazılmamış bir anahtarın eriştiği dizin sayısıyla süresi dolmuş anahtarın yanıtsız bıraktığı sorgunun sayılması ve kursun yirmi üç dersinin küme, sıra ve bedel tablosunda toplanması.

Önceki ders bir dizinin bütün olarak kopyalanmasını ve yeniden üretilmesini ele aldı; ortak
varsayımı, kümeye erişen herkesin bütün dizini görmeye yetkili olduğuydu. Kütüphane kataloğunda
bu doğru değildir: bağış koşuluyla kapatılmış kayıtlar, ödünç geçmişi ve edinim yazışmaları aynı
kümede durur ama herkese açık değildir. Bu ders kimlik doğrulamayı değil, arama kümesinin **yetki
yüzeyini** ölçer.

#### Yetki Yüzeyi

Bir rol burada üç şeyin çarpımıdır: erişilebilen **dizinler**, uygulanan **belge süzgeci** ve
görülen **alanlar**. Düzenekte üç dizin var — açık katalog, ödünç kayıtları, edinim kaynağı — ve
katalog kayıtlarının bir bölümünde yalnız görevlilerin gördüğü bir iç **not** alanı bulunuyor.
Küme yine **süreç içi bir modeldir**: bir rol yalnızca bu üçlüdür.

**AK21 — bağış partisi 1500 kayıttır, tek konudan gelir ve erişimi bağış koşuluyla
kısıtlanmıştır.** Doğrusal etkilidir: parti büyüdükçe aşağıdaki istatistik sapması büyür.
**AK22 — ödünç ve kaynak kayıtları ilgili kitabın ad ve konu terimlerini taşır. AK23 — anahtar
yüz sorguluk akışın altmışıncısında dolar.**

```js
// kume/yetki.mjs — dizin, belge ve alan duzeyinde erisim denetimi. Uretec ve ters dizin onceki
// derslerle ayni. Yetki SUREC ICI BIR MODELDIR: kimlik dogrulama yoktur, bir rol yalnizca
// (dizinler, belge suzgeci, alanlar) uclusudur.

const ORTAK = ["kitap", "yazar", "eser", "metin", "bolum", "baski", "sayfa", "dil", "cilt", "yayin"];
const OZEL = {
  cocuk: ["masal", "resimli", "okul", "oyun", "hayvan", "cizgi"], oyku: ["kisa", "anlati", "derleme", "gunluk", "yalnizlik", "kasaba"],
  roman: ["kahraman", "kent", "kusak", "ev", "yolculuk", "mektup"], tarih: ["imparatorluk", "belge", "arsiv", "savas", "yuzyil", "vakayiname"],
  gezi: ["deniz", "yol", "harita", "sehir", "liman", "gemi"], siir: ["dize", "olcu", "imge", "ses", "sessizlik", "kafiye"],
  deneme: ["dusunce", "elestiri", "okuma", "zaman", "not", "soylesi"], bilim: ["olcum", "deney", "kuram", "veri", "gozlem", "denklem"],
};
const NADIR = ["fener", "kuyu", "ipek", "kule", "bahce", "kar", "ada", "koru", "tas", "cinar", "kirlangic", "demirci", "pusula", "kehribar"];
const KONU = Object.keys(OZEL);

function derlem({ adet = 4000, tohum = 20260731 } = {}) {
  let s = tohum % 2147483647;
  const r = () => (s = (s * 48271) % 2147483647) / 2147483647;
  const sec = (a) => a[Math.floor(r() * a.length)], belge = [];
  for (let i = 1; i <= adet; i += 1) {
    const konu = sec(KONU), oz = OZEL[konu], soz = [sec(oz), sec(NADIR)];
    if (r() < 0.5) soz.push(sec(ORTAK));
    for (let j = 0, n = 10 + Math.floor(r() * 7); j < n; j += 1)
      soz.push(r() < 0.45 ? sec(ORTAK) : r() < 0.85 ? sec(oz) : sec(NADIR));
    belge.push({ id: i, konu, yil: 1990 + Math.floor(r() * 36), metin: [soz[0], soz[1], konu, ...soz.slice(2)].join(" ") });
  }
  return belge;
}

function dizinle(belge) {
  const gonderi = new Map(), uzunluk = new Map();
  for (const d of belge) {
    const t = d.metin.split(" "), sayim = new Map();
    for (const x of t) sayim.set(x, (sayim.get(x) ?? 0) + 1);
    uzunluk.set(d.id, t.length);
    for (const [x, n] of sayim) {
      if (!gonderi.has(x)) gonderi.set(x, []);
      gonderi.get(x).push([d.id, n]);                       // gonderi girisi: kimlik + siklik
    }
  }
  const ort = [...uzunluk.values()].reduce((a, b) => a + b, 0) / (belge.length || 1);
  return { gonderi, uzunluk, N: belge.length, ort };
}

// Puanlama: terim sikligi, ters belge sikligi ve uzunluk. Puan, ISTATISTIGI HANGI DIZIN
// VERIYORSA ondan gelir; dersin ikinci olcumu tam olarak bu secimi karsilastirir.
function ara(dz, terim, k) {
  const N = dz.N, ort = dz.ort, puan = new Map();
  let taranan = 0;
  for (const t of terim) {
    const g = dz.gonderi.get(t) ?? [], df = g.length;
    const idf = Math.log(1 + (N - df + 0.5) / (df + 0.5));
    for (const [id, tf] of g) {
      taranan += 1;
      const norm = tf + 1.2 * (0.25 + 0.75 * dz.uzunluk.get(id) / ort);
      puan.set(id, (puan.get(id) ?? 0) + idf * tf * 2.2 / norm);
    }
  }
  const sirali = [...puan].sort((a, b) => b[1] - a[1] || a[0] - b[0]).slice(0, k);
  return { aday: sirali.map(([id, p]) => ({ id, p })), taranan, eslesen: puan.size };
}


const K = 10, AKIS = 100, OMUR = 60, KISITLI = 1500;
const SORGU = ["oyku", "fener", "kitap", "bagis"];   // bagis kaydi olan, adinda fener gecen oyku kitabi
const s = (x, n) => String(x).padStart(n);

// Katalog: 4000 acik kayit + bagis kosuluyla kisitli, tek konudan gelen bir parti (ayri tohum,
// kimlikler 4000 sonrasina tasinir). Odunc ve kaynak ayri dizinlerdir.
const ACIK = derlem({ adet: 4000 });
const KATALOG = [...ACIK, ...derlem({ adet: 20000, tohum: 20260801 }).filter((d) => d.konu === "oyku")
  .slice(0, KISITLI).map((d, i) => ({ ...d, id: 4001 + i, kisitli: true }))];
const ODUNC = derlem({ adet: 900, tohum: 20260901 }).map((d) => ({ ...d, id: 20000 + d.id }));
const KAYNAK = derlem({ adet: 400, tohum: 20261001 }).map((d) => ({ ...d, id: 30000 + d.id }));

// Ic not alani: edinim yazismasi, kendi sozlugu ve tohumu var.
const NOT_SOZ = ["bagis", "kosul", "sart", "edinim", "satin", "alma", "yazisma", "fatura", "vasiyet"];
let n = 20261101;
const r = () => (n = (n * 48271) % 2147483647) / 2147483647;
for (const d of KATALOG) if (r() < 0.35)
  d.not = Array.from({ length: 3 + Math.floor(r() * 4) }, () => NOT_SOZ[Math.floor(r() * 9)]).join(" ");

// Bir (belge kumesi, alan) cifti icin dizin; "not" alaninda yalnizca notu olan belge vardir.
const kur = (belge, alan) => dizinle(alan === "not"
  ? belge.filter((d) => d.not).map((d) => ({ id: d.id, metin: d.not })) : belge);

// Sorgu her (dizin, alan) ciftinde puanlanir ve puanlar toplanir; suz belge duzeyi denetimidir.
// ham suzmeden onceki eslesmedir: sayaca bu yazilirsa kisitli belgeler disari sizar.
function sor(dizinler, terim, suz = () => true) {
  const puan = new Map();
  let taranan = 0, ham = 0;
  for (const dz of dizinler) {
    const c = ara(dz, terim, Infinity);
    taranan += c.taranan; ham += c.eslesen;
    for (const a of c.aday) if (suz(a.id)) puan.set(a.id, (puan.get(a.id) ?? 0) + a.p);
  }
  const y = [...puan].sort((a, b) => b[1] - a[1] || a[0] - b[0]).map(([id]) => id);
  return { tum: y, ilk: y.slice(0, K), eslesen: y.length, taranan, ham };
}
const ortak = (a, b) => a.filter((x) => b.includes(x)).length;

console.log(`katalog ${KATALOG.length} belge (4000 acik + ${KISITLI} bagis kosuluyla kisitli), odunc ${ODUNC.length}, kaynak`);
console.log(`${KAYNAK.length}, not alani ${KATALOG.filter((d) => d.not).length} kayitta. Tohum 20260731 ve 20261101. Sorgu "oyku fener kitap bagis".\n`);
console.log("rol / anahtar         | dizin | belge | alan       | yanitlanan | taranan giris | eslesen | ilk 3 sonuc");
console.log("----------------------|-------|-------|------------|------------|---------------|---------|------------------");
for (const [ad, kume, izin, alan, yanit] of [
  ["okuyucu", [KATALOG], (d) => !d.kisitli, ["metin"], AKIS],
  ["gorevli", [KATALOG, ODUNC], () => true, ["metin", "not"], AKIS],
  ["gorevli, not kapali", [KATALOG, ODUNC], () => true, ["metin"], AKIS],
  ["kapsamsiz anahtar", [KATALOG, ODUNC, KAYNAK], () => true, ["metin", "not"], AKIS],
  ["suresi dolmus anahtar", [], () => true, [], OMUR],
]) {
  const c = sor(kume.flatMap((x) => alan.map((a) => kur(x.filter(izin), a))), SORGU);
  console.log(`${ad.padEnd(21)} | ${s(kume.length, 5)} | ${s(kume.reduce((t, x) => t + x.filter(izin).length, 0), 5)} | ` +
    `${(alan.join(", ") || "-").padEnd(10)} | ${s(`${yanit}/${AKIS}`, 10)} | ${s(c.taranan, 13)} | ${s(c.eslesen, 7)} | ${c.ilk.slice(0, 3).join(" ") || "-"}`);
}

const once = sor([kur(ACIK, "metin")], SORGU);
const sonra = sor([kur(KATALOG, "metin")], SORGU, (id) => id <= 4000);
console.log("\nbelge duzeyi suzme (okuyucu rolu, iki uygulama, ayni 4000 acik kayit):");
console.log("uygulama  | istatistigi veren kume  | eslesen | raporlanan sayac | ilk 10 ortak | yer degistiren belge");
for (const [ad, kume, c, sayac] of [["once suz", "izinli 4000 kayit", once, once.eslesen],
  ["sonra suz", "tam katalog 5500", sonra, sonra.ham]]) {
  console.log(`${ad.padEnd(9)} | ${kume.padEnd(23)} | ${s(c.eslesen, 7)} | ${s(sayac, 16)} | ` +
    `${s(ortak(c.ilk, once.ilk) + "/10", 12)} | ${s(c.tum.filter((x, i) => once.tum[i] !== x).length, 20)}`);
}
console.log(`ilk 10, once suz : ${once.ilk.join(" ")}`);
console.log(`ilk 10, sonra suz: ${sonra.ilk.join(" ")}`);

console.log(`\nkosumdan bagimsiz: kapsami yazilmamis anahtar en yetkili rolle ayni ucluyu alir ve` +
  ` suresi dolmus anahtar ${AKIS - OMUR} sorguyu yanitsiz birakir.`);
```

```
katalog 5500 belge (4000 acik + 1500 bagis kosuluyla kisitli), odunc 900, kaynak
400, not alani 1931 kayitta. Tohum 20260731 ve 20261101. Sorgu "oyku fener kitap bagis".

rol / anahtar         | dizin | belge | alan       | yanitlanan | taranan giris | eslesen | ilk 3 sonuc
----------------------|-------|-------|------------|------------|---------------|---------|------------------
okuyucu               |     1 |  4000 | metin      |    100/100 |          2998 |    2463 | 1613 3589 2806
gorevli               |     2 |  6400 | metin, not |    100/100 |          6882 |    4743 | 4551 4589 5265
gorevli, not kapali   |     2 |  6400 | metin      |    100/100 |          6130 |    4542 | 20332 20557 20765
kapsamsiz anahtar     |     3 |  6800 | metin, not |    100/100 |          7175 |    4986 | 4551 4589 5265
suresi dolmus anahtar |     0 |     0 | -          |     60/100 |             0 |       0 | -

belge duzeyi suzme (okuyucu rolu, iki uygulama, ayni 4000 acik kayit):
uygulama  | istatistigi veren kume  | eslesen | raporlanan sayac | ilk 10 ortak | yer degistiren belge
once suz  | izinli 4000 kayit       |    2463 |             2463 |        10/10 |                    0
sonra suz | tam katalog 5500        |    2463 |             3963 |         7/10 |                 1433
ilk 10, once suz : 1613 3589 2806 493 1188 1100 3214 308 476 1012
ilk 10, sonra suz: 1613 3589 2806 493 1188 3127 1100 3214 3695 3561

kosumdan bagimsiz: kapsami yazilmamis anahtar en yetkili rolle ayni ucluyu alir ve suresi dolmus anahtar 40 sorguyu yanitsiz birakir.
```

#### Üç Düzeyde Aynı Sorgu

Birinci tablo tek bir soruyu beş yetki altında koşturuyor. Okuyucu tek dizine ve 4000 açık kayda
erişip 2463 belge alıyor; görevli iki dizine ve 6400 kayda erişiyor, eşleşen 4743'e çıkıyor ve ilk
üç sonucun üçü de değişiyor. Kümeyi büyüten yalnız izin değil, izinle gelen başka dizinlerin aynı
sıraya girmesidir.

Üçüncü satır alan düzeyini yalıtıyor: aynı görevli, aynı iki dizin, aynı 6400 kayıt, tek fark not
alanının kapatılması. Eşleşen 4542'ye iniyor — 201 kayıt yalnızca not alanından eşleşiyordu — ve
ilk üç, katalogdan ödünç kayıtlarına geçiyor. Bir alanı kapatmak listeden belge silmekle kalmaz,
kalanların puan bileşenlerinden birini de siler.

Dördüncü satır varsayılan açık kurulumdur: kapsamı yazılmamış anahtar üç dizine ve 6800 kayda
erişiyor, hiçbir role verilmemiş bir genişlik alıyor ve taranan girişi 7175'e çıkarıyor. Beşincisi
süreyi gösteriyor: süresi dolmuş anahtar yüz sorgunun kırkını yanıtsız bırakıyor.

#### Süzmenin Puanlamaya Sızması

İkinci tablo dersin asıl ölçümüdür. Okuyucu rolü iki biçimde uygulanabilir: kısıtlı 1500 kayıt ya
baştan dizinin dışında tutulur (**önce süz**), ya da tam katalog üzerinden puanlanıp listeden
atılır (**sonra süz**). İkisi de aynı 2463 belgeyi döndürüyor — küme birebir aynı.

Sıra aynı değil. İlk on sonucun yalnız yedisi ortak: altıncı sırada 3127, dokuzuncu ve onuncuda
3695 ile 3561 beliriyor, buna karşılık 308, 476 ve 1012 listeden düşüyor; derin sırada 2463
belgenin 1433'ü yer değiştirmiş. Sebep tek bir sayıdır: ters belge sıklığı. Sonra süzen kurulumda
`oyku` terimi 5500 belgede sayılıyor ve kısıtlı parti tek konudan geldiği için o terimin ayırt
ediciliği düşüyor. Okuyucunun göremeyeceği belgeler, görebildiği belgelerin sırasını belirliyor.

Aynı kurulum bilgi de sızdırıyor: raporlanan sayaç önce süzende 2463, sonra süzende 3963 ve
aradaki 1500 kısıtlı partinin tam boyudur — sayaç okuyucuya göremediği kayıtların sayısını
söylüyor. Süzmenin puanlamadan önce mi sonra mı uygulandığı bir başarım ayrıntısı değildir: hem
sırayı hem sızıntıyı belirler.

#### Özet

- Yetki üç ayrı kesme yapar: okuyucu 1 dizin ve 4000 kayıtla 2463, görevli 2 dizin ve 6400 kayıtla
  4743 belge aldı; ilk üç sonuç tümüyle değişti. Not alanı kapatılınca eşleşen 4542'ye indi.
- Kapsamı yazılmamış anahtar üç dizine ve 6800 kayda erişti, taranan girişi 2998'den 7175'e
  çıkardı; süresi dolmuş anahtar yüz sorgunun kırkını yanıtsız bıraktı.
- Belge düzeyi süzme kümeyi değiştirmeden sırayı değiştirdi: aynı 2463 belgede ilk onun yalnız
  yedisi ortaktı, 1433 belge yer değiştirdi ve sayaç kısıtlı 1500 kaydı sızdırdı.

#### Kurs Kapanışı

| Ders | Değişen küme | Değişen sıra | Bedel |
|---|---|---|---|
| Motor ve Veritabanı Farkı | LIKE 68, dizin 12 | plan düzeni, kimlik düzeni | 124.863 → 1.076 bayt |
| Ters Dizin | konum 23 → 10 | gönderi listesi kimlik sırasında | kesişim 19.622 → 470 |
| Çözümleyici Zinciri | 80 / 122 / 463 | ilk beşin beşi de değişti | 187.780 → 146.080 bayt |
| Eşlemeler ve Alan Tipleri | `çocuk` 93'e karşı 0 | ilk onun onu oynadı | `konu` 8.203 → 11.686 bayt |
| Dinamik ve Açık Eşleme | çatışma 111 belge attı | düşen belge sırada görünmez | üstveri 1.595 → 3.022 bayt |
| Belge Yaşam Döngüsü | `#10` kümeden kümeye geçti | 7. sıradan 509. sıraya | 119.722 → 136.322 bayt |
| Sorgu Dili Yapısı | 4.211 / 132 / 71 | 2.023. basamaktan ilk ona | 21.303 → 7.576 giriş |
| Terim ve İfade Sorguları | 757 → 144 | bitişik eşleşme 1 → 10 | 216.480 → 330.578 bayt |
| Bağlam Sorguları | olumsuz 512, isteğe bağlı 0 | ilk onun dokuzu değişti | giriş 2.678 → 4.638 |
| Süzme ve Sorgu Ayrımı | iki bağlamda da 1.138 | ortalama 173 basamak | puan hesabı 2.276 → 1.138 |
| İlgililik Puanlaması | kesinlik 0,353 → 0,640 | bileşen yokluğu 499 basamak | anma için 567 sonuç |
| Puan Ayarlama | ağırlık 325, alan seçimi 199 | ilk onun onu değişti | 698 → 265 giriş |
| Toplama Sorguları | 208 / 168 / 1 kova | sıra kova sırasıdır | 1.045 kova, 25.080 bayt |
| Vurgulama ve Sayfalama | 60 eşleşmeden 12'si dışarıda | sıra değişmez | 20. sayfa 800 aday |
| Anlamsal ve Vektör Arama | 0,5 eşiğinde 383 | ilk onun altısı eşanlamlıdan | 153.600 bayt |
| Düğüm Rolleri | geciken durum 800 kaydı reddetti | ilk beş tamamen değişti | eşgüdüm %0,8 → %11,4 |
| Parçalar ve Kopyalar | düğüm kaybında 515 → 379 | ilk on ortak 10,00 → 4,75 | aday 10 → 160 |
| Bölünmüş Beyin | azınlıkta 210 kayıt kayboldu | iki yarı iki liste verdi | 600 → 1590 ileti |
| Toplu Dizinleme | yenileme kapalıyken 600 eksik | ilk on 6/10 | 4000 bölüt, 24.000 arama |
| Bölüt Birleştirme | değişmedi, 18.775 | 50 basamağın 42'si farklı | 3.193.775 → 2.552.007 bayt |
| Dizin Yaşam Döngüsü | 10.262 → 12.274 | ilk onun ikisi silinmiş günden | giriş 19.476 → 1.993 |
| Anlık Görüntü ve Geri Yükleme | 7.879 → 17.387 | 49 basamak değişti | 10.790.530 → 3.180.562 bayt |
| Erişim Denetimi | okuyucu 2.463, görevli 4.743 | ilk on ortak 7/10 | giriş 2.998 → 7.175 |

Tablonun üç sütunu kursun kuralıdır: **bir arama kararı ancak küme ve sıra üzerindeki etkisiyle
savunulur.** "Daha alakalı sonuç" bir karar değildir; bir çözümleyici, bir ağırlık, bir parça
sayısı ya da bir yetki kuralı ancak hangi belgelerin hangi sırayla döndüğünü ve neye mal olduğunu
söyleyerek savunulur.

Bu kurs M17'nin yedincisi ve sonuncusudur. İlişkisel Kuram verinin nasıl modelleneceğini, SQL
Temelleri o modelin nasıl sorgulanacağını, İleri SQL aynı sorgunun neden yavaşladığını, Veritabanı
Yönetimi çalışan bir dizgenin nasıl işletileceğini, İlişkisel Olmayan Veri Modelleri başka bir
yolun neyi kazandırıp neyi bıraktığını, Bellek İçi Depolar bellek bütçesinin nasıl harcandığını
kurdu. Bu kurs son boşluğu kapattı: anahtarı bilinmeyen veriye metnin içinden gitmeyi.

Buradan sonrası ölçüm alışkanlığıdır ve başlangıcı kendi verinin erişim örüntüsüdür: sorular
anahtarla mı geliyor yoksa metnin içinden mi, kaç belge dönüyor, hangi sırayla ve o sırayı kim
belirliyor. Yirmi üç dersin yaptığı tek şey buydu.

