İçeriğe geç
academia.sh

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, each tanımlayan her sınıfa map, sort, min_by, group_by ve tally gibi yöntemler kazandıran bir modüldür.
  • Bu dersin yanıtlayanı modüldür: (1..5).each_slice(2) iletisini Range değil Enumerable yanıtlar.
  • Kendi sınıfına include Enumerable ve tek bir each eklemek 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; sum aralıkta modülden, dizide Array’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.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat