Ders 09 / 12
`Enumerable` Modülü
Dizinin kendisinde olmayan yineleme yöntemlerinin Enumerable modülünden geldiği, kendi sınıfınıza tek bir each ile aynı yeteneğin verildiği ve modülün her işi üstlenmediği ölçülür.
Yayın Güncelleme
Bu derste
- Enumerable yöntemlerinin sahibini owner ile ölçer
- Kendi sınıfına include Enumerable ve tek bir each ile yineleme yöntemleri kazandırır
- sort_by, group_by, min_by ve tally yöntemlerinin sonuçlarını okur
- Aynı adlı bir yöntemin dizide, aralıkta ve modülde farklı sahipleri olabileceğini ölçer
İçindekiler
Önceki derste karma.map yönteminin Hash‘te değil Enumerable modülünde tanımlı olduğu görüldü. Bu ders o modüle bakar. Modül (module), yöntemlerin bir sınıfa karıştırıldığı (include) bir yöntem topluluğudur; kendi başına nesne üretmez. M08’de nesne yönelimli temeller kuruldu; burada yalnız bu modülün hangi iletileri yanıtladığı ölçülür.
Bu dersin yanıtlayanı modüldür. Dizi, aralık ve karma yapı farklı sınıflardır, ama birçok yineleme iletisini ortak bir modül yanıtlar.
Modülün Yanıtladığı İletiler
İlk ölçüm bir aralığa each_slice iletisini gönderir. Çekirdek sonucu, yöntemin sahibine bakarak sınıflandırır: sahip bir Class ise yanıtlayan sınıf, değilse modüldür.
# yanit.rb — bir iletiyi kimin yanitladigini siniflandiran yardimcilar
# Siniflandirma sirayla sorar: once ifade bir ileti mi (defined?),
# sonra iki farkli blok iki farkli sonuc veriyor mu, sonra yontemin sahibi kim.
def yanitlayan(tanim, sahip: nil, sonuc: nil, bloklu: nil)
return ["sozdizimi", "defined? #{tanim}"] unless tanim == "method"
if bloklu
birinci, ikinci = bloklu.call
return ["blok", "#{birinci.inspect} / #{ikinci.inspect}"] unless birinci == ikinci
end
tur = sahip.instance_of?(Class) ? "sinif" : "modul"
[tur, "#{sahip}, sonuc #{sonuc.inspect}"]
end
# dilim.rb — each_slice iletisini kim yanitliyor
require_relative "yanit"
aralik = (1..5)
sahip = aralik.method(:each_slice).owner
sonuc = aralik.each_slice(2).to_a
tur, kanit = yanitlayan(defined?(aralik.each_slice(2)), sahip: sahip, sonuc: sonuc)
puts "#{tur} #{kanit}"
modul Enumerable, sonuc [[1, 2], [3, 4], [5]]
Bu, çekirdeğin dilimleme satırıdır: yanıtlayan modül, sahip Enumerable. Range sınıfı each_slice yöntemini kendisi tanımlamıyor; aralığa each ile elemanlarını vermeyi bilmesi yeterli. Aynı modülün diğer yöntemleri bu dersin ikinci ölçümündedir.
# araclar.rb — Enumerable yontemleri
meyveler = %w[elma armut kiraz muz]
puts meyveler.sort_by { |m| m.size }.first
puts meyveler.group_by { |m| m.size }.inspect
puts meyveler.min_by { |m| m.size }
puts %w[a b a].tally.inspect
puts (1..6).select(&:even?).inspect
muz
{4 => ["elma"], 5 => ["armut", "kiraz"], 3 => ["muz"]}
muz
{"a" => 2, "b" => 1}
[2, 4, 6]
sort_by en kısa adı başa aldı, group_by adları uzunluğa göre gruplayan bir karma yapı kurdu, min_by en kısa adı verdi. tally her elemanın kaç kez geçtiğini saydı. select(&:even?) aralıktan çift sayıları seçti.
Kendi Sınıfınıza Karıştırmak
Modülün yeteneği ancak bir sınıfa karıştırıldığında bir sınıfın olur. En küçük biçimi: sınıf yalnız each tanımlar ve include Enumerable der. Sınıf tasarımı M08’in konusudur; burada sınıf yalnız ölçüm için en küçük hâlinde kurulur.
# sayilar.rb — kendi sinifimiza Enumerable karistirmak
class Sayilar
include Enumerable
def each
yield 3
yield 1
yield 2
end
end
sayilar = Sayilar.new
puts sayilar.map { |n| n * 2 }.inspect
puts sayilar.sort.inspect
puts sayilar.min_by { |n| -n }
puts "map=#{sayilar.method(:map).owner} each=#{sayilar.method(:each).owner}"
puts sayilar.to_a.inspect
puts sayilar.include?(2)
puts sayilar.first
puts sayilar.reduce(:+)
puts sayilar.each_with_index.to_a.inspect
puts sayilar.is_a?(Enumerable)
[6, 2, 4] [1, 2, 3] 3 map=Enumerable each=Sayilar [3, 1, 2] true 3 6 [[3, 0], [1, 1], [2, 2]] true
Sınıf yalnız each yazdı; map, sort ve min_by ek tanım yazmadan geldi. min_by { -n } en büyük sayıyı verdi, çünkü eksi değeri en küçük olan 3’tür. Son satır paylaşımı gösterir: map’in sahibi Enumerable, each’in sahibi Sayilar sınıfı. to_a elemanları each’in verdiği sırayla topladı: [3, 1, 2]. include?, first ve reduce de aynı tek each’e dayandı; reduce(:+) elemanları toplayıp 6 verdi. each_with_index her elemana sıra numarasını ekledi. Son satır, sınıfın örneklerinin Enumerable olarak tanındığını doğrular. Sınıf <=> tanımlamadı, çünkü sort burada sayıları karşılaştırırken sayıların kendi karşılaştırmasını kullanır; sınıfın örneklerini kendi aralarında karşılaştırmak ayrı bir iştir. KB8. Bu sınıf each ile üç değer verir; each sonsuz bir dizi üretirse sort gibi tümünü okuyan yöntemler hiç bitmez, ama bu davranış burada ölçülmez.
Modülün Döndürdüğü Türler
Modülün yöntemleri alıcının türünden bağımsız olarak belirli türler döndürür. Bu, modülün kullanıcıya verdiği bir sözleşmedir: aralığa, diziye ya da kendi sınıfınıza aynı iletiyi gönderdiğinizde sonuç türünü alıcıya bakmadan bilirsiniz.
# turler.rb — Enumerable yontemlerinin dondurdugu turler
aralik = (1..3)
puts aralik.map { |n| n }.class
puts aralik.select { |n| n > 1 }.class
puts aralik.group_by { |n| n.odd? }.class
puts aralik.tally.class
puts aralik.each_slice(2).class
puts aralik.min_by { |n| -n }.class
Array Array Hash Hash Enumerator Integer
map ve select dizi, group_by ve tally karma yapı döndürdü; bloksuz each_slice bir yineleyici verdi; min_by tek bir eleman verdi. Aralık burada alıcıydı, ama sonuç türleri aralığa değil modülün tanımına bağlı. Diziler ve Karma Yapılar dersinin Hash#select örneği bunun sınırını gösterir: orada sınıfın kendi tanımı karma yapı döndürmüştü. KB9. Bu tablo yalnız aralık alıcısıyla ölçüldü; aynı yöntemlerin kendi sınıfınızda aynı türleri döndürdüğü yukarıdaki Sayilar ölçümünden map için görülmüştür, diğerleri için varsayılmaz.
Zincirleme Yöntemler
Enumerable’ın asıl gücü, küçük yöntemlerin birbirine bağlanmasıdır. Aşağıdaki ölçüm beş yöntemi bir aralık üzerinde koşturur ve her birinin sahibini yanına basar.
# zincir.rb — her yontemin sonucu ve sahibi
s = (1..4)
puts "each_with_object=#{s.each_with_object([]) { |n, a| a << n * n }.inspect} sahip=#{s.method(:each_with_object).owner}"
puts "inject=#{s.inject(:+)} reduce=#{s.reduce(1) { |a, n| a * n }} sahip=#{s.method(:inject).owner}"
puts "zip=#{s.zip(%w[a b c d]).inspect} sahip=#{s.method(:zip).owner}"
puts "partition=#{s.partition(&:even?).inspect} sahip=#{s.method(:partition).owner}"
puts "each_cons=#{s.each_cons(2).to_a.inspect} sahip=#{s.method(:each_cons).owner}"
each_with_object=[1, 4, 9, 16] sahip=Enumerable inject=10 reduce=24 sahip=Enumerable zip=[[1, "a"], [2, "b"], [3, "c"], [4, "d"]] sahip=Enumerable partition=[[2, 4], [1, 3]] sahip=Enumerable each_cons=[[1, 2], [2, 3], [3, 4]] sahip=Enumerable
each_with_object boş bir diziyi taşıyarak kareleri topladı. inject(:+) toplamı, reduce(1) { ... } çarpımı verdi; inject’in sahibi Enumerable olarak basıldı. zip iki diziyi çiftledi, partition blok doğru olanları ve olmayanları iki ayrı diziye ayırdı, each_cons(2) ardışık ikilileri verdi. Beş yöntemin beşinde de basılan sahip Enumerable oldu: Range kendi each’ini tanımlasa da bu zincirin yanıtlayanı modüldür. KB27. Sahip ölçümü yalnız Range alıcısıyla yapıldı; reduce’un sahibi ayrıca basılmadı.
Tembel Zincir
lazy zinciri hemen çalıştırmaz; sonucu istenen kadar elemanı üretir. Sonsuz bir aralık üzerinde select ve first(3) bu yüzden tamamlanabilir. with_index ise elemanlara sıra ekler ve ilk sıra numarası verilebilir.
# tembel.rb — lazy zincir ve with_index
puts (1..Float::INFINITY).lazy.select(&:even?).first(3).inspect
puts (1..3).lazy.class
puts "lazy sahip=#{(1..3).method(:lazy).owner}"
puts %w[a b].each.with_index(1).to_a.inspect
puts %w[a b].map.with_index { |h, i| "#{i}#{h}" }.inspect
[2, 4, 6] Enumerator::Lazy lazy sahip=Enumerable [["a", 1], ["b", 2]] ["0a", "1b"]
Sonsuz aralıkta first(3) ilk üç çift sayıyı bulunca durdu ve [2, 4, 6] verdi. lazy iletisini Enumerable yanıtladı ve dönen nesnenin sınıfı Enumerator::Lazy oldu. each.with_index(1) sırayı birden başlattı; map.with_index ise bloğa elemanla birlikte sırayı verdi ve dönüştürülmüş diziyi üretti. KB28. Yalnız bu üç zincir ölçüldü.
Dışarıdan Yineleyici
Bloksuz çağrılan each bir yineleyici döndürür; next onu elle ilerletir. Elemanlar bittiğinde StopIteration fırlatılır.
# dis_yineleyici.rb — next ile elle ilerletme e = [1, 2, 3].each puts e.next puts e.next puts e.next sonuc = begin e.next rescue StopIteration "bitti" end puts sonuc puts e.size
1 2 3 bitti 3
Üç next çağrısı elemanları sırayla verdi. Dördüncü çağrı StopIteration fırlattı, rescue onu yakaladı ve bitti basıldı. size 3 döndürdü. KB29. size yalnız bu örnekte ölçüldü; boyutu önceden bilinmeyen yineleyiciler ölçülmedi.
Karışımdan Gelen Yöntemler
Kendi sınıfınız yalnız each tanımlayıp Enumerable’ı karıştırınca onlarca yöntem kazanır. Aşağıdaki Kutu sınıfı elemanlarını bir dizide tutar ve each içinde dizinin each yöntemine devreder. Ardından dokuz farklı yöntem çağrılır.
# kutu.rb — yalniz each tanimlayan sinif
class Kutu
include Enumerable
def initialize(*elemanlar)
@elemanlar = elemanlar
end
def each(&blok)
@elemanlar.each(&blok)
self
end
end
k = Kutu.new(3, 1, 2)
puts "sort=#{k.sort.inspect} sahip=#{k.method(:sort).owner}"
puts "min=#{k.min} max=#{k.max} sum=#{k.sum}"
puts "map=#{k.map { |n| n * 2 }.inspect}"
puts "each_slice=#{k.each_slice(2).to_a.inspect}"
puts "first=#{k.first} count=#{k.count} sort_by=#{k.sort_by { |n| -n }.inspect}"
puts "each sahip=#{k.method(:each).owner}"
sort=[1, 2, 3] sahip=Enumerable min=1 max=3 sum=6 map=[6, 2, 4] each_slice=[[3, 1], [2]] first=3 count=3 sort_by=[3, 2, 1] each sahip=Kutu
Kutu yalnız each tanımladı; sort’un sahibi Enumerable olarak basıldı. min, max, sum, map, each_slice, first, count ve sort_by hep each üzerinden çalıştı ve beklenen sonuçları verdi. Yanıtlayan modüldür; each’in sahibi ise Kutu’dur. Modül, elemanlara nasıl ulaşılacağını sınıftan öğrenir. KB42. Yalnız bu dokuz yöntem denendi; karışımın her yönteminin çalıştığı varsayılmaz.
each Olmayınca
Modül each’i kendisi sağlamaz, sınıfın tanımlaması gerekir. each tanımlamayan bir sınıf karışımı alır ama yöntemler çalışmaz.
# bos.rb — each tanimlamayan sinif
class Bos
include Enumerable
end
sonuc = begin
Bos.new.map { |x| x }
rescue NoMethodError => hata
hata.class
end
puts sonuc
puts Bos.new.respond_to?(:each)
NoMethodError false
Bos sınıfı Enumerable’ı karıştırdı ama each tanımlamadı. map çağrısı NoMethodError fırlattı ve respond_to?(:each) false verdi. Sözleşme tek yönlüdür: Enumerable yöntemleri, sınıfın each yöntemini çağırabileceğini varsayar. KB43. Hata iletisi basılmadı; yalnız sınıfı ölçüldü.
Sorgu Yöntemleri ve Sahipleri
any?, all?, none?, find ve take_while koleksiyonu sorgular. Bir dizide bu yöntemlerin hepsi Enumerable’dan mı gelir? Sahip satırı bunu ölçer.
# sorgular.rb — sorgu yontemleri ve sahipleri
s = [1, 2, 3, 4]
puts "any=#{s.any? { |n| n > 3 }} all=#{s.all?(Integer)} none=#{s.none?(&:zero?)}"
puts "find=#{s.find { |n| n > 2 }} take_while=#{s.take_while { |n| n < 3 }.inspect}"
puts "sahip=#{s.method(:take_while).owner} #{s.method(:find).owner} #{s.method(:any?).owner}"
any=true all=true none=true find=3 take_while=[1, 2] sahip=Array Array Array
Sorgu sonuçları beklenen çıktı: any?, all?(Integer) ve none?(&:zero?) doğru, find 3, take_while [1, 2] verdi. Üç yöntemin sahibi de Array olarak basıldı: dizi bu yöntemleri Enumerable’a bırakmadan kendi tanımlıyor. Önceki bölümde Kutu için sort’un sahibi Enumerable‘dı; yani yanıtlayan alıcının sınıfına bağlıdır. KB44. Ölçüm Ruby 4.0.7’de ve yalnız Array alıcısıyla yapıldı.
Karışımın Başka Yöntemleri
Karışımla gelen yöntemler yalnız toplama ve sıralamayla sınırlı değildir. En küçük ve en büyüğü birlikte veren minmax, ilk n küçüğü veren min(2), sırayı da ileten each_with_index, tembel zincir ve tersten gezme de each’e dayanır. Aşağıdaki Raf sınıfı yine yalnız each tanımlar.
# raf.rb — each tanimlayan ikinci sinif ve ek yontemler
class Raf
include Enumerable
def initialize(*elemanlar)
@elemanlar = elemanlar
end
def each(&blok) = @elemanlar.each(&blok)
end
r = Raf.new(4, 2, 9)
puts r.minmax.inspect
puts r.min(2).inspect
puts r.each_with_index.map { |n, i| n * i }.inspect
puts r.lazy.map { |n| n + 1 }.first(2).inspect
puts r.entries.inspect
puts r.reverse_each.to_a.inspect
[2, 9] [2, 4] [0, 2, 18] [5, 3] [4, 2, 9] [9, 2, 4]
minmax [2, 9], min(2) [2, 4] verdi. each_with_index.map her elemanı sırasıyla çarptı: [0, 2, 18]. lazy.map { ... }.first(2) yalnız ilk iki elemanı dönüştürdü: [5, 3]. entries elemanları ekleniş sırasıyla, reverse_each tersine verdi. Altı çağrının hiçbirinde Raf yeni bir yöntem tanımlamadı; hepsi each’ten türedi. KB52. Bu yöntemlerin sahibi basılmadı; yalnız sonuçları ölçüldü.
Sıralama İçin Comparable
sort elemanları <=> ile karşılaştırır. Bu karşılaştırmayı bilmeyen nesneler sıralanamaz. <=> tanımlayıp Comparable’ı karıştıran bir sınıf ise hem sort’a hem min’e hem de < gibi işleçlere kavuşur.
# karsilastir.rb — comparable ve sort
class Nesne; end
sonuc = begin
[Nesne.new, Nesne.new].sort
rescue ArgumentError => hata
hata.class
end
puts sonuc
class Etiket
include Comparable
attr_reader :boy
def initialize(boy)
@boy = boy
end
def <=>(diger) = boy <=> diger.boy
end
puts [Etiket.new(3), Etiket.new(1)].sort.map(&:boy).inspect
puts [Etiket.new(3), Etiket.new(1)].min.boy
puts Etiket.new(1) < Etiket.new(2)
puts "sahip=#{Etiket.new(1).method(:<).owner}"
ArgumentError [1, 3] 1 true sahip=Comparable
<=> tanımı olmayan iki Nesne sıralanamadı: sort ArgumentError fırlattı. Etiket yalnız <=> tanımladı ve Comparable’ı karıştırdı; sort [1, 3], min 1 verdi. < işleci de çalıştı ve <’ün sahibi Comparable olarak basıldı. İşleci yanıtlayan modüldür; sınıf yalnız karşılaştırma kuralını verdi. KB53. Ölçüm tek bir alanla yapıldı.
Sınırlayıcı Ölçüm
Modül her işi üstlenmez. Aynı adlı yöntem farklı alıcılarda farklı sahipten gelebilir: sınıf kendi tanımını yazdıysa o yanıtlar.
# sahipler.rb — ayni ad, farkli sahipler
puts "(1..3).sum #{(1..3).method(:sum).owner}"
puts "[3, 1].sum #{[3, 1].method(:sum).owner}"
puts "[1].sort_by #{[1].method(:sort_by).owner}"
puts "[3, 1].sort #{[3, 1].method(:sort).owner}"
puts "[3, 1].min #{[3, 1].method(:min).owner}"
puts "(1..3).min #{(1..3).method(:min).owner}"
(1..3).sum Enumerable [3, 1].sum Array [1].sort_by Enumerable [3, 1].sort Array [3, 1].min Array (1..3).min Range
sum aralıkta Enumerable’dan, dizide Array’den geliyor. sort_by dizide de Enumerable’a ait; ama sort ve min dizide Array’in kendi yöntemleri. Aralık için min ise Range‘in kendi yöntemi. Sonuçlar Ruby 4.0.7’ye aittir. Altı satır, üç ayrı sahip verdi: Enumerable, Array ve Range. sum ve min adları her iki alıcıda da çalışır, çağıran taraf farkı görmez; ama iletiyi alıcıya göre bir sınıf ya da modül yanıtlar. Bu yüzden owner sorusu, yöntemin adından değil alıcının türünden başlar. KB10. Altı satırın hiçbiri sonucun değerini karşılaştırmaz; yalnız sahip sorulur, yani bu ölçüm modülün ve sınıfın aynı sonucu verip vermediğini söylemez. Yani aynı ad, alıcıya göre modülün ya da sınıfın yanıtıdır; bunun nedeni sonraki kursun konusudur ve burada açıklanmaz.
Özet
Enumerable,eachtanımlayan her sınıfamap,sort,min_by,group_byvetallygibi yöntemler kazandıran bir modüldür.- Bu dersin yanıtlayanı modüldür:
(1..5).each_slice(2)iletisiniRangedeğilEnumerableyanıtlar. - Kendi sınıfına
include Enumerableve tek bireacheklemek yeterlidir;map’in sahibi modül,each’in sahibi sınıf çıkar. - Sınırlayıcı ölçüm: aynı ad alıcıya göre farklı sahipten gelir;
sumaralıkta modülden, dizideArray’den gelir.
Sonraki Adım
Bu derste bir yöntemin gövdesi yield ile bloğu çağırdı. Sonraki ders bloğu bir nesneye dönüştürür: Proc ve lambda, aynı kodu bir değişkende tutmanın iki yoludur ve call iletisinin sahibi bu iki yol için aynıdır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.