Ders 07 / 12
Diziler ve Karma Yapılar
Ruby'nin iki temel koleksiyonu olan dizi ve karma yapının hangi yöntemleri kendisinin, hangilerini ortak bir modülün yanıtladığı ve eksik bir anahtarın değerini bloğun belirlediği ölçülür.
Yayın Güncelleme
Bu derste
- Dizi ve karma yapının temel işlemlerini yazar ve sonuçlarını okur
- Bir koleksiyon yönteminin hangi sınıfta ya da modülde tanımlı olduğunu owner ile ölçer
- Varsayılan değer bloğunun eksik anahtarda nasıl yanıt verdiğini gösterir
- fetch ile köşeli parantez arasındaki farkı ve bloğun yalnız anahtar yokken koştuğunu ölçer
İçindekiler
Semboller ve Dizgiler dersi, "ad".to_sym iletisini String sınıfının yanıtladığını ve elde edilen :ad sembolünün, aynı değeri tek bir nesne olarak taşıdığı için equal? ile karşılaştırılabildiğini ölçtü. Sembolün asıl işi o derste henüz görünmedi: karma yapıda anahtar olmak. Bu ders iki temel koleksiyonu, diziyi ve karma yapıyı, ve aynı kursun sorusunu bu koleksiyonlara yöneltiyor: bir yönteme gelen iletiyi kim yanıtlıyor?
Kursun dört yanıtlayanı değişmez: sınıf, modül, blok ve sözdizimi. Düzyazıda ölçü kategorisi kastedildiğinde “yanıtlayan sınıf” yazılır; çıplak “sınıf” Ruby’nin Class‘ıdır. Değişkenler, tipler ve döngüler M01/K02’de kavram olarak kuruldu; burada dizi (array), sıralı ve sıfırdan numaralanan bir eleman listesidir. Karma yapı (hash) ise anahtardan değere giden bir eşlemedir ve elemanlarını eklenme sırasıyla tutar.
Temel İşlemler
İlk ölçüm yalnız davranışı gösterir. Dizi köşeli parantezle yazılır, << sona eleman ekler, negatif indis sondan sayar, aralık bir dilim verir. Karma yapı süslü parantez ve anahtar: değer biçimiyle yazılır; bu biçimde anahtarlar semboldür.
# temel.rb — dizi ve karma yapinin temel islemleri
dizi = [10, 20, 30]
dizi << 40
puts "ilk=#{dizi[0]} son=#{dizi[-1]} adet=#{dizi.size} disarda=#{dizi[10].inspect}"
puts "dilim=#{dizi[1..2].inspect} ilk_iki=#{dizi.first(2).inspect}"
karma = { ad: "Ayse", yas: 30 }
karma[:sehir] = "Izmir"
puts "ad=#{karma[:ad]} yok=#{karma[:mail].inspect} anahtarlar=#{karma.keys.inspect}"
karma.each { |anahtar, deger| puts "#{anahtar} -> #{deger}" }
puts karma.select { |_, deger| deger.is_a?(String) }.inspect
puts karma.map { |anahtar, deger| anahtar }.inspect
ilk=10 son=40 adet=4 disarda=nil
dilim=[20, 30] ilk_iki=[10, 20]
ad=Ayse yok=nil anahtarlar=[:ad, :yas, :sehir]
ad -> Ayse
yas -> 30
sehir -> Izmir
{ad: "Ayse", sehir: "Izmir"}
[:ad, :yas, :sehir]
Dizi sınırının dışındaki indis hata vermez, nil döndürür; karma yapıda olmayan anahtar da öyle. Bu iki sessiz nil bu dersin ikinci yarısındaki sorunun zeminidir: eksik bir anahtarın sonucunu belirleyen şey nedir? Karma yapı eklenme sırasını korur; anahtarlar satırındaki sıra sehir atamasının sona eklendiğini gösteriyor. select ve map aynı iki parametreli blok biçimini aldı, ama biri karma yapı döndürdü, öteki dizi. Bunun nedeni sonraki bölümde ölçülen sahiplik ayrımıdır. KB1. Ölçümler sembol anahtarlı karma yapılarla yapıldı; dizgi anahtarlı karma yapılar bu derste ölçülmedi ve sonuçlar onlara taşınmaz.
Yöntemi Hangi Tanım Taşıyor
Aynı koleksiyon üzerinde yazılan iki yöntem, aynı biçimde çağrılsa da farklı yerlerde tanımlı olabilir. method(:ad).owner, o yöntemi tanımlayan sınıfı ya da modülü verir. Bu ders arama sırasını göstermez; yalnız sonucu, yani sahibi okur.
# koleksiyon.rb — koleksiyon yontemlerinin sahibi kim
dizi = [1, 2, 3]
karma = { a: 1, b: 2 }
puts "dizi.size #{dizi.method(:size).owner}"
puts "dizi.map #{dizi.method(:map).owner}"
puts "karma.each #{karma.method(:each).owner}"
puts "karma.select #{karma.method(:select).owner}"
puts "karma.fetch #{karma.method(:fetch).owner}"
puts "karma.map #{karma.method(:map).owner}"
dizi.size Array dizi.map Array karma.each Hash karma.select Hash karma.fetch Hash karma.map Enumerable
Ruby 4.0.7’de beş yöntemi bir sınıf karşıladı: size ve map Array’de, each, select ve fetch Hash’te tanımlı. Altıncısı farklı: karma.map Hash’te değil, Enumerable modülünde tanımlı. Yani aynı map adı dizide sınıfın, karma yapıda modülün yanıtıdır. Hangi yöntemin hangi tanımda durduğu sürümden sürüme taşınabilir; bu yüzden sonuç dilin sözü değil bu sürümün ölçümü olarak okunur. Enumerable modülü üçüncü derstir ve burada yalnız adı anılır. Önceki bölümde select karma yapı, map dizi döndürdü: select Hash’in kendi yöntemi olduğu için sonucu da karma yapı olarak kurar, Enumerable’dan gelen map ise her zaman dizi üretir.
Eksik Anahtarın Değerini Kim Veriyor
Temel işlemlerde karma[:mail] nil döndürdü. Bu davranışı değiştirmek için karma yapı kurulurken bir varsayılan değer verilebilir. İki biçimi vardır: Hash.new(0) sabit bir değer, Hash.new { ... } ise bir blok verir ve anahtar eksik kaldığında o blok koşar. Blok, anahtarı parametre olarak alabilir; böylece sonuç anahtara bağlı olabilir. M12/K02, PHP’de paylaşılan varsayılan bir değerin değişikliği nereye ulaştırdığını ölçmüştü; burada o soru sorulmuyor, yalnız varsayılan değeri kimin ürettiği ölçülüyor.
Kursun ortak yardımcısı yanit.rb önce birebir verilir. Bir ifadeyi sırayla üç soruyla sınıflandırır: defined? bir yöntem çağrısı mı diyor, iki farklı blok iki farklı sonuç veriyor mu, yöntemin sahibi bir Class mı. Çıktı bloğu olmadığı için yalnız diske yazılı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
Şimdi çekirdeğin varsayilan deger gözlemi koşar. İki Hash.new bloğu aynı [:x] iletisine bakıyor: biri her zaman 0, ötekisi anahtarın metin karşılığını döndürüyor. Ardından sabit varsayılanlı biçim ve defined?’in iki durumu basılır.
# varsayilan.rb — eksik anahtarin degerini kim veriyor
require_relative "yanit"
sifir = Hash.new { 0 }
metin = Hash.new { |_, anahtar| anahtar.to_s }
tur, kanit = yanitlayan(defined?(Hash.new), bloklu: -> { [sifir[:x], metin[:x]] })
puts "#{tur} #{kanit}"
puts Hash.new(0)[:x]
puts defined?(Hash.new)
puts defined?(Hash.new { 0 })
blok 0 / "x" 0 method expression
Birinci satır, çekirdeğin varsayilan deger satırıdır: blok yanıtlayan. Aynı [:x] iletisi iki karma yapıda 0 ve "x" verdi; iletiyi alan nesnenin sınıfı aynıydı (Hash), fark yalnız verilen bloktaydı. Bu yüzden yanıtı sınıf ya da modül değil blok belirler. İkinci satırda sabit varsayılanlı Hash.new(0)[:x] da 0 verdi; orada bir blok yoktur, değer kurulurken verilmiştir.
Son iki satır kuralın bir inceliğini gösteriyor. defined?(Hash.new) "method" döndürür, ama bloklu çağrı olan defined?(Hash.new { 0 }) "expression" döndürür. Çekirdek bu yüzden iletiyi bloksuz sorgular ve bloğun etkisini ikinci adımda ölçer; bloklu biçimi sorgulasaydı birinci adım iletiyi sözdizimi sayardı. KB2. Bloklu defined? sonucu Ruby 4.0.7’de ölçüldü; başka bir sürümde aynı sonuç varsayılmaz. Bu ayrıntı Bloklar dersinde adlandırılır.
Dizi İşlemleri
Temel İşlemler bölümü yalnız << ve tek indisli erişimi yazdı. Dizinin sık kullanılan öteki işlemleri sona ekleme, sondan çıkarma ve dilimlemedir. Sonsuz uçlu aralık a[1..] ilk elemandan sonrasını verir, first(2) baştan iki eleman alır. push ve << sona ekler; pop son elemanı hem diziden çıkarır hem döndürür.
# dizi_islem.rb — dilimleme, ekleme ve cikarma
a = [5, 3, 8, 1]
puts "a[1..]=#{a[1..].inspect} a[-1]=#{a[-1]} first(2)=#{a.first(2).inspect}"
a.push(9)
a << 7
puts "sonra=#{a.inspect}"
puts "pop=#{a.pop} kalan=#{a.inspect}"
a[1..]=[3, 8, 1] a[-1]=1 first(2)=[5, 3] sonra=[5, 3, 8, 1, 9, 7] pop=7 kalan=[5, 3, 8, 1, 9]
Dilimleme ve first(2) birer dizi döndürdü ve a’yı değiştirmedi: push satırına gelindiğinde a hâlâ özgün dört elemanı taşıyordu. push(9) ve << aynı işi yaptı ve dizi altı elemana çıktı. pop 7 döndürdü ve dizi beş elemana indi: pop’un iki etkisi vardır, bir değer döndürmesi ve diziyi değiştirmesi. KB19. Dilimin yeni bir dizi mi yoksa aynı belleğin bir görünümü mü olduğuna bakılmadı; yalnız yazdırılan değerler ölçüldü.
Yerinde Değiştiren Biçim
Birçok dizi yönteminin iki biçimi vardır: yeni bir dizi döndüren ve diziyi yerinde değiştiren. Sıralamada bu çift sort ve onun yerinde sıralayan biçimidir. Fark equal? ile ölçülür: iki değişken aynı nesneyi mi gösteriyor?
# yerinde.rb — yeni dizi ile ayni dizinin degismesi
a = [3, 1, 2]
b = a.sort
puts "yeni=#{b.inspect} a=#{a.inspect} ayni_nesne=#{a.equal?(b)}"
c = a.sort!
puts "yerinde a=#{a.inspect} ayni_nesne=#{a.equal?(c)}"
puts "sahip=#{a.method(:sort!).owner}"
yeni=[1, 2, 3] a=[3, 1, 2] ayni_nesne=false yerinde a=[1, 2, 3] ayni_nesne=true sahip=Array
sort yeni bir dizi verdi: b sıralıydı, a özgün sırasında kaldı ve equal? false döndü. Yerinde sıralayan biçim a’nın kendisini sıraladı ve aynı nesneyi geri verdi: a [1, 2, 3] oldu, equal? true. Yerinde sıralayan biçimin sahibi Array olarak basıldı, yani yanıtlayan bir sınıftır; iki biçimin farkı yöntemin sonucunun yeni nesne mi yoksa alıcının kendisi mi olduğundadır. KB20. Ölçüm yalnız sıralama çiftine yapıldı; başka çiftlerin aynı kalıbı izlediği varsayılmaz.
Karma Yapıda Sıra ve İç İçe Erişim
Karma yapı eklenme sırasını korur ve to_a onu çift dizisine çevirir. İç içe karma yapılarda dig anahtarları sırayla izler; yol bir yerde kesilirse hata fırlatmak yerine nil döndürür.
# karma_ek.rb — ekleme sirasi, to_a ve dig
k = { b: 1, a: 2 }
k[:c] = 3
puts k.keys.inspect
puts k.to_a.inspect
ic = { kullanici: { adres: { sehir: "Izmir" } } }
puts ic.dig(:kullanici, :adres, :sehir)
puts ic.dig(:kullanici, :telefon, :no).inspect
[:b, :a, :c] [[:b, 1], [:a, 2], [:c, 3]] Izmir nil
k önce b ve a anahtarlarıyla kuruldu, c sona eklendi; keys bu sırayı, to_a aynı sırayı çiftler hâlinde verdi. Alfabetik bir sıralama yoktur. dig üç anahtarı izleyip Izmir‘i buldu; ikinci çağrıda :telefon yoktu ve dig hata fırlatmadan nil verdi. KB21. Sıra davranışı Ruby 4.0.7’de ve yalnız bu örnekle ölçüldü.
Paylaşılan Varsayılan Nesne
Varsayılan değeri blok yerine bir nesne olarak vermek, Hash.new([]) biçiminde, bir tuzak taşır: o nesne tektir ve her eksik anahtar için aynısı döner. M12/K02 paylaşılan varsayılan değerin davranışını başka bir dilde ölçmüştü; burada Ruby’deki çıktı yalnız okunur.
# ortak.rb — paylasilan varsayilan nesne ile blok bicimi
ortak = Hash.new([])
ortak[:a] << 1
ortak[:b] << 2
puts "ortak=#{ortak.inspect} varsayilan=#{ortak.default.inspect} anahtarlar=#{ortak.keys.inspect}"
ayri = Hash.new { |h, k| h[k] = [] }
ayri[:a] << 1
ayri[:b] << 2
puts ayri.inspect
ortak={} varsayilan=[1, 2] anahtarlar=[]
{a: [1], b: [2]}
ortak[:a] << 1 eksik anahtar için varsayılan diziyi aldı ve ona ekledi; karma yapıya bir anahtar atanmadı. Bu yüzden ortak boş kaldı, anahtarlar [] oldu ve varsayılan dizi [1, 2] oldu: :a ve :b aynı diziye yazdı. Blok biçimi ise her eksik anahtarda bloğu koşturup sonucu h[k] = [] ile karma yapıya yazdı; iki anahtar iki ayrı dizi aldı. Eksik anahtarın değerini ilk biçimde tek bir nesne, ikinci biçimde her seferinde blok veriyor. KB22.
Karma Yapıyı Dönüştürmek
Karma yapıdan yeni bir karma yapı ya da tek bir değer üretmek sık gerekir. transform_values değerleri dönüştürür, to_h bloğuyla anahtarları da değiştirebilir, sum ise Enumerable üzerinden gelir. Dizi tarafında compact nil elemanları atar, uniq yinelenenleri eler, flatten iç içe diziyi düzleştirir.
# donustur.rb — karma yapi donusumleri ve dizi temizligi
k = { a: 1, b: 2 }
puts k.transform_values { |v| v * 10 }.inspect
puts k.to_h { |a, v| [a.to_s, v] }.inspect
puts k.sum { |_, v| v }
puts k.min_by { |_, v| -v }.inspect
puts "sahip=#{k.method(:transform_values).owner} #{k.method(:sum).owner} #{k.method(:to_h).owner}"
d = [3, 1, 2, 3, nil]
puts d.compact.uniq.sort.inspect
puts [[1, [2]], [3]].flatten.inspect
{a: 10, b: 20}
{"a" => 1, "b" => 2}
3
[:b, 2]
sahip=Hash Enumerable Hash
[1, 2, 3]
[1, 2, 3]
transform_values yalnız değerleri çarptı ve anahtarlar korundu; to_h bloğu her çifti [anahtar, değer] olarak döndürünce anahtarlar dizgiye çevrildi. sum değerleri toplayıp 3 verdi, min_by -v değeri en küçük olan çifti, yani [:b, 2]’yi seçti. Sahip satırı ayrımı gösterir: transform_values ve to_h Hash’te, sum ise Enumerable’dadır. compact, uniq ve sort zinciri nil’i ve yinelenen 3’ü atıp [1, 2, 3] verdi; flatten iç içe diziyi [1, 2, 3] yaptı. KB38. Bu yöntemlerin başka alıcılarda aynı sahibi taşıdığı varsayılmaz.
Sınırlayıcı Ölçüm
Blok her eksiklikte mi yanıtlıyor, yoksa yalnız belirli bir durumda mı? fetch yöntemi bunu açıkça ölçer. fetch anahtarı arar; bulursa değeri döndürür. Bulamazsa bir blok verilmişse bloğu koşturur, verilmemişse hata fırlatır. Aşağıdaki ölçüm bloğun kaç kez koştuğunu bir sayaçla sayar. Bu sayaç, kaç kez koştuğu sorusunun bir cevabı olarak okunmaz: M10/K03 o soruyu başka bir eksende ölçmüştü, burada sayaç yalnız bloğun hiç çağrılmadığını göstermek içindir.
# fetch.rb — blok yalniz anahtar yoksa yanitliyor
cagri = 0
karma = { a: 1 }
var = karma.fetch(:a) { cagri += 1; 0 }
yok = karma.fetch(:b) { |anahtar| cagri += 1; anahtar.to_s }
puts "var=#{var.inspect} yok=#{yok.inspect} cagri=#{cagri}"
bloksuz = begin
karma.fetch(:b)
rescue KeyError => hata
hata.class
end
puts "bloksuz=#{bloksuz}"
puts "kose=#{karma[:b].inspect}"
puts "sahip=#{karma.method(:fetch).owner}"
var=1 yok="b" cagri=1 bloksuz=KeyError kose=nil sahip=Hash
fetch aynı eksik anahtar için üç farklı sonuç verdi. Bloklu çağrı "b" döndürdü, bloksuz çağrı KeyError fırlattı, köşeli parantez nil döndürdü. Anahtar var olduğunda ise sayaç artıran bloklu fetch(:a) 1 döndürdü ve sayaç yalnız bir kez arttı: o artış, anahtarı olmayan çağrıya aittir. Yani blok anahtar bulunduğunda hiç çağrılmadı. Bloğun yanıtladığı durum eksik anahtardır; var olan anahtarın değeri hiçbir blok sormadan karma yapının kendisinden gelir.
Bu, ilk bölümdeki yanıtlayanı daraltır. fetch’in sahibi Hash yani bir sınıf, ama sonucu eksik anahtarda blok belirliyor. Çekirdeğin sınıflandırması bu yüzden blokluyu ayrı bir adımda ölçer: iletinin sahibi her zaman bir sınıf ya da modüldür, soru sonucun kimden geldiğidir. Hash.new { ... } gibi fetch de varsayılanı çağıranın elinde bırakır; ikisinin farkı, Hash.new bloğunun karma yapıya kalıcı olarak bağlanması, fetch bloğunun ise yalnız o çağrıya ait olmasıdır.
Özet
- Dizi sıralı ve sıfırdan numaralanan bir listedir; karma yapı anahtardan değere giden, eklenme sırasını koruyan bir eşlemedir. Sınır dışı indis ve eksik anahtar
nildöndürür. - Ruby 4.0.7’de
size,map(dizide),each,selectvefetchkendi sınıflarında tanımlıdır;karma.mapiseEnumerablemodülündedir. - Bu dersin yanıtlayanı bloktur:
Hash.new { 0 }[:x]0,Hash.new { |_, k| k.to_s }[:x]"x"verdi; iletiyi alan sınıf aynıydı, fark verilen bloktaydı. defined?bloklu çağrıda"expression"döndürür; çekirdek iletiyi bloksuz sorgular.- Sınırlayıcı ölçüm: blok yalnız anahtar yokken yanıtlar. Sayaç artıran bloklu
fetch(:a)var olan anahtarda1döndürür ve blok hiç koşmaz.
Sonraki Adım
Bu derste blok iki karma yapının sonucunu ayırdı, ama blokun kendisi henüz bir konu olarak ele alınmadı: { |_, k| k.to_s } ve { 0 } yalnız yazıldı. Bloklar dersi aynı map iletisini iki farklı blokla koşturur ve bir yöntemin, kendisine verilen bloğu nasıl çağırdığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.