Türkçe Metinlerde Tablo Tabanlı ASCII Dönüşümü

Türkçe Metinlerde Tablo Tabanlı ASCII Dönüşümü

Türkçe metinleri deterministik bir karakter tablosuyla ASCII karşılıklarına dönüştüren yöntemi inceler. Unicode davranışı, büyük-küçük harf, performans ve veri kaybı sınırları ele alınır.

Türkçe bir metni ASCII karakter kümesine indirgemek, birkaç harfi karşılıklarıyla değiştirmekten daha geniş bir problemdir. Dönüşümün büyük-küçük harf davranışı, ayraçların işlenmesi, Unicode gösterimi, bellek tahsisi ve aynı girdinin her çalıştırmada aynı sonucu üretmesi birlikte ele alınmalıdır. Paylaştığım C# kodunda bu problem, karakter kodunu doğrudan indis olarak kullanan üç sabit dönüşüm tablosuyla çözülmüştür.

Bu yardımcı katmanı, AI destekli kod üretim araçlarının yaygınlaşmasından önce geliştirdim. Hedefim genel amaçlı ve genişletilebilir bir transliterasyon framework'ü kurmak değildi. Türkçe metinlerin arama, adlandırma ve doğal dil işleme süreçlerinde kullanılabilecek sade bir ASCII karşılığını düşük işlem maliyetiyle üretmek istiyordum. Kodun yapısı da bu tercihi açık biçimde yansıtır. Düzenli ifade, sözlük, zincirleme Replace, kültüre bağlı harf dönüştürme veya çalışma anında oluşturulan eşleme nesneleri kullanılmaz.

Doğrudan adreslemeli dönüşüm

Algoritmanın çekirdeğinde üç karakter tablosu bulunur:

Harf büyüklüğünü mümkün olduğunca koruyan ASCII tablosu

Bütün desteklenen harfleri küçük ASCII harfine indirgeyen tablo

Bütün desteklenen harfleri büyük ASCII harfine indirgeyen tablo

Her tablodaki indis, girişteki char değerine karşılık gelir. İndiste bulunan karakter ise dönüşüm sonucudur. İşlem matematiksel olarak şu biçimde ifade edilebilir:

y = T[x]

Burada x giriş karakterinin sayısal UTF-16 kod birimi, T dönüşüm tablosu ve y çıkış karakteridir.

Bu yapı bir arama sözlüğü kullanmaz. Hash hesaplamaz, anahtar karşılaştırmaz ve karakter türüne göre dallanan uzun bir koşul zinciri yürütmez. char değeri doğrudan dizi indisi gibi kullanılır. Tek karakter için zaman karmaşıklığı bu nedenle Theta(1) değerindedir.

İşlemci düzeyinde tablo erişimi yine sınır denetimi ve önbellek davranışına tabidir. Buna rağmen algoritmik maliyet giriş karakterinden bağımsızdır. Türkçe karakter, ASCII harf, rakam veya noktalama işareti aynı sayıda tablo erişimiyle işlenir.

Üç ayrı tablo kullanılması, harf dönüşümünü ASCII'leştirmeden sonra ikinci bir adıma bırakmaz. Örneğin önce Türkçe karakteri ASCII karşılığına dönüştürüp daha sonra ToLower çağırmak yerine, küçük harf sonucu doğrudan küçük harf tablosundan alınır. Böylece her karakter için ikinci bir dönüşüm ve kültür çözümlemesi gerekmez.

Bu karar Türkçedeki i, I, ı ve İ ilişkisi açısından önemlidir. Türkçe büyük-küçük harf eşlemesi genel ve kültürden bağımsız Latin eşlemesiyle aynı değildir. Unicode standardı da Türkçe için noktalı ve noktasız i eşleşmelerini yerel ayara bağlı özel durum olarak tanımlar. Sabit tablo, uygulamanın istediği ASCII sonucunu kültür bilgisinden bağımsız ve deterministik biçimde kodlar.

Dönüşüm tablosu bir fonksiyon olarak düşünüldüğünde aynı giriş her zaman aynı çıkışı üretir:

T(x) = y

Ancak fonksiyon bire bir değildir. c ile ç, g ile ğ, i ile ı gibi farklı karakterler aynı ASCII değerine indirgenebilir. Bu nedenle ters fonksiyon genel durumda yoktur:

T(x1) = T(x2), x1 != x2

Bu özellik bir hata değil, ASCII'leştirmenin doğal sonucudur. Fakat üretilen değerin özgün metni yeniden kurmak için kullanılamayacağı açık olmalıdır.

Dize dönüşümünün maliyeti

Dize alan üç temel metot aynı işlem düzenini izler. Girdi önce char[] dizisine kopyalanır. Dizi tek geçişte yerinde dönüştürülür. Son aşamada bu diziden yeni bir string oluşturulur.

Uzunluğu n olan metin için işlem sayısı doğrusaldır:

T(n) = a.n + b
T(n) = Theta(n)

Her karakter tam bir kez okunur ve tam bir kez yazılır. İç içe döngü, geri tarama veya metnin başından tekrar başlayan bir arama yoktur.

Ek alan karmaşıklığı da Theta(n) değerindedir. Bunun nedeni çalışma tamponu olarak kullanılan char[] dizisi ve sonuç için oluşturulan yeni dizedir. .NET içindeki string değiştirilemez olduğu için farklı içerik taşıyan yeni bir sonuç nesnesi zaten gereklidir. ToCharArray() çağrısı da kaynak dizedeki UTF-16 kod birimlerini yeni bir karakter dizisine kopyalar.

Bu yaklaşımın önemli bir özelliği allocation sayısının metin uzunluğundan bağımsız olmasıdır. Her karakter için ayrı nesne üretilmez. StringBuilder kapasite artışı, ara string değerleri veya zincirleme Replace sonuçları oluşmaz. Normal akışta bir çalışma dizisi ve bir sonuç dizesi yeterlidir.

Zincirleme karakter değiştirme yaklaşımında metin, dönüştürülecek her harf grubu için yeniden taranabilir. k ayrı değiştirme işlemi bulunan böyle bir çözümün maliyeti yaklaşık olarak şu hale gelir:

T(n, k) = Theta(k.n)

Türkçe için k küçük ve sabit kabul edilirse asimptotik sonuç yine doğrusal görünür. Ancak aynı karakter dizisi birden fazla kez okunur ve her değişiklik yeni metin tahsisine yol açabilir. Tablo tabanlı sürüm ise dönüşüm kümesinin büyüklüğünden bağımsız olarak metni yalnız bir kez dolaşır.

Tam UTF-16 Basic Multilingual Plane alanını kapsayan bir tablo kurulursa her tablo 65.536 char içerir. Bir char 16 bit olduğundan üç tablonun salt karakter verisi için teorik alt sınır şöyledir:

3 x 65.536 x 2 = 393.216 bayt

Paylaşılan kaynakta tabloların tamamının gösterilmediği açıkça belirtilmiştir. Bu nedenle gerçek kapsama alanı ve bellek boyutu bu sürüm üzerinden doğrulanamaz. Yine de tasarımın temel değiş tokuşu açıktır. Daha büyük sabit tablo karşılığında çalışma anındaki koşullar, sözlük nesneleri ve çoklu geçişler kaldırılmıştır.

Snake case durum makinesi

Kodun en yoğun bölümü snake_case üreten metottur. Bu işlem yalnız karakterleri küçültmez. Metni tek geçişte dönüştürür, geçersiz ayraçları birleştirir, baştaki ayraçları atar ve sondaki alt çizgiyi kaldırır.

Algoritma aynı char[] dizisini hem giriş hem çıkış tamponu olarak kullanır. İki indis tutulur:

i = okunacak giriş konumu n = yazılacak çıkış konumu

Her yinelemede i bir artar. n ise yalnız çıkışa karakter yazıldığında artar. Bu nedenle işlem boyunca şu değişmez korunur:

0 <= n <= i <= inputLength

Yazma indisi hiçbir zaman henüz okunmamış girişin önüne geçmez. Bu özellik, ayrı bir ikinci tampon olmadan güvenli yerinde sıkıştırma yapılmasını sağlar.

Karakter önce küçük harf ASCII tablosundan geçirilir. Ardından harf veya rakam olup olmadığı denetlenir. char.IsLetterOrDigit, yalnız ASCII karakterleri değil, Unicode içindeki harf ve ondalık rakam kategorilerini kabul eder. Bu nedenle çıktının kesin olarak ASCII olması yalnız bu metoda bağlı değildir. Dönüşüm tablosunun bütün desteklenen girişleri ASCII değerlerine indirmesi gerekir.

Harf veya rakam olan karakter doğrudan yazılır. Diğer karakterler ayraç adayı sayılır. Durum değişkeni, çıkışın başında bulunulduğunu veya son yazılan karakterin ayraç olduğunu gösterir.

Bu tek bitlik durum şu kuralları uygular:

  1. Metnin başındaki ayraçlar yazılmaz.
  1. Ardışık ayraçlardan yalnız ilki alt çizgiye dönüşür.
  1. İki alt çizgi yan yana gelemez.
  1. Geçerli karakterden sonra gelen ilk ayraç korunur.
  1. İşlem sonunda kalan tek alt çizgi çıkarılır.

Örneğin kavramsal giriş şu yapıda olsun:

[ayraç][kelime][ayraç][ayraç][kelime][ayraç]

Üretilen çıktı şöyledir:

kelime_kelime

Algoritmanın doğruluğu döngü değişmezi üzerinden açıklanabilir. Her adımın başında dizinin ilk n elemanı, işlenmiş giriş önekinin geçerli snake_case sonucudur. Bu bölümde başta alt çizgi yoktur ve ardışık iki alt çizgi bulunmaz. Yeni karakter geçerliyse sona eklenir. Geçersizse yalnız önceki çıktı geçerli karakterle bitmişse bir alt çizgi eklenir. Döngü tamamlandığında tek olası ihlal sondaki alt çizgidir. Son koşul bunu kaldırır.

Bu metodun zaman karmaşıklığı Theta(n) değerindedir. En iyi ve en kötü durumda bütün giriş karakterleri incelendiği için asimptotik maliyet değişmez. Çıktı uzunluğu m için şu sınır geçerlidir:

0 <= m <= n

Alan karmaşıklığı çalışma dizisi nedeniyle Theta(n) değerindedir. Yerinde sıkıştırma, bunun dışında ikinci bir n uzunluklu geçici karakter tamponu gerektirmez.

Sondaki alt çizginin kaldırıldığı bölümde okuma indisi geçici olarak son çıkış konumunu hesaplamak için yeniden kullanılmıştır. Döngü sona erdiği için eski okuma değerine ihtiyaç kalmaz. Bu, ek değişken kullanmadan uzunluğu bir azaltan küçük fakat bilinçli bir uygulama tercihidir.

Unicode sınırları

Kodun hızını sağlayan doğrudan indisleme, desteklenen karakter alanının açık biçimde sınırlandırılmasını gerektirir. C# içindeki char, tam bir Unicode karakterini değil, tek bir UTF-16 kod birimini temsil eder. Değeri U+0000 ile U+FFFF arasındadır.

Basic Multilingual Plane dışındaki bir Unicode kod noktası iki char değerinden oluşan surrogate pair ile temsil edilir. Kod her char değerini bağımsız işlediği için bu tür karakterleri tek bir anlamlı birim olarak göremez. Türkçe Latin harfleri BMP içinde bulunduğundan hedef veri kümesi yalnız Türkçe metin ve yaygın noktalama işaretleriyle sınırlandırılmışsa bu sorun ortaya çıkmaz. Genel Unicode metin işleyicisi olarak kullanıldığında ise Rune düzeyinde bir tasarım gerekir.

Tablo uzunluğu de ayrı bir ön koşuldur:

table.Length > inputChar

Bu koşul sağlanmazsa doğrudan indisleme sınır dışı erişim üretir. Paylaşılan tablolar kısaltıldığı için gösterilen kaynak tek başına çalıştırılabilir tam eşleme olarak değerlendirilemez. Üretim sürümünde desteklenen en yüksek karakter koduna kadar bütün indislerin tanımlanması veya sınır dışındaki değerler için ayrı fallback uygulanması gerekir.

Unicode normalizasyonu daha ince bir sorundur. Görsel olarak aynı metin, önceden birleştirilmiş tek kod noktasıyla veya temel harf ve birleştirici işaret dizisiyle gösterilebilir. Unicode NFC ve NFD biçimleri bu kanonik eşdeğerlik sorununu düzenler.

Tablo tabanlı işlem önceden birleştirilmiş Türkçe harfi doğru dönüştürürken, aynı harfin ayrıştırılmış gösteriminde temel harfi ve birleştirici işareti ayrı ayrı işler. Birleştirici işaret tablosu uygun tanımlanmamışsa kanonik olarak eşdeğer iki giriş farklı sonuç üretebilir. Dış kaynaklardan gelen Unicode metinlerde dönüşümden önce NFC veya tasarlanan politikaya göre NFD normalizasyonu uygulanmalıdır.

NLP ve arama sistemlerindeki yeri

ASCII'leştirme, Türkçe doğal dil işlemede bütün amaçlar için kullanılabilecek kayıpsız bir normalizasyon değildir. Alfabe küçülürken sözcükler arasındaki bazı ayrımlar da kaybolur. ç, ş, ğ, ö, ü ve ı harflerinin temel Latin karşılıklarına indirgenmesi farklı sözcüklerin aynı dizeye dönüşmesine yol açabilir.

Bu kayıp arama toleransı için yararlı olabilir. Kullanıcı Türkçe klavye kullanmadan sorgu yazdığında ASCII anahtar üzerinden eşleşme sağlanabilir. Aynı özellik URL bölümü, dosya adı, teknik kimlik ve sistemler arası sade metin aktarımı için de kullanılabilir.

Makine öğrenmesi tarafında ise kullanım amaca bağlıdır. ASCII'leştirme sözlük boyutunu azaltabilir ve eski veri kümelerindeki yazım farklılıklarını birleştirebilir. Buna karşılık Türkçenin ayırt edici harflerini silerek modele verilen dil bilgisini azaltır. Bu nedenle özgün metni silmek yerine iki gösterimi birlikte tutmak daha güvenli bir veri tasarımıdır:

original_text normalized_ascii_text

ASCII karşılığı arama ve eşleme anahtarı olarak kullanılabilir. Dil modeli, adli kayıt, hukuki belge veya yeniden üretilebilir analiz için özgün metin korunur.

Üretilen snake_case değeri de tek başına benzersiz kimlik değildir. Farklı girişler aynı sonuca dönüşebilir. Değer veritabanı anahtarı, dosya kimliği veya URL slug olarak kullanılacaksa çakışma denetimi, sayısal ek veya içerikten türetilen kısa bir kimlik gerekir.

Bu kodu geliştirirken benimsediğim yaklaşım, metin işlemede genel amaçlı soyutlamanın her zaman gerekli olmadığını gösterdi. Girdi alanı biliniyor, çıktı politikası sabit ve gecikme önemliyse doğrudan adreslemeli tablo oldukça etkili bir çözümdür. Üç dönüşüm tablosu harf eşleme ve büyük-küçük harf politikasını tek erişimde birleştirir. İki indisli sıkıştırma ise snake_case üretimini ayrı tampon ve düzenli ifade olmadan tamamlar.

Algoritmanın gücü, karmaşık görünmesinden değil, işlem sınırlarının açık olmasından gelir. Her karakter bir kez işlenir. Çıktı uzunluğu giriş uzunluğunu aşmaz. Ayraç kuralları tek bitlik durumla korunur. Aynı girdi aynı sonucu üretir. Unicode kapsamı ve geri döndürülemezlik sınırı doğru tanımlandığında bu yapı, yüksek trafikli metin işleme yolları için ölçülü ve öngörülebilir bir çözüm sunar.

Bu sayfanın QR kodu