Sayma — kelime sıklığı, oyun analitiği, Zipf yasası
Dizinleme — ters dizin: etiketle eşya arama inverted index
Seyrek vektörler — çoğu sıfır olan devasa tablolar sparse vector
Oyunda — Markov zinciriyle NPC repliği üretmek
🎮 Bu dersin sonunda…
…oyununuzu 4 dile çevirecek tabloyu, sohbet filtresini, “ateş VE efsanevi” eşya aramasını ve kendi kendine konuşan bir NPC’yi birkaç satırla yazabileceksiniz.
🤔 ISINMA: HEPSİ AYNI SORU
Bunların ortak noktası ne?
“Bu kullanıcı adı yasaklı mı?”
“menu.play Almancada ne yazılacak?”
“Bu maçta hangi silah kaç kez kullanıldı?”
“Ateş etiketli eşyalar hangileri?”
Hepsi bir sembol tablosu: put(anahtar, değer) ve get(anahtar). Değer bazen yok (küme), bazen sayı (sayaç), bazen liste (dizin).
Hangi uygulama? Hash tablosu (sıra gerekmiyorsa, en hızlı) ya da dengeli BST (sıra gerekiyorsa). Bu sunumda ikisini de “hazır” kullanacağız: Python dict/set, C# Dictionary/HashSet, JS Map/Set.
Mini sözlük
set
küme
Tekrarsız anahtarlar topluluğu; sadece “var mı?”
whitelist · blacklist
izin listesi · yasak listesi
Bir kümede olanlara izin ver / olanları engelle
dictionary · lookup
sözlük · arama
Anahtardan değere
localization (l10n)
yerelleştirme
Oyunu başka dillere/kültürlere uyarlamak
frequency counter
sıklık sayacı
anahtar → kaç kez görüldü
inverted index
ters dizin
kelime/etiket → geçtiği belgeler/eşyalar
sparse vector · matrix
seyrek vektör · matris
Çoğu elemanı sıfır olan; sadece doluları saklanır
Markov chain
Markov zinciri
Bir sonraki durumun sadece şimdikine bağlı olduğu rastgele süreç
LEVEL 0
Kümeler
sets — sadece anahtar, değer yok
kümeler
sözlükler
sayma
dizinleme
seyrek vektörler
oyunda: Markov replikleri
Küme API’si ve üç dildeki karşılığı
add(key)
ekle (zaten varsa hiçbir şey olmaz)
contains(key)
var mı?
remove(key)
çıkar
size()
kaç eleman?
dil
hash tabanlı
BST tabanlı (sıralı)
Python
set
— (kütüphane: sortedcontainers)
C#
HashSet<T>
SortedSet<T>
JavaScript
Set
—
🎮 Oyunda kümelerAçılmış bölümler · kazanılmış başarımlar · ziyaret edilmiş kareler (flood fill ve gelecek sunumdaki graf aramaları!) · yasaklı kelimeler · bu karede zaten hasar almış düşmanlar (aynı patlama iki kez vurmasın).
Sohbet filtresi: yasak listesi
⚠️ Türkçe tuzağı: C#/JS’te "AYDIN".ToLower() → “aydin” (noktalı i!) olabilir. Türkçe için ToLower(new CultureInfo("tr-TR")) / toLocaleLowerCase("tr"). Gerçek filtreler ayrıca “a.p.t.a.l”, “4ptal” gibi hileleri de yakalamak zorunda.
Küme işlemleri: birleşim, kesişim, fark
Ali ve Ayşe’nin kazandığı başarımlar:
Kesişimintersection: ikisinin de kazandığı
Birleşimunion: en az birinin kazandığı
Farkdifference: Ali’de olup Ayşe’de olmayan → “Ayşe’ye ipucu ver!”
Kesişimin maliyeti: küçük kümedeki her eleman için büyük kümede “var mı?” → hash ile ~küçük kümenin boyu kadar.
🔗 Başka gözle: etkileşimli anlatımlar ve yazılar
Aynı konuyu başka bir anlatımla, kendi elinizle oynayarak görmek çok işe yarar. Kartlar yeni sekmede açılır; çoğu İngilizce ama görsel ve etkileşimli.
Bir patlama, alanındaki düşmanlara hasar veriyor. Aynı düşmanın birden fazla çarpışma kutusu var ve hasarı bir kez almalı. En uygun yapı hangisi?
Tam bir küme sorusu: “bu düşman bu patlamada zaten vuruldu mu?” if (!hit.Contains(enemy)) { hit.Add(enemy); Damage(enemy); }. Liste de çalışır ama her kontrol N; 200 düşmanlık patlamada 200² = 40.000 adım.
LEVEL 1
Sözlükler
dictionary clients — anahtardan değere
kümeler
sözlükler
sayma
dizinleme
seyrek vektörler
oyunda: Markov replikleri
🎮 Yerelleştirme tablosu: CSV’den sözlüğe
Anahtar ve değer sütunlarını değiştirin: aynı veri, farklı sözlükler. “tr” sütununu anahtar yapıp “İksir”i arayın — ya da “anahtar” → “de” yapıp Almanca oyunu açın. Anahtar sütunu olarak “en”i seçmek neden sorun olabilir?
CSV’den sözlük kurmak — kod
🎒 [1:] / .Skip(1) / .slice(1): ilk elemanı atla. Gerçek CSV’de virgül içeren metinler tırnakla yazılır (“Merhaba, yolcu”) — onlar için hazır CSV kütüphanesi kullanın.
Eşya, düşman, yetenek, diyalog… hepsi “ID → satır” sözlükleri. Unity’de ScriptableObject’ler ya da JSON dosyaları da aynı fikir.
Ağ üzerinden “ejder kılıcı” yerine ID (örn. 1042) yollanır: daha az bayt, sunucu ID’yi sözlükte arar.
Yerelleştirmede metin hiçbir zaman koda gömülmez: kodda sadece "npc.greet" anahtarı yaşar. Dil değişince sadece sözlük değişir.
Quiz 2 · sözlükler
Yerelleştirme tablosunu yanlışlıkla “en” sütununu anahtar yaparak kurdunuz. İngilizcede “Shield” iki farklı yerde (eşya adı ve yetenek adı) geçiyor. Ne olur?
Sembol tablosunda her anahtarın bir değeri vardır; aynı anahtarla tekrar put eski değeri günceller. Bu yüzden yerelleştirmede anahtar dilden bağımsız, benzersiz bir ID olur: item.shield, skill.shield.
LEVEL 2
Sayma
frequency counter — anahtar → kaç kez
kümeler
sözlükler
sayma
dizinleme
seyrek vektörler
oyunda: Markov replikleri
Kelime sıklığı — kendiniz deneyin
🎬 Ara: Zipf yasası
The Zipf Mystery Vsauce (Michael Stevens) · 21 dk 05 sn · YouTube’da aç
Kelime sıklığı demosunda gördüğümüz dağılım tesadüf değil: en sık kelime ikinciden yaklaşık iki, üçüncüden üç kat sık çıkar. Bu yasa oyunlarda da karşımıza çıkar (eşya kullanımı, oyuncu sayıları). İlk 8 dakika yeterli.
🤔 İzlerkenVideodaki “sıra × sıklık ≈ sabit” kuralı bizim demodaki hikâyede de tutuyor mu? Bir oyunun en sık kullanılan eşyasını önbellekte tutmak bu yasa yüzünden neden işe yarar?
Sayaç kodu: geleneksel ve modern
Zipf yasası ve oyun analitiği
Her dilde en sık kelime, ikinciden ~2 kat, üçüncüden ~3 kat sık geçer: sıklık ≈ 1 / sıra. Türkçede “bir”, “ve”, “bu”…
Şaşırtıcı biçimde oyunlarda da: en çok kullanılan silah, en çok seçilen karakter, en çok ölünen yer… az sayıda şey çoğu kullanımı alır.
Dengeleme ekibi tam olarak bu sayaçlara bakar: “Oyuncuların %60’ı aynı silahı seçiyorsa o silah fazla güçlü.”
⚙️ Hangi sembol tablosu?Sayarken binlerce put/get → hash. Sonuçları alfabetik yazdırmak istersek ya en sonda sıralarız ya da baştan BST (C# SortedDictionary) kullanırız. “En sık 10” için: hepsini sırala (N lg N) ya da 10’luk öncelik kuyruğu (Öncelik Kuyrukları sunumu: N lg 10!).
🧪 Kod laboratuvarı: canavar sayacı
Çalıştırın: sayılar neden NaN çıkıyor? (İpucu: ilk görüşte counts[w] nedir? undefined + 1 = ?) Düzeltin — önce geleneksel if ile, sonra ?? 0 ile.
LEVEL 3
Dizinleme
indexing — değer bir liste olunca
kümeler
sözlükler
sayma
dizinleme
seyrek vektörler
oyunda: Markov replikleri
Ters dizin: etiketten eşyalara
Normal tablo: eşya → etiketleri. Ters dizin: etiket → eşyalar. Arama motorlarının kalbi: kelime → içinde geçtiği sayfalar.
Ters dizin kurmak — kod
🎒 Değer bir liste (ya da küme): Dictionary<string, List<string>> = “string’den string listesine sözlük”. for name, tags in items.items() / foreach (var (name, tags) in items) / for (const [name, tags] of …): her çifti iki değişkene ayır.
Dizinler her yerde
Klasik örnekler
Dosya dizini: kelime → içinde geçtiği dosyalar (Spotlight, Windows arama)
Kitap dizini / konkordans: kelime → geçtiği sayfalar/konumlar
Arama motoru: kelime → web sayfaları (milyarlarca!)
🎮 Oyunda
Envanter / mağaza filtreleri (etiket → eşyalar)
Görev günlüğünde arama (kelime → görevler)
“Bu ses dosyası hangi seviyelerde kullanılıyor?” (varlık → sahneler) — oyun motorlarının “referans bul” özelliği
Yapay zekâ: “silah taşıyan düşmanlar” sorgusunu her karede taramak yerine dizinden okumak
🐍 Kod laboratuvarı: ters dizin (Python)
Bu kod her etiket için sadece son eşyayı tutuyor. Neden? (İpucu: index[t] = [name] her seferinde ne yapıyor?) Düzeltin. Bonus: setdefault ya da collections.defaultdict(list) ile tek satıra indirin.
Quiz 3 · dizinleme
10.000 eşyalı bir mağazada “ateş” etiketli 200, “efsanevi” etiketli 50 eşya var. Ters dizin ve hash kümesiyle “ateş VE efsanevi” sorgusunun maliyeti yaklaşık nedir?
Küçük kümeyi (50) gez, her eleman için büyük kümede (hash) “var mı?” sor: ~50 işlem. Dizinsiz yaklaşım 10.000 eşyanın etiketlerine tek tek bakardı. İki listeyi iç içe döngüyle karşılaştırmak (B) en kötüsü.
LEVEL 4
Seyrek vektörler
sparse vectors & matrices — sıfırları saklama
kümeler
sözlükler
sayma
dizinleme
seyrek vektörler
oyunda: Markov replikleri
Çoğu sıfır olan devasa tablolar
Doluluk kaydırıcısını oynatın. Sembol tablosu: (satır, sütun) → değer, sadece sıfır olmayanlar için. Sıfırları saklamak da onlarla çarpmak da israf.
Seyrek vektör — kod
SparseVector: indeks → değer sembol tablosu.
get(i): tabloda yoksa 0
put(i, 0): tablodan sil (sıfırları saklama!)
dot (iç çarpım): sadece dolu indeksleri gez → maliyet ~ dolu eleman sayısı, N değil
📜 Google ve PageRankWeb, milyarlarca sayfalık bir matris: sayfa i → sayfa j bağlantısı. Her sayfa ortalama ~10 bağlantı verir. Yoğun matris imkânsız; seyrek matrisle Google her gün bunun üstünde çarpım yapar.
Arka plan: sonsuz bir dünya belleğe nasıl sığar?
Minecraft’ın dünyası küplerden (blok) oluşur ve yatayda yaklaşık 60 milyon × 60 milyon blok genişliğindedir; yükseklik 384 blok.
Hepsini tek bir 3 boyutlu dizide tutmak: 60 M × 60 M × 384 ≈ 1018 hücre. Dünyadaki bütün diskler yetmez; üstelik neredeyse tamamı oyuncunun hiç gitmeyeceği yerlerdir.
Çözüm: dünya chunk denen parçalara bölünür: 16 × 16 blokluk dikey sütunlar. Yalnızca oyuncunun çevresindeki chunk’lar üretilip belleğe alınır (soldaki mavi kareler); oyuncu yürüdükçe öndekiler yüklenir, arkadakiler diske yazılıp bellekten atılır.
Yüklü chunk’lar bir sembol tablosunda (sözlükte) durur: anahtar chunk’ın koordinatı, değer chunk’ın kendisi.
anahtar (cx, cz)
değer
(0, 0)
Chunk: 16 × 16 × 384 blok
(1, 0)
Chunk …
(−2, 2)
Chunk …
(40, −7)
tabloda yok → henüz yüklenmedi
Görüş mesafesi 12 chunk ise bellekte (2 · 12 + 1)² = 625 chunk vardır; dünyanın geri kalan trilyonlarca chunk’ı için tek bayt harcanmaz. Çoğu hücresi boş olan böyle yapılara seyrek (sparse) denir: “boş”u saklamaz, yalnızca “var olan”ı bir tabloda tutarız.
🎮 Oyunda seyrek yapılar
Dünya haritasıAz önce gördük: yalnızca yüklü chunk’lar bir sözlükte durur: (cx, cz) → Chunk. Aynı fikir bir kez daha: oyuncunun değiştirdiği bloklar ayrı bir tabloda tutulur: (x, y, z) → blok. Dünya, tohumdan (seed) yeniden üretilebildiği için kayıt dosyasına yalnızca bu değişiklikler yazılır; el değmemiş dağları ve boş gökyüzünü kimse saklamaz.
Oyuncu × eşya matrisi1 milyon oyuncu × 10.000 eşya = 10 milyar hücre, ama her oyuncunun ~50 eşyası var. “Bu oyuncuya ne önerelim?” sistemleri seyrek vektörlerle çalışır (iki oyuncunun envanter vektörlerinin iç çarpımı = benzerlik).
🔮 Sonraki sunumlara köprüBir graf da seyrek bir matristir: “düğüm i → düğüm j bağlantısı var mı?” Bir oyun haritasındaki 10.000 bölgenin her biri sadece birkaç komşuya bağlı. Graflarda kullanacağımız komşuluk listeleri, satır başına bir seyrek vektörden başka bir şey değil!
🔗 Ayrıntısı: G2 · Uzaysal Bölümleme (uzaysal hash: sonsuz dünyada yalnızca dolu hücreler) sunumunda.
Quiz 4 · seyrek
10.000 × 10.000’lik bir matrisin her satırında ortalama 5 sıfır olmayan değer var. Seyrek gösterimle bir matris × vektör çarpımı yaklaşık kaç çarpma yapar?
Her satır için sadece dolu elemanlar: 10.000 satır × 5 = 50.000 çarpma. Yoğun gösterim 10.000² = 100 milyon çarpma yapar — 2.000 kat fazla, üstelik 800 MB bellek ister.
LEVEL 5
Oyunda: Markov replikleri
sembol tablosuyla “konuşan” bir NPC
kümeler
sözlükler
sayma
dizinleme
seyrek vektörler
oyunda: Markov replikleri
🗣️ Markov zinciriyle NPC repliği
Tablo: son kelime(ler) → ardından gelen kelimeler listesi. Üretirken sona bak, tablodan rastgele bir aday seç, ekle, tekrarla. “Önek uzunluğu 2” daha anlamlı ama kaynağa daha çok benzeyen cümleler üretir.
Markov üreteci — kod
🎮 Markov zincirleri oyunlarda
İsim üretimi: harf düzeyinde Markov (önek = son 2–3 harf) → “Tharogan”, “Eldrimel” gibi fantastik ama telaffuz edilebilir isimler. Birçok rogue-like ve strateji oyunu böyle yapar.
Müzik / seviye üretimi: “bu karoyu hangi karo izler?” tablosu (Wave Function Collapse’ın uzak akrabası)
Oyuncu modelleme: “oyuncu bu hamleden sonra genelde ne yapıyor?” → YZ rakip tahmin eder (dövüş oyunlarında “okuma”)
Büyük dil modelleri (ChatGPT gibi) bu fikrin çok çok gelişmiş hâli: “önceki kelimelere göre sonraki kelimenin olasılığı”. Ama tablo yerine milyarlarca parametreli bir sinir ağı kullanırlar.
Quiz 5 · markov
Markov tablosunu kurarken aynı sonraki kelimeyi listeye tekrar tekrar eklememizin (tekrarları silmememizin) amacı nedir?
Listede “ejderha” 3 kez, “köy” 1 kez varsa rastgele seçimde “ejderha” %75 olasılıkla gelir — kaynak metindeki gibi. Bellekten tasarruf için liste yerine bir sayaç sözlüğü de tutulabilir (kelime → kaç kez), sonra ağırlıklı rastgele seçim yapılır (Arama sunumundaki “loot tablosu”!).
Derin hikâye: Redis sıralı kümeleri ve oyun liderlik tabloları
Milyonlarca oyunculu bir oyunda “ilk 100”, “benim sıram kaç?”, “benim çevremdeki 10 oyuncu” sorularını her saniye binlerce kez cevaplamak gerekir.
Bellek içi veri deposu Redis’in sıralı küme (sorted set) türü bunun için yaygın kullanılır: ZADD skoru ekler/günceller, ZREVRANK sıralamayı, ZREVRANGE aralığı verir.
İçeride iki sembol tablosu birlikte çalışır: bir hash tablosu (oyuncu → skor, O(1)) ve skora göre sıralı bir atlamalı liste (skip list; ekleme, güncelleme, sıra ve aralık için O(log N)). Atlamalı liste, William Pugh’un 1990’da “dengeli ağaçlara olasılıksal bir alternatif” olarak önerdiği yapıdır.
DersGerçek sistemler çoğu zaman tek bir veri yapısıyla yetinmez: “anahtardan değere” için hash, “sıraya göre” için ağaç ya da atlamalı liste. Bu sunumdaki sembol tablosu API’si (get, put, rank, select, aralık) tam olarak bir liderlik tablosunun ihtiyacı.
Başarımlar: açılmış başarımlar kümesi; “bu ilk kez mi?” kontrolü O(1).
Seyrek ızgaralar: sonsuz dünyada sadece dolu hücreleri tutmak (koordinat → hücre).
Telemetri: olay türü → sayı (Counter); bölüm → ölüm konumları.
Liderlik tablosu: yukarıdaki hikâye; küçük oyunlar için SortedSet de yeter.
Sık hatalar
Üyelik testini listeyle yapmak (O(n)).
Her istekte bütün tabloyu sıralamak.
Sürekli büyüyen önbellek sözlüklerini hiç temizlememek (bellek sızıntısı).
Ters indeksi güncellemeyi unutmak (eşya silinince etiket listesinde kalır).
🤖 Bu konuyu bir YZ kod asistanıyla yazarken
Sık yapılan hatalar — kontrol edin
Liste ile üyelik testi
Her istekte tam sıralama
Sınırsız büyüyen önbellek
Ters indeksi tutarsız bırakmak
İyi bir istek (prompt) örneği
“C# (.NET 8) arka uç için StackExchange.Redis kullanan bir LeaderboardService yaz: SubmitScore(oyuncuId, skor) sadece daha yüksekse günceller (ZADD GT), GetTop(n), GetRank(oyuncuId), GetAround(oyuncuId, k). Haftalık tablolar için anahtar adına hafta numarası koy ve 8 gün sonra sona erdir. Redis yoksa SortedSet ile çalışan bir bellek içi sürüm ve ikisi için ortak birim testleri ekle.”
Hangi sorguların sık olduğunu yazın (rank mı, aralık mı?)
Ters indeksin güncellenmesini test ettirin
Bellek sınırı ve temizleme politikası isteyin
Sunucu tarafında Redis/SQLite seçeneğini sorun
🚀 Proje ve araştırma önerileri
[kolay]Başarım sistemi — Açılmış başarımlar kümesi ve olay sayaçları (Counter) ile basit bir başarım motoru.
[orta]Etiketli envanter — Ters indeksle “ateş VE kılıç” gibi etiket sorguları.
[orta]Liderlik tablosu servisi — Redis ya da SQLite ile; sıra ve “çevremdekiler” sorguları.
[araştırma]Bloom filtresi — “Bu prosedürel seviye daha önce üretildi mi?” sorusu için Bloom filtresi; yanlış pozitif oranını ölçün.
Sıra gerektiğinde (alfabetik çıktı, aralık, en küçük) hash yerine dengeli BST (SortedDictionary / SortedSet).
Alıştırmalar 🏋️
Kendi oyununuz için 3 dilli bir yerelleştirme CSV’si yazın; eksik çeviri varsa İngilizceye, o da yoksa anahtarın kendisine düşen bir T(key) fonksiyonu yazın.
Bir maç kaydındaki (silah adı listesi) en çok kullanılan 3 silahı bulun: (a) sözlük + sıralama, (b) Counter.most_common.
Envanter için “etiket1 VE etiket2 VE etiket3” sorgusu yazın. Kümeleri hangi sırayla kesiştirmek en hızlıdır?
Harf düzeyinde Markov ile 20 fantastik isim üretin (eğitim verisi: 30 Tolkien/ D&D ismi). Önek uzunluğu 1, 2, 3 için sonuçları karşılaştırın.
Sonsuz bir 2B karo haritasını (x, y) → karo sözlüğüyle tutun. Oyuncu etrafındaki 20×20 alanı çizerken kaç sözlük araması yapılıyor?
🤖 Bir YZ asistanından “Python’da bir küfür filtresi” isteyin. Türkçe karakterleri (I/ı, İ/i) doğru ele alıyor mu? “4ptal” gibi hileleri?
🎮 Bu sunumdaki oyunlar
Adı geçen oyunu tanımıyorsanız: oyuncuların kullandığı wiki sayfası ve oyunu kısaca gösteren bir video. ▶ video slaydın üstünde açılır (Esc ile kapanır); 🎯 işaretli bağlantılar doğrudan bu sunumdaki konuya gider.
Minecraft2011 · kum havuzu
Bloklardan oluşan, prosedürel üretilen, neredeyse sonsuz bir dünya.