25 Şubat 2025 Salı

Ruby Temelleri 5

https://ujk-ujk.blogspot.com/2025/02/ruby-temelleri-5.html
İçindekiler +

 

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

TipTanımlamaÇağırma ÖrneğiAtamalar
Gereklidef fn(a,b,c)fn(2,3,5)a=2, b=3, c=5
Değişken Sayıdafn(*rest)fn(2,3,5)rest=[2, 3, 5]
Default Değerlidef fn(a=0,b=1)fn(2,3)a=2, b=3
Anahtar-değerdef 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. 

TipTanımlamaÇağırma ÖrneğiAtamalar
Ger,Def,Değ,Gerdef 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,Keydef 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ğ,Keydef 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