Skip to content

Hash

Pek çok dilde Dictionary olarak geçen, Array’imsi, hatta Associative Array de denir, Key-Value çifti barındıran yine Array’e benzeyen başka bir taşıyıcıdır. Key-Value dediğimiz şey ise;

{key1: "value1", key2: "value2", ....}

şeklindedir. Yukarıdaki örnek, yeni syntax’ı kullanır. Ruby programcılarının alışık olduğu örnek:

{"key1" => "value1", "key2" => "value2", ...}

ya da

{:key1 => "value1", :key2 => "value2", ...}

şeklindedir. Hepsi aynı kapıya çıkar... Array’deki sıra (index) mantığı burada key’ler ile oluyor gibi düşünebilirsiniz. Key’ler unique’dir yani bir Hash içinde 2 tane aynı key’den olamaz.

Hash’de sonuçta bir class olduğu için new method’u ile Hash’i oluşturabiliriz;

Hash.new # => {}

Aynı Array’deki gibi Hash’in de hızlı oluşurma yolu var : h = {}. Hemen Hash’in nereden geldiğine bakalım:

Hash.class                                  # => Class
Hash.class.superclass                       # => Module
Hash.class.superclass.superclass            # => Object
Hash.class.superclass.superclass.superclass # => BasicObject
Hash.class.superclass.superclass.superclass.superclass # => nil

Dikkat ettiyseniz Hash’in bir üst sınıfı Module. Aynı Array’deki gibi. Peki bu modüller nelermiş?

Hash.included_modules # => [Enumerable, Kernel]

Eğer Hash’i oluştururken default değer geçersek, tanımsız olan key için değer atamış oluruz:

h = Hash.new("Tanımsız") # => {}
h[:isim] = "Uğur"        # => "Uğur"
h                        # => {:isim=>"Uğur"}
h[:soyad]                # => "Tanımsız"
h.default                # => "Tanımsız"

Olmayan bir key’e ulaşmak istediğimizde "Tanımsız" değeri geldi. Eğer bu default değeri atamasaydı ne olacaktı?

h = Hash.new               # => {}
h[:isim] = "Uğur"          # => "Uğur"
h[:soyad]                  # => nil

nil gelecekti. Biraz sonra göreceğimiz fetch method’unu lütfen aklınızda tutun!

Default değer tanımlama mantığında;

h = Hash.new { |hash, key| hash[key] = "User: #{key}" }
h["vigo"]             # => "User: vigo"
h["foobar"]           # => "User: foobar"
h["animal"] = "horse" # => "horse"
h                     # => {"vigo"=>"User: vigo", "foobar"=>"User: foobar", "animal"=>"horse"}

bu tarz ilginç bir yöntem de kullanılabilir. Normalde vigo key’ine karşılık value yok ama Hashin new method’unda yaptığımız bir blok işlemi ile olmayan key için değer ataması yaptığımız gibi key-value ataması da yapabiliyoruz.

Hash Class Method’ları

Hash’den bir instance oluşturmadan kullandığımız methodlardır.

Hash[ key, value, ... ] -> yeni_hash Hash[ [ [key, value], ... ] ] -> yeni_hash Hash[ object ] -> yeni_hash

Hash["user_count", 5]                            # => {"user_count"=>5}
Hash[ [["user_count", 5], ["active_users", 2]] ] # => {"user_count"=>5, "active_users"=>2}
Hash["user_count" => 5, "active_users" => 2]     # => {"user_count"=>5, "active_users"=>2}

Hash.new

Zaten ilgili örnekleri başta vermiştik, tekrar edelim:

h = Hash.new
h # => {}
h["user_count"] = 5
h # => {"user_count"=>5}

h = Hash.new { |hash, key| hash[key] = "User ID: #{key}" }
h["1"] # => "User ID: 1"
h["2"] # => "User ID: 2"
h # => {"1"=>"User ID: 1", "2"=>"User ID: 2"}

try_convert(obj) → hash ya da nil

Hash’e dönüşebilme ihtimali olan nesneyi Hash haline çevirir.

Hash.try_convert({"user_count"=>5})   # => {"user_count"=>5}
Hash.try_convert("user_count=>5")     # => nil

Hash Instance Method’ları

Hash instance’ı oluşturduktan sonra kullanacağımız method’lardır.

Önce klasik değer okuma ve değer atama işlerine bakalım. Zaten bu noktaya kadar kabaca biliyoruz nasıl değer atarız geri okuruz. Ama biraz kafaları karıştırmak istiyorum:

h = {username: "vigo", password: "1234"} # => {:username=>"vigo", :password=>"1234"}

Yukarıdaki gibi bir Hash’imiz var. Dikkat ettiyseniz, key,value olarak baktığımızda :username ve :password diye başlayan keyler var... Hatta:

h.keys # => [:username, :password]

diye de sağlamasını yaparız. Peki, yeni bir key tanımlasak? h["useremail"] = "vigo@example.com". Tekrar bakalım keylere:

h.keys # => [:username, :password, "useremail"]

Bir sonraki bölümde karşımıza çıkacak olan Symbol tipi ile karşı karşıyayız. Sadece symbol mu? hayır, karışık keyler var elimizde. Hemen sağlamasını yapalım:

h.keys.map(&:class) # => [Symbol, Symbol, String]

İlk iki key Symbol iken son key String oldu. Demek ki Hash içine key cinsi olarak karşık atama yapabiliniyor. Biraz sıkıntılı bir durum ama genel kültür mahiyetinde aklınızda tutun bunu!

Şimdi genel olarak Hash hangi method’lara sahip hemen bakalım:

h = Hash.new
h.methods # => [:rehash, :to_hash, :to_h, :to_a, :inspect, :to_s, :==, :[], :hash, :eql?, :fetch, :[]=, :store, :default, :default=, :default_proc, :default_proc=, :key, :index, :size, :length, :empty?, :each_value, :each_key, :each_pair, :each, :keys, :values, :values_at, :shift, :delete, :delete_if, :keep_if, :select, :select!, :reject, :reject!, :clear, :invert, :update, :replace, :merge!, :merge, :assoc, :rassoc, :flatten, :include?, :member?, :has_key?, :has_value?, :key?, :value?, :compare_by_identity, :compare_by_identity?, :entries, :sort, :sort_by, :grep, :count, :find, :detect, :find_index, :find_all, :collect, :map, :flat_map, :collect_concat, :inject, :reduce, :partition, :group_by, :first, :all?, :any?, :one?, :none?, :min, :max, :minmax, :min_by, :max_by, :minmax_by, :each_with_index, :reverse_each, :each_entry, :each_slice, :each_cons, :each_with_object, :zip, :take, :take_while, :drop, :drop_while, :cycle, :chunk, :slice_before, :lazy, :nil?, :===, :=~, :!~, :<=>, :class, :singleton_class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :singleton_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]

Dikkat ettiyseniz method’ların bir kısmı Array ile aynı çünki ikisi de Enumerable modülünü kullanıyor.

Şimdi sıradan başlayalım! Önceki Array bölümünde anlattığım ortak method’ları pas geçeceğim!

rehash

Hash’e key olarak Array verebiliriz. Yani h[key] = value mantığında key olarak bildiğiniz Array geçebiliriz.

a = [ "a", "b" ]
c = [ "c", "d" ]
h = { a => 100, c => 300 } # => {["a", "b"]=>100, ["c", "d"]=>300}

h Hash’inin keyleri nedir?

h.keys                     # => [["a", "b"], ["c", "d"]]

2 key’i var biri ["a", "b"] ve diğeri ["c", "d"] nasıl yani?

h[a]                       # => 100
h[["a", "b"]]              # => 100
h[c]                       # => 300
h[["c", "d"]]              # => 300

Şimdi işleri karıştıralım. a Array’inin ilk değerini değiştirelim. Bakalım h ne olacak?

a[0] = "v"                 # => "v"
a                          # => ["v", "b"]
h[a]                       # => nil ????????

h[a] patladı? nil döndü. İşte şimdi imdadımıza ne yetişecek?

h.rehash                   # => {["v", "b"]=>100, ["c", "d"]=>300}
h[a]                       # => 100

to_hash, to_h, to_a, to_s

Tip dönüştürmeleri için kullanılırlar. to_h ve to_hash eğer kendisi Hash ise sonuç yine kendisi olur. to_a ise Hash’den Array yapmak için kullanılır. Tahmin edeceğiniz gibi to_s de String’e çevirmek için kullanılır.

h = {:foo => "bar"}
h                                 # => {:foo=>"bar"}
h.to_hash                         # => {:foo=>"bar"}
h.to_h                            # => {:foo=>"bar"}
["foo", "bar"].respond_to?(:to_h) # => true
[[:foo, "bar"]].to_h              # => {:foo=>"bar"}

[["a", 1], ["b", 2]].to_h   # => {"a"=>1, "b"=>2}

h.to_a                            # => [[:foo, "bar"]]
h.to_s                            # => "{:foo=>\"bar\"}"

== ve eql? Eşitlik

Hash içinde key’lerin sırası eşitlik kontrolünde önemli değildir. İçerik önemlidir. Eşitlik kontrolü için kullanılırlar.

h1 = { "a" => 100, "c" => 200 }
h2 = { 70 => 350, "x" => 22, "y" => 11 }
h3 = { "y" => 11, "x" => 22, 70 => 350 }

h1 == h2 # => false
h2 == h3 # => true

h1.eql?(h2)  # => false
h2.eql?(h3)  # => true

h2 ile h3 key sıraları farklı olmasına rağmen içerik bazında eşittirler.

fetch

Hash içinden sorgu yaparken kullanılır. Eğer olmayan key çağırırsanız exception oluşur. Bu method güvenli bir yöntemdir. Aksi takdirde nil döner ve kompleks işlerde Silent Fail yani dipsiz kuyuya düşer bir türlü hatanın yerini bulamazsınız!

h = {:user => "vigo", :password => "secret"}
puts h.fetch(:user) # "vigo"
puts h.fetch(:email)

KeyError: key not found: :email

Keza eğer key’e karşılık yoksa default değer ataması yapabilirsiniz:

h = {:user => "vigo", :password => "secret"}
h.fetch(:user)               # => "vigo"
h.fetch(:email, "Not found") # => "Not found"

Block kabul ettiği için artistlik hareketler yapmak da mümkün :)

h = {:user => "vigo", :password => "secret"}
h.fetch(:email) { |element| "key: #{element} is not defined!" } # => "key: email is not defined!"

store

Atama yapmanın farklı bir yöntemidir.

h = {:user => "vigo", :password => "secret"}
h.store(:email, "vigo@example.com") # => "vigo@example.com"
h                                   # => {:user=>"vigo", :password=>"secret", :email=>"vigo@example.com"}

# ya da

h[:url] = "http://webbox.io"        # => "http://webbox.io"
h                                   # => {:user=>"vigo", :password=>"secret", :email=>"vigo@example.com", :url=>"http://webbox.io"}

default, default=

Karşılığı olmayan keyler için varsayılan değer ataması yapılmışsa bunu bulmak için ya da varsayılan değeri atamak için kullanılır. En başta benzer işler yaptık:

h = Hash.new(10)
h[:user_age]            # => 10
h                       # => {}
h.default               # => 10
h.default(:user_weight) # => 10

ya da

h = Hash.new
h                         # => {}
h.default = 100           # => 100
h[:user_weight]           # => 100
h[:foo]                   # => 100

key

Value’den key’i bulmak için kullanılır. Eğer key’i olmayan bir value kullanırsanız sonuç nil döner!

h = {:user => "vigo", :password => "secret"}
h.key("vigo")   # => :user
h.key("foobar") # => nil

size, length, count

Aynı işi yaparlar, Arrar gibi Hash’in boyunu / uzunluğunu verir.

h = {:user => "vigo", :password => "secret"}
h.length # => 2
h.size   # => 2
h.count  # => 2

Key, Value Kontrolleri

keys, values, values_at

Tahmin edeceğiniz gibi keys ile Hash’e ait key’leri, values ile sadece key’lere karşılık gelen değerleri, values_at ile verdiğimiz key’lere ait değerleri alırız.

h = {:user => "vigo", :password => "secret", :email => "vigo@foo.com"}
h.keys                        # => [:user, :password, :email]
h.values                      # => ["vigo", "secret", "vigo@foo.com"]
h.values_at(:user, :password) # => ["vigo", "secret"]

key?, value?, has_key?, has_value?

Soru işareti ile biten method’lar bize her zaman Boolean yani true ya da false döner demiştik. Acaba Hash’in içinde ilgili key var mı? ya da value var mı?

h = {:user => "vigo", :password => "secret", :email => "vigo@foo.com"}
h.key?(:user)          # => true
h.has_key?(:user)      # => true

h.key?(:full_name)     # => false
h.has_key?(:full_name) # => false

h.value?("vigo")       # => true
h.has_value?("vigo")   # => true

h.value?("lego")       # => false
h.has_value?("lego")   # => false

include?, member?

key? ya da has_key? ile aynı işi yapar.

h = {:user => "vigo", :password => "secret", :email => "vigo@foo.com"}
h.include?(:user)        # => true
h.member?(:user)         # => true

empty?

Hash’in içinde eleman var mı yok mu?

{:user => "vigo", :password => "secret", :email => "vigo@foo.com"}.empty? # => false
{}.empty? # => true

all?, any?, one?, none?

Array bölümünde görmüştük, Enumerable modülünden gelen bu özellik aynen Hash’de de kullanılıyor. all? da tüm elemanlar, verilen koşuldan nil ya da false dışında bir şey dönmek zorunda, aksi halde sonuç false oluyor:

# value’su boş olan var mı?
{:user => "vigo", :password => "secret", :email => "vigo@foo.com"}.all?{ |k,v| v.empty? } # => false
{:user => "", :password => "", :email => ""}.all?{ |k,v| v.empty? }                       # => true
{:user => "vigo", :password => "", :email => ""}.all?{ |k,v| v.empty? }                   # => false

any? de içlerinden biri false ya da nil dönmezse sonuç trueolur. one? da sadece bir tanesi true dönmelidir. none da ise block’daki işlem sonucu her eleman için false olmalıdır.

{:is_admin => true, :notifications_enabled => true}.all?{ |option, value| value } # => true
{:is_admin => true, :notifications_enabled => false}.any?{ |option, value| value } # => true
{:is_admin => true, :notifications_enabled => false}.one?{ |option, value| value } # => true
{:is_admin => false, :notifications_enabled => false}.one?{ |option, value| value } # => false
{:is_admin => false, :notifications_enabled => false}.all?{ |option, value| value } # => false
{:is_admin => false, :notifications_enabled => false}.none?{ |option, value| value } # => true
{:is_admin => false, :notifications_enabled => false}.any?{ |option, value| value } # => false

shift

Hash’den key-value çiftini silmek için kullanılır. Her seferinde ilk key-value çiftini siler.

h = {:user => "vigo", :password => "secret", :email => "vigo@foo.com"}
h.shift # => [:user, "vigo"]
h       # => {:password=>"secret", :email=>"vigo@foo.com"}
h.shift # => [:password, "secret"]
h       # => {:email=>"vigo@foo.com"}
h.shift # => [:email, "vigo@foo.com"]
h       # => {}

delete, delete_if, keep_if

Hash’den key kullanarak eleman silmek için delete method’u kullanılır.

h = {:user => "vigo", :password => "secret", :email => "vigo@foo.com"}
h.delete(:user) # => "vigo"
h               # => {:password=>"secret", :email=>"vigo@foo.com"}

Block kullanıldığında, eğer olmayan bir key kullanılmışsa, bununla ilgili işlem yapmamızı sağlar:

h.delete(:phone){ |key| "-#{key}- bulunamadı?" } # => "-phone- bulunamadı?"

delete_if de ise direk block kullanarak koşullu silme işlemi yapabiliyoruz.

# 40’dan büyükleri silelim
h = { point_a: 10, point_b: 20, point_c: 50 } # => {:point_a=>10, :point_b=>20, :point_c=>50}
h.delete_if{ |k,v| v > 40 }                   # => {:point_a=>10, :point_b=>20}
h                                             # => {:point_a=>10, :point_b=>20}

keep_if ise delete_if in tam tersi gibidir. Eğer block’daki koşul true ise key-value çiftini tutar, aksi halde siler:

# 20’dan küçükleri tutalım sadece
h = { point_a: 10, point_b: 20, point_c: 50 }
                         # => {:point_a=>10, :point_b=>20, :point_c=>50}
h.keep_if{ |k,v| v < 20} # => {:point_a=>10}
h                        # => {:point_a=>10}

invert

Hash’in key’leri ile value’lerini yer değiştirmek için kullanılır.

h = { "a" => 100, "b" => 200 } # => {"a"=>100, "b"=>200}
h.keys                         # => ["a", "b"]
h.invert                       # => {100=>"a", 200=>"b"}

merge, update, merge!

İki Hash’i birbiryle birleştirmek için merge kullanılır.

h1 = { "a" => 100, "b" => 200 }
h2 = { "x" => 1, "y" => 2, "z" => 3 }
h1.merge(h2) # => {"a"=>100, "b"=>200, "x"=>1, "y"=>2, "z"=>3}
h1           # => {"a"=>100, "b"=>200}

Dikkat ettiyseniz h1 ile h2 yi birleştirdik ama h1in orijinal değerini bozmadık. Eğer bu birleşmenin kalıcı olmasını isteseydik ya update ya da merge! kullanmamız gerekecekti!

h1 = { "a" => 100, "b" => 200 }
h2 = { "x" => 1, "y" => 2, "z" => 3 }
h1.update(h2) # => {"a"=>100, "b"=>200, "x"=>1, "y"=>2, "z"=>3}
h1            # => {"a"=>100, "b"=>200, "x"=>1, "y"=>2, "z"=>3}

h1 = { "a" => 100, "b" => 200 }
h2 = { "x" => 1, "y" => 2, "z" => 3 }
h1.merge!(h2) # => {"a"=>100, "b"=>200, "x"=>1, "y"=>2, "z"=>3}
h1            # => {"a"=>100, "b"=>200, "x"=>1, "y"=>2, "z"=>3}

replace

Hash’in içeriğini başka bir Hash ile değiştirmek için kullanılır. Aslında varolan Hash’i başka bir Hash’e çevirmek gibidir. Neden replace kullanılıyor? Tamamen hafızadaki adresleme ile ilgili. replace kullanıldığı zaman, aynı Hash kullanılıyor, yeni bir Hash instance’ı yaratılmıyor.

h1 = { "a" => 100, "b" => 200, "c" => 0 }
h1.__id__ # => 70320602334320

Hafızadaki h1 Hash’nin nesne referansı : 70320602334320. Şimdir replace ile değerlerini değiştirelim:

h1.replace({ "foo" => 1, "bar" => 2 })
h1        # => {"foo"=>1, "bar"=>2}
h1.__id__ # => 70320602334320

Referansları aynı : 70320602334320. Eğer direkt olarak atama yapsakdık h1 gibi görünen ama bambaşka yepyeni bir Hash’imiz olacaktı.

h1 = { "foo" => 1, "bar" => 2 }
h1.__id__ # => 70216424232360

İterasyon ve Block Kullanımı

Aynı Array’lerdeki gibi Hash’lerde de iterasyon ve block kullanmak mümkün.

each, each_pair, each_value, each_key

each ve each_pair kardeş gibidirler:

h = { "a" => 100, "b" => 200, "c" => 0 }
h.each { |key, value| puts "key: #{key}, value: #{value}" }
h.each_pair { |key, value| puts "key: #{key}, value: #{value}" }

# key: a, value: 100
# key: b, value: 200
# key: c, value: 0

each_value sadece value, each_key de sadece key döner.

h = { "a" => 100, "b" => 200, "c" => 0 }

h.each_value { |value| puts "value: #{value}" }

# value: 100
# value: 200
# value: 0

h.each_key { |key| puts "key: #{key}" }

# key: a
# key: b
# key: c

each_entry, each_slice, each_cons

Hash’deki key-value çifti Array şeklinde bir entry olur:

h = { "a" => 100, "b" => 200, "c" => 0 }
h.each_entry{ |o| puts "o: #{o}" }

# o: ["a", 100]
# o: ["b", 200]
# o: ["c", 0]

each_slice ile entry’leri parçacıklara ayırırız:

h = { "a" => 100, "b" => 200, "c" => 0 }

# 2’li dilimlere ayırdık
h.each_slice(2){ |s| puts "slice: #{s}" }

# slice: [["a", 100], ["b", 200]]
# slice: [["c", 0]]

each_cons ise each_slice gibi çalışır ama farkı örnekteki gibidir:

h = { "a" => 100, "b" => 200, "c" => 0 }
h.each_cons(2){ |s| puts "grup: #{s}" }

# grup: [["a", 100], ["b", 200]]
# grup: [["b", 200], ["c", 0]]

Neticede, 3 key-value çifti vardı. 2’li grupladık ama sonuç each_slice daki gibi dönmedi. ["b", 200] tekrar etti, çıktı gruplaması mutlaka 2 eleman içerdi.

default_proc, default_proc=

Konunun başında varsayılan değer ataması yaparken şöyle bir örnek vermiştik:

h = Hash.new { |hash, key| hash[key] = "User: #{key}" }

eğer;

h.default_proc # => #<Proc:0x007f85f2250fd8@-:7>

deseydik, bu Hash’e ait Proc u görmüş olurduk. Yani bu Hash için varsayılan işlem prosedürünü tanımlamış olduk aslında. Örneği biraz genişletelim:

h = Hash.new {|obj, key| obj[key] = key * 4 } # => {}
h[1] # => 4
h[2] # => 8
h    # => {1=>4, 2=>8}

Key olarak sayı veriyoruz, gelen sayıdan da value üretiyoruz otomatik olarak. İşlemin çalışması için bir adet obje ve sayı geçmemiz gerekiyor parametre olarak. Aslında;

h.default_proc.call(Array.new, 9) # => 36
h.default_proc.call([], 9) # => 36
h.default_proc.call({}, 9) # => 36

şeklinde de, Hash’i sanki bir fonksiyon gibi kullanıp işleyebiliyoruz.

Daha sonra, önceden tanımladığımız bu prosedürü değiştirmek istersek default_proc= methodunu kullanıyoruz:

h = Hash.new { |hash, key| hash[key] = "User: #{key}" }
h.default_proc # => #<Proc:0x007feea39bbd80@-:7>
h[1] # => "User: 1"

# Yeni prosedür veriyoruz
h.default_proc = proc do |hash, key|
  hash[key] = "hello #{key}"
end
h # => {1=>"User: 1"}
h[2] # => "hello 2"
h # => {1=>"User: 1", 2=>"hello 2"}

compare_by_identity, compare_by_identity?

Hash’in key ve value’leri birbirine benziyor mu?

h = { "a" => 1, "b" => 2, :c => "c" }
h["a"] # => 1
h.compare_by_identity? # => false
h.compare_by_identity  # => {"a"=>1, "b"=>2, :c=>"c"}
h.compare_by_identity? # => true

# acaba key ile value benziyormu?
h["a"]                 # => nil

# burada benzer :)
h[:c]                  # => "c"