İçeriğe geç
academia.sh

Ders 04 / 17

Yapısal Ayrıştırma

Aynı 1.877 satırı yayımlayan bir sayfa dört yapı sürümünde iki seçici setiyle okunuyor ve kırılan seçici sayısının hasarı ölçmediği çıkıyor: araya tek bir sarmalayıcı katman girince konumsal setin yol seçicisi kırılır ve 1.877 satırın tamamı düşer, ama kırılma gürültülüdür. Araya bir sütun eklendiğinde hiçbir seçici kırılmaz, 1.877 kayıt çıkar, hiçbiri düşmez ve hepsi yanlış alandan okunmuştur; toplam yalnızca 60,1 m³ sapar, yüzde 0,16, ve 1.877 kaydın bölge alanı artık bir bölge adı bile değildir. Sunum katmanı yenilendiğinde bu kez anlamsal setin iki seçicisi kırılır ve çıkan kayıt sıfıra iner. Sayfanın kendi bildirdiği toplamla hesaplanan toplamı karşılaştırmak, sessiz kaymayı yakalayan tek denetimdir.

İçindekiler

Bir önceki dersin kaynağı verisini alan için tasarlanmış bir arayüzden verdi: alan adları yanıtın içindeydi, sayfa sınırı ilan edilmişti. Verinin bir kısmı ise böyle bir arayüzün arkasında değil, insan gözü için düzenlenmiş bir sayfanın yapısının içinde durur. Orada alan adı yoktur; kaydın hangi parçasının nerede olduğunu yalnızca konum ve görünüm işaretleri söyler.

Bu dersin ölçüsü şudur: sayfanın yapısı değiştiğinde kaç seçici kırılır, kaç satır düşer ve kaç satır yanlış alandan okunmuş olarak hayatta kalır. Ölçünün asıl sorusu sayının kendisi değil, kırılmanın duyulup duyulmadığıdır. Teknik soru burada da tek başına yeterli değildir: bir sayfadan kazıma yoluyla veri toplamak, kaynağın kullanım koşulları ve açık bir erişim izni olmadan teknik olarak mümkün olsa bile hukuki ve mesleki bir sorundur; burada izin verilmiş bir toplama varsayılır ve kişisel veri taşıyan alanlar sayfaya hiç konmamıştır.

  • VT15. Sayfa kurgudur ve node ile modellenir: belge ağacı bellekte kurulur, ağ çağrısı yapılmaz. Koşan kod bir toplama aracı değildir; yalnızca ayrıştırır, doğrular ve sayar. Modeldir.
  • VT16. Sayfa önceki iki dersin kümesini yayımlar — 1.877 okuma satırı, 38.690,7 m³.
  • VT17. Dört yapı sürümü aynı veriyi taşır, düzeni değişir: Y0 taban, Y1 tabloyu bir sarmalayıcı katmana alır, Y2 araya bir sütun ekler, Y3 sunum katmanını yeniler.
  • VT18. Bir seçici iki durumda kırık sayılır: hiçbir düğümle eşleşmiyorsa ya da üstbilgi metninden çözülmesi gereken sütun konumu bulunamıyorsa.
  • VT19. İki okumanın aynı sayılması için eşik 0,05 m³’tür, çünkü sayfa değerleri bir ondalıkla yayımlıyor ve altındaki bir fark yalnızca yuvarlamadan gelebilir.

Sayfanın Yapısı

// sayfa.mjs — kurgu bir kamu yayin sayfasinin belge agaci ve kucuk bir secici
// motoru. MODELDIR: ag cagrisi yoktur, sayfa bellekte kurulur ve ayni 1877
// okuma satirini yayimlar. Dort yapi surumu ayni veriyi tasir, duzeni degisir.
export const TOHUM = 4711;
const uretec = (t) => { let s = t >>> 0;
  return () => { s = (Math.imul(s, 1103515245) + 12345) >>> 0; return s / 4294967296; }; };
const r = uretec(TOHUM);
export const BOLGE = ["kuzey", "doğu", "merkez", "güney", "batı"];
export const KAYIT = [];
for (let i = 0; i < 1877; i++) {           // once 1400 abonenin ilk donemi, sonra 477 ikinci
  const n = i < 1400 ? i : i - 1400, desi = 50 + Math.floor(r() * 321);
  KAYIT.push({ abone_no: 10000 + n, donem: i < 1400 ? "2024-01" : "2024-02", desi,
    onceki: 50 + ((desi * 37 + 11) % 321), bolge: BOLGE[n % 5] });
}
export const kimlik = (k) => `${k.abone_no}-${k.donem}`;
export const m3 = (d) => d / 10;

// Dugum: { etiket, oz, cocuk }; cocuk ya dugum dizisi ya da metindir.
const d = (etiket, oz, cocuk) => ({ etiket, oz, cocuk });
export const metin = (n) => (typeof n.cocuk === "string" ? n.cocuk
  : n.cocuk.map(metin).join(""));
const cocuklar = (n) => (Array.isArray(n.cocuk) ? n.cocuk : []);
const soy = (n) => cocuklar(n).flatMap((c) => [c, ...soy(c)]);

// Secici dili: bosluk = herhangi bir alt duzey, ">" = dogrudan cocuk.
// Adim bicimi: etiket, .sinif, [ad=deger] ya da bunlarin birlesimi.
const parca = (p) => {
  const m = /^([\w-]*)(?:\.([\w-]+))?(?:\[([\w-]+)=([\w-]+)\])?$/.exec(p);
  return { etiket: m[1], sinif: m[2], ad: m[3], deger: m[4] };
};
const uyar = (n, t) => (!t.etiket || n.etiket === t.etiket)
  && (!t.sinif || (n.oz.sinif ?? "") === t.sinif)
  && (!t.ad || n.oz[t.ad] === t.deger);
export function bul(kok, secici) {
  let dugum = [kok], dogrudan = false;
  for (const p of secici.trim().split(/\s+/)) {
    if (p === ">") { dogrudan = true; continue; }
    const t = parca(p);
    dugum = dugum.flatMap((n) => (dogrudan ? cocuklar(n) : soy(n)).filter((x) => uyar(x, t)));
    dogrudan = false;
  }
  return dugum;
}

// Y0 taban; Y1 tabloyu bir sarmalayici katmana alir; Y2 araya "onceki_m3"
// sutunu ekler; Y3 sunumu yeniler: sinif adlari uretilmis adlarla degisir ve
// okuma sutununun basligi yeniden adlandirilir.
export function sayfa(surum) {
  const ek = surum === "Y2", yeni = surum === "Y3";
  const sn = (a) => (yeni ? { ozet: "b-7f2", toplam: "b-91c", satir: "b-4ad" }[a] : a);
  const baslik = ek ? ["abone_no", "donem", "onceki_m3", "okuma_m3", "bolge"]
    : ["abone_no", "donem", yeni ? "tuketim_m3" : "okuma_m3", "bolge"];
  const hucre = (k) => (ek ? [k.abone_no, k.donem, m3(k.onceki), m3(k.desi), k.bolge]
    : [k.abone_no, k.donem, m3(k.desi), k.bolge])
    .map((v) => d("hucre", {}, String(v)));
  const tablo = d("tablo", {}, [
    d("ustbilgi", {}, [d("satir", {}, baslik.map((b) => d("hucre", {}, b)))]),
    d("govde", {}, KAYIT.map((k) => d("satir",
      { sinif: sn("satir"), "veri-tur": "okuma" }, hucre(k)))),
  ]);
  const toplam = KAYIT.reduce((t, k) => t + m3(k.desi), 0).toFixed(1);
  return d("sayfa", {}, [
    d("ozet", { sinif: sn("ozet") }, [d("p", { sinif: sn("toplam") },
      `Toplam: ${toplam} m3`)]),
    d("bolum", {}, [surum === "Y1" ? d("sarmalayici", { sinif: "kutu" }, [tablo]) : tablo]),
  ]);
}

if (import.meta.url === `file://${process.argv[1]}`) {
  console.log(`tohum ${TOHUM}; sayfa ${KAYIT.length} okuma satiri yayimliyor, toplam ` +
    `${KAYIT.reduce((t, k) => t + m3(k.desi), 0).toFixed(1)} m3`);
  const y = (h) => h.map((v, j) => (j ? String(v).padStart([0, 8, 8, 44, 32][j])
    : String(v).padEnd(6))).join("");
  console.log(y(["surum", "dugum", "satir", "sutun basliklari", "ilk veri satiri"]));
  for (const s of ["Y0", "Y1", "Y2", "Y3"]) {
    const k = sayfa(s), satir = bul(k, "govde > satir");
    console.log(y([s, soy(k).length, satir.length,
      bul(k, "ustbilgi satir hucre").map(metin).join(","),
      bul(satir[0], "hucre").map(metin).join(",")]));
  }
}
tohum 4711; sayfa 1877 okuma satiri yayimliyor, toplam 38690.7 m3
surum    dugum   satir                            sutun basliklari                 ilk veri satiri
Y0        9396    1877               abone_no,donem,okuma_m3,bolge          10000,2024-01,18,kuzey
Y1        9397    1877               abone_no,donem,okuma_m3,bolge          10000,2024-01,18,kuzey
Y2       11274    1877     abone_no,donem,onceki_m3,okuma_m3,bolge     10000,2024-01,30.1,18,kuzey
Y3        9396    1877             abone_no,donem,tuketim_m3,bolge          10000,2024-01,18,kuzey

Dört sürümün ortak yanı sağdan ikinci sütunda görünmüyor: veri satırı sayısı dördünde de 1.877. Yayımlanan kayıt kümesi hiç değişmedi. Değişen şey düğüm sayısı ve sütun düzenidir. Y1 sayfaya tek bir düğüm ekler (9.396’dan 9.397’ye), Y2 her satıra bir hücre ekleyerek düğüm sayısını 11.274’e çıkarır, Y3 hiçbir düğüm eklemez ve yalnızca metinleri değiştirir.

Bu üç değişikliğin hiçbiri veriyi bozmaz. Y1 görünümü kutu içine almak, Y2 bir sütun daha yayımlamak, Y3 sunumu yenilemek için yapılmış olabilir; üçü de sayfayı yayımlayan taraf açısından sıradan bakım işleridir. Sayfanın yapısı bir sözleşme değildir ve yayımlayan tarafın kimseye haber verme yükümlülüğü yoktur. Ayrıştırıcının bağlandığı şey işte bu sözleşmesiz yüzeydir.

Son sütun Y2’nin neden özel olduğunu gösteriyor. İlk satırın hücreleri artık 10000,2024-01,30.1,18,kuzey: üçüncü hücrede 18 değil 30,1 duruyor ve 30,1 de bir okuma değeri gibi görünüyor. Yeni sütun sayısal ve aynı aralıkta olduğu için, üçüncü hücreyi okuma sanan bir ayrıştırıcı geçerli bir sayı bulur.

Yapı Değişince Ne Kırılır

// ayristir.mjs — ayni sayfayi iki secici seti okur. A1 konuma dayanir: yol ve
// sabit sutun sirasi. A2 anlama dayanir: satiri veri isareti, sutunu ustbilgi
// metni bulur. Olculen sey yapi degisince kac secici kirildigi ve kac satirin
// dustugu ya da yanlis alandan okunmus olarak hayatta kaldigidir.
import { sayfa, bul, metin, KAYIT, kimlik, m3, BOLGE } from "./sayfa.mjs";

const A1 = { ad: "A1 konumsal", satir: "bolum > tablo > govde > satir", toplam: "ozet > p",
  konum: () => ({ abone: 0, donem: 1, okuma: 2, bolge: 3 }) };
const A2 = { ad: "A2 anlamsal", satir: "satir[veri-tur=okuma]", toplam: ".ozet .toplam",
  konum: (kok) => {
    const b = bul(kok, "ustbilgi satir hucre").map(metin);
    return { abone: b.indexOf("abone_no"), donem: b.indexOf("donem"),
      okuma: b.indexOf("okuma_m3"), bolge: b.indexOf("bolge") };
  } };

// Esik 0.05 m3: okumalar bir ondalikla yayimlaniyor, altindaki bir fark
// yalnizca yuvarlamadan gelebilir.
const ESIK = 0.05;
const DOGRU = new Map(KAYIT.map((k) => [kimlik(k), m3(k.desi)]));
const HEDEF = KAYIT.reduce((t, k) => t + m3(k.desi), 0);

function coz(kok, a) {
  const kirik = [], satirlar = bul(kok, a.satir), basliklar = bul(kok, a.toplam);
  if (satirlar.length === 0) kirik.push(a.satir);
  if (basliklar.length === 0) kirik.push(a.toplam);
  const k = a.konum(kok);
  for (const [ad, i] of Object.entries(k)) if (i < 0) kirik.push(`sutun:${ad}`);
  const cikan = [];
  for (const s of satirlar) {
    const h = bul(s, "hucre").map(metin), okuma = Number(h[k.okuma]);
    if (!Number.isFinite(okuma)) continue;         // sessizce dusen satir
    cikan.push({ ad: `${h[k.abone]}-${h[k.donem]}`, okuma, bolge: h[k.bolge] });
  }
  const bildirilen = basliklar.length ? Number(/[\d.]+/.exec(metin(basliklar[0]))[0]) : null;
  return { kirik, cikan, bildirilen };
}

const y = (h) => h.map((v, j) => (j > 1 ? String(v).padStart([0, 0, 9, 7, 7, 7, 8, 11, 12][j])
  : String(v).padEnd(j ? 14 : 6))).join("");
console.log(y(["yapi", "ayristirici", "kirilan", "giren", "cikan", "dusen", "yanlis",
  "toplam m3", "bildirilen"]));
const kayit = {};
for (const surum of ["Y0", "Y1", "Y2", "Y3"]) {
  const kok = sayfa(surum);
  for (const a of [A1, A2]) {
    const r = kayit[surum + a.ad] = coz(kok, a);
    const t = r.cikan.reduce((s, x) => s + x.okuma, 0);
    const yanlis = r.cikan.filter((x) => !(Math.abs((DOGRU.get(x.ad) ?? -1) - x.okuma) < ESIK));
    console.log(y([surum, a.ad, r.kirik.length, KAYIT.length, r.cikan.length,
      KAYIT.length - r.cikan.length, yanlis.length, t.toFixed(1),
      r.bildirilen === null ? "-" : r.bildirilen.toFixed(1)]));
  }
}
const y1a = kayit["Y1A1 konumsal"], y2a = kayit["Y2A1 konumsal"], y3b = kayit["Y3A2 anlamsal"];
console.log(`\nY1'de tek bir katman eklendi ve A1'in yol secicisi ${y1a.kirik[0]} hicbir satir ` +
  `bulamadi: ${KAYIT.length} satirin tamami dustu, kirilma gurultuludur`);
const t2 = y2a.cikan.reduce((s, x) => s + x.okuma, 0);
console.log(`Y2'de A1'in kirilan secicisi yok, ${y2a.cikan.length} kayit cikti ve hicbiri ` +
  `dusmedi; ama sutunlar bir kaydigi icin toplam ${(t2 - HEDEF).toFixed(1)} m3 sapti ve ` +
  `${y2a.cikan.filter((x) => !BOLGE.includes(x.bolge)).length} kaydin bolge alani bir bolge ` +
  `adi degil`);
console.log(`Y2'de sayfanin kendi bildirdigi ${y2a.bildirilen.toFixed(1)} m3 ile A1'in ` +
  `hesapladigi ${t2.toFixed(1)} m3 arasindaki fark ${Math.abs(t2 - y2a.bildirilen).toFixed(1)} ` +
  `m3, esik ${ESIK} m3'un ${Math.round(Math.abs(t2 - y2a.bildirilen) / ESIK)} kati`);
console.log(`Y3'te A2'nin ${y3b.kirik.length} secicisi kirildi (${y3b.kirik.join(", ")}) ve ` +
  `cikan kayit ${y3b.cikan.length}; ayni surumde A1 hicbir sey kaybetmedi`);
yapi  ayristirici     kirilan  giren  cikan  dusen  yanlis  toplam m3  bildirilen
Y0    A1 konumsal           0   1877   1877      0       0    38690.7     38690.7
Y0    A2 anlamsal           0   1877   1877      0       0    38690.7     38690.7
Y1    A1 konumsal           1   1877      0   1877       0        0.0     38690.7
Y1    A2 anlamsal           0   1877   1877      0       0    38690.7     38690.7
Y2    A1 konumsal           0   1877   1877      0    1877    38630.6     38690.7
Y2    A2 anlamsal           0   1877   1877      0       0    38690.7     38690.7
Y3    A1 konumsal           0   1877   1877      0       0    38690.7     38690.7
Y3    A2 anlamsal           2   1877      0   1877       0        0.0           -

Y1'de tek bir katman eklendi ve A1'in yol secicisi bolum > tablo > govde > satir hicbir satir bulamadi: 1877 satirin tamami dustu, kirilma gurultuludur
Y2'de A1'in kirilan secicisi yok, 1877 kayit cikti ve hicbiri dusmedi; ama sutunlar bir kaydigi icin toplam -60.1 m3 sapti ve 1877 kaydin bolge alani bir bolge adi degil
Y2'de sayfanin kendi bildirdigi 38690.7 m3 ile A1'in hesapladigi 38630.6 m3 arasindaki fark 60.1 m3, esik 0.05 m3'un 1202 kati
Y3'te A2'nin 2 secicisi kirildi (.ozet .toplam, sutun:okuma) ve cikan kayit 0; ayni surumde A1 hicbir sey kaybetmedi

Taban yapıda iki set de aynı kümeyi çıkarıyor: 1.877 kayıt, sıfır düşen, sıfır yanlış, 38.690,7 m³. Aynı sonucu veren iki ayrıştırıcı aynı ayrıştırıcı değildir ve fark yalnızca yapı değişince ölçülebilir.

Y1 satırında bir katman, bir seçici ve 1.877 satır var. Araya giren tek sarmalayici düğümü A1’in dört adımlık yol seçicisini geçersiz kıldı, çünkü bolum > tablo doğrudan çocuk ister ve tablo artık bolumün torunu. Kırılan seçici sayısı 1, düşen satır 1.877. Bu kayıp gürültülüdür: çıkan kayıt sıfırdır ve bunu fark etmemek için hiçbir denetim yapmamak gerekir. A2’nin satır seçicisi satir[veri-tur=okuma] yolu değil işareti aradığı için aynı sayfada 1.877 satır bulur.

Y2 dersin asıl bulgusudur. A1’in hiçbir seçicisi kırılmadı: yol yerinde, dört indis de sınırlar içinde, 1.877 kayıt çıktı ve hiçbiri düşmedi. Ama sütunlar bir kaydığı için okuma alanına önceki dönem okuması geldi ve 1.877 kaydın tamamı yanlış değer taşıyor. Toplam 38.630,6 m³, yani gerçekten yalnızca 60,1 m³, yüzde 0,16 uzakta. Bir eşik, bir büyüklük kontrolü, bir “makul mü” bakışı bu sayıyı geçirir. Bölge alanı ise artık okuma değerini taşıyor: 1.877 kaydın bölge alanı bir bölge adı bile değil. Kırılan seçici sayısı hasarı ölçmez — Y1’de o sayı 1 iken tek bir yanlış değer üretilmedi, Y2’de 0 iken tek bir doğru değer üretilmedi.

Y3 aynı sorunun yönünü ters çeviriyor. Sunum yenilenince A2’nin sınıfa dayalı toplam seçicisi ve üstbilgi metnine dayalı okuma sütunu birlikte kırıldı: 2 kırık seçici, sıfır çıkan kayıt. Konuma dayanan A1 ise aynı sürümde hiçbir şey kaybetmedi, çünkü sınıf adı da başlık metni de onun umurunda değil. Anlamsal seçiciler daha dayanıklı değildir; başka şeylere bağlıdır ve hangi setin dayanacağı, yapının hangi yönde değişeceğine bakar. Ölçülmeden bilinemez.

Son satırlar tek gerçek savunmayı gösteriyor. Sayfa kendi toplamını yayımlıyor ve Y2’de bu sayı 38.690,7 m³ iken ayrıştırılan küme 38.630,6 m³ veriyor; fark 60,1 m³, eşiğin 1.202 katı. Hiçbir seçicinin kırılmadığı, hiçbir satırın düşmediği, toplamın makul göründüğü durumda hatayı yakalayan tek şey kaynağın kendi bildirdiği sayıyla karşılaştırma oldu. Bir ayrıştırıcıyı doğrulamak, seçicilerin eşleşip eşleşmediğine bakmak değil, çıkan kümeyi kaynağın kendi beyanına karşı saymaktır.

Özet

  • Sayfanın yapısı bir sözleşme değildir; aynı 1.877 satır dört düzende yayımlanabilir ve düzen değişimi yayımlayan taraf için sıradan bir bakım işidir.
  • Araya tek bir katman girmesi konuma dayalı yol seçicisini kırar: 1 kırık seçici, 1.877 düşen satır, sıfır çıkan kayıt. Bu kırılma gürültülüdür ve fark edilmemesi zordur.
  • Araya bir sütun girmesi hiçbir seçiciyi kırmaz: 1.877 kayıt çıkar, sıfır düşer ve 1.877’sinin değeri yanlıştır; toplam yalnızca 60,1 m³, yüzde 0,16 sapar ve bölge alanı bölge adı olmaktan çıkar.
  • Sunum katmanının yenilenmesi bu kez anlamsal seti kırar (2 seçici, sıfır kayıt) ve konumsal set aynı sürümden zarar görmez; dayanıklılık setin türünden değil, değişimin yönünden çıkar.
  • Kırılan seçici sayısı hasarı ölçmez; ayrıştırılan kümeyi kaynağın kendi bildirdiği toplamla karşılaştırmak, sessiz kaymayı yakalayan tek denetimdir.

Sonraki Adım

Dört derste veri dört yoldan geldi ve her yolda kaç satırın kaybolduğu sayıldı. Bu derslerin ortak varsayımı, kümenin tamamının toplanabilir olmasıydı: dosya okunabiliyordu, sorgu koşabiliyordu, arayüz sonuna kadar sayfalanabiliyordu, sayfa baştan sona ayrıştırılabiliyordu. Kütle büyüdüğünde bu varsayım düşer ve yerine bir karar gelir — bütün kayıtlar yerine bir alt küme alınır. Sonraki ders dört örnekleme kararının aynı soruya verdiği dört cevabı ölçer ve asıl soruyu sorar: alınan alt küme kütleyi ne kadar temsil ediyor ve dışarıda kalan kayıtlar kimler.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat