https://ujk-ujk.blogspot.com/2025/02/ruby-temelleri-5.html
Selam , Ruby temel tekniklerine kaldığımız yerden devam ediyoruz.
Metodlar
Metodlar bir kısım aksiyonu tekrar tekrar kullanılması için isimlendirilmiş bloklar içine toplamaya yarar. Aynı şeyleri tekrar tekrar yazmanıza gerek kalmadan yapabilmenizi sağlayarak kodunuzun takibini kolaylaştırır. Bu başlık altında Ruby metodlarının tanımlaması, kullanılması, parametreleri, "yield" ifadelerini göreceğiz.
Bir metod ismi olan bir kod bloğudur. Bir ya da birkaç nesneye bağlanır ve isminin arkasından verilen parametreleri olur.
def merhaba(isim)
"Merhaba, #{isim}"
end
Bir metod çağrısı, metod adını, metodun uygulandığı nesneyi (bazen buna receiver - alıcı da denir), ve verilen metod parametrelerine değer aktaran bir ya da birkaç argümanı belirtir. Metod bitmeden önce oluşan son değer metoddan geri dönen değerdir.
merhaba "Dünya"
#=> "Merhaba, Dünya"
Eğer metodun uygulandığı nesne (metodun alıcısı) belirtilmemişse self nesnesidir.
>> self
=> main
>> self.merhaba "Dünya"
=> "Merhaba, Dünya"
Diğer programlama dillerinde bir nesneye bağlanmayan metodlara fonksiyon denir, bir nesneye ait olanlara metod denir. Ancak Ruby tamamen nesne temelli olduğu için her şey bir nesneye bağlıdır ve sadece metodlar olur.
-- Metod parametreleri hakkında
Tip | Tanımlama | Çağırma Örneği | Atamalar |
Gerekli | def fn(a,b,c) | fn(2,3,5) | a=2, b=3, c=5 |
Değişken Sayıda | fn(*rest) | fn(2,3,5) | rest=[2, 3, 5] |
Default Değerli | def fn(a=0,b=1) | fn(2,3) | a=2, b=3 |
Anahtar-değer | def fn(a:0,b:1) | fn(a:2,b:3) | a=2, b=3 |
Bu argüman tiplerini karıştırarak değişik amaçlara göre kullanabiliriz. Metoda verilecek minimum argüman sayısı tanımlamasında Gerekli olarak bildirilenler kadardır. Ekstra argümanlar öncelikle default değeri olan parametrelere , sonra da *rest parametresine atanır.
Tip | Tanımlama | Çağırma Örneği | Atamalar |
Ger,Def,Değ,Ger | def fn(a,b=1,*mid,z) | fn(2,97) | a=2, b=1, mid=[], z=97 |
| | fn(2,3,97) | a=2, b=3, mid=[], z=97 |
| | fn(2,3,5,97) | a=2, b=3, mid=[5], z=97 |
| | fn(2,3,5,7,97) | a=2, b=3, mid=[5,7], z=97 |
Ger,Key,Key | def fn(a,g:6,h:7) | fn(2) | a=2, g=6, h=7 |
| | fn(2,h:19) | a=2, g=6, h=19 |
| | fn(2,g:17,h:19) | a=2, g=17, h=19 |
Değ,Key | def fn(**ks) | fn(a:2,g:17,h:19) | ks={a:2, g:17, h:19} |
| | fn(four:4,five:5) | ks={four:4, five:5} |
Bu sonuncu örnek oldukça kullanışlı olabiliyor.
-- Bir metod tanımlamak
Metodlar def deyimi kullanarak tanımlanır (define kısaltması), arkasından metod adı ve varsa parametreleri parantez içinde gelir. def ve end deyimleri arasında kalan Ruby kod bloğu metodun gövdesini oluşturur.
def merhaba(isim)
"Merhaba, #{isim}"
end
-- Blok argümana yield
Metodumuza içinde defalarca çalıştırabileceği bir kod bloğunu argüman olarak verebiliriz. Kod bloğu bir proc/lambda bloğudur.
def metod1(arg1,arg2)
puts "Önce buradayız: #{arg1}"
yield
puts "Sonra buradayız: #{arg2}"
yield
end
metod1('başlangıç','son') { puts "Şu anda yield içindeyiz" }
#=> Önce buradayız: başlangıç
#=> Şu anda yield içindeyiz
#=> Sonra buradayız: son
#=> Şu anda yield içindeyiz
Dikkat ederseniz metod tanımında argümanda bir kod bloğu vereceğimize dair bir ifadede bulunmadık, zaten kod bloğunu da diğer gerekli argümanlar gibi parantez içinde de vermedik. Metod çağrısı arkasından bir boşluk bırakıp kod bloğunu girdik.
Bir Ruby metoduna her zaman bu şekilde bir kod bloğu gönderebilirsiniz, ama metod içinde yield yoksa verdiğiniz kod bloğu hiç bir işe yaramaz. Buna karşılık metod gövdesinde yield kullanılmış ve metod çağrılırken bir kod bloğu verilmemişse bir hata mesajı gelecektir (no block given (yield) - LocalJumpError).
yield çağrısı yaparken parametre de kullanabilirsiniz.
def metod1(arg)
puts "yield öncesi"
yield arg
puts "yield sonrası"
end
metod1('Ümit') { |isim| puts "Benim adım #{isim}" }
#=> yield öncesi
#=> Benim adım Ümit
#=> yield sonrası
Tabi ki bu argüman metodun parametre değeri olmak zorunda değil herhangi başka bir argüman da verilebilir.
yield yardımıyla başka kodun içinde iterasyon yapabiliriz.
def gerisayım(sayı)
sayı.times do |i|
yield(sayı-i)
end
end
gerisayım(5) { |i| puts "Çağrı numarası #{i}" }
#=> Çağrı numarası 5
#=> Çağrı numarası 4
#=> Çağrı numarası 3
#=> Çağrı numarası 2
#=> Çağrı numarası 1
Aslında sınıf tanımları içinde foreach, each ve times gibi metodlar genelde yield kullanarak gerçekleştirilir.
Blok verilmeden yield çağrısı yapılırsa hata verir demiştik. Bloğun verilip verilmediğini block_given? metodu ile test edebiliriz.
class Çalışanlar
def isimler
ret = []
@çalışanlar.each do |çalışan|
if block_given?
yield(çalışan.isim)
else
ret.push(çalışan.isim)
end
end
ret
end
end
Bu örnekte Çalışanlar sınıfında @çalışanlar adında üzerinde iterasyon yapılan bir liste var. isimler metodu eğer kendisine bir blok verildiyse her çalışan.isim değeri için bu bloğu çalıştırıyor, ama blok verilmemişse sadece bu isim değerini dönen array değere ekliyor.
-- Default parametre değerleri
def hayvan_sesi_yap(ses = 'Vakvak')
puts ses
end
hayvan_sesi_yap "Muuu" #=> Muuu
hayvan_sesi_yap #=> Vakvak
Metod çağrısı yapılırken eğer bir parametreye değer girilmezse default değeri kullanılır. Default değer yukarıdaki örnekteki gibi parametre adı yanında eşittir ile girilen değerdir.
Bir'den çok parametre için de default verilebilir.
def hayvan_sesi_yap(ses = 'Vakvak', volume = 10)
play_sound(ses, volume)
end
hayvan_sesi_yap "Muuu" #=> Muu , volume = 10
hayvan_sesi_yap #=> Vakvak , volume = 10
Ancak burada ilk değeri vermeden ikinciyi vermek mümkün olamaz. Böyle bir şey istersek anahtar-değer parametre kullanırız.
def hayvan_sesi_yap(ses: 'Vakvak', volume: 10)
play_sound(ses, volume)
end
hayvan_sesi_yap volume: 1 #=> Fısıldayan Vakvak
Ya da hash parametre içinde opsiyonları belirleyebiliriz.
def hayvan_sesi_yap(opsiyonlar = {})
opsiyonlar[:ses] ||= 'Vakvak' # default değerler
opsiyonlar[:volume] ||= 11
play_sound(opsiyonlar[:ses], opsiyonlar[:volume])
end
hayvan_sesi_yap volume: 1 #=> Fısıldayan Vakvak
hayvan_sesi_yap volume: 18, ses: "Muuu" #=> Bağıran inek
Default parametre değerleri herhangi bir Ruby işlem sonucu da olabilir. Bu işlem metodun kapsamında çalıştırılır, yani burada yerel değişkenler de kullanabilirsiniz. Bir örnek olsun diye şunu verelim.
def hayvan_sesi_yap(ses = ( raise 'TUU-too-TUU-too...' ))
p ses
end
hayvan_sesi_yap "Muuu"
hayvan_sesi_yap
# "Muuu"
# in `hayvan_sesi_yap': TUU-too-TUU-too... (RuntimeError)
-- Yıldız operatörü , değişken sayıda parametre
def hoşgeldiniz(*misafirler)
misafirler.each { |misafir| puts "Hoşgeldin #{misafir}!" }
end
hoşgeldiniz "Ümit" #=> Hoşgeldin Ümit!
hoşgeldiniz "Ümit", "Ahmet", "Suna" #=> Hoşgeldin Ümit!
#=> Hoşgeldin Ahmet!
#=> Hoşgeldin Suna!
Bir küçük not
hoşgeldiniz ["Ümit", "Ahmet", "Suna"]
#=> Hoşgeldin ["Ümit", "Ahmet", "Suna"]!
Çünkü tek bir argüman verdik , bir'den fazla değer var ama bir array içinde olduğu için tek bir argüman kabul edilir.
Ama bir array listeniz varsa şu yapılabilir.
hoşgeldiniz *["Ümit", "Ahmet", "Suna"]
-- Gerekl, default ve değişken sayıda parametre karışımı
def my_mix(name,valid=true, *opt)
puts name
puts valid
puts opt
end
Çağrı örnekleri:
my_mix('me')
# "me"
# true
# []
my_mix('me', false)
# "me"
# false
# []
my_mix('me', true, 5, 7)
# "me"
# true
# [5, 7]
-- Metodu blok olarak kullanmak
Ruby'de birçok metod kod bloklarını argüman olarak alır.
>> [0, 1, 2].map {|i| i + 1}
=> [1, 2, 3]
Eğer blok içinde yapacağımız birçok iş varsa onları bir metod içinde birleştirip iterasyona kod bloğuna çevirip verebiliriz.
?> def inc(num)
?> num + 1
>> end
>> [0, 1, 2].map &method(:inc)
=> [1, 2, 3]
-- Tuple argüman
Bir array içinde verilen değerleri hızlı biçimde isimlendirilmiş değişkenlere geçirebiliriz.
def besle( miktar, (hayvan, yiyecek) )
p "#{miktar} tane #{hayvan} biraz #{yiyecek} yiyor."
end
besle 3, [ 'tavşan', 'çim' ]
# => "3 tane tavşan biraz çim yiyor."
-- Metod tanımları da bir işlemdir
Ruby 2.x'de bir metod tanımlamak metod adını içeren bir sembol değeri döner.
?> def hello
>> end
=> :hello
Bu ilginç metaprogramming fırsatları sunar. Mesela bir metod diğeri tarafından çevrelenebilir.
class Class
def kaydedilen(isim)
original_method = instance_method(isim)
define_method(isim) do |*args|
puts "#{isim} metodunu #{args.inspect} argümanları ile çağırmak."
original_method.bind(self).call(*args)
puts "#{isim} tamamlandı."
end
end
end
class Yemek
def initialize
@yiyecek = []
end
kaydedilen def ekle(*items)
items.each do |item|
@yiyecek << item
end
end
def yiyecek
p @yiyecek
end
end
yemek = Yemek.new
yemek.ekle "Kahve"
yemek.ekle "Patates", "Pilav"
yemek.yiyecek
çıktısı
ekle metodunu ["Kahve"] argümanları ile çağırmak.
ekle tamamlandı.
ekle metodunu ["Patates", "Pilav"] argümanları ile çağırmak.
ekle tamamlandı.
["Kahve", "Patates", "Pilav"]
-- Tanımlanmamış anahtar kelime argümanları çift yıldız ile almak
Operatör ** ile * operatörüne benzer olarak değişken sayıda argüman alabiliriz , ama bu sefer key-value çiftleri olarak.
def opsiyonlar(gerekli_key:, opsiyonel_key: nil, **diğer_opsiyonlar)
diğer_opsiyonlar
end
opsiyonlar(gerekli_key: 'Bitti!', foo: 'Foo!', bar: 'Bar!')
# sonuç
=> {:foo=>"Foo!", :bar=>"Bar!"}
Eğer yukarıda **diğer_opsiyonlar parametresi kullanılmamış olsa, ArgumentError: unknown keyword: foo, bar hatası verecektir.
def çift_yıldız_olmadan(gerekli_key:, opsiyonel_key: nil)
# hiç bir şey yapma
end
çift_yıldız_olmadan(gerekli_key: 'Bitti!', foo: 'Foo!', bar: 'Bar!')
# sonuç
=> unknown keywords: :foo, :bar (ArgumentError)
Çift yıldız yöntemi özellikle opsiyonlarınız bir hash içindeyse ve bunları metoda filtreleme yapmadan vermek istiyorsanız çok faydalıdır.
my_hash = { gerekli_key: true, foo: 'Foo!', bar: 'Bar!' }
opsiyonlar **my_hash
my_hash değerinin metoda gönderilirken önüne ** koyarak işlendiğini kaçırmayalım lütfen.
method_missing
Bir nesnede çağrılan metod bulunamayınca onun method_missing metodu çağrılır. Tanımı şöyledir.
def method_missing(method, *args, &block)
# bulunamayan metodla ilgili yapılacaklar
end
Parametrelerin açıklamaları
Parametre Detay
--------- -----
method Çağrılan ve bulunamayan metod adı. Sembol şeklinde
*args Bu metoda gönderilen argümanlar
&block Metoda gönderilen kod bloğu ya da {}
Not: Bu metodun sonunda hata vermesi gereken yerlerde de sessiz kalmaması için super ifadesini kullanmak gereklidir.
Örneğin bu method_missing tanımlaması bir soruna sebep olacak.
class Hayvan
def method_missing(method, *args, &block)
bişey, de = method.to_s.split("_")
if de == "de"
puts bişey
end
end
end
Hayvan.new.moo_de # moo
Hayvan.new.vakvak_de # vakvak
Ancak işimizi görmeyen başka bir olmayan metod çağrılınca sistem hata vermeyip sessiz kalacaktır.
Hayvan.new.foo # hiç bir şey olmaz
Ayıkladığımız metodlar dışındaki sistemin hata vermesi için super çağrımızı ekleyelim.
class Hayvan
def method_missing(method, *args, &block)
bişey, de = method.to_s.split("_")
if de == "de"
puts bişey
else
super
end
end
end
Ayrıca şu anda
Hayvan.new.respond_to?(:moo_de)
sorgusu hala false veriyor , halbuki metoda cevap veriliyor, bunu da çözmek için respond_to_missing? metodunun üzerine yazmalıyız.
def respond_to_missing?(method, include_private = false)
method.to_s.end_with?("_de") || super
end
-- Kod bloğunu işlemek
Metoda verilen blok kodu da kullanabiliriz.
class Hayvan
def method_missing(method, *args, &block)
if method.to_s == "çalıştır"
block.call
else
super
end
end
end
Hayvan.new.çalıştır { puts "Bloktan gelen kod" }
-- parametreleri kullanmak
Metoda verilen argümanlara da erişebiliriz.
class Hayvan
def method_missing(method, *args, &block)
bişey, de = method.to_s.split("_")
if de == "de"
return puts bişey.upcase if args.first == "bağır"
puts bişey
else
super
end
end
end
Hayvan.new.moo_de # moo
Hayvan.new.moo_de("bağır") # MOO
Sayılar
Ruby'de değişik sayı tipleri var. Versiyon 2.4 öncesi Fixnum ve Bignum adında 2 değişik Integer tip vardı fakat bunlar kaldırılıp sadece Integer kaldı. Ayrıca BigDecimal da kaldırıldı.
Hiyerarşi şöyle
Numeric
Integer # 1
Float # 1.0
Complex # (1+0i)
Rational # Rational(2, 3) == 2/3
En çok kullanılanlar
- Integer : pozitif ve negatif tamsayıları ifade etmek için kullanılır.
- Float : kayan noktalı sayıları ifade etmek için kullanılır.
-- String'i sayıya çevirmek
Integer metodu ile string'den tamsayıya dönüşüm yapılır.
>> Integer("123")
=> 123
>> Integer("0xFF")
=> 255
>> Integer("0b100")
=> 4
>> Integer("0555")
=> 365
İkinci argümanda sayının hangi temele göre yazıldığı verilebilir.
>> Integer('100', 2)
=> 4 (1 x 2^2)
>> Integer('74', 8)
=> 60 (7 x 8 + 4)
>> Integer('NUM', 36)
=> 30910
>> Integer('ff', 16)
=> 255
Eğer parametre dönüştürülemez bir değerse hata verecektir.
>> Integer("hello")
#=> invalid value for Integer(): "hello" (ArgumentError)
>> Integer("23-hello")
#=> invalid value for Integer(): "23-hello" (ArgumentError)
Dönüştürmeyi String#to_i metodu kullanarak da yapabiliriz, ama bu metod biraz daha müsamahakardır.
>> "23".to_i
=> 23
>> "23-hello".to_i
=> 23
>> "hello".to_i
=> 0
String#to_i metodu da sayının temelini belirten bir argüman alabilir.
>> "10".to_i(2)
=> 2
>> "10".to_i(3)
=> 3
>> "A".to_i(16)
=> 10
-- Bir Integer tanımlamak
0 # Integer 0 değeri
123 # Integer 123 değeri
1_000 # Integer 1000 değeri '_' okunabilirlik için kullanılabilir
Default olarak sayı temeli 10'dur fakat istenirse değişik sayı temelleri kullanılabilir.
0xFF # Hexadecimal ifade 255, 0x ile başlanır
0b100 # Binary ifade 4, 0b ile başlanır
0555 # Octal ifade 365, sayılardan önce sıfır konur
-- Sayıları yuvarlamak
Belirtilen yerdeki rakam 5 ya da da daha büyükse round metodu ile sol taraf bir üste yuvarlanır.
>> 4.89.round #=> 5
>> 4.25.round #=> 4
>> Math::PI.round(1) #=> 3.1
>> Math::PI.round(2) #=> 3.14
>> Math::PI.round(3) #=> 3.142
>> Math::PI.round(4) #=> 3.1416
>> 1115.22.round(-1) #=> 1120 - noktaya gör bir geriye yuvarla
Noktalı sayılar floor metodu ile tamsayı kısmına yuvarlanır (her zaman aşağı yuvarlanır)
>> 4.9999.floor #=> 4
>> 4.9999.floor(1) #=> 4.9
Tersini yapıp üste doğru yuvarlamak için ceil metodu kullanılır.
>> 4.0001.ceil #=> 5
>> 4.0001.ceil(1) #=> 4.1
-- Tek ve çift sayılar
Sayıların çift olmasını even? metodu ile test edebiliriz.
>> 4.even? #=> true
>> 5.even? #=> false
Sayıların tek olmasını da odd? metodu ile test ederiz.
>> 4.odd? #=> false
>> 5.odd? #=> true
-- Sayıyı string'e çevirmek
Integer#to_s metodu ile tamsayıları string'e dönüştürürüz.
>> 2.to_s #=> "2"
>> 234.to_s #=> "234"
Parametresinde dönüştürme sayı temelini verebiliriz.
>> 2.to_s 2 #=> "10"
>> 3.to_s 3 #=> "10"
>> 10.to_s 16 #=> "a"
>> 12345.to_s(36) #=> "9ix"
Float#to_s metodu ile de noktalı sayıları string'e dönüştürürüz.
>> 3.22.to_s #=> "3.22"
>> -1.998.to_s #=> "-1.998"
-- Kesirli sayılar
Rational ile kesirli sayıları pay ve payda olarak ifade ederiz.
>> r1 = Rational(2,3) #=> (2/3)
>> r2 = 2.5.to_r #=> (5/2)
>> r3 = r1 + r2 #=> (19/6)
>> r3.numerator #=> 19
>> r3.denominator #=> 6
Kesirli sayı tanımlamanın başka yolları.
>> Rational("2/3") #=> (2/3)
>> Rational(3) #=> (3/1)
>> Rational(3, -5) #=> (-3/5)
>> Rational(0.2) #=> (3602879701896397/18014398509481984)
>> Rational("0.2") #=> (1/5)
>> 0.2.to_r #=> (3602879701896397/18014398509481984)
>> 0.2.rationalize #=> (1/5)
>> "1/4".to_r #=> (1/4)
Burada 0.2.to_r ifadesinin karşılığı bilgisayarların ikili sistemde kayan noktalı sayıları işlerken nasıl minik minik hatalar yaptığını anlatır gibi. Mesela benim bilgisayarımın hesap makinesinde o bölmenin sonucu 0.20000000000000001110223024625157 çıkıyor. Muhasebe programları yazan birçok kişi bırakın çarpmayı, bölmeyi basit toplama, çıkarma işlemlerinde bile böyle virgülden sonra bilmem kaçıncı basamaklarda birikenlerin kuruşluk hatalara sebep vermemesi için Float sayılar değil virgülü 2 veya 4 basamak sağa kaydırarak tamsayılar ile çalışırlar.
-- Kompleks sayılar
Hatırlıyorum bilimsel ifadelerde hesap yapmak için kompleks sayılar kullanılıyordu, -1 değerinin kareköküne i (ya da j) deniyordu galiba. Gerçel ve sanal bileşenleri olan sayılardı.
>> 1i #=> (0+1i)
>> 1.to_c #=> (1+0i)
>> rectangular = Complex(2,3) #=> (2+3i)
>> polar = Complex("1@2") #=> (-0.4161468365471424+0.9092974268256817i)
#=> uzunluk=1 - açı=2 (radyan olarak)
>> 1i.polar #=> [1, 1.5707963267948966] - uzunluk=1 , açı=PI/2 (90 derece)
>> polar.rectangular #=> [-0.4161468365471424, 0.9092974268256817]
>> rectangular.polar #=> [3.605551275463989, 0.982793723247329]
>> rectangular + polar #=> (1.5838531634528576+3.909297426825682i)
# sanal ve gerçel kısımları ayrı ayrı toplanmış
-- Sayılarda bölme işlemleri
Sayılarda bölme yaparken dönecek değere dikkat etmemiz gerekiyor. İki tamsayıyı bölersek sonuç tamsayı bölümü yapılarak bulunur. Eğer sonucu noktalı sayı olarak istiyorsak en az bir sayının noktalı sayı olması gerekir.
>> 3 / 2 #=> 1 (tamsayı bölme)
>> 3 / 3.0 #=> 1.0
>> 16 / 2 / 2 #=> 4 (tamsayı bölme)
>> 16 / 2 / 2.0 #=> 4.0
>> 16 / 2.0 / 2 #=> 4.0
>> 16.0 / 2 / 2 #=> 4.0
Ruby İterasyonlar
İterasyon, liste şeklinde verilmiş bir değerler kümesinde her eleman için aynı işlemleri tekrar tekrar yapmak döngüsüne verilen addır. İterasyon için kullanılan metodlar vardır, bunların en yaygını each metodu.
-- each metodu
each metodu verilen listedeki her eleman için kendisine verilen kod bloğunu çalıştırır.
(1..4).each {|i| i.even? ? p("çift") : p("tek")}
# "tek"
# "çift"
# "tek"
# "çift"
[1,2,3].each {|x| p "Sayı #{x}"}
# "Sayı 1"
# "Sayı 2"
# "Sayı 3"
{key1: 11, key2: 18.6}.each {|key,value| p "key: #{key} - value: #{value}"}
>> {key1: 11, key2: 18.6}.each {|key,value| p "key: #{key} - value: #{value}"}
# "key: key1 - value: 11"
# "key: key2 - value: 18.6"
Daha uzun bloklar için do..end kullanmak daha uygun olur.
(1..4).each do |x|
if x.even?
puts "#{x} sayısı çifttir"
else
puts "#{x} sayısı tektir"
end
end
İterasyonu verilen sıranın tersine yapmak için reverse_each metodu kullanılabilir.
>> [1,2,3].reverse_each {|i| p i}
#=> 3 2 1
Array elemanlarının index numaralarını da almak için each_with_index metodu kullanılır.
[1,2,3].each_with_index {|v,ix| print v,ix,"\n"}
gibi.
-- Sınıf yapılarında uygulama
Enumerable Ruby'nin en popüler modülü, amacı iterasyonlar için map, select, reduce vb. metodları sağlamak. Enumerable kullanan sınıflar içinde örneğin Array, Hash ve Range var. İterasyon metodlarını kendi sınıflarınızda kullanmak için include Enumerable satırını eklemelisiniz ve each metodunu sınıfınız için tanımlamalısınız.
class DoğalSayılar
include Enumerable
def initialize(üst_sınır)
@üst_sınır = üst_sınır
end
def each(&blok)
0.upto(@üst_sınır).each(&blok)
end
end
n = DoğalSayılar.new(6)
p n.reduce(:+) # => 21
p n.select(&:even?) # => [0, 2, 4, 6]
p n.map { |number| number ** 2 } # => [0, 1, 4, 9, 16, 25, 36]
-- Karmaşık nesnelerde iterasyon
-- -- Array içinde
İç içe array'lerde iterasyon yapılabilir.
[[1, 2], [3, 4]].each { |(a, b)| p "a: #{ a }", "b: #{ b }" }
# "a: 1" "b: 2"
# "a: 3" "b: 4"
Şu şekil yazılmasına da imkan vardır.
[[1, 2], [3, 4]].each { |a, b| p "a: #{ a }", "b: #{ b }" }
# "a: 1" "b: 2"
# "a: 3" "b: 4"
-- -- Hash değerlerde
Key-value çiftleri üzerinden iterasyon yapılır.
{a: 1, b: 2, c: 3}.each { |çift| p "çift: #{ çift }" }
# "çift: [:a, 1]"
# "çift: [:b, 2]"
# "çift: [:c, 3]"
İstersek anahtar ve değer elemanlarını ayrı ayrı da alabiliriz.
{a: 1, b: 2, c: 3}.each { |(k, v)| p "k: #{ k } - v: #{ v }" }
# "k: a - v: 1"
# "k: b - v: 2"
# "k: c - v: 3"
-- for .. in iterasyonu
Aynı şeyin laciverti, ama görsel olarak başka bir ifade biçimidir. Aralarında küçücük bir kapsam farklılığı var ama.
for i in 4..13
puts "bu #{i}. sayı"
end
# bu 4. sayı
# bu 5. sayı
# bu 6. sayı
# ...
Array için de kullanılabilir.
isimler = ["Ümit", "Hasan", "Ebru"]
for isim in isimler
p "Merhaba, #{isim}."
end
# "Merhaba, Ümit."
# "Merhaba, Hasan."
# "Merhaba, Ebru."
for isim in isimler şeklindeki notasyon bana daha mantıklı geliyor. Şimdi gelelim aradaki minik farka.
?> for i in [1,2,3] do
?> puts i
>> end
>> puts i
=> 1
=> 2
=> 3
=> 3
>> [1,2,3].each { |n| puts n }
>> puts n
=> 1
=> 2
=> 3
=> "undefined local variable or method `n' for main:Object (NameError)
for in döngüsü i değişkenini içinde bulunduğu kapsamda tanımlarken, each döngüsü n değişkenini blok içi kapsamında tanımlıyor. Bu sebeple dışarıdan n değerine ulaşılamıyor. İşe yarar mı bilmem, ama böyle bir fark var.
-- index değeri ile iterasyon
Bazı durumlarda iterasyon yapılan elemanın küme içindeki pozisyonu olan index değerine de ihtiyaç duyulabilir. Bu amaçla Ruby with_index metodu tüm iterasyonlara uygulanabilir. Bu metodu kullanınca index değeri bloğa geçirilen ikinci argüman olur.
[2,3,4].map.with_index { |e, i| puts "Array içinde #{i} indexli eleman => #{e}" }
# Array içinde 0 indexli eleman => 1
# Array içinde 1 indexli eleman => 3
# Array içinde 2 indexli eleman => 4
with_index metoduna başlangıç index numarası argüman olarak verilerek index'lerin başlayacağı sayı belirtilebilir (default sıfırdır).
[2,3,4].map.with_index(1) { |e, i| puts "Array içinde #{i} indexli eleman => #{e}" }
# Array içinde 1 indexli eleman => 1
# Array içinde 2 indexli eleman => 3
# Array içinde 3 indexli eleman => 4
Sadece each metoduna özgü each_with_index şeklinde bir birleşik metod vardır, ve bunun tek farkı argüman almayıp her zaman index değerini sıfırdan başlatmasıdır.
[2,3,4].each_with_index { |e, i| puts "Array içinde #{i} indexli eleman => #{e}" }
gibi.
-- map iterasyonu
Bu metod iterasyon yaparken blokla dönüştürülmüş değerlerden oluşan yeni bir küme döner, orjinali değiştirmez.
>> arr = [1, 2, 3]
>> arr.map { |i| i + 1 } #=> [2, 3, 4]
>> arr #=> [1, 2, 3]
Bununla beraber map! metodu orjinal kümeyi değiştirir.
>> arr.map! { |i| i + 1 } # => [2, 3, 4]
>> arr #=> [2, 3, 4]
Eleman yerine bloktan dönen değer yerleştirilecektir, mesela.
>> arr.map! { |i| [0, i+1] }
#=> [[0, 3], [0, 4], [0, 5]]
Regexp - Düzenli ifadeler
Regular expression lafı Türkçemize düzenli ifade diye çevriledurmuş yıllardır. Kısaca Regexp denilen bu değer ifadeleri aslında bize bir şablon verir. Regexp kurallarına uygun olarak verilen string'ler içinde bu şablonla eşleşen yazılar bulunur. Mesela e-mail adresi içinde @ ve .com var mı gibi kontrollerde çok işe yarar. Bir regexp değeri iki bölü işareti arasında bildirilir (/ /).
-- eşleşme operatörü
if /mer/ =~ 'merhaba'
puts "'merhaba' kelimesi içinde 'mer' var"
end
Burada sıralama önemlidir, çoğu durumda /mer/ =~ 'merhaba' ve 'merhaba' =~ /mer/ aynı sonucu verir.
Bu karşılaştırma true/false şeklinde bir değer dönmez, Eşleşmenin index numarasını döner
>> /hab/ =~ "merhaba"
=> 3
Eşleşme olmazsa nil değer döner , bu yüzden nil dönmeyeceğinde true kabul edilecek denerek if bloklarında direk kullanılabilir. Eğer ille de boolean değer istersek ilerde göreceğimiz === metodunu kullanırız.
-- Regexp ile case yapıları
Case yapılarında değişik eşleşmeler için işlem yapabiliriz.
case "Ruby is #1!"
when /\APython/
puts "Yuuuh."
when /\ARuby/
puts "Çok haklısın."
else
puts "Pardon, anlamadım."
end
Direk olarak regexp değerlerinin koşullara konabilmesinin sebebi when ifadelerinin karşılaştırmayı == operatörü ile değil === operatörü ile yapmasıdır.
>> /hab/ == "merhaba"
=> false
>> /hab/ === "merhaba"
=> true
-- İsimlendirilmiş ya da normal gruplar
Eşleşmelerde gruplandırmaları ( ... ) şeklinde parantez içinde yaparız.
>> /a (ab[0-9]) (bc[0-9])/ =~ "a ab1 bc3"
=> 0
>> $~ #=> #<MatchData "a ab1 bc3" 1:"ab1" 2:"bc3">
>> $1 #=> "ab1" - ilk eşleşen grup
>> $2 #=> "bc3" - ikinci eşleşen grup
Tek tek kaçıncı eşleşmeydi diye sayacağımıza Ruby bize gruplara isim verme imkanı da sunar.
>> /a (?<x>ab[0-9]) (?<y>bc[0-9])/ =~ "a ab1 bc3"
=> 0
>> $~ #=> #<MatchData "a ab1 bc3" x:"ab1" y:"bc3">
>> x #=> "ab1"
>> y #=> "bc3"
(?<x>...) formatında yazılan bir grup ile x değişkenine o grupla eşleşen yazıyı alabiliriz, çok daha kolayca.
Hem isimlendirilmiş hem normal grup varsa kafa karışabilir.
>> "Hi, my name is Ümit" =~ /h(i|ello), my name is (?<name>.*)/i
=> 0
>> $~ #=> #<MatchData "Hi, my name is Ümit" name:"Ümit">
>> $1 #=> "Ümit" - 'i' olmasını bekliyorduk
Halbuki iki grubu da isimli versek.
>> match_data = "Hi, my name is Ümit".match(/h(?<x>i|ello), my name is (?<name>.*)/i)
=> #<MatchData "Hi, my name is Ümit" x:"i" name:"Ümit">
>> match_data[:x]
=> "i"
>> match_data[:name]
=> "Ümit"
>> match_data[1]
=> "i"
>> match_data[2]
=> "Ümit"
Eşleşen grupların index değeri açılan parantez işaretlerinden sayılır.
>> reg = /(((a)b)c)(d)/
>> match = reg.match 'abcd'
>> match[0] #=> "abcd"
>> match[1] #=> "abc"
>> match[2] #=> "ab"
>> match[3] #=> "a"
>> match[4] #=> "d"
>> match[5] #=> nil
-- Regexp miktar belirteçleri
Eşleşmesi beklenen string'in kaç defa tekrarlandığını belirten ifade şekilleridir.
- Sıfır ya da bir tane
şablon: /a?/
>> "bar" =~ /bar?/ #=> 0
# ba olacak sıfır yada dbir tane r olacak
>> $~ #=> #<MatchData "bar">
>> "barr" =~ /bar?/ #=> 0
>> $~ #=> #<MatchData "bar">
>> "ba" =~ /bar?/ #=> 0
>> $~ #=> #<MatchData "ba">
- Sıfır ya da daha çok
>> "bar" =~ /bar*/ #=> 0
# ba olacak sıfır yada daha fazla r olacak
>> $~ #=> #<MatchData "bar">
>> "barr" =~ /bar*/ #=> 0
>> $~ #=> #<MatchData "barr">
>> "ba" =~ /bar*/ #=> 0
>> $~ #=> #<MatchData "ba">
"barr" eşleşmesine dikkat edelim, öncekinde eşleşen kelime "bar" idi bunda "barr".
- Bir ya da daha fazla
şablon: /a+/
>> "bar" =~ /bar+/ #=> 0
# en az bir tane r olacak
>> $~ #=> #<MatchData "bar">
>> "barr" =~ /bar+/ #=> 0
>> $~ #=> #<MatchData "barr">
>> "ba" =~ /bar+/ #=> nil
- Sayısını net belirterek
şablon: /a{2,4}/ # İki, üç veya dört tane
>> "bar" =~ /bar{2,4}/ #=> nil
>> "barr" =~ /bar{2,4}/ #=> 0
>> $~ #=> #<MatchData "barr">
>> "barrrrr" =~ /bar{2,4}/ #=> 0
>> $~ #=> #<MatchData "barrrr">
Son satırda eşleşen olarak 4 'r' çekiliyor 5. 'r' eşleşen kelimede yok, dikkat ederseniz.
şablon: /a{2,}/ # en az tane
>> "bar" =~ /bar{2,}/ #=> nil
>> "barr" =~ /bar{2,}/ #=> 0
>> $~ #=> #<MatchData "barr">
>> "barrrrr" =~ /bar{2,}/ #=> 0
>> $~ #=> #<MatchData "barrrrr">
ve üst sınırlı
şablon: /a{,4}/ # en fazla 4 tane - sıfır dahil
>> "bar" =~ /bar{,4}/ #=> 0
>> $~ #=> #<MatchData "bar">
>> "ba" =~ /bar{,4}/ #=> 0
>> $~ #=> #<MatchData "ba">
>> "barrrrr" =~ /bar{,4}/ #=> 0
>> $~ #=> #<MatchData "barrrr">
5. 'r' ye dikkat.
Miktar belirteçleri biraz aç gözlüdür, eşleşme yapabildikleri sürece en fazla karakteri almaya bakarlar. Bu normalde pek fark edilmez (yukarıdaki 5. 'r' gibi), ama diyelim şöyle isimlendirilmiş grubumuz var.
>> /(?<meslek>.*) Ümit Kayacık/ =~ 'Mühendis Ümit Kayacık'
>> $~[:meslek]
=> "Mühendis"
Ama diyelim 2. kısım da opsiyonel olsun.
>> /(?<meslek>.*) (Ümit Kayacık)?/ =~ 'Mühendis Ümit Yılmaz'
>> $~[:meslek]
=> "Mühendis Ümit"
* ifadesi eşleşebildiği kadar öteki tarafa sarktı. Yukarıda da isimli ve isimsiz grup karışımlarında sorun olabilir demiştik.
Sorunu çözmek için ikinci bir soru işareti kullanabiliriz.
>> /(?<meslek>.*?) (Ümit Kayacık)?/ =~ 'Mühendis Ümit Yılmaz'
>> $~[:meslek]
=> "Mühendis"
Bir niteleyici arkasına ? koymak eşleşebildiği kadar gitmesini engeller.
>> "barrrr" =~ /bar+/ #=> 0
>> $~ #=> #<MatchData "barrrr">
>> "barrrr" =~ /bar+?/ #=> 0
>> $~ #=> #<MatchData "bar">
-- Karakter seçmeler
Karakterleri ayrı ayrı belirtebiliriz.
>> /[acf]/ # a veya c veya f harfi
>> /[acf]/ =~ "afad" #=> 0
>> /[acf]/ =~ "mine" #=> nil
>> /[acf]/ =~ "mina" #=> 3
>> /[acf]/ =~ "ceviz" #=> 0
Bir aralık belirtebiliriz.
>> /[a-z]/ # a'dan z'ye harfler
>> /[a-z]/ =~ "s" #=> 0
>> /[a-z]/ =~ "ş" #=> nil
>> /[a-z]/ =~ "." #=> nil
>> /[a-z]/ =~ "<>" #=> nil
>> /[a-z]/ =~ "6" #=> nil
Türkçe karakterler için ne yaparız, bilemiyorum.
Aralık ve ayrıca harf belirtimini birleştirebiliriz.
/[a-cz]/ # 'a' veya 'b' veya 'c' veya 'z'
Baştaki tire karakter olarak algılanır.
/[-a-c]/ # '-' veya 'a' veya 'b' veya 'c'
Seçmeler başına ^ eklenerek ters ifade edilebilirler.
>> /[^a-c]/ # (a veya b veya c) değil!
>> /[^a-c]/ =~ "zeytin" #=> 0
>> /[^a-c]/ =~ "abc" #=> nil
Bazı karakter seçicileri şunlardır.
^ #: Satırın başındaki eşleşmeyi yakalar:
>> /^bar/.match("foo\nbar") #<MatchData "bar">
>> /^ar/.match("foo\nbar") #=> nil
>> /^fo/.match("foo\nbar") #<MatchData "fo">
$ #: Satırın sonundaki eşleşmeyi yakalar:
>> /bar$/.match("foo\nbar") #<MatchData "bar">
>> /ba$/.match("foo\nbar") #=> nil
>> /foo$/.match("foo\nbar") #<MatchData "foo">
\A # String'in başındaki eşleşmeyi yakalar
>> /\Afoo/.match("foo\nbar") #=> #<MatchData "foo">
>> /\Abar/.match("foo\nbar") #=> nil
\Z # String sonu eşleşme bir tane \n olabilir.
>> /bar\Z/.match("foo\nbar") #=> #<MatchData "bar">
>> /bar\Z/.match("foo\nbar\n") #=> #<MatchData "bar">
>> /bar\Z/.match("foo\nbar\n\n") #=> nil
\z # net String sonu eşleşme \n kabul edilmez.
>> /bar\z/.match("foo\nbar") #=> #<MatchData "bar">
>> /bar\z/.match("foo\nbar\n") #=> nil
. # newline hariç herhangi bir karakter
>> /./.match("foo") #=> #<MatchData "f">
>> /./.match("\n") #=> nil
>> /./.match("\\") #=> #<MatchData "\\">
>> /./.match("[") #=> #<MatchData "[">
\s # herhangi whitespace karakter - /[ \t\r\n\f\v]/ ile aynı iş
>> /\s/.match "foo bar" #=> #<MatchData " ">
>> /\s/.match "foo\nbar" #=> #<MatchData "\n">
>> /\s/.match "\tfoo bar" #=> #<MatchData "\t">
\S # whitespace olmayan karakter - /[^ \t\r\n\f\v]/ ile aynı iş
>> /\S/.match "foo bar" #=> #<MatchData "f">
>> /\S/.match "foo\nbar" #=> #<MatchData "f">
>> /\S/.match "\tfoo bar" #=> #<MatchData "f">
\d # herhangi rakam - [0-9] ile aynı iş
>> /\d/.match "f12" #=> #<MatchData "1">
>> /\d/.match "foo" #=> nil
\D # herhangi rakam olmayan karakter - [^0-9] ile aynı iş
>> /\D/.match "123foo" #=> #<MatchData "f">
>> /\D/.match "123" #=> nil
\w # kelime içinde kullanılan karakter - [a-zA-Z0-9_]
>> /\w/.match(' foo') #=> #<MatchData "f">
>> /\w/.match(' _') #=> #<MatchData "_">
>> /\w/.match('. ') #=> nil
\W # kelime içinde kullanılmayan karakter - [^a-zA-Z0-9_]
>> /\W/.match(' ') #=> #<MatchData " ">
>> /\W/.match('_') #=> nil
\b # kelime sınırlarını bulur, braket içinde backspace bulur
>> /foo\b/.match('foo bar') #=> #<MatchData "foo">
>> /bar\b/.match('foo bar') #=> #<MatchData "bar">
>> /foo\b/.match('foobar') #=> nil
>> /foo\b/.match("foo\bbar") #=> #<MatchData "foo">
>> /foo[\b]/.match("foo\bbar") #=> #<MatchData "foo\b">
Herhangi bir özel karakter için önüne ters slash ekleriz.
\ için \\
>> /\\/ =~ 'foo\nbar' #=> 3
>> 'foo\nbar'.match(/\\/) #=> #<MatchData "\\">
[] için \[\]
>> "foo[]" =~ /\[\]/ #=> 3
>> /\[\]/.match("foo[]") #=> #<MatchData "[]">
-- Bir regexp tanımlamak
Ruby'de regexp değer tanımlamanın 3 şeki var.
- Bölü işaretleri ile - / /
- %r{ } kullanarak
- Regex.new metodu ile
# Aşağıdaki satırlar aynı
regexp_slash = /hello/
regexp_bracket = %r{hello}
regexp_new = Regexp.new('hello')
string_to_match = "hello world!"
# tüm bu sorgular 'doğru' değer döner
string_to_match =~ regexp_slash # => 0
string_to_match =~ regexp_bracket # => 0
string_to_match =~ regexp_new # => 0
-- match? - boolean sonuç
match? metodu $~ ve bağlı global değişkenlere dokunmadan eşleşme olup olmadığını döner. Eğer ikinci bir argüman verilmişse eşleşme sorgusunun kaçıncı karakterden başlayacağını bildirir.
>> /foo/.match("foo bar") #=> #<MatchData "foo">
>> /R.../.match?("Ruby") #=> true
>> /R.../.match?("Ruby", 1) #=> false
>> /P.../.match?("Ruby") #=> false
>> $~ #=> #<MatchData "foo">
-- Genel regexp kullanımları
Düzenli ifadeler string'ler içinde arama yapmak ya da bir kısmını değiştirmek için yaygın kullanılır.
string = "Çok uzun bir string"
string[/uz/] # uz döner
string[/kısa/] # nil
string[/kısa/].nil? # true
Bu sayede hızlı kontroller yapılabilir.
puts "buldum" if string[/uz/]
Mesela aranan 2. grubu bulmak için
string[/(Ç.k).+(b.r)/, 2] # "bir" döner
Değiştirme işlemlerinde de kullanılır. Bu örnekte \1 ilk eşleşmeyi \2 ikinci eşleşmeyi verir.
string.gsub(/(u..n).+(b.r)/, '\1 olmayan \2')
#=> "Çok uzun olmayan bir string"
Regexp çok yordu, bu bölümü de burada bitirelim. Önümüzde Comparable modülü var.
Şimdilik kalın sağlıcakla..
Hiç yorum yok:
Yorum Gönder