Immutable alternative to `delete` in Ruby - ruby

Is there a version of Hash#delete as below:
hash = {a: 1}
hash.delete(:a) # => 1
hash # => {}
that returns a hash without :a, without mutating the original hash so that it would have its original value?

Use Hash#reject.
hash.reject { |k,_| k == :a }
#=> {}
hash
#=> {:a=>1}
This of course does not depend on the hash having a single key-value pair.

Related

hash merge vs assignment

I have a simple case of a hash that's passed along that is missing a field.
I'm wondering which is the preferred way of adding the field with some default and whether or not are there benefits to choosing one over the other and when?
hash['field'] = some_value
vs
hash.merge!({ 'field' => some_value })
edit: meant to use merge!
First, an important note: Hash#merge returns a new object, whereas Hash#[]= and Hash#merge! mutate the existing object:
hash = {a: :b}
hash.merge(c: :d) # => {a: :b, c: :d}
puts hash # => {a: :b} (!!!)
hash[:c] = :d
puts hash # => {a: :b, c: :d}
hash.merge!(e: :f)
puts hash # => {a: :b, c: :d, e: :f}
The main use-case for using Hash#merge! over Hash#[]= is (as the name suggests!) when you have two existing hashes that need to be merged together:
hash1 = {a: :b, c: :d}
hash2 = {e: :f, g: :h}
hash1.merge!(hash2)
puts hash1 # => {a: :b, c: :d, e: :f, g: :h}
This is equivalent to, but more convenient than, the much more verbose approach of looping through all values in hash2:
hash2.each do |key, value|
hash1[key] = value
end
I'm wondering which is the preferred way of adding the field with some default
I'd store the defaults in an extra hash (even if it is just a single value), so I can easily add other values later:
defaults = { foo: 1, bar: 2 }
Then, I'd merge the defaults in a way that preserve the given value, either by creating a new hash:
hash = { foo: 100, baz: 300 }
hash = defaults.merge(hash)
#=> {:foo=>100, :bar=>2, :baz=>300}
or by changing the hash in-place:
hash = { foo: 100, baz: 300 }
hash.merge!(defaults) { |key, given_value, default_value| given_value }
#=> {:foo=>100, :baz=>300, :bar=>2}
the first approach changes the hash itself (no new object is created)
the second creates new instance of Hash class with keys from both original hash and { 'field' => some_value } (compare object_id of hash and of result of hash.merge({ 'field' => some_value }) - they'll be different)
which one to choose?
it depends - if changing state of hash is not a problem for you, then the first approach is ok.

Create a hash dynamically and assign each key a different value

How can I dynamically create a hash, giving each of its key a different value? For example:
hash = {}
(1..9).each{|key| hash[key] = ' '}
creates a hash with keys 1 to 9, where every key has the same value, a space. How can I do the same, keys 1 to 9, but with each key holding a different value.
If the values do not matter and just have to be different:
hash = Hash[(1..9).zip(1..9)]
# => {1=>1, 2=>2, 3=>3, 4=>4, 5=>5, 6=>6, 7=>7, 8=>8, 9=>9}
hash = Hash[(1..9).zip('a'..'z')]
# => {1=>"a", 2=>"b", 3=>"c", 4=>"d", 5=>"e", 6=>"f", 7=>"g", 8=>"h", 9=>"i"}
For bigger hashes, performance can be improved by not creating intermediate arrays:
hash = (1..1000).inject({}) { | a, e | a[e] = e; a }
You can use this for dynamic hash creation with distinct key values.
hash = {}
(1..9).each_with_index{|key,index| hash[key] = index}
1..9
>> hash
hash
{1=>0, 2=>1, 3=>2, 4=>3, 5=>4, 6=>5, 7=>6, 8=>7, 9=>8}
p (1..9).zip(("a".."i").to_a.shuffle).to_h
# => {1=>"a", 2=>"d", 3=>"b", 4=>"h", 5=>"e", 6=>"g", 7=>"f", 8=>"i", 9=>"c"}

Modifying an existing hash value by x and returning the hash

I'm trying to increment all values of a hash by a given amount and return the hash. I am expecting:
add_to_value({"a" => 1, "c" => 2,"b"=> 3}, 1)
# => {"a" => 2, "c" => 3,"b"=> 4}
I'm thinking:
def add_to_value(hash, x)
hash.each {|key,value| value + x}
end
This returns:
{"a"=>1, "b"=>3, "c"=>2}
Why is hash sorted alphabetically?
You're super close, without any extra gems needed:
def add_to_value(hash, x)
hash.each {|key,value| hash[key] += x }
end
Just iterate the hash and update each value-by-key. #each returns the object being iterated on, so the result will be the original hash, which has been modified in place.
If you want a copy of the original hash, you can do that pretty easily, too:
def add_to_value(hash, x)
hash.each.with_object({}) {|(key, value), out| out[key] = value + x }
end
That'll define a new empty hash, pass it to the block, where it collects the new values. The new hash is returned from #with_object, and is thus returned out of add_to_value.
You can do the following to increment values:
hash = {}
{"a" => 1, "c" => 2,"b"=> 3}.each {|k,v| hash[k]=v+1}
hash
=>{"a"=>2, "c"=>3, "b"=>4}
And the hash will be sorted as you want.
The problem becomes trivial if you use certain gems, such as y_support. Type in your command line gem install y_support, and enjoy the extended hash iterators:
require 'y_support/core_ext/hash'
h = { "a"=>1, "c"=>3, "b"=>2 }
h.with_values do |v| v + 1 end
#=> {"a"=>2, "c"=>4, "b"=>3}
As for your sorting problem, I was unable to reproduce it.
Of course, a less elegant solution is possible without installing a gem:
h.each_with_object Hash.new do |(k, v), h| h[k] = v + 1 end
The gem also gives you Hash#with_keys (which modifies keys) and Hash#modify (which modifies both keys and values, kind of mapping from hash to hash), and banged versions Hash#with_values!, #with_keys! that modify the hash in place.

Hash with array as key

I'm defining a hash with an array as a key and another array as its value. For example:
for_example = {[0,1] => [:a, :b, :c]}
Everything is as expected below.
my_hash = Hash.new([])
an_array_as_key = [4,2]
my_hash[an_array_as_key] #=> []
my_hash[an_array_as_key] << "the" #=> ["the"]
my_hash[an_array_as_key] << "universal" #=> ["the", "universal"]
my_hash[an_array_as_key] << "answer" #=> ["the", "universal", "answer"]
But if I try to access the keys:
my_hash #=> {}
my_hash.keys #=> []
my_hash.count #=> 0
my_hash.values #=> []
my_hash.fetch(an_array_as_key) # KeyError: key not found: [4, 2]
my_hash.has_key?(an_array_as_key) #=> false
Rehash doesn't help:
my_hash #=> {}
my_hash.rehash #=> {}
my_hash.keys #=> []
But the values are saved:
my_hash[an_array_as_key] #=> ["the", "universal", "answer"]
Am I missing something?
To understand this, You need to understand the difference between Hash::new and Hash::new(ob). Suppose you define a hash object using Hash::new or hash literal {}. Now whenever you will write a code hsh[any_key], there is two kind of output may be seen, if any_key don't exist, then default value nil will be returned,otherwise whatever value is associated with the key will be returned. The same explanation will be applicable if you create any Hash object using Hash.new.
Now Hash.new(ob) is same as Hash.new, with one difference is, you can set any default value you want, for non existent keys of that hash object.
my_hash = Hash.new([])
my_hash[2] # => []
my_hash[2].object_id # => 83664630
my_hash[4] # => []
my_hash[4].object_id # => 83664630
my_hash[3] << 4 # => [4]
my_hash[3] # => [4]
my_hash[3].object_id # => 83664630
my_hash[5] << 8 # => [4, 8]
my_hash[5] # => [4, 8]
my_hash[5].object_id # => 83664630
Now see in the above example my_hash has no keys like 2,3 and 4. But the object_id proved that, all key access results in to return the same array object. my_hash[2] is not adding the key to the hash my_hash, rather trying to access the value of the key 2 if that key exist, otherwise it is returning the default value of my_hash. Remember all lines like my_hash[2],my_hash[3] etc is nothing but a call to Hash#[] method.
But there is a third way to go, may be you are looking for, which is Hash::new {|hash, key| block }.With this style you can add key to the hash object if that key doesn't exist, with a default value of same class instance,but not the same instance., while you are doing actually Hash#[] method call.
my_hash = Hash.new { |hash, key| hash[key] = []}
my_hash[2] # => []
my_hash[2].object_id # => 76312700
my_hash[3] # => []
my_hash[3].object_id # => 76312060
my_hash.keys # => [2, 3]

Exposing key/value pair in code block parameters

Is it possible to expose the key/value pair from an array of hashes in the parameter of the block?
array = [{:a=>'a'}, {:b=>'b'}] # an array of hashes
array.each {|key, value| puts "#{key},#{value}"}
array.map {|key, value| "(#{key},#{value})"}
array.inject([]) {|accum, (key,value)| key == :a ? value : accum}
Currently the results of the code block parameters |key, value| are (hash, nil)
I would like to get (symbol, string) in the declaration of the |key,value| parameters. Is this possible or am I stuck having to pass in a hash and extracting the key/value pair myself?
I know that passing a hash instead of an array will automatically give access to the key/value pair, but much of Ruby returns arrays.
UPDATE: It seems possible with arrays, but not hashes?
a = [['a','b'],['c','d']]
a.each {|(x,y)| puts "#{x}=>#{y}"} # => a=>b
# => c=>d
a.each {|x| p x} # => ["a", "b"]
# => ["c", "d"]
Ok.. If I catch you wright :
array = [{:a=>'a'}, {:b=>'b'}]
array.map {|k,v| [k,v]}
# => [[{:a=>"a"}, nil], [{:b=>"b"}, nil]]
you are getting above. But you want [[:a,"a"], [:b,"b"]] . No It is not possible. With #each or #map, when you have array of hashes like you given,you can't assign the block parameter k,v as :a,"a" and :b,"b" with each pass.
More simply try on your IRB,you will get to see the effect:
k,v = {:a=>'a'}
p k,v
# >> {:a=>"a"}
# >> nil

Resources