İçeriğe geç
academia.sh

Ders 07 / 11

Yinelenen Kayıtların Kaldırılması

Aynı 2.037 satırlık sayfaya beş tekilleştirme ölçütü uygulanır ve beş farklı sayı kalır: 2.005, 1.400, 1.877, 1.965 ve yine 1.877. Ölçüt yalnız abone numarası olduğunda 637 satır silinir ve toplam 42.645,6 m³'ten 29.828,2 m³'e iner, çünkü silinen satırların çoğu yinelenme değil ikinci dönemdir; metin kırpma kararı tek başına 88 satır oynatır. Aynı ölçütle ilk giriş yerine son giriş tutulduğunda satır sayısı 1.877'de sabit kalır ama toplam 200,1 m³ ayrışır. İşlem bitince silinmiş sayfadan dört kararın sıfırı, not düşülmüş sayfadan ikisi iki adımda, izli sayfadan dördü bir adımda geri okunur.

İçindekiler

Bir önceki konu tek tek hücreleri doğru hesaplamayı kurdu: başvuru türü, koşullu toplama, sınır karşılaştırması, metin kırpma, gün sayma ve arama. Hepsinin ortak sınırı şuydu — her formül bir hücreye bakıyordu ve tablonun bütününe bakan hiçbir sayı üretilmedi. Verinin kendisinin temiz olup olmadığı da hiç sorulmadı.

Bu ders tablonun bütününe bakan ilk işlemi yapar: yinelenen kayıtları kaldırmak. İşlem tek bir komut gibi görünür, ama içinde en az üç karar vardır. Hangi sütunlar aynı olursa iki satır aynı sayılır. Metin karşılaştırılırken görünmeyen boşluk ve harf farkı yok sayılır mı. Aynı sayılan iki satırdan hangisi kalır. Üçü de rapor edilen sayıyı oynatır ve üçü de işlem bittikten sonra tablodan okunamaz. Dersin iki sayısı budur: kalan satır sayısı ve kaç kararın geri okunabildiği.

  • TO1. Elektronik tablo node ile modellenir: hücre bir eşlemede durur, formül kendi metnini taşıyan küçük bir değerlendiricidir, aralık bir dilim işlevidir. Veri kümesi de kurgudur — önceki kursun 1.400 abonesi, saha ziyaret kayıtlarına açılmış halidir.
  • TO2. Yinelenme iki ayrı nedenden doğar: aynı abone ve dönem çiftinin ikinci kez girilmesi, ve bir abonenin iki dönemde okunması. İkincisi yinelenme değildir; ölçüt bu ikisini ayırt etmezse silme işlemi veri siler.
  • TO3. Sayaç numarası saha ziyaretinde elle girilir, bu yüzden baştaki boşluğu ve küçük harfi taşıyabilir. Metin karşılaştırması kırpma ve harf birliği kararına bağlıdır.
  • TO4. Kararın kurtarılabilirliği sayfa taranarak ölçülür: karar bir hücre formülünde yazılıysa bir adımda okunur, ayrı bir not hücresinde duruyorsa iki adım ister — önce not bulunur, sonra hangi sütuna ait olduğu eşleştirilir — hiçbir hücrede yoksa okunamaz.

Ölçüt Değişince Kaç Satır Kalıyor

// sayfa.mjs — elektronik tablo MODELIDIR: hucreler bir eslemede durur, formul
// kucuk bir degerlendiricidir, aralik bir dilim islevidir. Hicbir uygulama
// taklit edilmez; isler gorev adiyla anilir. Veri kumesi kurgudur.
export const TOHUM = 20260218, ABONE = 1400;
const karistir = (x) => { x = Math.imul(x ^ (x >>> 16), 2246822507);
  x = Math.imul(x ^ (x >>> 13), 3266489909); return (x ^ (x >>> 16)) >>> 0; };
const uretec = (t) => { let s = karistir(t);
  return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };
const ayrik = (u, w) => { let t = 0;
  for (let i = 0; i < w.length; i++) { t += w[i]; if (u < t) return i; } return w.length - 1; };
export const BOLGE = [["kuzey", "KZ", 0.28, 21], ["guney", "GN", 0.22, 17],
  ["dogu", "DG", 0.18, 26], ["bati", "BT", 0.14, 14], ["merkez", "MR", 0.18, 23]]
  .map(([ad, kod, pay, taban]) => ({ ad, kod, pay, taban }));

// Elektronik tablo modeli. Hucre adi harf+sayidir; deger ya duz bir degerdir
// ya da bir formuldur. Formul kendi metnini tasir: karar orada yazilidir.
const HARF = (j) => String.fromCharCode(65 + j);
export const formul = (yazi, f) => ({ tur: "formul", yazi, f });
export class Tablo {
  constructor(baslik, satirlar) {
    this.baslik = baslik; this.h = new Map(); this.n = satirlar.length;
    baslik.forEach((b, j) => this.h.set(`${HARF(j)}1`, b));
    satirlar.forEach((s, i) => baslik.forEach((b, j) =>
      this.h.set(`${HARF(j)}${i + 2}`, s[b])));
  }
  oku(ad) { const v = this.h.get(ad); return v && v.tur === "formul" ? v.f(this) : v; }
  yaz(ad, v) { this.h.set(ad, v); return this; }
  harf(b) { return HARF(this.baslik.indexOf(b)); }
  aralik(a) { const [s, b] = a.split(":");
    const j = s.charCodeAt(0) - 65, i0 = +s.slice(1), i1 = +b.slice(1);
    return Array.from({ length: i1 - i0 + 1 }, (_, k) => this.oku(`${HARF(j)}${i0 + k}`)); }
  sutun(b) { return this.aralik(`${this.harf(b)}2:${this.harf(b)}${this.n + 1}`); }
  satirlar() { return Array.from({ length: this.n }, (_, i) =>
    Object.fromEntries(this.baslik.map((b) => [b, this.oku(`${this.harf(b)}${i + 2}`)]))); }
}

// Saha ziyaret kayitlari: abone basina bir ya da iki donem, bir bolumu elle
// giris yuzunden iki kez yazilmis.
const SATIR = [];
for (let i = 0; i < ABONE; i++) {
  const r = uretec(TOHUM + i);
  const b = BOLGE[ayrik(r(), BOLGE.map((x) => x.pay))];
  const taban = Math.max(1.2, b.taban * Math.exp((r() + r() + r() - 1.5) * 0.62));
  const sayac = `${b.kod}-${String(1000 + i).padStart(4, "0")}`;
  const donemler = r() < 0.34 ? ["2026-01", "2026-02"] : ["2026-01"];
  for (const d of donemler) {
    const t = Math.round(taban * (d === "2026-02" ? 0.94 : 1) * 100) / 100;
    SATIR.push({ abone_no: 10001 + i, sayac_no: sayac, bolge: b.ad, donem: d,
      tuketim_m3: t });
    // Cift giris: ayni abone ve donem ikinci kez yazilir. Sayac numarasi bu kez
    // bosluklu ya da kucuk harfli girilmis olabilir; okuma duzeltilmis olabilir.
    if (r() < 0.09) {
      const bicim = ayrik(r(), [0.34, 0.28, 0.38]);
      const duzeltme = r() < 0.45;
      SATIR.push({ abone_no: 10001 + i,
        sayac_no: bicim === 0 ? ` ${sayac}` : bicim === 1 ? sayac.toLowerCase() : sayac,
        bolge: b.ad, donem: d,
        tuketim_m3: duzeltme ? Math.round(t * 1.12 * 100) / 100 : t });
    }
  }
}
export const SAYFA = new Tablo(
  ["abone_no", "sayac_no", "bolge", "donem", "tuketim_m3"], SATIR);

// Tekillestirme olcutu: satirdan bir anahtar metni uretir. Olcut degisince
// ayni sayfadan farkli sayida satir kalir.
export const OLCUT = [
  ["O1 tam satir", (s) => Object.values(s).join("|")],
  ["O2 abone_no", (s) => `${s.abone_no}`],
  ["O3 abone_no + donem", (s) => `${s.abone_no}|${s.donem}`],
  ["O4 sayac_no + donem", (s) => `${s.sayac_no}|${s.donem}`],
  ["O5 sayac_no (kirpilmis) + donem",
    (s) => `${s.sayac_no.trim().toUpperCase()}|${s.donem}`],
];
export const tekille = (satirlar, anahtar, tut = "ilk") => {
  const g = new Map();
  for (const s of satirlar) { const a = anahtar(s);
    if (!g.has(a) || tut === "son") g.set(a, s); }
  return [...g.values()];
};
export const topla = (a) => a.reduce((x, y) => x + y, 0);

if (import.meta.url === `file://${process.argv[1]}`) {
  const satirlar = SAYFA.satirlar();
  console.log(`model (tohum ${TOHUM}): ${ABONE} abone, sayfada ${SAYFA.n} satir; ` +
    `ham toplam ${topla(SAYFA.sutun("tuketim_m3")).toFixed(1)} m3`);
  const y = (h) => String(h[0]).padEnd(32) + h.slice(1).map((v) => String(v).padStart(11)).join("");
  console.log("\n" + y(["tekillestirme olcutu", "kalan", "silinen", "toplam m3"]));
  const kalan = {};
  for (const [ad, f] of OLCUT) {
    const l = tekille(satirlar, f);
    kalan[ad.slice(0, 2)] = l;
    console.log(y([ad, l.length, SAYFA.n - l.length,
      topla(l.map((s) => s.tuketim_m3)).toFixed(1)]));
  }
  console.log(`\nen gevsek olcut ile en siki olcut arasinda ` +
    `${kalan.O1.length - kalan.O2.length} satir fark var; toplam ` +
    `${(topla(kalan.O1.map((s) => s.tuketim_m3)) -
      topla(kalan.O2.map((s) => s.tuketim_m3))).toFixed(1)} m3 oynuyor`);
}
model (tohum 20260218): 1400 abone, sayfada 2037 satir; ham toplam 42645.6 m3

tekillestirme olcutu                  kalan    silinen  toplam m3
O1 tam satir                           2005         32    41949.2
O2 abone_no                            1400        637    29828.2
O3 abone_no + donem                    1877        160    39147.7
O4 sayac_no + donem                    1965         72    40912.2
O5 sayac_no (kirpilmis) + donem        1877        160    39147.7

en gevsek olcut ile en siki olcut arasinda 605 satir fark var; toplam 12121.0 m3 oynuyor

Beş ölçüt beş sayı verdi ve hiçbiri hata üretmedi. O1 tam satır en güvenli görünen ölçüttür ve en az işi yapar: 2.037 satırın yalnız 32’sini siler, çünkü ikinci kez girilmiş bir satırda okuma düzeltilmişse iki satır birebir aynı değildir. İhtiyat burada sorunun 128 satırını olduğu gibi bırakır.

O2 abone_no ise en temiz sonucu verir — 1.400 satır, abone başına tam bir satır — ve rapor edilen toplamı 42.645,6 m³’ten 29.828,2 m³’e indirir. Silinen 637 satırın çoğu yinelenme değil, ikinci dönemin okumasıdır. Sayfaya sonradan bakan biri 1.400 satır görür, sayının abone başına tekil olduğunu doğrular ve eksik olanın ne olduğunu göremez: yanlış ölçüt bir hata mesajı değil, düzgün görünen bir tablo üretir.

Kalan üç sayı metin kararını ölçüyor. O4 ham sayaç numarasıyla karşılaştırır ve 1.965 satır bırakır; O5 aynı ölçütü kırpılmış ve harf birliğine getirilmiş metinle uygular ve 1.877’ye iner. Aradaki 88 satır tek bir kararın bedelidir. O3 ile O5’in aynı sayıyı vermesi ise ölçütlerin denk olduğunu göstermez: bu kümede sayaç numarası abone başına tekil olduğu için çakışıyorlar, bir hanenin ikinci sayacı eklendiğinde ayrışırlar.

Aynı Ölçüt, İki Tutma Kararı

// olcut.mjs — ayni sayfa, bu kez iki soru: yinelenen grupta hangi satirin
// tutuldugu ve kararin sayfadan geri okunup okunamadigi.
import { SAYFA, OLCUT, Tablo, formul, tekille, topla } from "./sayfa.mjs";
const satirlar = SAYFA.satirlar();
const y = (w) => (h) => h.map((v, j) => (j ? String(v).padStart(w[j])
  : String(v).padEnd(w[0]))).join("");

// Ayni olcut, iki tutma karari: gruptaki ilk giris ya da son giris.
const y1 = y([32, 10, 9, 12]);
console.log("ayni olcut, iki tutma karari");
console.log(y1(["olcut", "tutulan", "kalan", "toplam m3"]));
for (const [ad, f] of OLCUT.slice(2, 4)) {
  for (const tut of ["ilk", "son"]) {
    const l = tekille(satirlar, f, tut);
    console.log(y1([ad, tut, l.length, topla(l.map((s) => s.tuketim_m3)).toFixed(1)]));
  }
}
const i3 = tekille(satirlar, OLCUT[2][1], "ilk"), s3 = tekille(satirlar, OLCUT[2][1], "son");
console.log(`O3 icin iki karar da ${i3.length} satir birakir, toplam ` +
  `${(topla(s3.map((s) => s.tuketim_m3)) -
    topla(i3.map((s) => s.tuketim_m3))).toFixed(1)} m3 ayrisir`);

// Tek bir abonenin izi: hangi satiri hangi olcut yutuyor.
const ornek = satirlar.filter((s) => s.abone_no === 10029);
const y2 = y([12, 10, 8, 6, 6, 6, 6, 6]);
console.log(`\nabone 10029 sayfada ${ornek.length} satir tutuyor`);
console.log(y2(["sayac_no", "donem", "m3", ...OLCUT.map((o) => o[0].slice(0, 2))]));
for (const s of ornek) {
  console.log(y2([`"${s.sayac_no}"`, s.donem, s.tuketim_m3,
    ...OLCUT.map(([, f]) => tekille(ornek, f).includes(s) ? "kalir" : "-")]));
}

// Kararin kurtarilabilirligi: formulde yaziliysa bir adim, ayri bir not
// hucresinde duruyorsa iki adim, hicbir hucrede yoksa okunamaz.
const KOD = ["K1 olcut sutunlari", "K2 metin kirpma", "K3 tutulan satir",
  "K4 silinen sayisi"];
const geriOku = (t) => KOD.map((k) => {
  const v = [...t.h.values()], n = k.slice(0, 2);
  const f = v.some((x) => x && x.tur === "formul" && x.yazi.includes(n));
  const not = v.some((x) => typeof x === "string" && x.startsWith("karar ") && x.includes(n));
  return f ? 1 : not ? 2 : null;
});

const silinmis = new Tablo(SAYFA.baslik, i3);
const notlu = new Tablo(SAYFA.baslik, i3)
  .yaz("G1", "karar K1 ve K3: yinelenenler abone ve doneme gore kaldirildi, ilk giris tutuldu");
const anahtar = (s) => `${s.abone_no}|${s.donem}`;
const gorulen = new Map();
const izli = new Tablo([...SAYFA.baslik, "tekil_anahtar", "grup_sirasi"],
  satirlar.map((s) => {
    const a = anahtar(s); gorulen.set(a, (gorulen.get(a) ?? 0) + 1);
    const sira = gorulen.get(a);
    return { ...s,
      tekil_anahtar: formul("K1 abone_no ve donem birlestirilir; K2 sayac_no " +
        "kirpilir ve harf birligine getirilir",
        () => `${s.abone_no}|${s.donem}|${s.sayac_no.trim().toUpperCase()}`),
      grup_sirasi: formul("K3 grupta ilk giris tutulur, sonrakiler isaretlenir",
        () => sira) };
  }));
izli.yaz("H2", formul("K4 grup_sirasi birden buyuk olan satir yinelenendir",
  (t) => t.sutun("grup_sirasi").filter((v) => v > 1).length));

const y3 = y([22, 16, 13, 12]);
console.log("\nkarar sayfadan geri okunabiliyor mu");
console.log(y3(["karar", "silinmis sayfa", "notlu sayfa", "izli sayfa"]));
const sonuc = [silinmis, notlu, izli].map(geriOku);
KOD.forEach((k, j) => console.log(y3([k,
  ...sonuc.map((s) => (s[j] ? `${s[j]} adim` : "okunamaz"))])));
console.log(["silinmis", "notlu", "izli"].map((ad, j) =>
  `${ad} ${sonuc[j].filter(Boolean).length}/${KOD.length}`).join(", ") +
  " karar geri okunabiliyor");
console.log(`izli sayfa ${izli.n} satir ve ${izli.oku("H2")} isaretli satir tutuyor; ` +
  `silinmis sayfa ${silinmis.n} satir tutuyor ve ${SAYFA.n - silinmis.n} satirin ` +
  `nereye gittigini soylemiyor`);
ayni olcut, iki tutma karari
olcut                              tutulan    kalan   toplam m3
O3 abone_no + donem                    ilk     1877     39147.7
O3 abone_no + donem                    son     1877     39347.8
O4 sayac_no + donem                    ilk     1965     40912.2
O4 sayac_no + donem                    son     1965     41023.3
O3 icin iki karar da 1877 satir birakir, toplam 200.1 m3 ayrisir

abone 10029 sayfada 3 satir tutuyor
sayac_no         donem      m3    O1    O2    O3    O4    O5
"KZ-1028"      2026-01   12.74 kalir kalir kalir kalir kalir
"kz-1028"      2026-01   14.27 kalir     -     - kalir     -
"KZ-1028"      2026-02   11.97 kalir     - kalir kalir kalir

karar sayfadan geri okunabiliyor mu
karar                   silinmis sayfa  notlu sayfa  izli sayfa
K1 olcut sutunlari            okunamaz       2 adim      1 adim
K2 metin kirpma               okunamaz     okunamaz      1 adim
K3 tutulan satir              okunamaz       2 adim      1 adim
K4 silinen sayisi             okunamaz     okunamaz      1 adim
silinmis 0/4, notlu 2/4, izli 4/4 karar geri okunabiliyor
izli sayfa 2037 satir ve 160 isaretli satir tutuyor; silinmis sayfa 1877 satir tutuyor ve 160 satirin nereye gittigini soylemiyor

İlk tablo kursun ölçüsünü açıkça gösteriyor. O3 ölçütü altında hangi satırın tutulduğu kararı satır sayısını hiç değiştirmez — iki durumda da 1.877 satır kalır — ama toplam 200,1 m³ ayrışır. Sayfaya bakan biri satır sayısını doğrulayabilir ve doğru sayıyı bulur; toplamın hangi tutma kararıyla üretildiğini ise doğrulayacak hiçbir şey yoktur. Satır sayısı bir denetim ölçüsü değildir.

İkinci tablo tek bir abonenin üç satırını izliyor. Abone 10029’un ocak okuması iki kez girilmiştir ve ikinci girişte hem sayaç numarası küçük harflidir hem de okuma 12,74’ten 14,27’ye kaymıştır; üçüncü satır şubat okumasıdır. O2 ölçütü üç satırdan ikisini siler ve silinenlerden biri gerçek yinelenme, biri şubat okumasıdır. O4 hiçbirini silmez, çünkü küçük harfli kod farklı bir metin sayılır. O3 ile O5 doğru olanı yapar ve iki satır bırakır. Beş ölçüt aynı kayıt için üç farklı sonuç veriyor ve hangisinin ne yaptığı yalnız satırlar hâlâ ortadayken görülebilir.

12,74 ile 14,27 arasındaki seçim ayrı bir karardır ve ölçütle ilgisi yoktur: ikinci giriş bir düzeltme de olabilir, ikinci bir hatalı giriş de. Hangisinin doğru okuma olduğunu veri söylemez; karar sayfanın dışındaki bir bilgiye dayanmak zorundadır.

Karar Nerede Duruyor

Üçüncü tablo dersin ikinci sayısıdır. Silinmiş sayfa 1.877 düzgün satır tutar ve dört kararın sıfırını taşır. Tablo doğru görünür, çünkü tablonun yanlış görünmesini sağlayacak hiçbir şey kalmamıştır.

Not düşülmüş sayfa dört karardan ikisini iki adımda geri verir. Notu yazan kişi ölçüt sütunlarını ve tutma kararını yazmış, metin kırpmayı yazmamıştır — onu bir karar değil, bir temizlik saymıştır. Oysa ölçülen fark tam da oradadır: kırpma 88 satır oynatır, tutma kararı sıfır satır. Elle tutulan notlar en sessiz kararı kaçırır.

İzli sayfa 2.037 satırın hepsini bırakır, 160 satırı işaretler ve dört kararı da bir adımda verir; kararlar hücrelerde formül olarak durduğu için sayfayı açan ikinci kişi onları okumak üzere kimseye sormaz. Bedeli iki yerdedir: sayfa küçülmez ve bundan sonraki her özet işleminin işaret sütununu hesaba katması gerekir. Bu bedel kursun kuralının fiyatıdır — elektronik tablo bir hesap aracıdır, bir kayıt aracı değildir; kayıt ancak fazladan bir sütunla satın alınır.

Özet

  • Tekilleştirme ölçütü satırdan üretilen bir anahtardır; aynı 2.037 satırlık sayfada beş ölçüt 2.005, 1.400, 1.877, 1.965 ve 1.877 satır bırakır.
  • Yalnız abone numarasına dayanan ölçüt en temiz tabloyu üretir ve yinelenme yerine ikinci dönemi siler: toplam 42.645,6 m³’ten 29.828,2 m³’e iner, hiçbir uyarı görünmez.
  • Metin kırpma ve harf birliği kararı tek başına 88 satır oynatır.
  • Aynı ölçüt altında ilk yerine son girişi tutmak satır sayısını 1.877’de sabit tutar ama toplamı 200,1 m³ ayırır; satır sayısı bir denetim ölçüsü değildir.
  • İşlem bittikten sonra dört karardan silinmiş sayfada sıfırı, not düşülmüş sayfada ikisi iki adımda, izli sayfada dördü bir adımda geri okunur.

Sonraki Adım

Tekilleştirme kalan satırların hepsinin dolu olduğunu varsaydı. Oysa 1.877 satırın bir bölümünde tüketim hücresi boştur, bir bölümünde sıfır yazar ve elektronik tablo bu ikisini bazı işlemlerde ayırır, bazılarında ayırmaz. Aynı sütunun ortalaması boş hücreler dışlandığında bir sayı, sıfır sayıldığında başka bir sayı, sütun ortalamasıyla doldurulduğunda üçüncü bir sayı verir. Sonraki ders bu üç kararı yan yana hesaplar ve doldurma bittikten sonra hangi hücrenin başlangıçta boş olduğunun hâlâ bilinip bilinmediğini ölçer.

İ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