4 Ocak 2009 Pazar

Ruby güzel bir program

Ruby nedir?
Ruby, 1990'ların ortalarında Japonya'da ortaya çıkmış, nesne yönelimli bir programlama dilidir.C++, Java gibi diğer birçok dilin aksine herşey, ama herşey birer nesnedir(int, float da dahil).Herşeyin nesne olması gibi birçok özelliğini ilk dinamik nesne yönelimli programlama dili olan Smalltalk'tan almıştır(dinamiklik konusuna umarım daha sonra gelicez) ve perl'le de syntax açısından çeşitli benzerlikleri bulunmaktadır.Derlenen değil de, yorumlanan bir dil olduğu için
hız konusunda bi miktar yavaş kalır ama 1.9 sürümünden itibaren gelen bytecode compiler ile bir miktar hızlanmıştır.Ayrıca rubyforge.org adresinde de birçok kütüphane bulunabilir

Ruby Kurulumu
Windows altinda ruby kurulumu, normal bir program kurulumu kadar basittir. www.ruby-lang.org/tr/ adresine gidin ve hafiften türkçe olan sitedeki(daha tam çevrilebilmis degil ) "Download Ruby" linkine tiklayin. eger windows altindaysaniz ruby on windows kismindan "One-Click Installer" i indirin. Bu indirdiginiz kurulumu çalistirip yükleyin.yükledikten sonra ruby kurulumunun path'e eklenmesi için yeniden baslatmaniz gerekir. o yüzden kurulumdan sonra bir yeniden baslatmayi tavsiye ederim.
Eger linux altindaysaniz, kullandiginiz dagitimin paket yöneticisinde ruby geliyordur, oradan yükleyiverin.dikkat edin bazi dagitimlarda irb ruby ile birlikte gelmiyor(ubuntuda ayriydi mesela) o durumda irb paketini de yükleyin.tabi ben illa ki derleyip öyle yükleyecem de diyebilirsiniz. cidden böyle diyorsaniz muhtemelen ne yapmaniz gerektigini internetten arayip bulabileceksinizdir.

Ruby Editörleri
Windows için olan kurulum dosyasinda ruby ile birlikte SciTe adli editör de kuruluyor. syntax highlighting, code folding gibi temel özellikleri sunuyor. fena degil. onun disinda windows için Nodepad++, linux için KEdit ve GEdit ruby destekliyor(SciTe ile ayni özellikler). bu özelliklere sahip bir de Geany var rastladigim ruby destekleyen. bunlarin disinda, textmate var ancak sadece mac os x destekliyor ve ücretli. bu yüzden ben sadece bakip özenmekle yetiniyorum. textmate'te ayrica code snippet destegi de var.ancak bu kadari yetmez, class browser filan da lazim diyorsaniz netbeans ve eclipse pluginler araciligiyla ruby destekliyorlar ve benim simdiye kadar kullandigim en iyi ruby IDE'leri(özellikle eclipse) ve bunlari tavsiye ederim.

Ruby Kütüphaneleri
Ruby, özellikle son yillarda oldukça popüler bir dil haline gelmesi sebebiyle bir çok güzel kütüphaneye kavustu.Bu kütüphaneler, genelde(az sayida istisna disinda) http://rubyforge.org adresinde bulunuyorlar ve bu kütüphaneleri yüklemenin çok kolay bir yolu var. Rubygems
Az önce kurulumda path e eklenmesinden bahsetmistim. ruby ile birlikte bir miktar daha çalistirilabilir dosya ekleniyor. bunlardan biri de "gem". gem, ruby kütüphanelerini yükleyip güncellememizi sagliyor. "gem install" paket yüklememizi, "gem update" de varolan paketleri güncellememizi sagliyor. eger update i baska bir argüman olmadan kullanirsak varolan bütün paketleri günceller.
örnegin;
Kod:
gem install asd
"asd" gem ini yükler
Kod:
gem install zxc zbdk
"zxc" ve "zbdk" gemlerini yükler
Kod:
gem update
yüklü olan ve güncellesmesi gereken tüm gemleri günceller.
Kod:
gem update asd
sadece "asd" gemini günceller
Kod:
gem list
yüklü olan tüm gemleri listeler
Kod:
gem outdated
güncellenmesi gereken gemleri listeler
Kod:
gem help
yardim zibidiklarini gösterir

bu arada, unix türevleri altinda gem kurulumu yaparken root yetkilerine sahip olmaniz gerektigini tahmin etmissinizdir herhalde.

bu kadar kurulum isi yeter, simdi derslerimize devam edebiliriz.

Exception'lar
Exception'lar, yani kuraldisi durumlar, bir hata durumunda ortaya çikar ve eger ilgilenilmezse hata verirler. Ruby'deki exception'lar StandardError sinifindan türetilir. Kendiniz bir exception olusturmak için "raise" keywordünü kullanabiliriz. raise ile birlikte bir exception sinifi ve istersek bir mesaj verebiliriz.ayrica eger exception sinifi belirtmezsek RuntimeError olur. mesela;
Kod:
def karekok(sayi)

if sayi < 0
raise "0'dan kücük sayi olmaz!"
end
Math.sqrt(sayi)
end
seklinde bir fonksiyon yazip o fonksiyona 0'dan küçük bir sayi gönderirsek bir RuntimeError gönderir.Ayrica, baska bir fonksiyonun gönderdigi exception'la ilgilenmek için begin..rescue..end blogunu kullanabiliriz.Mesela daha önceki bir derste yazdigimiz Karmasik sinifini kullanarak bir karekok fonksiyonu yazmak istersek(0'dan küçük sayilarin karekoku sanal sayidir) bunu kullanabiliriz.Bu arada Math.sqrt eger 0'dan küçük bir sayi verilirse Errno::EDOM hatasi verir.
Kod:
def karekok(sayi)

begin
sonuc = Math.sqrt(sayi)
rescue Errno::EDOM
sonuc = Karmasik.new(0,Math.sqrt(-sayi))
end
sonuc
end

Test::Unit
Test::Unit, yazdigimiz kodu otomatik olarak test edebilmemize olanak sagliyan ve ruby kurulumuyla birlikte gelen bir kütüphanedir.Günümüzdeki programlama tekniklerinde, otomatik testler çok önemli bir yer tutar.Genelde ilk olarak testler yazilir, sonra o testleri geçecek kod yazilir.
Test::Unit'i kodda kullanmak için, 'test/unit' kütüphanesini require etmek gerekir.
Test::Unit'de, testler Test::Unit::TestCase sinifindan türetilmis bir sinifa yazilir ve bu sinifin "test_" ile baslayan tüm instance metodlari test metodu olarak kabul edilip çalistirilir.
Bir seyi test ederken "assert" komutu ve yine "assert_" ile baslayan bir grup komut kullanilir.
assert(boolean) => verilen argümanin true olup olmadigini kontrol eder
assert_equal(beklenen, gercek) => beklenenin gerceke esit olup olmamasini test eder
assert_not_equal => assert_equal'in tersi
assert_nil => argümanin nil oldugunu test eder
assert_not_nil => assert_nil'in tersi
assert_instance_of(beklenen, gercek) => gercek'in beklenen siniftan olup olmadigini test eder
assert_respond_to(mesaj,nesne) => nesnenin mesaja cevap verip vermedigini test eder
assert_raise(exception){ blok } => verilen blokta belirtilen exception'un raise edilip edilmedigini test eder.
assert_nothing_raised(exception){ blok } => assert_raise'in tersi

ayrica, bir TestCase'in setup ve teardown metodlari bulunabilir. Setup, testler yapilmadan önce test verilerini yaratmak için kullanilir. teardown ise, gerekirse bu test verilerini yok etmek için kullanilir.

mesela, çok basit bir TestCase yazalim. bu, ayri bir sinifi test etmiyor.örnek amaçli gibi
Kod:
require 'test/unit'


class ZirtTest < Test::Unit::TestCase #isimler konusunda çok yaraticiyimdir ^^
def setup
@sayi1 = 3
@sayi2 = 4
end

def test_true
assert true
end
def test_topla
assert_equal 7, @sayi1+@sayi2
end
def test_nilcan
assert_not_nil @sayi1
end
def test_exception
assert_nothing_raised{ @sayi1 + @sayi2 }
end
def test_eksi_karekok
assert_raise(Errno::EDOM){ Math.sqrt(@sayi1-@sayi2) }
end
end
simdi eger bu testi bir konsolda çalistirirsaniz söyle bir çikti almaniz olasidir:
Alıntı:
D:\progz\ruby>zirt_test.rb
Loaded suite D:/progz/ruby/zirt_test
Started
.....
Finished in 0.0 seconds.

5 tests, 5 assertions, 0 failures, 0 errors
TestSuite'ler
TestSuite'ler, sadece bir test grubudur ve birden fazla test sinifini ayni anda çalistirmamiza yarar. bir nevi kisayoldur yani. bir örnekle daha rahat açiklanabilirler bence.mesela bir programimizda ZartTest, ZurtTest ve ZirtTest adli üç tane test olsun. biz de bütün testleri bir suite'te toplayalim

Kod:
require 'test/unit/testsuite' #TestSuite için gerekli dosya

#Testlerimiz
require 'zart_test'
require 'zurt_test'
require 'zirt_test'

class Tests
def self.suite
suite = Test::Unit::TestSuite.new("testcanlar") #buradaki argument testSuite'imizin ismi
suite << ZartTest.suite
suite << ZurtTest.suite
suite << ZirtTest.suite
suite
end
end
burada, suite adli sinif metodu calistirilir ve bu bir testSuite döndürür.bu dosya çalistirildiginda belirttigimiz 3 test sinifi ardarda çalistirilacaktir.

Rake
Rake, Jim Weirich(baba adamdir ) tarafindan gelistirilmis bir library olup, bir çesit makefile kütüphanesidir. Ruby'de derleme filan yok, make ne alaka? derseniz, çesitli görevleri otomatiklestirmek içindir. mesela testleri çalistirmak, koddan dokümentasyon olusturmak, eger varsa svn/git sistemlerine kodu göndermek falan filan.Ayrica sadece ruby degil, baska programlama dilleri için de kullanilabilir.yani java veya c++ dosyalariniz için de kullanilabilir bu görevleri yapmak amaçli.

Rake, standart ruby kurulumu ile gelen bir kütüphane degildir.Bu nedenle, rake gem'ini ayrica yüklememiz gerekir.bunun için bir konsolda 'gem install rake' yazmaniz yeterlidir.Bu arada, ruby'nin yakin bir zamanda çikacak olan 1.9 sürümünde rake standart olarak yüklü gelmektedir




Rake, bu görevleri tanimlamak için bir tür DSL(domain spesific language) kullanir. Görev tanimlamak için, task metodu kullanilir, bu metoda ismi ve bir blok geçirilir.
mesela;
Kod:
require 'rake'


task 'merhaba' do
puts "merhaba!!"
end
bu dosyayi Rakefile adiyla(uzantisiz) kaydettigimiz klasörde 'rake merhaba' komutunu verirsek merhaba görevini çalistiracak ve ekrana "merhaba!!" yazdiracaktir.
Rake görevleri, birbirine bagimli olabilir.mesela bir görev yapimadan önce, baska bir görevin gerçeklestirilmesi gerekebilir.mesela kurulumdan önce testlerin yapilmasi gibi.Bunun için task metoduna isim yerine tek elemanli bir hash veririz.bu hash'in anahtari görevin ismi, degeri ise bu görevden önce gerçeklestirilmesi gereken görevlerdir.ayrica bu deger bir array olabilir ve bu sekilde birden fazla görev belirtebiliriz.
örnegin;
Kod:
task 'hoscakal' => 'merhaba' do

puts "güle güle!!"
end
seklinde bir görevi Rakefile'imiza ekleyip 'rake hoscakal' komutunu verirsek ekrana önce "merhaba!!", sonra da "güle güle!" yazdiracaktir.
ayrica, bu görevlere bir açiklama eklemek de oldukça kolay.bunun için task'tan önce desc metodunu kullanabiliriz.yani az önceki hoscakal görevini söyle yazabiliriz;
Kod:
desc "Önce merhaba, sonra da güle güle der"

task 'hoscakal' => 'merhaba' do
puts "güle güle!!"
end
Simdi biraz daha ise yarar bir görev yazalim.testleri otomatik çalistirma.test/tests.rb adli dosyada Tests isimli bir testSuite'in oldugunu varsayiyorum.
Kod:
desc "testleri require eder'

task 'loadtests' do
unless defined? Tests
require 'test/tests'
end
end

desc "Bir konsolda testleri çalistirir"
task 'test' => 'loadtests' do
Test::Unit::UI::Console::TestRunner.run(Tests)
end

desc "Grafiksel bir arayüzde testleri çalistirir"
task 'guitest' => 'loadtests' do
Test::Unit::UI::GTK2::TestRunner.run(Tests)
end
artik 'rake test' veya 'rake guitest' diyerek testlerimizi çalistirabiliriz.


Ornekcan
Ee bir süredir geleneksel hale getirdigimiz her dersin sonunda bir örnek yapma zibidigina devam ediyoruz.Bu sefer basit bir oyunumsu yazacagiz.ilk olarak testlerimizi yazalim.Bunun için bir oyuncunun sahip olacagi metodlari dusunmemiz gerekir. Oyuncu sinifinda, saglik, zirh ve saldiri özellikleri ve bunlarin okuyuculari olmali. ayrica bir olu? metodu oyuncunun ölüp ölmedigini kontrol etmeli.son olarak da, saldir metodu baska bir oyuncuya saldirabilmeli.Bu saldirida savunanin alacagi zarar, saldiranin saldiri gucu/ savunanin zirhi olmali.Bu sekilde saldirma fonksiyonunu test edebiliriz.

Kod:
require 'test/unit'

require 'oyuncu'

class OyuncuTest < Test::Unit::TestCase
def setup
#Argümanlar sirayla saglik, zirh, saldiri
@oyuncu1 = Oyuncu.new(100,1,10)
@oyuncu2 = Oyuncu.new(50,2,15)
@bahtsiz = Oyuncu.new(10,0.5,1) #cidden bahtsiz :D
end
def test_saldir
@oyuncu2.saldir(@oyuncu1)
@oyuncu1.saldir(@oyuncu2)
assert_equal 85, @oyuncu1.saglik
assert_equal 45, @oyuncu2.saglik
end
def test_geber
@oyuncu1.saldir(@bahtsiz)
assert @bahtsiz.olu?
end
def test_geberik
saglik = @oyuncu2.saglik.to_s.to_i #degerini atadik, referansini degil
@oyuncu1.saldir(@bahtsiz) #oldugunu garantileyelim :p
@bahtsiz.saldir(@oyuncu2)
assert_equal saglik, @oyuncu2.saglik #olu arkadas saldiramaz
end
end
simdi, oyuncu sinifimizi yazabiliriz.
Kod:
class Oyuncu

attr_reader :saglik, :zirh, :saldiri

def initialize(saglik,zirh,saldiri)
@saglik,@zirh,@saldiri = saglik,zirh,saldiri
@olu = false
end

def saldir(o)
unless self.olu? o.olu?
o.savun(@saldiri)
end
end
def savun(miktar)
@saglik -= miktar/@zirh
if @saglik <= 0
self.ol!
end
end

def olu?
@olu
end

private
def ol!
@olu = true
end
end
su anda bu kodun testleri geçmesi gerekli.zaten artik bu kadar dersten sonra bu kodu anlayabilmeniz lazim.
Bu arada, initialize fonksiyonunda alacagi 3 argümanin sayisini hatirlamayabiliriz(oluyo arada, ayrica zaman zaman 3'ten çok daha fazla argüman gerekebilir). bu durumda, fonksiyona bir hash geçirerek bunu halledebiliriz.bunun için, initialize fonksiyonunu biraz degistirelim
Kod:
class Oyuncu

def initialize(*args)
if args.first.is_a? Hash
opts = args.first
@saglik, @zirh, @saldiri = opts[:saglik], opts[:zirh], opts[:saldiri]
else
@saglik, @zirh, @saldiri = args
end
end
end
Bu sekilde, Oyuncu'yu "Oyuncu.new(:saglik => 31, :zirh => 3, :saldiri => 42)" seklinde yaratabiliriz.bunu da testimize ekleyebiliriz
Kod:
class OyuncuTest < Test::Unit::TestCase

def test_keyword_init
kw = Oyuncu.new(:saglik => 31, :zirh => 3, :saldiri => 42)
assert_equal 31 kw.saglik
assert_equak 3 kw.zirh
assert_equal 42 kw.saldiri
end
end
Testler güzel seylerdir, onlari sevin . ayrica testlerin yeterince kapsamli olup olmadigini anlamak için, kendinize "bu kodu testlerin hala geçmesini saglayacak durumda bozabilir miyim?" diye sormalisiniz, eger kodu bozabiliyorsaniz ve testler hala geçiyorsa, testler yeterli degildir.Ve kodunuzun kolay test edilebilmesi için kodun islemsel, mantiksal kisminin görsel kismindan kesin bir sekilde ayri olmasi gerekir.
Merhaba Dünya!
**Bunu yazmadan geçmek mümkün değil**
Perl'ün "Bir işi yapmanın birden fazla yolu var" anlayışına (bir yere kadar) sahip olan ruby'de tabi ki ekrana "Merhaba Dünya!" yazdırmanın birçok yolu var.
Kod:
print "Merhaba Dunya!"
Kod:
puts "Merhaba Dunya!"
Kod:
p "Merhaba Dunya!"
Kod:
$stdout << "Merhaba Dunya!"
Bu kadar yolun farkı ne diyorsanız, print ve $stdout << yazdırılan şeyin sonuna yeni satır karakteri eklemezken, puts ekliyor. p ise raw bir şekilde ekrana yazdırdığından dolayı bu örnekte başına ve sonuna tırnak işareti koyuyor.

Nesneler ve Mesajlar
Daha önce de bahsettiğim gibi ruby'de herşey bir nesnedir ve bunlara çeşitli mesajlar gönderilebilir ("mesaj gönderme", diğer birçok dilde metod çağırma olarak geçer").Örneğin;
Kod:
-3.abs
satırında Fixnum sınıfına ait bir nesneye "abs" mesajını gönderiyoruz, bu Fixnum sınıfının abs fonksiyonunu ve bu sayının mutlak değeri olan 3 ü elde ediyoruz.Ruby'de bir fonksiyon çağırırken eğer argument yollamıyorsak paranteze ihtiyaç yoktur, dolayısıyla sanki bir değişkene erişiyormuş gibi fonksiyon çağırabiliriz.

Kod:
3 + 5

=> 8
bu satırda da aslında 3 nesnesine "+" mesajını 5 argümanıyla yolluyoruz ve 8 sonucunu alıyoruz.Yani operatörler de aslında mesaj gönderiyor

Bir başka örnek;
Kod:
a = [1,3,true,["asd",3.5]].size

=> 4 # Bu "#" işareti ruby'de yorum karakteridir ve o satırın sonuna kadar geçerlidir.
#Ayrıca => işareti ile belirttiğim şey yukarıdaki işlemin sonucudur.
Ruby'de dynamic typing sayesinde değişken tanımlamaya gerek yoktur.Direkt olarak bir değişkene atama yaparsanız o değişken tanımlanmış olur.Ayrıca yine bu nedenle listelerde veya fonksiyon argümanlarında sınıf kısıtlaması yoktur.

Bir örnek daha;
Kod:
3.times { puts "asd" }
Bu kod, ekrana 3 kez "asd" yazdırır.Burada 3'e times mesajını bir blokla yolluyoruz.Bir blok, bir ruby kodu parçasıdır ve küme parantezleri yada do...end anahtar sözcükleriyle sınırlandırılır.Bloklar, güvenin bana, gerçekten çok yararlıdırlar.

Kontrol Yapıları
ruby'de de if ler diğer bir çok dildeki gibidir
örneğin;
Kod:
if 3 + 5 == 8

puts "heyoo!!"
end
ekrana tahmin edebileceğiniz gibi "heyoo!!'" yazdırır.C syntaxlı dillerden farklı olarak if teki ifadenin etrafına parantez koymak gereksizdir(ki döngü vs. lerde de gerek yoktur).Diğer bir çok kod bloğu gibi if ler de "end" ile bitirilir.
ayrıca, "unless" keywordü de "if not" anlamına gelir ve eğer verilen ifade yanlışsa o bloktaki kodu çalıştırır

if vs. için bir diğer kullanım şekli de;
"ifade if koşul" biçimindedir.Örneğin;
Kod:
puts "heyoo!! if 3 + 5 == 8
bir üstteki if ile aynı sonucu verecektir.Bu yöntem unless, while gibi şeyler için de kullanılabilir

Döngüler
Ruby'de de while döngüsü bulunur ve diğer dillerdeki while lara benzer.Sadece bir örnekle geçiştireceğim bunu;
Kod:
a = 1

b = 5
c = 3
while a < b
c += 1
a += 1
end
c
=> 7
burada pek görülecek birşey yok

ruby'de for döngüsü de bulunur ancak aslında bu döngüye hiç ihtiyaç yoktur.Çünkü for döngüsünün 2 temel kullanım amacını karşılayan farklı yöntemler vardır.bir şeyi belli bir kez yapmak için int'lerin "times" metodu kullanılır ki yukarıda bahsedip örnek göstermiştim.Ama hadi yeni bir özellik göstererek bir örnek daha veriyorum;
Kod:
sonuc = 0

5.times do |i|
sonuc += i
end
sonuc
=> 10
burada, bu blokta "|" karakterleri arasında verdiğimiz i, bu bloğun argümanıdır ve "times" metodu her dönüşünde 0'dan başlayarak bir sayı döndürür bu argümana ancak bu argümanın oraya konulması zorunlu değildir.

for döngüsünün 2. kullanım amacı olan array'lerin her elemanıyla birşeyler yapma ise, Array vb. sınıflarda bulunan "each" metoduyla halledilir.Örneğin;
Kod:
arr = [ 1,2,3,4,5,6,7 ]

arr.each do |a|
p a**2
end
bu kod, arr arrayinin tüm elemanlarının karesini ekrana yazdırır(** operatörü kuvvet alma işlemi için kullanılır). each metodu bir blok alır ve argüman olarak o arrayin o sıradaki elemanını verir.
Değişmezler
Değişmezler(sabitler) bir tür değişken gibidir ama çalışma esnasında değiştirilemez, sabit bir değeri tutarlar.Ruby'de çok basittirler.Eğer bir değişkenin ismi büyük harfle başlıyorsa o değişken sabittir.
Kod:
Sabit = 42

puts Sabit**2
Sabit += 2
"Sabit += 2" satırında sabit bir terimi değiştirmeye çalıştığımızdan hata verir.

Hash ler
"Hash" sınıfı, map, hashtable, dictionary gibi isimlerle de bilinen, anahtar-değer çiftlerinden oluşan bir veri yapısıdır.Bir nevi Array lere benzerler ama index olarak istediğimiz her sınıftan şey kullanabiliriz(yeterki bu nesne "hash" mesajına cevap versin -ki muhtemelen stringden başka kullanmanıza pek gerek olmayacak). Hashler, bloklar gibi küme parantezi arasında belirtilir ve virgülle ayrılan anahtar-değer çiftleri anahtar => değer şeklinde gösterilir.Ayrıca, hash lerin each metodu da anahtar, değer şeklinde 2 değer döndürür her dönüşte

Örneklerle;
Kod:
h = { "isim" => "kuzux, "yer" => nil, "hede" => "hodo" }
Eleman erişimi
Kod:
h[:isim]

=> "kuzux"

h["asd"] = "dsa"
Yukarıdaki kod parçasında, yeni, ufak bir kavram görüyoruz Semboller. iki nokta işaretiyle başlayan Semboller, bir çeşit stringdirler ama daha az metod içerirler ve hafızada saklanmaları farklıdır.Her bir String nesnesi, aynı değerde başka bir string olmasına bakılmadan hafızada yeni bir yerde saklanırken, semboller eğer aynı değerde hafızada başka bir sembol varsa ona referans eder.Tamam karışık görünüyor ama çok da önemli değil zaten semboller "to_s" metoduyla stringe, stringler de "to_sym" metoduyla sembole çevrilebilir

Her çift üzerinden döngü
Kod:
h.each do |anahtar, deger|

puts "#{anahtar}: #{deger}
end
Yukarıdaki kod, daha önceki 2 örnekten sonra çalıştırılırsa,
isim: kuzux
yer: nil
hede: hodo
asd: dsa
çıktısını üretecektir ancak sıralama böyle olmayabilir çünkü hash ler verileri sıralı bir şekilde tutmazlar.
Ayrıca, yukarıda stringin içine değişken yerleştirilmesini görüyorsunuz.string içinde #{} arasına istediğiniz bir değişkeni veya ruby kodunu koyup çalıştırabilirsiniz.Ancak, bu sadece çift tırnak arasına yazılmış stringlerde olur, tek tırnakta olmaz.

Fonksiyonlar
yaklaşık her dilde olduğu gibi ruby'de de fonksiyonlar vardır.olmasa cidden şaşılması gerekti zaten.Java, c# gibi dillerin aksine, ruby'de her fonksiyonun bir sınıf içine yazılması gerekmez.bir sınıf içine yazılmayan her fonksiyon, aslında bir sınıfa aittir ama bu otomatik olarak halledilir. Fonksiyonlar "def" keyword ü ile belirtilir.
Kod:
def merhaba(isim)

puts "Merhaba, #{isim}"
end
bu fonksiyon, görülebileceği şekilde bir argüman alıyor ve ona merhaba diyor

Fonksiyonlardan deger dondurme, diger bircok dildeki gibi "return" komutuyla yapılır.örneğin,
Kod:
def kare(sayi)

return sayi ** 2 # ** operatörü kuvvet alma anlamına gelir
end
ancak, ruby'nin bir güzelliği daha, fonksiyonlardan değer döndürmek için asalında return komutuna gerek yoktur.Fonksiyonda son yapılan işlemin sonucu fonksiyonun değeri olarak döndürülür.Böylece, yukarıdaki fonksiyonu şöyle de yazabiliriz;
Kod:
def kare(sayi)

sayi ** 2
end
fonksiyon aynı sonucu verecektir

Bir fonksiyonun argümanlarına varsayılan değerler verebiliriz.Mesela;
Kod:
def merhaba(isim="kuzux")

puts "Merhaba, #{isim}"
end
fonksiyonunu argümansız olarak çağırdığımızda, ekrana "Merhaba, kuzux" yazacaktır

Ayrıca, bir fonksiyonun son argümanının başında * varsa, o argüman sayısı belli olmayan argümandır.
Kod:
def merhaba(*isimler)

isimler.each do |isim|
puts "Merhaba, #{isim}"
end
end
şeklinde bir fonksiyon yazdığımızda ve merhaba("ahmet","mehmet","asd") dediğimizde ekrana
Merhaba, ahmet
Merhaba, mehmet
Merhaba, asd
yazdıracaktır(iyice çocuk programına döndü bu da )

Bu tür fonksiyonlarda değişken sayıda argüman alan kısım, eğer hiç argüman verilmemişse boş bir Array olur.
Kod:
def merhaba(*isimler)

if isimler.empty?
isimler << "kuzux"
end
isimler.each do |isim|
puts "Merhaba, #{isim}"
end
end
Burada gördüğümüz 2 yeni şey var.Array(ve hash vs.) lerin "empty?" metodu, o array in boş
olup olmadaığını gösterir(ve evet, fonksiyon isimlerinde "?" ve "'!" bulunabilir.Ve de array'lerin "<<" operatörü, o arrayin sonuna bir eleman ekler.Yukarıdaki örnekteki fonksiyonu artık argümansız çağırırsak, ekrana "Merhaba, kuzux" yazdıracaktır.

Ayrıca, daha önce de gördüğümüz gibi, bir fonksiyonun son argümanı bir blok olabilir.Bunun için, son argümanın başına bir "&" koymamız yeterli(aslında koymadan da olur ama koyarsak blok gelmek zorunda, başka bir tür değişken olmaz)
Kod:
def ifcan(kosul,&blok)

if kosul
blok.call
end
end
bu fonksiyon da eğer verilen koşul doğruysa, verilen bloğu çağırıyor.
Kod:
ifcan(3<5)> 
ekrana gayet "heyoo!" yazdırır.

Yield
Aslında bunu geçen derste bloklarla birlikte göstermem gerekiyordu ama unutmuşum. "yield" komutu, fonksiyona eğer bir blok verilmişse, onu çağırır. yield için çağıracağımız blokun argüman olarak belirtilmesi gerekmez.geçen dersteki "ifcan" fonksiyonunda bloku sadece çağırdığımız, onunla başka bir iş yapmadığımız için yield kullanabiliriz. yani yield blok kullanmanın kısayoludur, ama bloku sadece çağırabilirsiniz, başka bir fonkisyona gönderemezsiniz
Kod:
def ifcan(kosul)

if kosul
yield
end
end
bu da ifcan fonksiyonunun yield ile yazılmış hali.
ayrıca, yield ile bloka değer de yollanabilir. mesela;
Kod:
def iki_yolla

yield 2
end

iki_yolla{|x| x+1}
# => 3
burada da yield ile bloka bir argüman yollamayı görüyoruz.

Modüller
Modüler, fonksiyon ve sınıfları gruplamak için kullanılırlar. Diğer programlama dillerinde "namespace" olarak da geçiyor olabilirler.
bir örnek;
Kod:
module Asd

PI = 3.141519
def Asd.merhaba
puts "modulden merhaba!"
end
end

Asd.merhaba # "modulden merhaba!" yazdırır
p Asd::PI #3.141519
bir modül içinde fonksiyon tanımlarken, modül ismi.fonksiyon ismi şeklinde tanımlıyoruz. Ayrıca, modüller sabit olduğundan(yani = ile değiştirilemiyorlar) isimlerinin ilk harfleri büyük harf olmak zorunda. Modüllerin sabitlerine de "::" operatörü ile erişebiliyoruz.

Nesne Yönelimli Programlama
Nesne Yönelimli Programlama(OOP), yaklaşık son 20 yıla damgasını vurmuş olan programlama paradigmasıdır. ruby de bunu köküne kadar destekler, hatta fazlasını bile yapar.insan ruby'nin nesne sistemiyle tanıştıktan sonra java'ya "bu da bişey mi?oop değil lan bu" şeklinde bakar oluyor.
Ruby, c++, java gibin diğer object oriented(ımsı) dillerdeki yaklaşık herşeyi içeriyor.mesela bir sınıf örneği;
Kod:
class Merhabaci

def initialize(isim)
@isim = isim
end
def merhaba_de
puts "Merhaba, #{isim}
end
end

m = Merhabaci.new("kuzux")
m.merhaba_de
# "Merhaba, kuzux" yazdırır.
Burada oldukça basit bir sınıf örneği görüyoruz. "initialize" fonksiyonu, yeni bir obje yaratıldığında çağrılır.constructor dur yani. "@" işareti ile başlayan değişkenlerse o objenin "instance variable" larıdır. üye değişken de diyebiliriz bunlara. sınıfların üye değişkenleri, private'dır, sınıflarda public değişken tanımlanamaz. ancak o değişken için erişim fonksiyonları tanımlanır. sınıf tanımı içinde tanımladığımız fonksiyonlar da üye fonksiyonlar, yani metodlardır.

Erişim metodları
Erişim metodları için örnek;
Kod:
class Merhabaci

def isim
@isim
end
def isim= (i)
@isim = i
end
end

m.isim #=> "kuzux"
m.isim = "zbdk"
m.merhaba_de #"Merhaba, zbdk" yazdırır.
Bu kodda, çok önemli birşey görüyoruz. dilin dinamik yapısını ortaya koyan birşey. daha önce tanımladığımız bir sınıfı, tekrar açıp ona birşeyler ekleyebiliyoruz.Bu şekilde sadece kendi yazdığımız sınıfları değil, dilin kendi built-in sınıflarını da değiştirebiliyoruz.Ayrıca, sınıflarda sonu "=" ile biten fonksiyonlar değişkenlere erişim için kullanılır.

hemen yukarıda bahsettiğim sınıflara yeni birşeyler ekleme özelliğine dönersek;
Kod:
class Integer

def div_f(other)
self.to_f/other.to_f
end
end
yukarıdaki kod, integer sınıfına div_f adında bir fonksiyon ekler ve bununla float olarak bölme yapabiliriz(normalde ruby'de int/int int döndürür). ayrıca burada gördüğümüz self değişkeni, bir sınıftayken o instance'ın kendisini belirtir. c++ daki this gibi yani.


ayrıca, az önce gösterdiğim erişim metodlarını basit bir şekilde tanımlamanın çok kısa bir yolu vardır. o "isim" ve "isim=" fonksiyonları yerine
Kod:
class Merhabaci

attr_accessor :isim
end
yazabiliriz. burada attr_accessor, bir değişken için okuma-yazma şeklinde erişim metodları tanımlıyor. aynı şekilde attr_reader sadece okuma, attr_writer da sadece yazma metodu tanımlamakta.

Sınıf Metodları
Sınıf metodları, java ve c++ daki statik metodlar gibidir. sınıfın bir instance'ında değil, doğrudan sınıfta çağrılırlar
Kod:
class Merhabaci

def self.merhaba_de(isim)
puts "Merhaba, #{isim}"
end
end

Merhabaci.merhaba_de("kuzux")
#Merhaba, kuzux yazdırır
sınıf metodları, sınıf içinde self.metod adı şeklinde tanımlanır. Ayrıca her sınıf da bir obje olduğundan, sınıf metodlarında da sınıfın kendisine erişmek için self kullanabiliriz.

Büyükçene bir örnek*
*büyükçene: daha önceki örneklere kıyasla
Bir süredir yazdığımız Merhabaci sınıfının tamamı bu da. commentler ile çeşitli kodların ne işe yaradığını yazdım.
Kod:
class Merhabaci

attr_accessor :isimler

def self.isim_sor
puts "Bikac isim girin(bosluklarla ayrilmis)"
isimler = gets.chomp # girdi almayı unutmadınız di mi?
self.new(isimler.split) # burada yeni bir merhabaci instance i olusturuyoruz.
# String#split fonksiyonu da belirtilen karakterlerden string i ayırır, bir array döndürür.eğer argümansız kullanılırsa boşluk karakterlerinden ayırır.
end

def initialize(isimler)
if isimler.is_a? String #burada isimler string mi diye kontrol ediyoruz
@isimler = isimler.split
else
@isimler = isimler
end
end

def isim_ekle(isim)
@isim << isim # array'in sonuna ekliyoruz
end

def merhaba_de
@isimler.each do |isim| #isimler array inin her elemanı için
puts "Merhaba, #{isim}
end
end

def hoscakal_de
puts "Hoscakalin, #{@isimler.join(", ")}"
# join fonksiyonu bir arrayin elemanlarını belirtilen string i aralarına koyarak birbirine ekler.
end
Kalıtım
Kalıtım(inheritance), nesne yönelimli programlamanın en önemli unsurlarından biridir.Bir sınıfın, başka bir sınıfın sahip olduğu tüm özelliklere sahip olması
ve onlara yeni birşeyler ekleyebilmesidir.yani bir nevi bir sınıfın, başka bir sınıfın alt-sınıfı olmasıdır. kalıtım, tekli veya çoklu kalıtım olarak ikiye ayrılır.
Tekli kalıtımda, bir sınıf sadece bir sınıftan türetilmiş olabilir. çoklu kalıtımda ise, bir sınıf birden fazla sınıftan türetilmiş olabilir. smalltalk, java, c# vb. diller tekli kalıtım, c++ ve python çoklu kalıtım kullanmaktadır. Ruby de tekli kalıtım kullanan dillerden biridir.Bir sınıf tanımlarken "<" işaretinden sonra üst sınıf belirtilerek alt-sınıf tanımlanır. eğer belirtilmezse, bu sınıfın üst-sınıfı Object olur.
örnek;
Kod:
class Ust

def kimsin?
puts "Ben ust sinifim"
end
def asd
puts "asd"
end
end

class Alt < Ust
def kimsin?
puts "Ben alt sinifim"
end
end

a = Alt.new
a.kimsin? # "Ben alt sinifim" yazdırır
a.asd # "asd" yazdırır
Bu örnekte de gördüğümüz gibi, bir sınıfın instance'ına bir mesaj gönderirsek, önce kendi sınıfında arar, sonra da sırayla üst-sınıflarına bakar.
ayrıca, alt sınıftan da üst sınıftaki bir fonksiyonu "super" komutuyla çağırabilirsiniz.örneğin;
Kod:
class Alt < Ust

def asd
puts "dsa"
super
end
end

a.asd # "dsa" "asd" yazdirir.
bu şekilde de super komutunun kullanımını gördük.
geleneksel kalıtım örneği olan çokgenlere geçelim şimdi de

Çokgen Örneği

öncelikle, üst sınıfımızı tanımlayalım
Kod:
class Cokgen

end
açıkçası, burada çokgenlerin aslında o kadar da ortak fonksiyonları olmadığı için üst sınıfı boş bırakabiliriz

dikdörtgen sınıfı
Kod:
class Dikdortgen < Cokgen

attr_accessor :en, :boy
def initialize(en,boy)
@en, @boy = en, boy
end
def cevre
(@en + @boy) * 2
end
def alan
@en * @boy
end
end
üçgen sınıfı
Kod:
class Ucgen < Cokgen

attr_accessor :taban, :yukseklik
def initialize(taban,yukseklik)
@taban, @yukseklik = taban, yukseklik
end

def cevre #ikizkenar ucgen oldugunu varsayalim :D
ikizkenar = Math.sqrt(@yukseklik**2 + (@taban/2)**2) #pisagor
@taban + 2*ikizkenar
end

def alan
@taban * @yukseklik / 2
end
end
ve de kare
Kod:
class Kare < Dikdortgen

def initialize(a)
super(a,a)
end
end
ve evet bu kadar.özellikle kare sınıfını görünce kalıtımın faydalarını anlamışsınızdır umarım

Operatörleri Aşırı Yükleme
Operatörleri aşırı yükleme(operator overloading), ne yazık ki java'da olmayan ve benim java'ya kıl kapmamı sağlayan bir olaydır. Standart sayılar üzerinde işlem yapar gibi çeşitli operatörleri kendi yazdığınız sınıflarda kullanabilmenizi sağlar.
Örneğin, öncelikle bir karmaşık sayı sınıfı tanımlayalım
Kod:
class Karmasik

attr_accessor :reel, :sanal
def initialize(reel=0, sanal=0)
@reel, @sanal = reel, sanal
end
end
şimdi java'da olsa 4 işlem için topla(), cikar() gibi fonksiyonlar tanımlamamız gerekirdi. ruby'de ise gayet rahat bir şekilde operatör kullanabiliyoruz

öncelikle, karmaşık sayılarla reel sayılar arasında işlem yapabilmek için, Numeric sınıfına ve Karmasik sinifina birer to_k fonksiyonu ekliyoruz.bu bu sayıları karmaşık sayıya çevirecek
Kod:
class Numeric #Integer, Float filan ın üst sınıfı

def to_k
Karmasik.new(self) # sanal kısım 0
end
end
class Karmasik
def to_k
self
end
end
şimdi, esas operatörlerimizi tanımlayabiliriz.

Kod:
class Karmasik

def +(diger)
diger = diger.to_k
Karmasik.new(@reel + diger.reel, @sanal + diger.sanal)
end

def -(diger)
diger = diger.to_k
Karmasik.new(@reel - diger.reel, @sanal - diger.sanal)
end

def *(diger)
#(a+bi)(c+di) = (ac-bd) + (bc+ad)i
diger = diger.to_k
r = @reel*diger.reel - @sanal*diger.sanal
s = @sanal*diger.reel + @reel*diger.sanal
Karmasik.new(r,s)
end

def /(diger)
#(a+bi)/(c+di) = (ac+bd)/(c^2+d^2) + (bc-ad)/(c^2+d^2)i
diger = diger.to_k
payda = diger.reel**2 + diger.sanal**2
r = (@reel*diger.reel + @sanal*diger.sanal)/payda
s = (@sanal*diger.reel - @reel*diger.sanal)/payda
Karmasik.new(r,s)
end
end
operatörleri tanımlamak, + - şeklinde fonksiyonlar yazmak şeklinde yapılıyor ve oldukça kolay.
Dosya İşlemleri
Ruby'de dosya işlemleri File sınıfıyla yapılır. Bir dosyayı açmak için File.new veya File.open, kapatmak için File#close metodları kullanılır
örneğin;
Kod:
f = File.open("dosya.txt")

puts f.read #dosya.txt nin içeriğini yazdırır.
f.close
eğer File.new veya File.open a bir blok verirseniz o blokta dosya nesnesini döndürür
ve dosyayı ayrıca kapatmanıza gerek kalmaz.
Kod:
File.open("dosya.txt") do |f|

puts f.read
end
dosyaları açarken diğer dillerde olduğu gibi erişim karakterleri vardır. bunlar;
"r" -> okuma
"w" -> yazma
"r+" -> okuma ve yazma
"a" -> sonuna ekleme
dir. eğer hiçbirini kullanmazsanız "r" kabul eder. "w" dosyayı boşaltır(truncate eder) ve sonra üzerine yazar.

dosyaya yazmak amaçlı örnek verecek olursak;
Kod:
File.open("dosya.txt", "w") do |f|

f.puts "dosyaya yaziyorum"
end
puts File.read("dosya.txt") # "dosyaya yaziyorum" yazdirir
burada gördüğünüz gibi, File#puts metodu açık olan dosyaya yazmak için kullanılır. Ayrıca File.read metodu da dosyayı açıp içeriğini almanın kısayoludur.

Dosyalarla ilgili işe yarar fonksiyonlar
Bir dosyanın var olup olmadığını kontrol etmek:
bu iş için File.exist? fonksiyonu kullanılır. eğer o isimde bir dosya varsa true, yoksa false döndürür.

Bir dosyanın dizin(klasör) olup olmadığını kontrol etmek:
bu iş için de File.directory? fonksiyonu kullanılır. eğer belirtilen adres bir dizinse true, değilse false döndürür. aynı işin tam tersi için de File.file? fonksiyonu kullanılabilir.

Bir dosyanın boyutunu öğrenmek:
bu iş için de File.size fonksiyonu kullanılır. dosyanın byte cinsinden boyutunu döndürür.

Bir dosyanın yazılabilir olup olmadığını kontrol etmek:
File.writable? fonksiyonu bu işe yarar. eğer şu an kullanıcının o dosyaya yazma izni varsa true döndürür.

Dosyanın bulunduğu dizin ve dizin hariç dosya adı:
File.split fonksiyonu, ilk elemanı dizin adı, ikinci elemanı dosya adı olan bir array döndürür.

Dosya uzantısı:
File.extname fonksiyonu, dosya uzantısını verir. eğer dosyanın uzantısı yoksa, boş bir string döndürür.

Dizin yolu birleştirme:
bildiğiniz gibi, klasör yolları(path), windows'ta \ , unix türevlerinde / ile ayrılır. bu nedenle bir dizin adını dosya adıyla birleştirirken, File.join fonksiyonunu kullanıyoruz. Mesela;
Kod:
File.join("asd","zxc")
dediğimizde program windows altında çalışıyorsa "asd\\zxc", linux veya mac altında çalışıyorsa "asd/zxc" döndürür


Klasör işlemleri
ruby'de dizin(klasör)lerle ilgili işlemler, Dir sınıfı ile yapılır.

Şu anki açık olan dizini gösterme:
Bu, Dir.pwd fonksiyonuyla yapılır.

Bulunulan dizini değiştirme:
Dir.chdir fonksiyonuyla yapılır. ayrıca, eğer bir blok verilirse o dizine gider, bloğu çağırır ve daha önce çalışılan dizine geri döner.örneğin;
Kod:
Dir.chdir("c:\\Program Files\\")  #Stringlerde \ geleneksel olarak yeni satır, tab gibi şeyleri ifade etmek için kullanıldığından dolayı \\ yazmak zorundayız.

puts Dir.pwd #c:\Program Files\ yazdırır.
Dir.chdir("c:\\Documents and Setttings") do
puts Dir.pwd #c:\Documents and Settings yazdırır.
end
puts Dir.pwd #c:\Program Files\ yazdırır.
Bir dizindeki dosyaların listesini alma:
bunun için Dir.entries fonksiyonu kullanılır. fakat döndürülen listede, o dizini sembolize eden "." ve bir üst dizini gösteren ".." elemanları da bulunur.

Bir dizindeki bütün dosyalar üzerinde işlem yapma
bunun için Dir.foreach fonksiyonunu kullanırız. aslında bu Dir.entries.each e bir kısayoldan ibarettir.

Bir klasördeki belli bir şablona uyan dosyaları alma:
buna "glob" işlemi denir ve Dir.glob veya Dir.[] fonksiyonları kullanılır.

mesela, içinde "asd.txt", "dir.rb", "zbdk.rb" ,"dim.jpg", "zxc.txt" dosyaları olan bir klasör olsun ve o anda da o klasörde bulunuyor olalım
Kod:
Dir["*"] #içinde dizinin tüm elemanları bulunan bir array döndürür(. ve .. yoktur)


Dir["*.txt"] # =>["asd.txt", "zxc.txt"]
Dir["di?.*"] # di ile başlayan, sonrasında bir karakter olan ve bir nokta olup sonra da devam eden dosyaları görüntüler
# => ["dir.rb", "dim.jpg"]
Dir["di[rm]*"] #dim veya dir ile başlayan dosyaları döndürür.
# => ["dir.rb", "dim.jpg"]

Dir["*.{jpg,rb}"] # uzantısı jpg veya rb olan dosyaları döndürür.
# => ["dir.rb", "zbdk.rb" ,"dim.jpg"]

Dir["[^z]*"] # z ile başlamayan dosyaları döndürür.
# => ["asd.txt", "dir.rb", "dim.jpg"]

Mixin'ler
Mixin'ler, java'daki interface veya smalltalk'taki traitler gibi çoklu kalıtım desteği olmayan dile hafiften böyle birşey getirirler.
ruby'de mixin'ler aslında sadece birer modüldürler.mesela bir modül tanımlıyalım
Kod:
module Asd

def self.asd
puts "mixin im ben"
end
end
Asd.asd # "mixin im ben" yazdırır
bir mixin'i bir sınıfa dahil etmek oldukça kolaydır. sınıf içinde "include" keyword ünü kullanarak(ki esasen keyword değil bir sınıf metodudur o)
yukarıdaki modülü bir mixin olarak bir sınıfa dahil edelim
Kod:
class Lalala

include Asd
end
Asd.new.asd # "mixin im ben" yazdırır
ama eğer bir modüldeki metodların instance değil de sınıf metodu olmasını istiyorsak, include değil extend komutunu kullanmalıyız.
Kod:
class Aaa

extend Asd
end
Aaa.asd # "mixin im ben" yazdırır
ve son olarak, bir modül include edildiğinde, eğer varsa, o modülün included fonksiyonunu çağırır.bu şekilde, bir modülün hem instance, hem class metodları eklemesini sağlayabiliriz.Mesela;
Kod:
module Asd

def self.included(sinif)
sinif.extend SinifMetodlari
end
module SinifMetodlari
def asd
puts "sinif metoduyum ben"
end
end
end

class Ooo
include Asd
end

Ooo.asd # "sinif metoduyum ben" yazdırır
Ooo.new.asd # "mixin im ben" yazdırır
Çeşitli yararlı modüller
ruby, mixin olarak kullanabilmemiz için çeşitli modüller sunar ve bunlar oldukça işe yaramaktadır.
Comparable
Bu , sınıfımızda tanımlayacağımız bir "<=>" operatöründen yola çıkarak <, >, == gibi karşılaştırma operatörleri tanımlar
örneğin;
Kod:
class Oyuncu

include Comparable
attr_reader :puan, :isim
def initialize(isim,puan)
@isim, @puan = isim, puan
end
def <=>(diger)
@puan <=> diger.puan
end
end
Burada, <=> operatörü, oyuncunun puanıyla diğer verilen oyuncunun puanını karşılaştırır
küçük bi not: ilginç bir şekilde, "<=>" operatörüne "uzay gemisi" de denir

Singleton
Singleton, en bilinen tasarım şablonlarından(design patterns) birisidir.Bir sınıfa ait tek bir nesnenin(instance) olmasını ve bu nesneye tüm uygulama çapında erişilebilmesini sağlar.Bunu örneğin, bir program yazdığımızda bu programın konfigürasyon işlerini halleden sınıf bir singleton olabilir.o sınıfın sadece bir kez konfigürasyonu okuması güzel birşeydir.Singleton sınıflarda sadece tek bir instance olduğu için new metoduyla yeni bir nesne yaratamıyoruz, onun yerine "instance" metoduyla singleton'a erişiyoruz.örnek:
Kod:
class Config

include Singleton
def initialize
@conf = {}
end
def [](anahtar)
@conf[anahtar]
end
end

def mesaj_belirle
Config.instance[:mesaj] = "Singleton Ornegi"
end
def mesaj_yazdir
puts Config.instance[:mesaj]
end

mesaj_belirle
mesaj_yazdir # "Singleton Ornegi" yazdırır.
Enumerable
Enumerable, Array, Hash, String gibi birçok sınıfta kullanılan, bir koleksiyonun her elemanı üzerinde işlem yapmamızı sağlayan bir modüldür. kullanılabilmesi için "each" fonksiyonunun tanımlı olması gerekir.bir sınıfta kullanılmasında birşey yok, each fonksiyonunu tanımlayıp Enumerable modülünü include edeceksiniz. Enumerable la birlikte gelen metodlar daha önemli.

collect ya da map:
bir listedeki her elemana verilen bloku uygular ve sonucu bir array'de toplar.
Kod:
a = (1..3).to_a

a.collect{ |x| x*2}
# => [2, 4, 6]
each_with_index:
bir listedeki her elemanı ve onun indeksini yield eder.
Kod:
a = ["hasan", "huseyin"]

a.each_with_index do |isim, indeks|
puts "#{indeks}: #{isim}"
end
# 0: hasan
# 1: huseyin
# yazdirir.
select ya da find_all:
her elemanı yield eder.eğer verilen blokun sonucu true ise o elemanı bir listeye ekler, o listeyi döndürür.sql'deki SELECT FROM WHERE gibi çalışır yani
Kod:
a = (1..10).to_a

a.select{|x| x%4==0} #4 e tam bölünenler
# => [4, 8]
reject
select in tam tersidir. eğer verilen blok false döndürürse döndürülecek olan listeye ekler
Kod:
a = (1..4).to_a

a.reject{|x| x%2==0}
# => [1, 3]
entries ya da to_a
bir listenin yield edilen tüm elemanlarını bir array'a koyar.eğer bir seferde birden fazla eleman yield ediliyorsa, alt listeler de oluşturur.

include?
bir listedeki yield edilen tüm değerlere bakar, eğer verilen eleman birisine eşitse true, değilse false döndürür. yani elemanın listede olup olmadığına bakar
Kod:
a = (1..5).to_a

a.include? 3 # => true
a.include? 6 # => false
count
eğer argüman verilmezse, o listedeki tüm elemanların sayısını döndürür.
eğer belli bir argüman verilirse, o listede bulunan o elemanın sayısını döndürür
eğer bir blok verilirse, o bloku sağlayan elemanların sayısını döndürür.
Kod:
a = [1,2,3,4,5,5,5,5]

a.count # => 8
a.count 5 # => 4
a.count{ |x| x%2 == 0} # => 2
aslında bu kadarının daha kısa olmasını ve daha çok şey anlatacağımı tahmin ediyordum, ama çok uzadı. bidahaki sefere Struct'ları, internet'ten belli bir adrese erişmeyi ve kodu dokümante etmeyi görücez, mümkünse en son bir de örnek yapıcaz
Math modülü
ruby standart kütüphanesinin bir parçası olan Math modülü, birçok matematiksel fonksiyonu içerir.
Math modülünde PI ve E sabitleri tanımlıdır.
içerdiği fonksiyonlar ise:
sqrt: karekök hesaplar
sin: sinüs
cos: kosinüs
tan: tanjant
cot: kotanjant
sinh: hiperbolik sinüs
cosh: hiperbolik kosinüs
tanh: hiperbolik tanjant
coth: hiperbolik kotanjant
* yukarıdaki fonksiyonlar argüman olarak radyan cinsinden açı alır
asin: ters sinüs
acos: ters kosinüs
atan: ters tanjant (-PI/2)..(PI/2) arası değer döndürür
atan2: ters tanjant -PI..PI arası değer döndürür
acot: ters kotanjant
* yukarıdaki fonksiyonların döndürdüğü açı da radyan cinsindendir
hypot(x,y): pisagor, (x^2+y^2)^(1/2)

log: natürel logaritma
log10: 10 tabanında log
exp(x): E^x

erf: hata işlevi
erfc: ters hata işlevi(1 - erf)

bunların dışında frexp ve ldexp adlı 2 fonksiyon daha var, ama ben henüz ne yaptıklarını anlayabilmiş değilim

Struct'lar
ruby'de sınıfların da diğer herşey gibi bir nesne olmalarından dolayı, dinamik olarak sınıf yaratılabilir ve struct lar bu şekilde bir sınıf döndürür. Döndürülen sınıf c'deki struct'lara benzer, tüm değişkenleri public olan(yani attr_accessor tanımlanmış olan) bir sınıf döndürür.Genel olarak bir sınıfın üst sınıfı olarak kullanılır.örneğin;
Kod:
class Nokta2b < Struct.new(:x, :y)

end

n = Nokta2b.new(3,5)
n.x #=> 3
n.y #=>5
burada gördüğünüz gibi struct.new'e istediğimiz değişkenleri yollayarak bir sınıf elde ediyoruz. Tabi ki bu sınıftan bir sınıf türettiğimizden dolayı bu sınıfta fonksiyonlar tanımlayabilir, normal bir sınıf içinde ne yapabiliyorsak yapabiliriz.mesela, kartezyen koordinat sistemi üzerindeki bu noktanın (0,0) noktasına uzaklığını hesaplayalım ve buna göre karşılaştırma operatörleri tanımlayalım.
Kod:
class Nokta2b < Struct.new(:x, :y)

include Comparable
def uzaklik
Math.hypot(x,y)
end
def <=>(nokta)
uzaklik <=> nokta.uzaklik
end
end

Nokta2b.new(3,4).uzaklik # => 5
bir struct dan türetilmiş bir sınıftan yeni bir obje yaratırken, eğer gerekli tüm argümanları vermezsek, bunların değeri nil olur.bunu unutmamak gerek.
Kod:
n = Nokta2b.new(3)

n.y # => nil
Gelin bir de 3 boyutlu nokta şeysi yazalım
Kod:
class Nokta3b < Struct.new(:x,:y,:z)

include Comparable
def uzaklik
Math.sqrt(x**2+y**2+z**2)
end
def <=>(n)
uzaklik <=> n.uzaklik
end
end
tamam yazdık sınıfımızı, ancak bunu aynı zamanda 2 boyutlu nokta olarak da iş görmesini istiyoruz ve ilk yaratılırken x ve y'nin mutlaka bulunmasını istiyoruz.nasıl yapacağız? tabi ki yeni bir initialize metodu tanımlayarak!
Kod:
class Nokta3b < Struct.new(:x,:y,:z)

def initialize(x,y,z=0)
super(x,y,z)
end
end
artık noktamız hem 2, hem 3 boyutlu nokta olarak görev yapabilecek . öyleyse Nokta3b demeye gerek yok, Nokta diyebiliriz. sınıflar da birer nesne olduğundan, onları bir değişken gibi atayabiliriz.
Kod:
Nokta = Nokta3b
dersek artık Nokta adlı bir sınıf varmış gibi kullanabiliriz.

open-uri kütüphanesi
open-uri adlı kütüphane, ruby'nin standart kütüphanelerine dahildir ve yine bu kütüphanelerden olan Net::HTTP ve Net::FTP yi kullanarak bunlardan daha kolay bir şekilde internetten
bilgi almaya yarar.
Kod:
require 'open-uri'
diyerek bu kütüphaneyi dahil edebiliriz. bundan sonra "open" fonksiyonu bir url alacak ve okuma amaçlı açılmış bir dosya gibi davranan bir nesne döndürecektir.
örneğin;
Kod:
site = open("http://google.com/xhtml")

puts site.read
diyerek google ın mobil sürümünün html kaynak kodunu yazdırabiliriz.

ayrıca bu open ile döndürülen objenin "meta" metoduyla bu sayfanın meta bilgilerine erişebilir, each metoduyla da her satırını yield ettirebiliriz.

sanırım dokümentasyonu bir süre sonra anlatıcam. şimdi içimden gelmedi pek.onun yerine dinamik olarak metod yaratmayı göstereceğim

Dinamik olarak metod yaratma
Dinamikliğin dibine vurmuş güzide dilimiz ruby'de, metodlar da başka bir metodu kullanarak yaratılabiliyor, bu metodun adı "define_method", bir isim ve bir blok alıyor kendileri. bir örnek vermek gerekirse
Kod:
kelimeler = ["asd", "zxc", "qwe"]

kelimeler.each do |k|
define_method(k) do
puts k
end
end
şimdi, asd,zxc ve qwe adında 3 metodumuz oldu.
Kod:
asd # "asd" yazdırır

qwe # "qwe" yazdırır
ayrıca, bu bloka gayet argüman da ekleyebiliriz.normal argümanlı bir blok tanımlar gibi
bunun dışında, sınıflardaki instance değişkenlerine erişim için de iki metod kullanılabilir.bunlar instance_variable_get ve instance_variable_set. get olanı, tek argüman alır ve verilen instance variable'ın değerini döndürür. set olan ise ilk argümanda verilen instance değişkenine 2.argümanda verilen değeri atar.Ayrıca bu fonksiyonlarda verilen değişken isimlerinin başındaki "@" işareti gereklidir.unutmayın.


Örnek Zamanı!!
Bu seferki örneğimiz ise vektörler. az önceki nokta sınıfını neden yazdırdım sanıdınız?
Öncelikle daha önce tanımladığımız 3 boyutlu nokta sınıfını bütün halde yazalım
Kod:
class Nokta < Struct.new(:x,:y,:z)

include Comparable
def initialize(x,y,z=0)
super(x,y,z)
end
def uzaklik
Math.sqrt(x**2+y**2+z**2)
end
def <=>(n)
uzaklik <=> n.uzaklik
end
end
sonra, vektör sınıfımızı düşünelim. vektörün içereceği , bir adet bitiş noktası(başlangıcını 0,0,0 olarak alıyoruz), toplama, çıkarma, çarpma(3 farklı şekilde ) operatörleri, uzunluğu ve 2 vektörün arasındaki açı.
Açı işinde fonksiyonlar radyan döndürdüğü için Numeric sınıfına radyan ve derece metodlarını ekleyelim
Kod:
class Numeric

def derece
self * (180/Math::PI)
end
def radyan
self * (Math::PI/180)
end
end
şimdi Vektör sınıfımızı yazmaya başlayabiliriz. Hem en az 2 koordinat, hem de bir nokta verilerek tanımlanabilmeli
Kod:
class Vektor

def initialize(*args)
if args[0].respond_to? :x
@bitis = args[0]
else
@bitis = Nokta.new(args)
end
end
end
şimdi de bu vektörün başlangıca olan uzaklığını bulalım. nokta sınıfında zaten bunu yazdığımız için çok kolay olacak. ayrıca o uzaklığa göre karşılaştırabiliriz şimdi.

Kod:
class Vektor

include Comparable
def uzunluk
@bitis.uzaklik
end
def <=>(v)
uzunluk <=> v.uzunluk
end
end
şimdi de x, y, z büyüklükleri için erişim metodu tanımlayalım. bu metodlar yaklaşık olarak aynı oldukları için, toptan, dinamik olarak tanımlamamız daha kısa sürecektir
Kod:
class Vektor

uzakliklar = [:x, :y, :z]
uzakliklar.each do |u|
define_method u do
@bitis.send(u)
end
define_method "#{u}=" do |deger|
@bitis.send("#{u}=", deger)
end
end
end
ve artık x, y, ve z için birer erişim metodumuz var.

artık operatörleri tanımlamaya başlayabiliriz.önce toplama ve çıkarma. bunlar basit, x, y ve z lerini diğerininkiyle topluyor ya da çıkartıyoruz.hatta istersek ikisini birden yukarıdaki dinamik metod tanımlama yöntemiyle halledebiliriz, daha kısa sürer yapması
Kod:
class Vektor

op = [:+, :-]
op.each do |o|
define_method(o) do |v|
Vektor.new(x.send(o,v.x),y.send(o,v.y),z.send(o,v.z))
end
end
end
vektörler için 3 tane çarpma var demiştim, biri bir skalerle çarpmak. bunda bütün değerlerini o skalerle çarpıyoruz. diğer ikisi ise, bir skaler döndüren "nokta çarpımı" ve vektör döndüren "çarpı çarpımı" olarak biliniyor. skaler çarpımı ve çarpı çarpımını * operatörü, nokta çarpımını ise ^ operatörüyle tanımlayalım.
Kod:
class Vektor

def *(v)
if v.respond_to? x #çarpı çarpımı
yeni_x = y * v.z - z*v.y
yeni_y = z * v.x - x*v.z
yeni_z = x * v.y - y*v.x
Vektor.new(yeni_x,yeni_y,yeni_z)
else #skaler çarpımı
Vektor.new(x*v,y*v,z*v)
end
end
def ^(v)
x * v.x + y*v.y + z*v.z
end
end
çarpma fonksiyonları da bu kadardı.son olarak, başka bir vektörle arasında olan açıyı alacağız.
Kod:
class Vektor

def aci(v)
sonuc = Math.acos(self^v / (uzunluk*v.uzunluk) #radyan
sonuc.derece

Shoulda
Shoulda, Test::Unit'e entegre olup onu biraz daha geliştiren bir kütüphane. "Davranış odaklı" testleri hallediyor. davranış odaklı testlerde, her test, kodun bir davranışını test eder.bu düşük ya da yüksek seviye olabilir. Bu tür testler için, RSpec adında bir kütüphane daha var ama o çok büyük geliyor. pek sevmiyorum. shoulda'da "context" komutuyla bir içerik tanımlıyoruz
ve bu metoda verdiğimiz blok içinde setup komutuyla bir setup fonksiyonu tanımlıyor, should ile de testlerimizi tanımlıyoruz. fakat ne yazık ki bu tür test kütüphaneleri türkçeyle iyi geçinemiyorlar pek. "Bir yorumlayıcı should + verildiğinde değerini arttırmalı" gibi bişeyler çıkıyor. bu nedenle bunun örneğini ingilizce vericem(kendi yazdığım bir brainfuck yorumlayıcısından alıntıdır bu arada)

Kod:
require "test/unit"

require "shoulda"
require "command"

class CommandTest < Test::Unit::TestCase
context "A single command" do
setup do
@command = Command.new(:+)
end

should "'s value should be what given to the constructor" do
assert_equal @command.value, "+"
end
end

context "A comment" do
setup do
@command = Command.new(:a)
end
should "'s value should be nil" do
assert_nil @command.value
end
end


context "Two commands with same value" do
setup do
@command1 = Command.new(:+, nil)
@command2 = Command.new(:+, nil)
end

should "be equal" do
assert_equal @command1, @command2
end
end

context "Two different commands" do
setup do
@command1 = Command.new(:+)
@command2 = Command.new(:-)
end

should "not be equal" do
assert_not_equal @command1, @command2
end
end

context "Two comments" do
setup do
@comment1 = Command.new(:a)
@comment2 = Command.new(:s)
end

should "be equal" do
assert_equal @comment1, @comment2
end
end

context "A command with more than one character" do
setup do
@command = Command.new("+-a")
end

should "be an array of commands made up from all the characters" do
assert_equal @command, [Command.new(:+),Command.new(:-),Command.new(:a)]
end
end
end
NOT: Bu test-odaklı geliştirme hakkında çok güzel bir video var. eğer ingilizce biliyorsanız izlemenizi şiddetle tavsiye ederim. http://rubyhoedown2008.confreaks.com...f-in-time.html


Nesne Seriyalizasyonu
Seriyalizasyon(serialisation u daha fazla türkçeleştiremedim ), herhangi bir nesnenin tüm özelliklerini belirten bir string reprezentasyonunun oluşturulması ve bunun geri yüklenebilmesidir. çeşitli durumlarda veri kaydederken lazım olur.
Ruby'de seriyalizasyon için 2 tane sınıf vardır, bunlardan biri Marshal, diğeri YAML'dır. Marshal'ın boyutu biraz daha küçük olur, YAML ise insanlar tarafından rahatlıkla okunabilir. istediğiniz zaman istediğinizi tercih edin. yalnız YAML kullanacaksanız require 'yaml' demeniz lazım. neyse, bu iki sınıfın da "load" ve "dump" adlı birer sınıf metodu bulunur ve bunlarla sırayla seriyalize edilmiş nesneyi yükleyebilir ya da kaydedebiliriz.
şimdi, örnek olarak caching i bulunan bir fibonacci sayısı şeysi yazalım. öncelikle, caching olmadan bir fibonacci fonksiyonu yazalım
Kod:
def fib(n)

if n==0 or n==1
return 1
end
fib(n-2) + fib(n-1)
end
şimdi ,işe yarar bir fibonacci fonksiyonumuz oldu ama büyükçene sayıları hesaplaması oldukça uzun sürüyor. o yüzden caching ekliycez. Bunun için öncelikle biraz refactoring yapıp fibonacci jeneratörümüzü ayrı bir sınıfa atalım
Kod:
require 'singleton'

class Fibonacci
include Singleton
def fibonacci(n)
if n==0 or n==1
return n
end
fib(n-2) + fib(n-1)
end
end

def fib(n)
Fibonacci.instance.fibonacci(n)
end
Caching için bir hash kullanabiliriz, ama bu hashimizin bütün anahtarları sayı olacağına göre, niye bir array kullanmıyoruz? bunun için Fibonacci sınıfımızı Array'den inherit ettirebiliriz.bu arada cachingimizi de hallettik.
Kod:
class Fibonacci < Array

include Singleton
def initialize
super
self[0] = self[1] = 0, 1
end

def fibonacci(n)
self[n] ||= fibonacci(n-1) + fibonacci(n-2)
end
end

def fib(n)
Fibonacci.instance.fibonacci(n)
end
e tamam cachingi yaptık da, program her yeniden açıldığında cache i baştan mı hesaplayacak? bunun için gayet yaml'a çevirebiliriz cacheimizi.böylece bu sorunumuz çözülür. cacheimizi de fibcache.yml dosyasında tutalım. yaml'da saklarken de array'e çevirerek saklıyalım(yoksa sorun çıkarabiliyo)
Kod:
class Fibonacci < Array

include Singleton
def initialize
super
if File.exist? "fibcache.yml"
load
end
self[0] = self[1] = 0, 1
end

def fibonacci(n)
self[n] ||= fibonacci(n-1) + fibonacci(n-2)
end

def save
File.open("fibcache.yml", "w") do |f|
f.puts YAML.dump(self.to_a)
end
end

private
def load
ary = YAML.load File.read("fibcache.yml")
ary.each_with_index do |a,i|
self[i] = a
end
end
end

def fib(n)
Fibonacci.instance.fibonacci(n)
end
tamamdır, bunu yazdık ama dosyaya ne zaman kaydedicez? her fibonacci hesapladığımızda kaydedersek çok yavaşlatabilir. programdan çıkarken kaydetmek en güzeli olur.bunun için programın sonunda Fibonacci.instance.save demek pek konforlu olmaz bunun yerine ruby'nin az kullanılan özelliklerinden biri olan BEGIN{ } ve END{ } bloklarını kullanıcaz. begin, program başlamadan önce, end ise sonra çalıştırılır. o halde, bitmiş kodumuz şöyle oluyor:
Kod:
class Fibonacci < Array

include Singleton
def initialize
super
if File.exist? "fibcache.yml"
load
end
self[0] = self[1] = 0, 1
end

def fibonacci(n)
self[n] ||= fibonacci(n-1) + fibonacci(n-2)
end

def save
File.open("fibcache.yml", "w") do |f|
f.puts YAML.dump(self.to_a)
end
end

private
def load
ary = YAML.load File.read("fibcache.yml")
ary.each_with_index do |a,i|
self[i] = a
end
end
end

def fib(n)
Fibonacci.instance.fibonacci(n)
end

END{ Fibonacci.instance.save }

Digest modülü
Digest modülü, ruby standart kütüphanesinde bulunur ve hashing işlerini görmemize yarar.hashing derken md5, sha1 gibi şeyleri kastediyorum bu arada. mesela bir veritabanında şifre saklayacaksak bunu doğrudan değil de hash olarak saklamamız çok daha iyi olur.
Digest modülünü require 'digest' diyerek dahil edebiliriz. Digest modülü altında bulunan kullanabileceğimiz hashing sınıfları ise MD5, SHA1, SHA256, SHA384 ve SHA512 dir.bunlardan MD5'in güvenlik amaçlı kullanılmasını tavsiye etmem çünkü neredeyse kırılmış durumda. SHA1 de şu an kırılmakta(tam olarak olmasa da bir miktar zayıflattılar, kaba kuvvetle kırmaları daha kolay).bu yüzden yeni nesil SHA256 daha iyi(384 ve 512 ye şimdilik gerek yok).
Bu sınıfları nasıl kullanıyoruz derseniz, Bu sınıfların hexdigest metoduna bir değişken veriyoruz ve o bize onaltılık formatta hashini döndürüyor.Mesela;
Kod:
Digest::SHA256.hexdigest("asd")

# => "688787d8ff144c502c7f5cffaafe2cc588d86079f9de88304c26b0cb99ce91c6"
şimdi bu ne derseniz, "asd" nin SHA256 formatında hash i olmakta. bu, şifreleri doğrudan saklamaya göre çok daha güvenli, ancak hala yeterince güvenli değil."Rainbow Table" adlı yöntemle kırılabilir. bunu önlemek içinse, salt denilen şeyi kullanıcaz. salt, rasgele üretilmiş bir hash değeridir ve şifrenin sonuna veya başına ekleyerek hashlenecek şifreyi anlamsız bir şeye dönüştürür, bu şekilde de bir sözlük kullanarak kırılmasını önler. bir şifreyi database'de saklarken, onun salt'ını da saklamamız gerekir eğer kullanıyorsak. mesela, bir şifreyi hashleme fonksiyonu yazalım. bunu yaparken, rasgele salt üreten birşey de yazabiliriz gayet
Kod:
def rasgele_salt

Digest::SHA1.hexdigest(Time.now.to_s)
end
def hashle(sifre,salt)
Digest::SHA256.hexdigest(sifre+salt)
end
rasgele_salt fonksiyonumuzda şu anki zamandan bir salt ürettik.burada SHA1 kullanabiliriz çünkü önemli olan anlamsız birşey elde etmek ve sha1 bize bunu veriyor ve sha256'dan daha hızlı.ayrıca şifreyi kontrol ederken de hashle fonksiyonunu kullanarak şifrenin hashini bulacağız.
yeni ders pek yazamasam da, arada sırada ruby ile yaptığım şeyleri göndermeye devam edebilirim. henüz yeterince başarılı olmayan bir sudoku çözücü:

cell.rb:
Kod:
class Cell

attr_reader :x,:y, :value
attr_reader :possible_values
alias row y
alias column x


def initialize(x,y,value=nil)
raise "Invalid Position" unless x.between?(0,8) and y.between?(0,8)
raise "Invalid Value" unless value.nil? or value.between?(1,9)
@x, @y, @value = x, y, value
if @value.nil?
@possible_values = *1..9
else
@possible_values = [value]
end
end

def group
(@x/3) + (@y/3) * 3
end

def set_value(v,trial_and_error=false)
@value = v
@possible_values = [v] unless trial_and_error
end

alias value= set_value

def remove!(num)
return nil if @value
num = num.value if num.is_a? Cell
raise "Invalid Value" unless num.between?(1,9)
@possible_values -= [num]
if @possible_values.length == 1
@value = @possible_values.first
puts "#{x}@#{y} => #{value} "
return @value
end
nil
end

def == (other)
x == other.x and y == other.y and value == other.value
end

end
cell_test.rb:
Kod:
require "test/unit"

require "cell"

class CellTest < Test::Unit::TestCase
def setup
@novalue = Cell.new(3,4)
@value = Cell.new(4,5,8)
end

def test_position
assert_equal 3, @novalue.x
assert_equal 4, @novalue.y
end

def test_group
assert_equal 4, @novalue.group
assert_equal 4, @value.group
end

def test_value
assert_equal 8, @value.value
end

def test_possible_values
assert_equal (1..9).to_a, @novalue.possible_values
assert_equal [8], @value.possible_values
end

def test_invalid_position
assert_raise RuntimeError, "Invalid Position" do
Cell.new(10,5)
end
end

def test_invalid_value
assert_raise RuntimeError, "Invalid Value" do
Cell.new(6,7,12)
end
end

def test_remove
@novalue.remove! 4
assert_equal @novalue.possible_values, (1..9).to_a - [4]
end

def test_invalid_remove
assert_raise RuntimeError, "Invalid Value" do
@novalue.remove! 12
end
end

def test_remove_all_but_one
(1..8).each do |i|
@novalue.remove! i
end
assert_equal @novalue.value, 9
end

def test_equal
whatever = Cell.new(3,4)
assert @novalue == whatever
assert @value != whatever
end

end
sudoku_parser.rb:
Kod:
class SudokuParser

attr_reader :atoms, :cells

def initialize(str)
@str = File.exist?(str) ? File.read(str) : str
@str.gsub!(/\s+/, "")
@atoms = []
@cells = []
init_atoms
init_cells
end

private
def init_atoms
@str.each_char do |char|
if char =~ /\d/
@atoms << char.to_i
else
@atoms << nil
end
end
end

def init_cells
@atoms.each_with_index do |atom, index|
x = index % 9
y = index / 9
@cells << Cell.new(x,y,atom)
end
end
end
sudoku_parser_test.rb:
Kod:
require "test/unit"

require "cell"
require "sudoku_parser"

class SudokuParserTest < Test::Unit::TestCase
def setup
@line = SudokuParser.new "23---41--"
@file = SudokuParser.new "test.sudoku"
end

def test_atoms
assert_equal @line.atoms, [2,3,nil,nil,nil,4,1,nil,nil]
end

def test_cells
assert_equal @line.cells.first, Cell.new(0,0,2)
assert_equal @line.cells.last, Cell.new(8,0)
end

def test_file
assert_equal @file.atoms, [3,nil,5,7]
end
end
test.sudoku: #test için işte
Kod:
3-57
sudoku_solver.rb:
Kod:
class SudokuSolver

def initialize(sudoku)
@sudoku = sudoku
end

def solve

@modified = false

reducing
category_completion

#if !@cells_being_tried.empty? and invalid?
# rollback
#end

#unless @modified
# trial_and_error(i)
#TODO: Trial & Error
#end

#p empty.length
solve unless @sudoku.finished? or !@modified
end

private
def reducing
@sudoku.empty.each do |cell|
reduce_cell_possibilities(cell,:row)
reduce_cell_possibilities(cell,:column)
reduce_cell_possibilities(cell,:group)
end
end

def reduce_cell_possibilities(cell,category)
curr_cat = @sudoku.send("nonempty_#{category}",cell.send(category))

curr_cat.each do |n|
@modified = true if cell.remove!(n)
end
end

def category_completion
categories = [:row, :column, :group]

categories.each do |category|
category_find_missing(category)
end
end

def category_find_missing(category)
alg = lambda do |cat|
9.times do |num|
possible = cat.select{ |c| c.possible_values.include?(num+1) }
if possible.length == 1 and possible.first.value.nil?
possible.first.value = num + 1
@modified = true
puts "#{possible.first.x}@#{possible.first.y} => #{possible.first.value}"
end
end
end

@sudoku.send("each_#{category}",&alg)
end

def rollback
@cells = @states.pop
cell = @cells_being_tried.pop

end

def trial_and_error
min_possibility = empty.map{|cell| cell.possible_values.length }.min
@min_cells = empty.select{ |cell| cell.possible_values.length == min_possibility }

min_cells.each do |cell|
@cells_being_tried << cell
try(cell)
end
end

def try(cell)
@states << @cells.dup
@states[-1].map!{ |c| c.dup }
cell.value = cell.possible_values.first
solve
end
end
sudoku.rb:
Kod:
require "cell"

require "sudoku_parser"
require "sudoku_solver"

class Sudoku
attr_reader :cells
def initialize(str=nil)
to_parse = str.nil? ? "-"*81 : str
@cells = SudokuParser.new(to_parse).cells
@states = []
@cells_being_tried = []
end

def values
cells.map{ |c| c.value }
end

def []= (x,y,value)
index = y*9+x
@cells[index].value = value
end


def empty
@cells.select{ |c| c.value.nil? }
end

def finished?
@cells.none?{ |c| c.value.nil? }
end

def solve
SudokuSolver.new(self).solve
end

def invalid?
rows_invalid? or cols_invalid? or groups_invalid?
end

def self.define_category(*args)
args.each do |a|
define_method a do |i|
@cells.select { |c| c.send(a) == i }
end

define_method "empty_#{a}" do |i|
send(a,i).select{ |c| c.value.nil? }
end
define_method "nonempty_#{a}" do |i|
send(a,i) - send("empty_#{a}",i)
end
define_method "#{a}_invalid?" do
send("each#{a}") do |cat|
return false if cat != cat.uniq
end
true
end
end
end

self.define_category :row, :column, :group

def each_row
9.times{ |i| yield row(i) }
end
def each_column
9.times{ |i| yield column(i) }
end
def each_group
9.times{ |i| yield group(i) }
end

def to_s
rows = []
each_row{|r| rows << r.map{|c| c.value.to_i.to_s}.join }
rows.join "\n"
end

end

if __FILE__ == $0
s = Sudoku.new("#{ARGV.first}.sudoku")
s.solve
File.open("#{ARGV.first}.sudoku.cozulmus", 'w') do |f|
f.puts s.to_s
end
end
sudoku_test.rb:
Kod:
require "test/unit"

require "sudoku"

class SudokuTest < Test::Unit::TestCase
def setup
@sudoku = Sudoku.new("vatan240808_1.sudoku")
end

def test_row
row = @sudoku.row(0).map{ |c| c.value }
assert_equal row, [nil,1,nil,nil,2,nil,nil,nil,nil]
end

def test_column
col = @sudoku.column(0).map{ |c| c.value }
assert_equal col, [nil,6,nil,nil,nil,nil,4,nil,nil]
end

def test_group
group = @sudoku.group(0).map{ |c| c.value }
assert_equal group, [nil,1,nil,6,9,nil,nil,3,nil]
end
end


1 yorum:

Adsız dedi ki...

bu kadar guzel anlatimli bir makale ancak bu kadar daginik cok yazilir. vakit ayirip duzenlerseniz epey dikkat ceker. ellerinize saglik

Yorum Gönder