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
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
Kod:
gem install zxc zbdk
Kod:
gem update
Kod:
gem update asd
Kod:
gem list
Kod:
gem outdated
Kod:
gem help
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
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
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, 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
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
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
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
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
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
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
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
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
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
Kod:
print "Merhaba Dunya!"
Kod:
puts "Merhaba Dunya!"
Kod:
p "Merhaba Dunya!"
Kod:
$stdout << "Merhaba Dunya!"
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
Kod:
3 + 5
=> 8
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.
Bir örnek daha;
Kod:
3.times { puts "asd" }
Kontrol Yapıları
ruby'de de if ler diğer bir çok dildeki gibidir
örneğin;
Kod:
if 3 + 5 == 8
puts "heyoo!!"
end
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
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
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
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
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
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" }
Kod:
h[:isim]
=> "kuzux"
h["asd"] = "dsa"
Her çift üzerinden döngü
Kod:
h.each do |anahtar, deger|
puts "#{anahtar}: #{deger}
end
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
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
Kod:
def kare(sayi)
sayi ** 2
end
Bir fonksiyonun argümanlarına varsayılan değerler verebiliriz.Mesela;
Kod:
def merhaba(isim="kuzux")
puts "Merhaba, #{isim}"
end
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
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
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
Kod:
ifcan(3<5)>
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
ayrıca, yield ile bloka değer de yollanabilir. mesela;
Kod:
def iki_yolla
yield 2
end
iki_yolla{|x| x+1}
# => 3
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
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.
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.
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
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
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
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 MerhabaciKalıtım
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(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ı
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
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.
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
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
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
Kod:
class Kare < Dikdortgen
def initialize(a)
super(a,a)
end
end
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
ö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
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
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
Kod:
File.open("dosya.txt") do |f|
puts f.read
end
"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
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")
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.
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
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
Kod:
class Aaa
extend Asd
end
Aaa.asd # "mixin im ben" yazdırır
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
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
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, 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]
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.
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]
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]
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
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
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
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
Kod:
n = Nokta2b.new(3)
n.y # => nil
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
Kod:
class Nokta3b < Struct.new(:x,:y,:z)
def initialize(x,y,z=0)
super(x,y,z)
end
end
Kod:
Nokta = Nokta3b
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
Kod:
require 'open-uri'
örneğin;
Kod:
site = open("http://google.com/xhtml")
puts site.read
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
Kod:
asd # "asd" yazdırır
qwe # "qwe" yazdırır
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
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
Kod:
class Vektor
def initialize(*args)
if args[0].respond_to? :x
@bitis = args[0]
else
@bitis = Nokta.new(args)
end
end
end
Kod:
class Vektor
include Comparable
def uzunluk
@bitis.uzaklik
end
def <=>(v)
uzunluk <=> v.uzunluk
end
end
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
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
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
Kod:
class Vektor
def aci(v)
sonuc = Math.acos(self^v / (uzunluk*v.uzunluk) #radyan
sonuc.derece
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
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
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
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
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
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
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"
Kod:
def rasgele_salt
Digest::SHA1.hexdigest(Time.now.to_s)
end
def hashle(sifre,salt)
Digest::SHA256.hexdigest(sifre+salt)
end
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
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
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
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
Kod:
3-57
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
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
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:
bu kadar guzel anlatimli bir makale ancak bu kadar daginik cok yazilir. vakit ayirip duzenlerseniz epey dikkat ceker. ellerinize saglik
Yorum Gönder