Anahtar-Kilit Koleksiyonlarında İlişkili Değerlerle Anahtarları Saklama
Ortak koleksiyonlarımızın sonuncusu anahtar-kilit koleksiyonudur. HashMap<K, V>
türü,
bu anahtarları ve değerleri belleğe nasıl yerleştireceğini belirleyen bir karma fonksiyonu kullanarak K türündeki anahtarların
V türündeki değerlerle eşlenmesini depolar. Birçok programlama dili bu tür bir veri yapısını destekler,
ancak genellikle hash, map, object, hash table, dictionary veya associative array gibi farklı isimler kullanırlar.
Anahtar-kilit koleksiyonları, vektörlerde olduğu gibi bir indeks kullanarak değil, herhangi bir türde olabilen bir anahtar kullanarak verileri aramak istediğinizde kullanışlıdır. Örneğin, bir oyunda, her anahtarın bir takımın adı ve değerlerin her takımın puanı olduğu bir anahtar-kilit koleksiyonunda her takımın puanını takip edebilirsiniz. Bir takım adı verildiğinde, skorunu alabilirsiniz.
Bu bölümün devamında a-k.k diyerek bahsedeceğimiz şey anahtar-kilit koleksiyonu olacaktır.
Bu bölümde a-k.k'nin temel API'sinin üzerinden geçeceğiz, ancak standart kütüphane tarafından HashMap<K, V>
üzerinde tanımlanan
fonksiyonlarda çok daha fazla güzellik gizlidir. Her zaman olduğu gibi, daha fazla bilgi için standart kütüphane dokümantasyonunu
kontrol edin.
Yeni Bir A-K.K Oluşturma
Boş bir a-k.k oluşturmanın bir yolu new
kullanmak ve insert
ile eleman eklemektir. Liste 8-20'de, isimleri
Blue ve Yellow olan iki takımın skorlarını takip ediyoruz. Blue takım 10 puanla başlar ve Yellow takım 50 puanla başlar.
fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); }
Öncelikle standart kütüphanenin koleksiyonlar bölümündeki HashMap
'i kullanmamız gerektiğini unutmayın.
Üç yaygın koleksiyonumuz arasında bu en az kullanılanıdır, bu nedenle başlangıçta otomatik olarak kapsama alınan
özelliklere dahil edilmemiştir. HashMap
standart kütüphaneden de daha az destek alır; örneğin bunları oluşturmak için
yerleşik bir makro yoktur.
Tıpkı vektörler gibi, a-k.k da verilerini yığın üzerinde saklar. Bu HashMap
'in String
türünde anahtarları ve i32
türünde değerleri
vardır. Vektörler gibi, a-k.k da homojendir: tüm anahtarlar birbiriyle aynı türde olmalıdır ve tüm değerler aynı türde olmalıdır.
A.K-K'da Değerlere Erişme
Liste 8-21'de gösterildiği gibi, get
metoduna anahtarı sağlayarak a.k-k'dan dönüş değerini alabiliriz.
fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); let team_name = String::from("Blue"); let score = scores.get(&team_name); }
Burada, skor Blue takımla ilişkilendirilen değere sahip olacak ve sonuç 10
olacaktır. get
metodu Option<&V>
döndürür;
a-k.k'da o anahtar için değer yoksa get
, None
döndürür. Bu program, skorlarda anahtar için bir giriş yoksa skoru sıfıra ayarlamak
için unwrap_or
öğesini çağırarak Option
'ı işler.
Bir a-k.k'daki her bir anahtar/değer çifti üzerinde, vektörlerde yaptığımıza benzer şekilde, bir for
döngüsü kullanarak yineleme yapabiliriz:
fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); for (key, value) in &scores { println!("{}: {}", key, value); } }
Bu kod, her bir çifti rastgele bir sırayla yazdıracaktır:
Yellow: 50
Blue: 10
A-K.K'lar ve Sahiplik
Copy
tanımını uygulayan i32
gibi türler için değerler a-k.k'a kopyalanır. String
gibi sahip olunan değerler için,
değerler taşınır ve a-k.k, Liste 8-22'de gösterildiği gibi bu değerlerin sahibi olur.
fn main() { use std::collections::HashMap; let field_name = String::from("Favorite color"); let field_value = String::from("Blue"); let mut map = HashMap::new(); map.insert(field_name, field_value); // field_name and field_value are invalid at this point, try using them and // see what compiler error you get! }
field_name
ve field_value
değişkenlerini, insert
çağrısı ile a-k.k'a taşındıktan sonra kullanamıyoruz.
Değerlere yapılan referansları a-k.k'a eklersek, değerler hash haritasına taşınmaz. Referansların işaret ettiği değerler en azından a-k.k geçerli olduğu sürece geçerli olmalıdır. Bölüm 10'daki “Referansları Yaşam Süreleriyle Doğrulama” bölümünde bu konular hakkında daha fazla konuşacağız.
A-K.K'unu Güncelleme
Anahtar ve değer çiftlerinin sayısı artırılabilir olsa da, her benzersiz anahtar aynı anda kendisiyle ilişkilendirilmiş yalnızca bir değere sahip olabilir (ancak bunun tersi geçerli değildir: örneğin, hem Blue takım hem de Yellow takım skorlar a-k.k'da depolanan 10 değerine sahip olabilir).
Bir anahtar-kilit eşlemedeki verileri değiştirmek istediğinizde, bir anahtarın zaten atanmış bir değere sahip olduğu durumu nasıl ele alacağınıza karar vermeniz gerekir. Eski değeri tamamen göz ardı ederek eski değeri yeni değerle değiştirebilirsiniz. Eski değeri tutup yeni değeri yok sayabilir, yalnızca anahtarın zaten bir değeri yoksa yeni değeri ekleyebilirsiniz. Ya da eski değer ile yeni değeri birleştirebilirsiniz. Şimdi bunların her birinin nasıl yapılacağına bakalım!
Bir Değerin Üzerine Yazma
Bir a-k.k'a bir anahtar ve bir değer eklersek ve daha sonra aynı anahtarı farklı bir değerle eklersek,
bu anahtarla ilişkili değer değiştirilecektir. Liste 8-23'teki kod iki kez insert
çağrısı yapsa da,
Blue takımın anahtarının değerini iki kez eklediğimiz için a-k.k yalnızca bir anahtar/değer çifti içerecektir.
fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Blue"), 25); println!("{:?}", scores); }
Bu kod {"Blue": 25}
yazdıracaktır. 10
'un orijinal değerinin üzerine yazılmıştır.
Yalnızca Bir Anahtar Mevcut Değilse Anahtar ve Değer Ekleme
Belirli bir anahtarın a-k.k'da bir değerle zaten var olup olmadığını kontrol etmek ve ardından aşağıdaki eylemleri gerçekleştirmek yaygındır: anahtar a-k.k'da varsa, mevcut değer olduğu gibi kalmalıdır. Anahtar mevcut değilse, onu ve değerini eklersiniz.
A-k.k'ları bunun için kontrol etmek istediğiniz anahtarı parametre olarak alan entry
adında özel bir API'ye sahiptir.
entry
metodunun geri dönüş değeri, var olabilecek veya olmayabilecek bir değeri temsil eden Entry
adlı bir enum
'dur.
Diyelim ki Yellow takımın anahtarının kendisiyle ilişkili bir değeri olup olmadığını kontrol etmek istiyoruz.
Eğer yoksa, 50
değerini eklemek istiyoruz ve aynı şeyi Blue takım için de yapmak istiyoruz.
entry
API'sini kullanarak yazacağımız kod Liste 8-24'e benzeyecektir:
fn main() { use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.entry(String::from("Yellow")).or_insert(50); scores.entry(String::from("Blue")).or_insert(50); println!("{:?}", scores); }
Entry
üzerindeki or_insert
metodu, ilgili Entry
anahtarı mevcutsa bu anahtarın değerine
değiştirilebilir bir referans döndürmek için tanımlanmıştır; mevcut değilse, parametreyi bu anahtarın yeni
değeri olarak ekler ve yeni değere değiştirilebilir bir referans döndürür.
Bu teknik, mantığı kendimiz yazmaktan çok daha temizdir ve ayrıca ödünç denetleyicisi ile daha iyi çalışır.
Liste 8-24'teki kod çalıştırıldığında {"Yellow": 50, "Blue": 10}
çıktısını verecektir.
entry
'e yapılan ilk çağrı Yellow takımın anahtarına 50
değerini ekleyecektir çünkü Yellow takımın zaten bir değeri yoktur.
İkinci entry
çağrısı a-k.k'unu değiştirmeyecektir çünkü Blue takım zaten 10
değerine sahiptir.
Eski Değere Dayalı Olarak Bir Değeri Güncelleme
A-K.K'lar için bir başka yaygın kullanım durumu da bir anahtarın değerini aramak ve ardından eski değere göre güncellemektir. Örneğin, Liste 8-25, bir metinde her bir kelimenin kaç kez geçtiğini sayan kodu göstermektedir. Kelimeleri anahtar olarak içeren bir a-k.k kullanırız ve o kelimeyi kaç kez gördüğümüzü takip etmek için değeri artırırız. Eğer bir kelimeyi ilk kez görüyorsak, önce 0 değerini ekleriz.
fn main() { use std::collections::HashMap; let text = "hello world wonderful world"; let mut map = HashMap::new(); for word in text.split_whitespace() { let count = map.entry(word).or_insert(0); *count += 1; } println!("{:?}", map); }
This code will print {"world": 2, "hello": 1, "wonderful": 1}
. You might see
the same key/value pairs printed in a different order: recall from the
section that
iterating over a hash map happens in an arbitrary order.
Bu kod {"world": 2, "hello": 1, "wonderful": 1}
yazdıracaktır. Aynı anahtar/değer çiftlerinin farklı bir sırada yazdırıldığını
görebilirsiniz: “A-K.K'daki Değerlere Erişim” bölümünden bir a-k.k üzerinde yinelemenin
rastgele bir sırada gerçekleştiğini hatırlayın.
split_whitespace
metodu, metin içindeki değerin boşluklarla ayrılmış alt dilimleri üzerinde bir yineleyici döndürür.
or_insert
metodu, belirtilen anahtar için değere değiştirilebilir bir referans (&mut V
) döndürür.
Burada bu değişebilir referansı count
değişkeninde saklarız, bu nedenle bu değere atama yapmak için önce yıldız işaretini (*
)
kullanarak count
referansını kaldırmamız gerekir. Değiştirilebilir referans for
döngüsünün sonunda kapsam dışına çıkar,
bu nedenle tüm bu değişiklikler güvenlidir ve ödünç alma kuralları tarafından izin verilir.
Şifreleme Fonksiyonları
Varsayılan olarak HashMap
, anahtar-kilit tablolarını içeren Hizmet Reddi (DoS) saldırılarına karşı direnç
sağlayabilen SipHash adlı bir şifreleme fonksiyonu kullanır1. Bu, mevcut en hızlı şifreleme algoritması değildir,
ancak performanstaki düşüşle birlikte gelen daha iyi güvenlik için yapılan takas buna değecektir.
Kodunuzun profilini çıkarırsanız ve varsayılan şifreleme fonksiyonunun amaçlarınız için çok yavaş olduğunu fark ederseniz,
farklı bir şifreleyici belirterek başka bir fonksiyona geçebilirsiniz. Bir şifreleyici, BuildHasher
tanımını uygulayan bir türdür.
Özellikler ve bunların nasıl uygulanacağı hakkında Bölüm 10'da konuşacağız. Kendi şifreleyicinizi sıfırdan yazmak zorunda değilsiniz;
crates.io, birçok yaygın hashing algoritmasını uygulayan şifreleyiciler sağlayan, diğer Rust kullanıcıları
tarafından paylaşılan kütüphanelere sahiptir.
Özet
Vektörler, dizgiler ve anahtar-kilit koleksiyonları, verileri depolamanız, erişmeniz ve değiştirmeniz gerektiğinde programlarda gerekli olan büyük miktarda işlevsellik sağlayacaktır.
İşte şimdi çözmeniz gereken bazı alıştırmalar:
- Bir tam sayı listesi verildiğinde, bir vektör kullanın ve listenin medyanını (sıralandığında orta konumdaki değer) ve modunu (en sık ortaya çıkan değer; bir a-k.k burada yardımcı olacaktır) döndürün.
- Dizgileri Domuz Latincesine dönüştürün. Her kelimenin ilk ünsüzü kelimenin sonuna taşınır ve “ay” eklenir, böylece “first” “irst-fay olur. Sesli harfle başlayan kelimelerin sonuna “hay” eklenir (“apple”, “apple-hay” olur). UTF-8 kodlamasıyla ilgili ayrıntıları aklınızda bulundurun!
- Bir a-k.k ve vektör kullanarak, bir kullanıcının bir şirketteki bir departmana çalışan isimleri eklemesine olanak tanıyan bir metin arayüzü oluşturun. Örneğin, “Sally'i Mühendisliğe Ekle” veya “Amir'i Satış Danışmanlığına Ekle”. Ardından kullanıcının bir departmandaki tüm kişilerin veya şirketteki tüm kişilerin alfabetik olarak sıralanmış bir listesini almasına izin verin.
Standart kütüphane API dokümantasyonları vektörlerin, dizgilerin ve a-k.k'ların bu alıştırmalar için yararlı olacak metodlarını açıklamaktadır!
İşlemlerin başarısız olabileceği daha karmaşık programlara giriyoruz, bu nedenle hata işlemeyi tartışmak için mükemmel bir zaman. Bunu daha sonra yapacağız!