Doğrusal arama

Eski bir espride değişik mesleklerin Afrika’da nasıl fil avladığı sorulur. Espriye göre bilgisayarcı aşağıdaki algoritmayı kullanır:

  1. Afrikaya gidilir
  2. En güneyde Ümit burnunda başlanır.
  3. Kuzeye doğru yavaşça ilerlerken kıta doğudan batıya doğru taranır.
  4. Doğudan batıya her taramada aşağıdaki işlemler yapılır:
1. Görülen her hayvan yakalanır.
2. Yakalanan hayvan bilinen bir fil ile karşılaştırılır.
3. Eğer yakaladığımız hayvan fil ise arama biter.

Yazılımlarda sık sık verilen bir listede belli bir elemanın olup olmadığını ararız. Bu işi için de çoğunlukla yukarıdakine benzer algoritmalar kullanırız. Listenin başından başlarız ve her adımda listenin sonuna gelip gelmediğimizi ve o anki elemanın aradığımız eleman olup olmadığını kontrol ederiz. Bu arama işleminde her adımda iki şeyi kontrol ettiğimize dikkatinizi çekmek istiyorum.

  1. Listenin sonuna geldik mi?
  2. O anki eleman aradığımız eleman mı?

Bu meseleyi ilerde tekrar ele alacağım. Şimdilik algoritmanın bu en basit halinin bir sümulasyonunu yapmak istedim. Aşağıdaki linkten simulasyonu deneyebilirsiniz. Simulasyonda aranan sayı 82 olacaktır ve listede bu sayı mevcut. Arama işleminde her adımda yapılan işlemler açıklama alanında gösteriliyor.

Simulasyon linki

Boyer-Moore Algoritması

Boyer-Moore metin arama algoritmasının simulasyonuna da aşağıdaki linkten ulaşabilirsiniz. Bu algoritmanın diğer arama algoritmalarından farkı aranan metin ile ana metin soldan sağa doğru değil de sağdan sola doğru karşılaştırılıyor. Karşılaştırmada uyumsuz bir harf bulunursa daha önceki simulasyonlarda gördüğümüz Good Suffix ve Bad Character kurallarına göre oluşturduğumuz tablolara göre aranan metnin ne kadar kaydırılması gerektiği okunuyor ve aranan metin sağa doğru bu iki sayının daha büyüğü kadar kaydırılıyor. Bu işlem metin bitene ya da aranan metin bulunana kadar devam ediyor.

Simulasyon linki

Good suffix rule (Boyer-Moore algoritması)

Boyer-Moore metin arama algoritması serisinde öğrenmeye çalışacağım ikinci algoritma good suffix rule adı altında biliniyor. Aranan metnin ne kadar sağa kayrıdılacağını belirleyen iki kuralın ikincisi ve diğer yöntemden (bad character rule) biraz daha karışıkmış.

Bad character kuralında sadece uyumsuzluk olan noktadaki karaktere bakmıştık. Bu sefer uyumsuzluğun olduğu noktadan önceki sonek kısmına bakacağım, çünkü aranan metni uyumsuz noktanın sağındaki karakterler de birbirine uymazken, sadece uyumsuzluk olan yerdeki karakterin doğru olacağı şekilde kaydırmak yeterli olmayacaktır. Yani bu şekilde bir kaydırmada aranan metnin bulunamayacağı açıktır.

Örneğin aradığımız metin HAABH ise ve aşağıdaki bir durum oluşmuşsa algoritmamız sağdan sola doğru karakterleri kontrol edecek ve en sondaki BH kısmının uyduğunu ve sonraki konumdaki H ve A karakterlerinin uyumsuz olduğunu görecek.

... A B A B H A A H B H ...
              H A A B H

Bu durumda daha önceden anlattığım bad character kuralı aranan metni sadece iki karakter sağa kaydırmayı teklif edecek, çünkü bir sonraki H karakteri iki karakter daha solda.

... A B A B H A A H B H ...
                  H A A B H

Burada ana metindeki sonekin (BH) aranan metinde eşleşen kısımdaki altmetne (AA) uymadığını kolayca görebiliriz. Demek ki bad character kuralı bize kaydırma miktarıyla ilgili yeterli bilgi vermiyormuş. Bu bilgiyi kullanmanın yolu da sonekin aranan metinde daha solda mevcut olup olmadığını kontrol etmek. Eğer varsa bu daha soldaki sonekle uyuşan kısmı ana metindeki sonekin altına getirecek kadar kaydırmak yeterli olabilir. Tabii ki bu noktada küçük bir optimizasyon yapmayı da düşünebiliriz. Örneğin son ekten hemen sonraki uyumsuz karakteri de aranan metindeki bir sonekle uyuşan bir sonraki altmetnin hemen sonunda arayabiliriz ama burada küçük bir sorun karşımıza çıkacak gibi. Hedefimiz sadece aranan metni kullanarak bu kaydırma miktarlarını hesaplamak, böylece aramaya başlamadan önce bir kereliğine bütün bu hesaplamaları yapabiliriz. Elimizde sadece aranan metin varsa da o uyumsuz noktada (yani sonekin hemen solundaki konum) hangi karakterin olduğunu henüz bilmiyoruz. Evet bu ciddi bir engel ama bunu aşmak için o noktada uyumsuz olabilecek bütün karakterler için bu hesapları bir kereliğine yapabiliriz. Buradaki sorun da hem bu bir kerelik işlemin oldukça uzun sürmesi hem de bütün bu bilgiler için hafızada çok fazla yer tutmamızın gerekmesi olacaktır. Bunun yerine çok daha basit bir bilgiyi kullanarak bazı durumlarda işimize yarayacak bir optimizasyon elde edebiliriz ama. O da, aranan metindeki daha soldaki sonek tekrarını ararken bulduğumuz altmetnin solundaki karakterin aranan metinde uyumsuzluğun olduğu karakterden farklı olup olmadığına bakabiliriz. Eğer aynıysa bu altmetin işimize yaramayacaktır çünkü altmetni kaydırdığımız zaman aynı uyumsuzluğu aynı yerde bir kere daha bulacağız. Örneğin aradığımız metnin BABHAABH olduğunu varsayalım ve aşağıdaki durumu inceleyelim.

... A B A B H A A H B H ...
        B A B H A A B H

Sağdan sola baktığımızda bulduğumuz uyumsuzluk üçüncü konumda (H ve A) ve sonekimiz BH. BH dizisini aranan metinde solda aradığımız zaman bir kere daha buluyoruz ama BH’nın hemen solundaki karakter en sonda olduğu gibi yine A. Bu durumda sonekleri altalta gelecek şekilde kaydırırsak aşağıdaki durumu elde edeceğimiz açık.

... A B A B H A A H B H ...
                B A B H A A B H

Yani aynı konumda yine aynı uyumsuzluğu (H ve A) elde edeceğiz. Bu uyumsuzluğun ana metindeki H karakterinden bağımsız olacağına dikkat edelim. Yani soneki aranan metinde sola doğru ararken bu sonekin hemen solundaki karakterin farklı bir karakter olmasına dikkat etmeliyiz. Aynı karakter ise aramaya devam edeceğiz, farklı ise aradığımız kaydırma miktarını bulduk diyeceğiz.

Bu linkte burada anlamaya çalıştığım algoritma için gerken tabloyu hesaplayan bir simulasyon hazırladım.

Bad Character algoritması

Bu yazı ve algoritma dizisinde de metin arama algoritmalarından Boyer-Moore algoritmasını öğrenmeye çalışacağım. Diğer bazı metin arama algoritmalarında olduğu gibi bu algortimada da aranan metin her adımda tek adım kaydırılmıyor, aksine aranan metnin özelliğine göre kaydırma miktarları optimize ediliyor. Boyer-Moore algoritmasının şimdiye kadar öğrendiğim algoritmalara göre iki farklı özelliği var. Birincisi, aranan metin ve ana metin karşılaştırması sağdan sola doğru yapılıyor. İkincisi de kaydırma miktarını bulmak için iki değişik alt algoritma kullanılıyor. Bu yazıda bu iki algoritmadan biri olan Bad Character algoritmasını anlamaya çalışacağım.

Bu algoritma için aşağıdaki animasyonu programlamaya çalıştım.

Bad Character Rule

Bu animasyon başlatıldığında henüz hazırlanmamış bir tablo ve tablonun üzerinde seçilmiş bir aranan metin gösteriliyor. Tabloda normalde bir metinde olabilecek bütün karakterler olmalı (küçük harfler, büyük harfler, rakamlar vb.) ama sadece fikri gösterebilmek için tabloyu küçük tuttum. Animasyonda ana metni kullanmadım çünkü bu algoritma için ana metin değil aranan metne herhangi bir konumda uymayan bir karakter önemli. Bunu da aranan metnin üzerinde hesaplanacak konumda gösterilen bir karakterle göstermeye çalıştım.

Animasyonda aranan metin olarak

 NNAAMAN

dizisini kullandım. Her tablo seçilen aranan metne göre ayrı oluşturulması gerekiyor. Tabloyu şöyle düşünmeye çalıştım. Aranan metinde ana metni sağdan sola doğru karşılaştırdığımızda ana metindeki ilk uyumsuz karakteri en soldaki sütundan buluyoruz. Ardından bu uyumsuzluğun aranan metindeki hangi pozisyonda olduğunu da birinci satırdan okuyoruz. Algoritma bittiğinde bu tablo bize o karakter ve o konum için aranan metni sağa doğru kaç karakter kaydırabileceğimizi söyleyecek.

Bu tabloyu hazırlamak aslında çok kolay ama bu şekliyle tablo oldukça fazla yer tutmakta, çünkü olası her karakter için tabloda yer harcanıyor ama bu ilk versiyon için bu basit yöntemi anlamak yeterliydi benim için. Literatürde bu sorunla ilgili çeşitli çözümler var ve ileride onlara da bakabilirim.

Tablodan sırayla her satırdaki harf için olası her pozisyonu alıyorum ve bu harfi aranan metnin üzerinde o pozisyonda gösteriyorum. Eğer seçtiğim harfler aranan metindeki harf aynı ise tabloda bu noktaya “-” işareti koyuyorum, çünkü bu algoritma sadece uyumsuzluk varken kullanılıyor. Eğer uyumsuzluk varsa aranan metinde sola doğru harfleri tek tek kontrol ediyorum. Seçilen harfle aynı harfi ilk bulduğum konuma kadar aranan metni sağa doğru kaydırabilirim, çünkü aradaki her durumda bir uyumsuzluğun çıkacağı kesin. Eğer bir harfi uyumsuzluğun çıktığı konumdan sonra bir daha bulamazsam da aranan metni uyumsuzluktan bir sonraki konuma kadar kaydırabilirim. Bu şekilde her karakter ve konum için aranan metni ne kadar kaydırmam gerektiğini tabloya işliyorum. Bu şekilde bu basit algoritma da tabloyu oluşturmuş oluyor.

Knuth-Morris-Pratt algoritması

Bu algoritma failure function yazısında oluşturduğumuz tabloyu kullanarak aradığımız metni uyumsuzluğun olduğu konuma göre değişik miktarlarda kaydırmaktadır. Bu şekilde aranan metni her adımda bir karakter kaydırma algoritmasına göre bir iyileştirme elde edeceğiz.

Aşağıdaki linkle animasyon sayfasına ulaşabilirsiniz. Sayfa yüklendiği zaman aranan metin için daha önceki yazıda tanımlanan tabloyu hesaplayacak. Ardından ana metni ve aranan metni gösterip uyumlu karakterleri yeşil renkle, uyumsuz olan ilk karakteri de kırmızı ile gösterecek. Ondan sonra uyumlu ortak metin uzunluğundan tabloda seçilen sütunu yeşi ile gösterip oradan kaydırma miktarını okuyacak. Aranan metin bu miktar kadar kaydırıldıktan sonra aynı işlemleri tekrarlıyor. Animasyon aranan metin bulunana kadar devam ediyor.

Animasyon

Rabin-Karp algoritması

Bu yazıda metin içinde yazı aramada kullanılan bu algoritmayı basitçe tanıtıp simülasyonunu göstereceğim. Basit yazı arama algoritmasında her adımda aradığımız yazıyı metin üzerinde kaydırıp tek tek aynı pozisyonda aynı harflerin olup olmadığına bakmıştık. Yani aradığımız yazıyı bulana kadar her adımda ilk farklı harfe rastlayana kadar her harfi defalarca karşılaştırmıştık.

Bu algoritmada aynı harfleri defalarca karşılaştırma sorununa ilginç bir çözüm sunuluyor. Rolling hash denen bu algoritmayı daha önceki yazımda ele alıp simülasyonunu vermiştim. Kabaca aynı yazı parçalarının hash değerleri aynı olmalıdır fikrinden yola çıkılarap geliştirilmiş bir algoritma bu rabin-karp algoritması. Tabii ki her adımda kaydırılmış yazı parçalarının hash değerlerini tekrar tekrar her harfi kullanarak hesaplamamak için rolling hash kullanılıyor. Metinde aranan parça ile aynı hash değerini veren bir parça bulunduğunda bu parçanın harflerini tek tek karşılaştırmak gerekiyor çünkü farklı metin parçaları aynı hash değerlerini üretebilir.

Simülasyonda da bunu yapıyorum. Her kaydırmadan kontrol edilen metin parçası gri renkle işaretleniyor. İlk adımda hash değeri bütün harf değerleri üzerinden hesaplanıyor. Ardından her kaydırmada rollin hash algoritması kullanılarak yeni parçanın hash değeri daha hızlı hesaplanıyor. Eğer bir yerde aynı hash değerleri bulunursa bu sefer o alt parçanın bütün harfleri aranan yazının harfleriyle tek tek karşılaştırılıyor. Eğer harfler aynı ise bu harf yeşile boyanıyor, değilse kırmızı. Bütün harfler yeşil ise o zaman aradığımız yazıyı bulduk demektir ve algoritma sonlanıyor.

Bu linkten simülasyonu kullanabilirsiniz.

Basit yazı arama algoritmsı

Uzunca bir metinde bir kelime aradığımız çok olmuştur. Bu aramalar için geliştirilmiş çok çeşitli algoritmalar var. Bu yazıda bu algoritmaların en basitinin simülasyonuyla ilgileneceğim.

Bu algoritmada metnin en başından başlanır ve harf harf kelime ve aranan kelime karşılaştırılır. Eğer karşılaştırılan harfler farklı ise aynı karşılaştırmaya metnin bir sonraki harfinden başlanır. Bu karşılaştırmalar ya metnin içinde bu kelime bulunana ya da metnin sonuna ulaşılana kadar devam edilir.

Simülasyon

MinMax

Daha önceki minimum ve maksimum ile ilgili simülasyonlarda gördüğümüz gibi elemanları sıralanmamış dizilerde yalnızca maksimum elemanı ya da minimum elemanı bulmak için dizinin eleman sayısının bir eksiği kadar karşılaştırma işlemi yapmamız gerekmekte. Bazı durumlarda aynı anda dizinin hem en küçük hem de en büyük elemanını bulmamız gerekir. O zaman her iki elemanı da ayrı ayrı bulmaya kalkarsak dizinin eleman sayısının iki katının iki eksiği kadar işlem yapmamız gerekecek.

Bu simülasyondaki algoritmayla gereken karşılaştırma sayısı sırayla önce en küçük sonra da en büyük elemanı bulmak için gereken karşılaştırma sayısında daha azdır. Bunun için en küçük ve en büyük sayıları beraber güncellememiz gerekiyor. Bu sırada kullanacağımız teknik ise eğer bir adımda kontrol ettiğimiz sayı o ana kadarki en küçük sayıdan daha küçükse o ana kadarki en büyük sayıyla karşılaştırmaya gerek yoktur, daha büyük olma şansı yoktur.

Bu yöntemi kullandığımızda ilk iki sayıyı okuduktan sonra bir karşılaştırma yaparız ve o ana kadarki en küçük ve en büyük sayıları buluruz. Ardından her yeni sayı ile o ana kadarki en küçük sayı karşılaştırılır. Eğer yeni sayı daha küçükse en küçük sayıyı güncelleriz ve dizideki bir sonraki sayıyla devam ederiz. Eğer yeni sayı en küçük sayıdan büyükse o zaman o ana kadarki en büyük sayı ile karşılaştırırız. Yeni sayı en büyük sayıdan büyükse o ana kadarki en büyük sayıyı güncelleriz.

Algoritmanın çalışmasını bu simülasyonda görebilirsiniz. Dizideki yeşil sayı o ana kadarki en küçük sayıyı, mavi sayı da en büyük sayıyı göstermekte.

Bir Kelime

Sonunda JavaScript öğrenme programım altında bir zamanlar televizyon ekranı başında oynadığımız “Bir Kelime Bir İşlem” yarışmasının “Bir Kelime” kısmını da bitirdim. Aşağıdaki linkten oyunu oynayabilirsiniz.

Sayfa ilk yüklendiğinde bütün imla kılavuzu da beraber yükleniyor. Bu şimdilik yaklaşık 1.5 MB yer tutuyor. Bundan sonra sayfa kapatılana kadar bir daha böyle bir yüklemeye gerek yok, yani oyun çevrimdışı da oynanabilir.

Oyun için önce rastgele yedi harf seçmek gerekiyor. Bu seçimi oyuncu ünlü ya da ünsüz olacak şekilde yapıyor. Bu tuşlar solda. Yedi harf de seçildiğinde oyun başlıyor. Oyuncu harflerin üzerine basarak kelimesini renkli alanda oluşturabiliyor. Bu sırada bir tane de joker harf kullanma hakkına sahip. Bu harfi joker tuşuna (ortadaki) bastıktan sonra aşağıda çıkan klavyeden seçerek yapıyor. Oyuncu en iyi kelimeyi bulduğunu düşündüğünde kabul tuşuna basarak çözümünü kontrol ettiriyor. Eğer kelime imla kılavuzunda mevcutsa puan alıyor, değilse alamıyor. Oyunun her anında programa o harflerle yazılabilecek en uzun kelimeler sorulabiliyor. Çözüm tuşuna basıldığında program bulunabilecek en uzun kelimeleri alttaki alanda gösteriyor.

Kullandığım çözüm bulma algoritması da basit bir şey. Önce her harf için bir map oluşturuyorum. Bu map içinde o harfin bulunma adedine göre kelimeleri grupluyorum. Yani bir grup içinde bir adet a harfi bulunan kelimelerden oluşuyor, bir başka grup içinde iki adet a harfi bulunduran kelimelerden. Bu şekilde bütün imla kılavuzunu hazırlıyorum. Oyunda çözüm istendiğinde seçilmiş harflerin N’li (N maksimum harf sayısı olan 7’den 2’ye kadar) kombinasyonlarını oluşturuyorum ve içinde bu harfleri seçildikleri adet kadar barındıran kelime gruplarının kesişimlerini buluyorum. Sonra bu kesişim kümesindeki kelimelerin uzunluklarına bakıyorum. Eğer kelimenin uzunluğu kombinasyondaki harf adedi kadarsa jokersiz, harf adedinden bir fazlaysa bir jokerli çözüm bulunmuş oluyor. Bu çözümlerin hepsini de çözüm kümesine ekliyorum.

 Bir Kelime

Julia kümesi

Mandelbrot kümesini bir çok insan duymuştur. Hani şeklin içine girdikçe büyük şeklin sonsuz tekrarıyla karşılaştığımız fraktal dizaynlar. Konunun matematiğine dalmayacağım, yani çok dalmayacağım. Sonuçta çoğu insan için bu şekilleri anlaşılır bir şekilde üretmenin yolunu javascript yardımıyla göstereceğim.

Öncelikle Julia kümesiyle başlayacağım. Mandelbrot kümesi başka bir yazının konusu olacak. Algoritmik olarak arada çok az bir fark olmasına rağmen matematiksel olarak farklı kümeler bunlar. Olay kompleks düzlemde geçmektedir. Hani kompleks sayılarla oluşturulan düzlem. Kompleks saylar deyince akla tabii ki meşhur \(i=\sqrt{-1}\) tanımı gelir.

Bu kompleks sayıların bir reel bir de sanal kısmı vardır. Yani verilen bir \(z \) kompleks sayısını \(z=a+ib \) şeklinde yazabiliriz. Burada \(a \) ve \(b \) sayıları bildiğimiz reel sayılardır. Aslında bu noktadan itibaren bu kompleks sayıları xy-koordinat sistemindeki noktalar gibi düşünebiliriz. \(a \) sayısı noktanın x koordinatını, \(b \) sayısı da noktanın y koordinatını göstersin. 

Şimdi algoritmamızda verilen bir \(z \) kompleks sayısını (ya da düzlemdeki noktasını) nasıl dönüştüreceğimize bakalım. 

\(f(z) = z^2 + c \)

Burada \(c \) sayısı da kompleks bir sayıdır. Bilmeyenler için kısaca yukarıdaki işlemi reel ve sanal kısımlar cinsinden yazacağım.

\(z = a + i b \) ve \(c = d + i e \) olsun.

\(z^2 = z \cdot z = {a \cdot a – b \cdot b} + i {2 \cdot a \cdot b} \)

\(z^2 + c= {a \cdot a – b \cdot b + d} + i {2 \cdot a \cdot b + e} \)

Şimdi hesap kısmını hallettiğimize göre yaptığımız dönüşümü indeksleyelim, yani her adımda bulduğumuz kompleks sayıya bir sıra numarası verelim.

\(z_{n+1} = z_n^2 + c \)    (1)

Hesapladığımız toplamı bir sonraki adımda yeni kompleks sayı olarak kullanacağız. Her adımda bu kompleks sayının belli bir alanın dışına çıkıp çıkmadığına bakacağız. Bunun için de bu sayının orijine uzaklığını hesaplayacağız.

\(\lvert z \rvert = a \cdot a + b \cdot b \)    (2)

Eğer bu nokta yarıçapı 2 olan dairenin dışındaysa sayının o adımdaki indeks numarasını alacağız ve başlangıçtaki noktamızı bu indekse göre ürettiğimiz bir renkle koordinat sistemimizde işaretleyeceğiz. Bu dönüşümler sırasında bazı başlangıç sayıları her zaman bu dairenin içinde kalabilir. Bu durumda programın devam etmesi için maksimum adım sayısı tanımlayacağız. En basit renklendirme olarak eğer maksimum adım sayısına ulaşılmışsa siyah, aksi durumda beyaz noktalar kullanılabilir.

Algoritma:

  1. \(c \) kompleks sayısı belirlenir. Bu sayı rastgele de seçilebilir ama her değer için güzel şekiller ortaya çıkmıyor.
  2. Çizim yapılacak ekranın her noktası için: Seçilen nokta bir \(z_0 \) kompleks sayısına dönüştürülür.
  3. Her adımda elde edilen kompleks sayı yarı çapı 2 olan dairenin dışına düşmediği ve maksimum adım sayısına erişilmediği sürece (1) numaralı dönüşüm uygulanır.
  4. Seçilmiş nokta için elde edilen adım sayısına göre bir renk üretilir.
  5. Seçilmiş nokta ekran üzerinde bu renge boyanır.

Örnek:

1. \(c=-0.5 + 0.5 i \) olsun.

2. Ekranımız 400 satır ve 400 sütundan oluşsun. Ekranın orta noktası düzlemimizin orijini olsun ve sol üst köşe koordinat sisteminde (-2, 2) noktası, sağ üst köşe (2, 2) noktası, sol alt köşe (-2, -2) noktası ve son olarak da sağ alt köşe (2, -2) noktası olsun. Şimdi ekran piksel konumlarını bu koordinatlara gönderen dönüşümü hazırlayalım.

Ekran  koordinatlarına x ve y diyelim.

\(z = a + bi\longrightarrow{a=\frac{x – 200}{100},b=\frac{y – 200}{100}} \)

Burada ekran noktası olarak x = 225 ve y = 350 alalım. Bu değerleri yukarıdaki dönüşüme koyarsak

\(z_0 =\frac{225 – 200}{100}+\frac{350 – 200}{100}i=0.25+1.5i \) kompleks sayısını buluruz.

3. (1) numaralı dönüşümü uygulamaya başlayalım.

\(z_1=z_0^2-0.5+0.5i=(0.25 + 1.5i)\cdot (0.25+1.5i)-0.5+0.5i \)

\(z_1=0.625-2.25-0.5+(0.75+0.5)i=-2.125+1.25i \)

\(\lvert z_1 \rvert = \sqrt{2.125^2 + 1.25^2}=\sqrt{6.078125}=2.46 \)

demek ki seçtiğimiz nokta daha ilk adımda yarıçapı 2 olan dairenin dışına çıkıyor. Bu durumda adım sayısı 1 oluyor ve bu nokta için 1 indeksli rengi kullanıyoruz. İşin görsel kısmı tamamen bu renk seçimine bağlı. İlk denemelerde oldukça basit bir renk şeması seçtim. Eğer maksimum adım sayısına erişilmişse siyah, aksi durumda beyaz noktalar kullandım. Bu bile yeterli aslında ama sonra daha renkli şekiller oluşturmak için HSV renk uzayını denedim.

Aşağıdaki linkte yazdığım javascript uygulamasını deneyebilirsiniz.

yilmazaksoy.com/apps/Julia.html

Reel(c) ve Sanal(c) parametrelerine istenilen değerleri verdikten sonra Başlat düğmesine basarak şeklin oluşmasını bekleyebilirsiniz. Eğer sisteminizde şeklin ortaya çıkması çok uzun sürüyorsa adım sayısını azaltabilirsiniz. Bu durumda program daha hızlanacaktır ama çıkan sonuçta daha az renk kullanılacaktır.

Örneğin Reel(c) = -0.7 ve Sanal(c) 0.25 için aşağıdaki şekil elde edilebilir.

Download (1)

Program:

Kullanıcı girdileri ve çizimin yapılacağı alanı tanımlamak için aşağıdaki gibi bir blok kullandım. Basit bir uygulama olduğundan hiç CSS de kullanmadım.

[code language=”html”]

<div id=”first”>
Adım sayısı: <input id=”maxIterations” type=”number”step=”1″/>
</div>
<div id=”last”>Reel (c): <input id=”c_real” type=”number”step=”any”/>
Sanal (c):<input id=”c_imaginary” type=”number”step=”any”/>
<button id=”start” onclick=”startDrawing()”>Başlat</button>
</div>
<canvas id=”drawingArea” width=”400″ height=”400″ style=”border:1px solid #000000;”></canvas>

[/code]

Bu giriş elemanlarına ilk değerlerini atamak için sayfa yüklendiğinde init adlı fonksiyon çağrılıyor.

[code language=”javascript”]
<body onload=”init()”>
<script>
var c_real = 0.0;
var c_imaginary = 0.0;
var maxIterations = 300;
function init() {
document.getElementById(“maxIterations”).value = ‘300’;
document.getElementById(“c_real”).value = ‘0’;
document.getElementById(“c_imaginary”).value = ‘0’;
}

</script>
[/code]

Başlat düğmesine basıldığında startDrawing() adlı fonksiyon çağrılıyor:

[code language=”javascript”]

function startDrawing() {
c_real = parseFloat(document.getElementById(“c_real”).value);
c_imaginary = parseFloat(document.getElementById(“c_imaginary”).value);
maxIterations = parseInt(document.getElementById(“maxIterations”).value);
var canvas = document.getElementById(“drawingArea”);
var width = canvas.width;
var height = canvas.height;
var i = 0;
var j = 0;
for ( i = 0; i < width; ++i) {
for ( j = 0; j < height; ++j) {
var value = calculatePoint(i, j, width, height);
var numberOfIterations = checkPoint(value, [c_real, c_imaginary], maxIterations, 4);
plotPoint(i, j, numberOfIterations);
}
}
var img = canvas.toDataURL(“image/png”);
document.write(‘<img src=”‘ + img +'”/>’);
}

[/code]

İlk önce c_real, c_imaginary ve maxIterations değişkenleri kullanıcı girdilerinden okunuyor. İlk iki değer reel sayı, adım sayısı ise tam sayı olduğundan parseFloat ve parseInt fonksiyonları yardımıyla değerler doğru tiplere dönüştürülüyor. Ardından çizim alanımız olan canvas değişkeni okunuyor ve bu alanın satır ve sütun sayısı elde ediliyor. Sonra içiçe döngüyle bu çizim alanının bütün noktaları işleniyor. calculatePoint fonksiyonu ile bulunduğumuz satır ve sütundaki ekran noktası için bir kompleks sayı üretiyoruz. checkPoint() fonksiyonu ile bu kompleks sayının kaç adımda yarıçapı 2 olan dairenin dışına çıktığını kontrol ediyoruz. Son olarak da elde ettiğimiz adım sayısını kullanarak bu noktanın rengini belirliyoruz ve noktayı çiziyoruz.

[code language=”javascript”]
var img = canvas.toDataURL(“image/png”);
document.write(‘<img src=”‘ + img +'”/>’);
[/code]

Bu son iki satır ile canvas’ın içindeki şekli HTML dökümanının içine tekrar yazıyorum ki kullanıcı şekli isterse kaydedebilsin. Normalde masaüstü tarayıcıları bu satırlar olmadan da canvas’ın içeriğini kaydedebiliyor ama akıllı telefonlar ve tabletlerde bu her zaman mümkün değil anladığım kadarıyla.

[code language=”javascript”]
function calculatePoint(x, y, width, height) {
var real = (x – (width / 2)) * 4.0 / width;
var imaginary = (y – (height / 2)) * 4.0 / height;
return [real, imaginary];
}
[/code]

Bu fonksiyon ekran koordinatlarını x ve y koordinatları -2 ve 2 arasındaki reel sayılar olacak şekilde yeni noktalara çeviriyor.

[code language=”javascript”]
function checkPoint(z, c, maxIterations, radius) {
var i = 0;
var p = z;
for ( i = 0; i < maxIterations; ++i) {
p = calculateNewValue(p, c);
if (!isPointInCircle(p, radius)) {
return i;
}
}
return i;
}
[/code]

Bu fonksiyon elimizdeki kompleks sayının kaç adımda dairenin dışına çıktığını buluyor. Tabii ki adım sayısı maksimum adım sayısını geçerse aramayı kesiyoruz. Her adımda bir sonraki kompleks sayıyı üretiyoruz (calculateNewValue) ve noktayı kontrol ediyoruz (isPointInCircle).

[code language=”javascript”]
function calculateNewValue(z, c) {
var realPart = z[0] * z[0] – z[1] * z[1] + c[0];
var imaginaryPart = 2 * z[0] * z[1] + c[1];
return [realPart, imaginaryPart];
}
[/code]

Bu fonksiyon da \(z_{n+1} = z_n^2 + c\) dönüşümünü uyguluyor. Burada kompleks sayıları iki elemanlı javascript dizileri olarak tanımladım (z ve c değişkenleri) bu nedenle [] index operatörü ile reel ve sanal kısımlarına erişebiliyoruz ve aynı şekilde iki elemanlı bir javascript dizisi geri döndürüyoruz.

[code language=”javascript”]
function isPointInCircle(z, radius) {
var norm = z[0] * z[0] + z[1] * z[1];
if (norm < radius) {
return true;
}
return false;
}
[/code]

Bu fonksiyonda aslında normu yukarıda tanımladığım gibi kullanmadım. Karekökü almadım, yerine yarıçapı 2 değil de karesi olan 4 olarak kullandım. Bu şekilde oldukça yavaş olan karekök işlemini optimize etmiş olduk.

[code language=”javascript”]
function plotPoint(x, y, index) {
var c = document.getElementById(“drawingArea”);
var ctx = c.getContext(“2d”);
if (index < maxIterations) {
ctx.fillStyle = HSVtoRGB(index/Math.PI, 1.0, 1.0);
} else {
ctx.fillStyle = HSVtoRGB(index/Math.PI, 1.0, 0.0);
}
ctx.fillRect(x, y, 1, 1);
}
[/code]

Bu fonksiyon da adım sayısına göre noktayı rengiyle beraber çiziyor. Dikkat edilirse nokta yerine büyüklüğü 1 piksel olan dikdörtgen kullandım, javascript’te nokta çizme fonksiyonu yok. Bu istediğime yeterince yakın sonuçlar verdi ama. HSVtoRGB fonksiyonu da adım sayısından canvas’ta kullanılacak RGB renk kodunu üretiyor. Bu fonksiyonun kodunu aşağıdaki 2 numaralı kaynaktan aldım. Sadece son satırını aşağıdaki gibi değiştirdim:

[code language=”javascript”]
return “#” + d2h(Math.round(r * 255)) + d2h(Math.round(g * 255)) + d2h(Math.round(b * 255));
[/code]

Amacım renk kodunu #ff23ff şeklinde RGB formatında elde etmekti. He renk için iki karakter kullanmak gerektiğinden de yardımcı olarak d2h (onluk düzenden onaltılık düzene çevirme) adlı bir fonksiyon kullandım (Fonksiyonu 3 numaralı kaynakta buldum).

[code language=”javascript”]
function d2h(d) {
var s = (+d).toString(16);
if (s.length < 2) {
s = ‘0’ + s;
}
return s;
}
[/code]

Tüm program linkte açılan sayfanın kaynak kodundan alınabilir. Bütün javascript rutinleri de sayfanın içindedir.

Kaynaklar:

  1. http://lodev.org/cgtutor/juliamandelbrot.html
  2. http://stackoverflow.com/questions/17242144/javascript-convert-hsb-hsv-color-to-rgb-accurately
  3. http://stackoverflow.com/questions/17204335/convert-decimal-to-hex-missing-padded-0