Ders 06 / 11
Arama İşlevleri
Aynı arama dört yazımla kuruluyor ve tablonun ortasına bir sütun eklenince yalnız biri kırılıyor: sütun numarası gömülü sabit olan dikey arama 48 kaydın 48'inde yanlış değer döndürüyor, hiçbirinde hata vermiyor ve toplam 1204,8 m³ yerine 1084,6 m³ gösteriyor. Eklenen sütun makul olmayan bir sayı taşısaydı aynı kırılma 201081,0 m³ ile görünür olurdu. Tablo bölgeye göre yeniden sıralandığında yaklaşık eşleşme 41 kaydı sessizce değiştiriyor ve o kararı taşıyan tek şey formüldeki bir sıfır.
İçindekiler
Önceki iki ders bir şeyi varsaymıştı: ziyaret kaydındaki kodun abone tablosunda bulunması ve karşılığındaki tüketimin getirilmesi. O işi yapan şey bir arama işlevidir ve arama iki soruyu ayrı cevaplar: hangi satır, hangi sütun. İkinciye ya bir sayı ya bir başlık adı cevap verir ve bu cevap dersin tamamıdır. Aynı arama dört biçimde yazılır, dördü de aynı sayıyı üretir; sonra bir sütun eklenir.
- TT19. Bir sütun eklendiğinde hücre başvuruları kayar, formül metnindeki sayı
sabitleri kaymaz. Modelin merkezindeki kural budur ve
sutunEkleişlevinde yazılıdır. - TT20. Tek satırlık ya da tek sütunluk aralık düz dizi, dikdörtgen aralık satır dizisi
olarak açılır.
dikeyaraanahtarı aralığın ilk sütununda arar. - TT21. Ziyaret kodları önceki dersin temizleme kararından geçmiş sayılır; burada ölçülen tek şey aramadır.
Aramanın İki Yazımı
Aşağıdaki blok beş sütunlu abone tablosunu, ziyaret listesini ve iki arama yazımını kurar.
// arama.mjs — elektronik tablo MODELI: arama islevleri ve sutun ekleme. // Sutun eklendiginde HUCRE BASVURULARI kayar, formuldeki SAYI SABITLERI kaymaz. export const TOHUM = 20260602, SATIR = 60, ZIYARET = 48; const uretec = (t) => { let s = t >>> 0; return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; }; const BOLGE = ["kuzey", "guney", "dogu", "bati", "merkez"]; const REF = /(\$?)([A-Z])(\$?)(\d+)/g; const coz = (a) => ({ s: a.charCodeAt(a[0] === "$" ? 1 : 0) - 65, r: +a.replace(/\D/g, "") }); const adi = (s, r) => String.fromCharCode(65 + s) + r; const ARALIK = /(\$?[A-Z]\$?\d+):(\$?[A-Z]\$?\d+)/g; // Tek satirlik ya da tek sutunluk aralik duz dizi, dikdortgen aralik satir dizisi olarak acilir. const ac = (a, b, H) => { const x = coz(a), y = coz(b), l = []; const tek = x.s === y.s || x.r === y.r; for (let r = x.r; r <= y.r; r++) { const s = []; for (let c = x.s; c <= y.s; c++) s.push(deger(H, adi(c, r))); if (tek) l.push(...s); else l.push(s); } return l; }; export const kaydir = (f, dS, dR) => f.replace(REF, (_, a, s, b, r) => (a ? "$" + s : String.fromCharCode(s.charCodeAt(0) + dS)) + (b ? "$" + r : String(+r + dR))); const bul = (k, d, tur) => { if (tur === 0) return d.indexOf(k) + 1; // tam eslesme: yoksa 0 let i = 0; // yaklasik eslesme: son k'dan buyuk olmayan for (let j = 0; j < d.length; j++) if (d[j] <= k) i = j + 1; return i; }; const ISLEV = { eslesme: (k, d, tur) => bul(k, d, tur), dizin: (d, s, c) => (c === undefined ? d[s - 1] : d[s - 1][c - 1]), dikeyara: (k, T, n, tur) => { const i = bul(k, T.map((r) => r[0]), tur); return i ? T[i - 1][n - 1] : "#YOK"; }, }; export function deger(H, ad) { const v = H[ad]; if (v === undefined) return 0; if (typeof v !== "string" || v[0] !== "=") return v; const ifade = v.slice(1) .replace(ARALIK, (_, a, b) => JSON.stringify(ac(a, b, H))) .replace(REF, (m) => JSON.stringify(deger(H, m.replace(/\$/g, "")))); return Function(...Object.keys(ISLEV), `return (${ifade});`)(...Object.values(ISLEV)); } // Sutun ekleme: konumdaki ve sagindaki hucreler bir sutun kayar, formullerdeki basvurular da // kayar. Formul metnindeki SAYI SABITLERI degismez — bu dersin tamami bu farkin uzerindedir. export function sutunEkle(H, konum, baslik, uret) { const k = konum.charCodeAt(0) - 65, Y = {}; const kay = (f) => f.replace(REF, (_, d1, s, d2, r) => { const c = s.charCodeAt(0) - 65; return (d1 ? "$" : "") + adi(c >= k ? c + 1 : c, "") + (d2 ? "$" : "") + r; }); for (const [ad, v] of Object.entries(H)) { const c = ad.charCodeAt(0) - 65, r = ad.slice(1); Y[adi(c >= k ? c + 1 : c, r)] = typeof v === "string" && v[0] === "=" ? "=" + kay(v.slice(1)) : v; } Y[konum + "1"] = baslik; for (let i = 0; i < SATIR; i++) Y[konum + (i + 2)] = uret(i); return Y; } export const YAZIM = [ ["dikey arama, sutun no 3", "=dikeyara(P2,$A$2:$E$61,3,0)"], ["dikey arama, basliktan", '=dikeyara(P2,$A$2:$E$61,eslesme("tuketim",$A$1:$E$1,0),0)'], ["eslesme-dizin, sutuna", "=dizin($C$2:$C$61,eslesme(P2,$A$2:$A$61,0))"], ["eslesme-dizin, basliktan", '=dizin($A$2:$E$61,eslesme(P2,$A$2:$A$61,0),eslesme("tuketim",$A$1:$E$1,0))'], ]; export function sayfa() { const r = uretec(TOHUM), H = {}; H.A1 = "abone"; H.B1 = "bolge"; H.C1 = "tuketim"; H.D1 = "okuma"; H.E1 = "tutar"; for (let i = 0; i < SATIR; i++) { const s = i + 2; H["A" + s] = "AB-" + String(1041 + i * 7).padStart(4, "0"); H["B" + s] = BOLGE[Math.floor(r() * 5)]; const t = Math.round((2 + r() * r() * 78) * 10) / 10; H["C" + s] = t; H["D" + s] = Math.round(t); H["E" + s] = Math.round((t * 8.4 + 25) * 100) / 100; } // Ziyaret tablosu: kodlar temizlenmis (onceki ders), her kayit bir aboneye ait. const z = uretec(TOHUM + 2); const sira = Array.from({ length: SATIR }, (_, i) => i); for (let i = SATIR - 1; i > 0; i--) { const j = Math.floor(z() * (i + 1)); [sira[i], sira[j]] = [sira[j], sira[i]]; } H.P1 = "ziyaret kodu"; for (let i = 0; i < ZIYARET; i++) H["P" + (i + 2)] = H["A" + (2 + sira[i])]; return H; } if (import.meta.url === `file://${process.argv[1]}`) { const H = sayfa(); const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j]) : String(v).padEnd(B[0]))).join(""); const y = g([8, 12, 10, 10, 12]); console.log(y(["hucre", "abone", "bolge", "tuketim", "tutar"])); for (const s of [2, 3, 61]) console.log(y(["A" + s, H["A" + s], H["B" + s], H["C" + s], H["E" + s]])); console.log(`\nBasliklar A1:E1 = ${[..."ABCDE"].map((c) => H[c + "1"]).join(", ")}; ` + `"tuketim" 3. sutun. Ziyaret kodu P2 = ${H.P2}.`); for (const [ad, f] of YAZIM.slice(0, 2)) console.log(`${ad}\n ${f}\n -> ${deger({ ...H, W2: f }, "W2")} m3`); }
hucre abone bolge tuketim tutar
A2 AB-1041 guney 9.8 107.32
A3 AB-1048 dogu 71.6 626.44
A61 AB-1454 dogu 21.2 203.08
Basliklar A1:E1 = abone, bolge, tuketim, okuma, tutar; "tuketim" 3. sutun. Ziyaret kodu P2 = AB-1286.
dikey arama, sutun no 3
=dikeyara(P2,$A$2:$E$61,3,0)
-> 28.9 m3
dikey arama, basliktan
=dikeyara(P2,$A$2:$E$61,eslesme("tuketim",$A$1:$E$1,0),0)
-> 28.9 m3
İki yazım da 28,9 m³ veriyor ve ikisi de aynı işlevi kullanıyor. Tek fark, “tüketim” sütununun
nasıl işaret edildiğidir. Birincisinde o sütun bir sayıdır: 3. Sayının nereden geldiği formülde
yazmaz; yazan kişi başlıkları saymış ve saydığı sayıyı formüle koymuştur. İkincisinde o sütun bir
addır ve başlık satırında her seferinde yeniden aranır.
Bu noktada ayrım bir üslup farkı gibi görünür; sonuç karşılaştıran bir denetim ikisini ayıramaz. Ayıran şey sayfanın değişime nasıl tepki verdiğidir.
Tablonun Ortasına Bir Sütun
Aşağıdaki blok dört yazımı kurar, sonra B ile C arasına “önceki dönem” sütununu ekler;
değerleri mevcut tüketimle aynı büyüklüktedir.
// kir.mjs — ayni arama dort yazimla; tablonun ortasina bir sutun eklenince ne oluyor. import { YAZIM, sayfa, deger, kaydir, sutunEkle, ZIYARET } from "./arama.mjs"; const H = sayfa(); const doldur = (K, f) => { const M = { ...K }; for (let i = 0; i < ZIYARET; i++) M["W" + (i + 2)] = kaydir(f, 0, i); return M; }; // B ile C arasina "onceki donem" sutunu eklenir: degerleri tuketimle ayni buyuklukte. const ONCEKI = (i) => Math.round(H["C" + (i + 2)] * 9) / 10; const oku = (M, s) => Array.from({ length: ZIYARET }, (_, i) => deger(M, s + (i + 2))); const say = (d) => d.filter((v) => typeof v === "number").reduce((a, b) => a + b, 0); const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j]) : String(v).padEnd(B[0]))).join(""); const y = g([26, 14, 14, 10, 8, 10]); console.log(y(["yazim", "toplam once", "toplam sonra", "degisen", "hata", "sessiz"])); for (const [ad, f] of YAZIM) { const A = oku(doldur(H, f), "W"); const E = sutunEkle(doldur(H, f), "C", "onceki donem", ONCEKI); const B = oku(E, "X"); // W sutunu ekleme sonrasi X oldu const degisen = B.filter((v, i) => v !== A[i]).length; const hata = B.filter((v) => typeof v !== "number").length; console.log(y([ad, say(A).toFixed(1), say(B).toFixed(1), degisen, hata, degisen - hata])); } // Ayni kirilma, eklenen sutun makul olmayan bir sayi tasidiginda gorunur hale gelir. console.log(""); for (const [ad, uret] of [["onceki donem", ONCEKI], ["sayac no", (i) => 4100 + i * 3]]) { const B = oku(sutunEkle(doldur(H, YAZIM[0][1]), "C", ad, uret), "X"); console.log(`Eklenen sutun "${ad}" ise kirik arama toplami ${say(B).toFixed(1)} m3, ` + `ilk deger ${B[0]}.`); }
yazim toplam once toplam sonra degisen hata sessiz dikey arama, sutun no 3 1204.8 1084.6 48 0 48 dikey arama, basliktan 1204.8 1204.8 0 0 0 eslesme-dizin, sutuna 1204.8 1204.8 0 0 0 eslesme-dizin, basliktan 1204.8 1204.8 0 0 0 Eklenen sutun "onceki donem" ise kirik arama toplami 1084.6 m3, ilk deger 26. Eklenen sutun "sayac no" ise kirik arama toplami 201081.0 m3, ilk deger 4205.
Dört yazımdan biri kırıldı: 48 kaydın 48’i değişti ve hiçbiri hata vermedi. Kırılan yazım hâlâ bir sayı döndürüyor ve sayı hâlâ bir tüketim büyüklüğünde. Rapor 1204,8 yerine 1084,6 m³ gösteriyor; fark yüzde 10, bir denetim toplamının onaylayacağı kadar.
Kırılmanın nedeni bir işlev değil, bir sabittir: sütun numarasını başlıktan okuyan ikinci yazım aynı işlevi kullanıyor ve kırılmadı. Bir sütunun kaçıncı olduğu bir olgu değil, bir andır; sütun eklenince o an geçer, sayı kalır.
Son iki satır hatanın görünürlüğünü ölçüyor. Eklenen sütun sayaç numarası taşısaydı aynı kırık formül 201081,0 m³ üretip yakalanacaktı; önceki dönem tüketimi taşıdığında 1084,6 m³ üretiyor ve yakalanmıyor. Bir hatanın görünürlüğü büyüklüğüyle değil, benzediği şeyle ölçülür.
Sıralama Değişince
Aramanın ikinci sabiti son değişkendir: tam eşleşme mi, yaklaşık mı. Aşağıdaki blok bu kararı iki sıralama altında ölçer, sonra dört yazımı tarar.
// sirala.mjs — yaklasik eslesme siralamaya bagimlidir; ve kararin formulden geri okunmasi. import { YAZIM, sayfa, deger, kaydir, SATIR, ZIYARET } from "./arama.mjs"; const H = sayfa(); // Tablo bolgeye gore yeniden siralanir: satirlar yer degistirir, aralik adlari degismez. const siralanmis = (K) => { const s = Array.from({ length: SATIR }, (_, i) => i + 2) .sort((a, b) => (K["B" + a] < K["B" + b] ? -1 : K["B" + a] > K["B" + b] ? 1 : K["A" + a] < K["A" + b] ? -1 : 1)); const Y = { ...K }; s.forEach((eski, j) => { for (const c of "ABCDE") Y[c + (j + 2)] = K[c + eski]; }); return Y; }; const doldur = (K, f) => { const M = { ...K }; for (let i = 0; i < ZIYARET; i++) M["W" + (i + 2)] = kaydir(f, 0, i); return M; }; const oku = (M) => Array.from({ length: ZIYARET }, (_, i) => deger(M, "W" + (i + 2))); const g = (B) => (h) => h.map((v, j) => (j ? String(v).padStart(B[j]) : String(v).padEnd(B[0]))).join(""); const S = siralanmis(H); const DOGRU = oku(doldur(H, "=dikeyara(P2,$A$2:$E$61,3,0)")); const y = g([20, 16, 10, 10, 8]); console.log(y(["tablo siralamasi", "son degiskene", "toplam", "yanlis", "hata"])); for (const [ad, K] of [["abone koduna gore", H], ["bolgeye gore", S]]) for (const [tur, son] of [["0 tam eslesme", "0"], ["1 yaklasik", "1"]]) { const d = oku(doldur(K, `=dikeyara(P2,$A$2:$E$61,3,${son})`)); const t = d.filter((v) => typeof v === "number").reduce((a, b) => a + b, 0); console.log(y([ad, tur, t.toFixed(1), d.filter((v, i) => v !== DOGRU[i]).length, d.filter((v) => typeof v !== "number").length])); } // Geri okuma taramasi: formul metnindeki gomulu sayilar ile baslik adlari sayilir. const yer = (f) => /"[a-z]+"/.test(f) ? "baslik adi" : /dizin\(\$[A-Z]\$\d+:\$[A-Z]\$\d+,/.test(f) ? "hucre araligi" : "gomulu sayi"; const z = g([26, 16, 8, 14]); console.log("\n" + z(["yazim", "sutun karari", "sabit", "eslesme turu"])); for (const [ad, f] of YAZIM) { const kalan = f.replace(/\$?[A-Z]\$?\d+/g, ""); // aralik ve hucre adlari sayilmaz const sabit = (kalan.match(/\d+/g) ?? []); console.log(z([ad, yer(f), sabit.length, sabit.at(-1) + " (gomulu)"])); }
tablo siralamasi son degiskene toplam yanlis hata abone koduna gore 0 tam eslesme 1204.8 0 0 abone koduna gore 1 yaklasik 1204.8 0 0 bolgeye gore 0 tam eslesme 1204.8 0 0 bolgeye gore 1 yaklasik 961.8 41 0 yazim sutun karari sabit eslesme turu dikey arama, sutun no 3 gomulu sayi 2 0 (gomulu) dikey arama, basliktan baslik adi 2 0 (gomulu) eslesme-dizin, sutuna hucre araligi 1 0 (gomulu) eslesme-dizin, basliktan baslik adi 2 0 (gomulu)
Birinci tablonun ilk iki satırı bir tuzak kuruyor: abone koduna göre sıralı bir tabloda tam eşleşme ile yaklaşık eşleşme aynı sonucu verir, yani seçen kişi hiçbir fark görmez. Üçüncü ve dördüncü satırlar sıralamayı bölgeye çeviriyor — bölge kırılımına bakmak isteyen birinin yapacağı sıradan bir iş. Tam eşleşme kıpırdamıyor; yaklaşık eşleşme 48 kaydın 41’inde başka bir satır döndürüyor, toplam 961,8 m³’e iniyor ve yine hata yok.
İkinci tablo kursun ölçüsünü derse uyguluyor. Sütun kararı üç yerde durabiliyor: gömülü bir
sayıda, bir hücre aralığında, bir başlık adında — ve kırılan tek yazım, kararı gömülü bir sayı
olandır. Ama son sütun daha rahatsız edici bir şey söylüyor: dördünde de eşleşme türü gömülü
bir sabittir. O 0 bir 1 olsaydı hiçbir hata çıkmayacak, tablo yeniden sıralandığı gün 41
kayıt sessizce değişecekti.
Kursun ilk dersinden beri sürdürülen kural burada tamamlanıyor: hücreye gömülü karar, yazılmamış karardır. Sütun numarası da eşleşme türü de birer karardır ve ikisi de bir sayı kılığında duruyor.
Özet
- Arama iki soruyu ayrı cevaplar — hangi satır, hangi sütun; ikinci cevap bir sayı da bir başlık adı da olabilir ve ikisi aynı sonucu üretir.
- Sütun eklendiğinde hücre başvuruları kayar, sayı sabitleri kaymaz: dört yazımdan yalnız biri kırıldı, 48 kaydın 48’inde yanlış değer döndürdü ve hiç hata vermedi.
- Toplam 1204,8 yerine 1084,6 m³ oldu; aynı kırılma başka bir sütunla 201081,0 m³ üretip görünür olurdu. Kıran şey işlev değil, gömülü sütun numarasıdır.
- Yaklaşık eşleşme sıralamaya bağımlıdır ve tablo sıralı olduğu sürece bunu belli etmez: bölgeye göre sıralandıktan sonra 41 kayıt sessizce değişti.
- Dört yazımın dördünde de eşleşme türü gömülü bir sayıdır; kursun kuralı burada tamamlanır — hücreye gömülü karar, yazılmamış karardır.
Sonraki Adım
Bu konu altı derste hücre düzeyindeki kararları ölçtü: başvuru türü, sayma ölçütü, koşul sınırı, temizleme kuralı, gün sayma kuralı, arama biçimi. Her derste bir sayı ile onu üreten karar yan yana duruldu ve kararın kaç adımda geri okunduğu sayıldı. Ama bütün bu ölçümler tek tek hücrelere bakıyordu; hiçbiri tablonun bütününe bakan bir sayı üretmedi.
Daha temel bir soru da hiç sorulmadı: bu tablo temiz mi. Altı ders boyunca 60 satırın 60 ayrı abone olduğu ve her satırın bir kez göründüğü varsayıldı. Sonraki konu iki eksiği birlikte ele alır: tekilleştirme ölçütü değişince kaç satırın kaldığını ve eksik değer kararlarının aynı ortalamaya kaç değer verdiğini ölçer, sonra tablonun bütününe bakan özetleri kurar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.