Szukaj…


Wprowadzenie

Hash to słownikopodobna kolekcja unikalnych kluczy i ich wartości. Znane również jako tablice asocjacyjne, są podobne do tablic, ale tam, gdzie tablica używa liczb całkowitych jako indeksu, Hash pozwala na użycie dowolnego typu obiektu. Pobierasz lub tworzysz nowy wpis w haszu, odwołując się do jego klucza.

Składnia

  • {first_name: „Noel”, second_name: „Edmonds”}

  • {: first_name => „Noel”,: second_name => „Edmonds”}

  • {„First Name” => „Noel”, „Second Name” => „Edmonds”}

  • {first_key => first_value, second_key => second_value}

Uwagi

Skróty w kluczach mapy Ruby do wartości przy użyciu tabeli skrótów.

Dowolny obiekt mieszalny może być używany jako klucze. Jednak bardzo często używa się Symbol ponieważ jest on ogólnie bardziej wydajny w kilku wersjach Ruby, ze względu na ograniczoną alokację obiektów.

{ key1: "foo", key2: "baz"  }

Tworzenie skrótu

Hash w Ruby to obiekt, który implementuje tablicę skrótów , mapując klucze na wartości. Ruby obsługuje określoną dosłowną składnię do definiowania skrótów za pomocą {} :

my_hash = {}  # an empty hash
grades = { 'Mark' => 15, 'Jimmy' => 10, 'Jack' => 10 }

Skrót można również utworzyć przy użyciu standardowej new metody:

my_hash = Hash.new  # any empty hash
my_hash = {}        # any empty hash

Skróty mogą mieć wartości dowolnego typu, w tym typy złożone, takie jak tablice, obiekty i inne skróty:

mapping = { 'Mark' => 15, 'Jimmy' => [3,4], 'Nika' => {'a' => 3, 'b' => 5} }
mapping['Mark']   # => 15
mapping['Jimmy']  # => [3, 4]
mapping['Nika']   # => {"a"=>3, "b"=>5}

Klucze mogą być dowolnego typu, w tym złożone:

mapping = { 'Mark' => 15, 5 => 10, [1, 2] => 9 }
mapping['Mark']  # => 15
mapping[[1, 2]]  # => 9

Symbole są powszechnie używane jako klucze skrótu, a Ruby 1.9 wprowadził nową składnię specjalnie w celu skrócenia tego procesu. Następujące skróty są równoważne:

# Valid on all Ruby versions
grades = { :Mark => 15, :Jimmy => 10, :Jack => 10 }
# Valid in Ruby version 1.9+
grades = { Mark: 15, Jimmy: 10, Jack: 10 }

Poniższy skrót (poprawny we wszystkich wersjach Ruby) jest inny , ponieważ wszystkie klucze są ciągami:

grades = { "Mark" => 15, "Jimmy" => 10, "Jack" => 10 }

Chociaż obie wersje składni można mieszać, odradza się poniższe.

mapping = { :length => 45, width: 10 }

W Ruby 2.2+ istnieje alternatywna składnia do tworzenia skrótu za pomocą klawiszy symboli (najbardziej przydatne, jeśli symbol zawiera spacje):

grades = { "Jimmy Choo": 10, :"Jack Sparrow": 10 }
# => { :"Jimmy Choo" => 10, :"Jack Sparrow" => 10}

Dostęp do wartości

Poszczególne wartości skrótu są odczytywane i zapisywane przy użyciu metod [] i []= :

my_hash = { length: 4, width: 5 }

my_hash[:length] #=> => 4

my_hash[:height] = 9

my_hash #=> {:length => 4, :width => 5, :height => 9 }

Domyślnie dostęp do klucza, który nie został dodany do skrótu, wynosi nil , co oznacza, że zawsze można bezpiecznie sprawdzić wartość klucza:

my_hash = {}

my_hash[:age] # => nil

Hashe mogą także zawierać klucze w ciągach znaków. Jeśli spróbujesz uzyskać do nich normalny dostęp, po prostu zwróci nil , zamiast tego uzyskujesz do nich dostęp za pomocą ich kluczy łańcuchowych:

my_hash = { "name" => "user" }

my_hash[:name]    # => nil
my_hash["name"]   # => user

W sytuacjach, w których oczekuje się lub wymagane są klucze, skróty mają metodę fetch , która zgłosi wyjątek podczas uzyskiwania dostępu do klucza, który nie istnieje:

my_hash = {}

my_hash.fetch(:age) #=> KeyError: key not found: :age

fetch przyjmuje wartość domyślną jako swój drugi argument, który jest zwracany, jeśli klucz nie został wcześniej ustawiony:

my_hash =  {}
my_hash.fetch(:age, 45) #=> => 45

fetch może również zaakceptować blok, który jest zwracany, jeśli klucz nie został wcześniej ustawiony:

my_hash = {}
my_hash.fetch(:age) { 21 } #=> 21

my_hash.fetch(:age) do |k|
  puts "Could not find #{k}"
end

#=> Could not find age

Hashe obsługują również metodę store jako alias dla []= :

my_hash = {}

my_hash.store(:age, 45)

my_hash #=> { :age => 45 }

Możesz także uzyskać wszystkie wartości skrótu za pomocą metody values :

my_hash = { length: 4, width: 5 }

my_hash.values #=> [4, 5]

Uwaga: To jest tylko dla Ruby 2.3+ #dig jest przydatny dla zagnieżdżonych Hash s. Wyodrębnia zagnieżdżoną wartość określoną przez sekwencję obiektów idx, wywołując dig na każdym kroku, zwracając zero, jeśli jakikolwiek krok pośredni jest zerowy.

h = { foo: {bar: {baz: 1}}}

h.dig(:foo, :bar, :baz)   # => 1
h.dig(:foo, :zot, :xyz)   # => nil

g = { foo: [10, 11, 12] }
g.dig(:foo, 1)            # => 11

Ustawianie wartości domyślnych

Domyślnie próba wyszukania wartości nieistniejącego klucza zwróci nil . Opcjonalnie można określić inną wartość, która ma zostać zwrócona (lub akcję, którą należy podjąć), gdy dostęp do skrótu uzyskuje się za pomocą nieistniejącego klucza. Chociaż jest to określane jako „wartość domyślna”, nie musi to być pojedyncza wartość; może to być na przykład wartość obliczona, taka jak długość klucza.

Domyślną wartość skrótu można przekazać do jego konstruktora:

h = Hash.new(0)

h[:hi] = 1 
puts h[:hi]  # => 1 
puts h[:bye] # => 0 returns default value instead of nil

Wartość domyślną można również określić w już zbudowanym skrócie:

my_hash = { human: 2, animal: 1 }
my_hash.default = 0
my_hash[:plant] # => 0

Należy zauważyć, że wartość domyślna nie jest kopiowana za każdym razem, gdy uzyskuje się dostęp do nowego klucza, co może prowadzić do zaskakujących wyników, gdy wartość domyślna jest typem odniesienia:

# Use an empty array as the default value
authors = Hash.new([])

# Append a book title 
authors[:homer] << 'The Odyssey'

# All new keys map to a reference to the same array:
authors[:plato] # => ['The Odyssey']

Aby obejść ten problem, konstruktor skrótu akceptuje blok, który jest wykonywany za każdym razem, gdy uzyskiwany jest dostęp do nowego klucza, a zwrócona wartość jest używana jako domyślna:

authors = Hash.new { [] }

# Note that we're using += instead of <<, see below
authors[:homer] += ['The Odyssey']
authors[:plato] # => []

authors # => {:homer=>["The Odyssey"]}

Zauważ, że powyżej musieliśmy użyć + = zamiast <<, ponieważ domyślna wartość nie jest automatycznie przypisywana do skrótu; użycie << dodałoby do tablicy, ale autorzy [: homer] pozostaliby niezdefiniowani:

authors[:homer] << 'The Odyssey' # ['The Odyssey']
authors[:homer] # => []
authors # => {}

Aby móc przypisać wartości domyślne podczas dostępu, a także obliczyć bardziej wyrafinowane wartości domyślne, blok domyślny jest przekazywany zarówno hash, jak i klucz:

authors = Hash.new { |hash, key| hash[key] = [] }

authors[:homer] << 'The Odyssey'
authors[:plato] # => []

authors # => {:homer=>["The Odyssey"], :plato=>[]}

Możesz także użyć domyślnego bloku, aby wykonać akcję i / lub zwrócić wartość zależną od klucza (lub niektórych innych danych):

chars = Hash.new { |hash,key| key.length }

chars[:test] # => 4

Możesz nawet tworzyć bardziej złożone skróty:

page_views = Hash.new { |hash, key| hash[key] = { count: 0, url: key } }
page_views["http://example.com"][:count] += 1
page_views # => {"http://example.com"=>{:count=>1, :url=>"http://example.com"}}

Aby ustawić domyślną wartość Proc na już istniejącym haszu, użyj default_proc= :

authors = {}
authors.default_proc = proc { [] }

authors[:homer] += ['The Odyssey']
authors[:plato] # => []

authors # {:homer=>["The Odyssey"]}

Automatyczne tworzenie Deep Hash

Skrót ma domyślną wartość dla kluczy, które są wymagane, ale nie istnieją (zero):

a = {}
p a[ :b ] # => nil 

Podczas tworzenia nowego skrótu można określić domyślne:

b = Hash.new 'puppy'
p b[ :b ]            # => 'puppy'

Hash.new pobiera również blok, który umożliwia automatyczne tworzenie zagnieżdżonych skrótów, takich jak zachowanie autoweryfikacji Perla lub mkdir -p :

# h is the hash you're creating, and k the key.
#
hash = Hash.new { |h, k| h[k] = Hash.new &h.default_proc }
hash[ :a ][ :b ][ :c ] = 3

p hash # => { a: { b: { c: 3 } } }

Modyfikowanie kluczy i wartości

Możesz utworzyć nowy skrót ze zmodyfikowanymi kluczami lub wartościami, w rzeczywistości możesz także dodawać lub usuwać klucze za pomocą wstrzykiwania (AKA, zmniejsz ). Na przykład, aby utworzyć skrót z kluczami strunowymi i dużymi literami:

fruit = { name: 'apple', color: 'green', shape: 'round' }
# => {:name=>"apple", :color=>"green", :shape=>"round"}

new_fruit = fruit.inject({}) { |memo, (k,v)| memo[k.to_s] = v.upcase; memo }

# => new_fruit is {"name"=>"APPLE", "color"=>"GREEN", "shape"=>"ROUND"}

Hash jest policzalną, w zasadzie zbiorem par klucz / wartość. Dlatego ma takie metody, jak each , map i inject .

Dla każdej pary klucz / wartość w haszu analizowany jest dany blok, wartość notatki przy pierwszym uruchomieniu jest wartością początkową przekazywaną do inject , w naszym przypadku pustym hashem {} . Wartość memo dla kolejnych ocen jest wartością zwracaną z oceny poprzednich bloków, dlatego modyfikujemy memo ustawiając klucz z wartością, a następnie zwracamy memo na końcu. Zwracana wartość końcowego bloku jest wartością zwracaną przez inject , w naszym przypadku memo .

Aby uniknąć konieczności podawania końcowej wartości, możesz zamiast tego użyć each_with_object :

new_fruit = fruit.each_with_object({}) { |(k,v), memo| memo[k.to_s] = v.upcase }

Lub nawet mapa :

1.8
new_fruit = Hash[fruit.map{ |k,v| [k.to_s, v.upcase] }]

(Zobacz tę odpowiedź, aby uzyskać więcej informacji, w tym jak manipulować skrótami w miejscu).

Iterating Over Hash

Hash zawiera moduł Enumerable , który zapewnia kilka metod iteracji, takich jak: Enumerable#each , Enumerable#each_pair , Enumerable#each_key i Enumerable#each_value .

.each i .each_pair iteracyjne nad każdej pary klucz-wartość:

h = { "first_name" => "John", "last_name" => "Doe" }
h.each do |key, value|
    puts "#{key} = #{value}"
end

# => first_name = John
#    last_name = Doe

.each_key iteruje tylko po klawiszach:

h = { "first_name" => "John", "last_name" => "Doe" }
h.each_key do |key|
  puts key
end

# => first_name
#    last_name

.each_value iteruje tylko wartości:

h = { "first_name" => "John", "last_name" => "Doe" }
h.each_value do |value|
    puts value
end

# => John
#    Doe

.each_with_index iteruje po elementach i zapewnia indeks iteracji:

h = { "first_name" => "John", "last_name" => "Doe" }
h.each_with_index do |(key, value), index|
    puts "index: #{index} | key: #{key} | value: #{value}"
end

# => index: 0 | key: first_name | value: John
#    index: 1 | key: last_name | value: Doe

Konwersja do iz tablic

Skróty można swobodnie konwertować do iz macierzy. Konwersja skrótu par klucz / wartość w tablicę spowoduje utworzenie tablicy zawierającej zagnieżdżone tablice dla pary:

{ :a => 1, :b => 2 }.to_a # => [[:a, 1], [:b, 2]]

W przeciwnym kierunku można utworzyć skrót z tablicy tego samego formatu:

[[:x, 3], [:y, 4]].to_h # => { :x => 3, :y => 4 }

Podobnie, skróty mogą być inicjowane za pomocą Hash[] i listy na przemian kluczy i wartości:

Hash[:a, 1, :b, 2] # => { :a => 1, :b => 2 }

Lub z tablicy tablic z dwiema wartościami:

Hash[ [[:x, 3], [:y, 4]] ] # => { :x => 3, :y => 4 }

Skróty można przekonwertować z powrotem na tablicę naprzemiennych kluczy i wartości za pomocą flatten() :

{ :a => 1, :b => 2 }.flatten # => [:a, 1, :b, 2]

Łatwa konwersja do iz tablicą pozwala Hash do pracy również z wielu Enumerable metod, takich jak collect i zip :

Hash[('a'..'z').collect{ |c| [c, c.upcase] }] # => { 'a' => 'A', 'b' => 'B', ... }

people = ['Alice', 'Bob', 'Eve']
height = [5.7, 6.0, 4.9]
Hash[people.zip(height)] # => { 'Alice' => 5.7, 'Bob' => '6.0', 'Eve' => 4.9 }

Uzyskiwanie wszystkich kluczy lub wartości skrótu

{foo: 'bar', biz: 'baz'}.keys   # => [:foo, :biz]
{foo: 'bar', biz: 'baz'}.values # => ["bar", "baz"]
{foo: 'bar', biz: 'baz'}.to_a   # => [[:foo, "bar"], [:biz, "baz"]]
{foo: 'bar', biz: 'baz'}.each   #<Enumerator: {:foo=>"bar", :biz=>"baz"}:each>

Przesłanianie funkcji skrótu

eql? Ruby używają metod hash i eql? w celu wykonania operacji skrótu i przypisania obiektów przechowywanych w skrócie do wewnętrznych pojemników skrótu. Domyślną implementacją hash w Ruby jest funkcja skrótu szmeru we wszystkich polach składowych obiektu mieszanego . Aby zastąpić to zachowanie, można przesłonić hash i eql? metody

Podobnie jak w przypadku innych implementacji skrótu, dwa obiekty aib zostaną zszyfrowane do tego samego segmentu, jeśli a. a.hash == b.hash i zostaną uznane za identyczne, jeśli a. a.eql?(b) . Tak więc, kiedy reimplementujemy hash i eql? należy zadbać o to, że jeśli i a b są równe pod eql? muszą zwrócić tę samą wartość hash . W przeciwnym razie może to spowodować zduplikowanie wpisów w haszu. I odwrotnie, zły wybór implementacji hash może spowodować, że wiele obiektów będzie korzystać z tego samego segmentu mieszania, skutecznie niszcząc czas wyszukiwania O (1) i powodując wywołanie eql? O (n) eql? na wszystkich przedmiotach.

W poniższym przykładzie tylko instancja klasy A jest przechowywana jako klucz, ponieważ została dodana jako pierwsza:

class A
  def initialize(hash_value)
    @hash_value = hash_value
  end
  def hash
    @hash_value # Return the value given externally
  end
  def eql?(b)
    self.hash == b.hash
  end
end

class B < A
end

a = A.new(1)
b = B.new(1)

h = {}
h[a] = 1
h[b] = 2

raise "error" unless h.size == 1
raise "error" unless h.include? b
raise "error" unless h.include? a

Filtrowanie skrótów

select zwraca nowy hash z parami klucz-wartość, dla których blok ma wartość true .

{ :a => 1, :b => 2, :c => 3 }.select { |k, v| k != :a && v.even? } # => { :b => 2 }

Jeśli nie potrzebujesz klucza lub wartości w bloku filtra, konwencja polega na użyciu _ w tym miejscu:

{ :a => 1, :b => 2, :c => 3 }.select { |_, v| v.even? } # => { :b => 2 }
{ :a => 1, :b => 2, :c => 3 }.select { |k, _| k == :c } # => { :c => 3 }

reject zwraca nowy hash z parami klucz-wartość, dla których blok ocenia na false :

{ :a => 1, :b => 2, :c => 3 }.reject { |_, v| v.even? } # => { :a => 1, :c => 3 }
{ :a => 1, :b => 2, :c => 3 }.reject { |k, _| k == :b } # => { :a => 1, :c => 3 }

Ustaw operacje na skrótach

  • Przecięcie skrótów

    Aby uzyskać przecięcie dwóch skrótów, zwróć klucze współdzielone, których wartości są równe:

    hash1 = { :a => 1, :b => 2 }
    hash2 = { :b => 2, :c => 3 }
    hash1.select { |k, v| (hash2.include?(k) && hash2[k] == v) } # => { :b => 2 }
    
  • Łączenie (scalanie) skrótów:

    klucze w skrócie są unikalne, jeśli klucz występuje w obu skrótach, które mają zostać scalone, jeden z skrótu wywoływanego przez merge jest zastępowany:

    hash1 = { :a => 1, :b => 2 }
    hash2 = { :b => 4, :c => 3 }
    
    hash1.merge(hash2) # => { :a => 1, :b => 4, :c => 3 }
    hash2.merge(hash1) # => { :b => 2, :c => 3, :a => 1 }
    


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow