A Hash is a dictionary-like collection of unique keys and their values. Also called associative arrays, they are similar to Arrays, but where an Array uses integers as its index, a Hash allows you to use any object type.
Hashes enumerate their values in the order that the corresponding keys were inserted.
A Hash can be easily created by using its implicit form:
grades = { "Jane Doe" => 10, "Jim Doe" => 6 }
Hashes allow an alternate syntax for keys that are symbols. Instead of
options = { :font_size => 10, :font_family => "Arial" }
You could write it as:
options = { font_size: 10, font_family: "Arial" }
Each named key is a symbol you can access in hash:
options[:font_size] # => 10
A Hash can also be created through its ::new method:
grades = Hash.new grades["Dorothy Doe"] = 9
Hashes have a default value that is returned when accessing keys that do not exist in the hash. If no default is set nil is used. You can set the default value by sending it as an argument to Hash.new:
grades = Hash.new(0)
Or by using the #default= method:
grades = {"Timmy Doe" => 8} grades.default = 0
Accessing a value in a Hash requires using its key:
puts grades["Jane Doe"] # => 0
Common Uses
Hashes are an easy way to represent data structures, such as
books = {} books[:matz] = "The Ruby Programming Language" books[:black] = "The Well-Grounded Rubyist"
Hashes are also commonly used as a way to have named parameters in functions. Note that no brackets are used below. If a hash is the last argument on a method call, no braces are needed, thus creating a really clean interface:
Person.create(name: "John Doe", age: 27) def self.create(params) @name = params[:name] @age = params[:age] end
Hash Keys
Two objects refer to the same hash key when their hash value is identical and the two objects are eql? to each other.
A user-defined class may be used as a hash key if the hash and eql? methods are overridden to provide meaningful behavior. By default, separate instances refer to separate hash keys.
A typical implementation of hash is based on the object’s data while eql? is usually aliased to the overridden
method:
class Book attr_reader :author, :title def initialize(author, title) @author = author @title = title end def ==(other) self.class === other and other.author == @author and other.title == @title end alias eql? == def hash @author.hash ^ @title.hash # XOR end end book1 = Book.new 'matz', 'Ruby in a Nutshell' book2 = Book.new 'matz', 'Ruby in a Nutshell' reviews = {} reviews[book1] = 'Great reference!' reviews[book2] = 'Nice and compact!' reviews.length #=> 1
See also Object#hash and Object#eql?
Convert an Array to a Hash
The Hash.[] method converts an even number of parameters to a Hash. (The Hash[] method depends on the Hash class, but don’t confuse the method with the class itself). For example:
Hash['A', 'a', 'B', 'b'] # => {"A"=>"a", "B"=>"b"}
You can convert an array to a hash using the Hash[] method:
array = ['A', 'a', 'B', 'b', 'C', 'c'] hash = Hash[*array] # => {"A"=>"a", "B"=>"b", "C"=>"c"}
The * (splat) operator converts the array into an argument list, as expected by Hash[].
You can similarly convert an array of arrays to a Hash, by adding flatten:
array = [['A', 'a'], ['B', 'b'], ['C', 'c']] hash = Hash[*array.flatten] # => {"A"=>"a", "B"=>"b", "C"=>"c"}
This also comes in handy when you have a list of words that you want to convert to a Hash:
Hash[*%w( A a B b C c )] # => {"A"=>"a", "B"=>"b", "C"=>"c"}
Add has_keys? method to Hash class
class Hash
def has_keys?(*_keys) (_keys - self.keys).empty? end
end
h = {1=>‘a’,2=>‘b’}
h.has_keys?(1,2) #-> true
h.has_keys?(1,3) #-> false
Create new Hash as subset of another a different way
or
only keys
old_hash = { :a => 'A', :b => 'B', :c => 'C', :d => 'D', :e => 'E', :f => 'F' } only_keys = [ :a, :c, :f ] new_hash = old_hash.delete_if { |k, v| !only_keys.include? k }
only values
old_hash = { :a => 'A', :b => 'B', :c => 'C', :d => 'D', :e => 'E', :f => 'F' } only_values = [ 'A', 'D', 'G' ] new_hash = old_hash.delete_if { |k, v| !only_values.include? v }
there are many ways to skin a cat :)
Convert a Hash to an Array of Arrays using map
Although you‘ll always have to_a and it‘s faster, this trick is too cool to ignore…
Convert a Hash to an Array of Arrays using Enumerable#map
keys to/from symbols
There’s probably a more effecient way to do this…
class Hash def keys_to_strings res = {} self.keys.each do |k| if self[k].is_a?(Hash) res[k.to_s] = self[k].keys_to_strings else res[k.to_s] = self[k] end end return res end def keys_to_symbols res = {} self.keys.each do |k| if self[k].is_a?(Hash) res[k.to_sym] = self[k].keys_to_symbols else res[k.to_sym] = self[k] end end return res end end
Add requires!
Useful for methods that take options = {}
class Hash def requires!(*params) params.each do |param| raise ArgumentError.new("Missing required parameter: #{param}") unless self.has_key?(param) end end end
Generalized Zip
My 5 cents.
I find trully useful this. Is a kind of generalized zip. You can combine 2 or more enumerables (arrays or others) of any size into a hash, array of arrays, .… The size of the result is the size of the bigest of the enumerables. For the shortests enumerables nil elements are used at the end.
# method compose def compose(*enumerables) res=[] enumerables.map(&:size).max.times do tupla=[] for enumerable in enumerables tupla << enumerable.shift end res << (block_given? ? yield(tupla) : tupla) end res end
some examples:
en1= [1, 2, 3, 4] en2= ['a', 'b', 'c', 'd', 'e'] en3= {:elem1 => "1", :elem2 => "2", :elem3 => "3"} p compose en1.dup, en2.dup, en3.dup p a1=compose(en2.dup, en1.dup) {|a,b| {a.to_sym => b}} p a1.inject({}) {|ac,item| ac.merge item} p a1.flatten p a2=compose(en2.dup, en1.dup).flatten p Hash[*a2] p a3=compose(en2.dup, en3.dup).flatten
Their outputs are:
#[[1, "a", [:elem1, "1"]], [2, "b", [:elem2, "2"]], [3, "c", [:elem3, "3"]], [4, "d", nil], [nil, "e", nil]] #[{:a=>1}, {:b=>2}, {:c=>3}, {:d=>4}, {:e=>nil}] #{:b=>2, :d=>4, :e=>nil, :c=>3, :a=>1} #[{:a=>1}, {:b=>2}, {:c=>3}, {:d=>4}, {:e=>nil}] #["a", 1, "b", 2, "c", 3, "d", 4, "e", nil] #{"a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>nil} #["a", :elem1, "1", "b", :elem2, "2", "c", :elem3, "3", "d", nil, "e", nil]
Create new Hash as subset of another
old_hash = {:a=>‘A’,:b=>‘B’,:c=>‘C’,:d=>‘D’,:e=>‘E’,:f=>‘F’}
only_keys = [:a,:c,:f]
new_hash = Hash[*old_hash.find_all{|k,v| only_keys.member?(k)}.flatten]
# => {:a=>“A”, :c=>“C”, :f=>“F”}
or for values
only_vals = [‘A’,‘D’,‘G’]
new_hash = Hash[*old_hash.find_all{|k,v| only_vals.member?(v)}.flatten]
# => {:a=>“A”, :d=>“D”}