Ruby - Create hash with keys and values as arrays - ruby-on-rails

I am new to Ruby and trying to find if I can create a hash which has keys as keys_Array and values as val_array.
Currently I have the following but it gives empty array.
key_hash = Hash.new { |hash, key|
hash.key = ["#{csv['values']} #{csv['keys']}"]
}
p key_hash.keys #empty array here

If you're trying to create a hash from two corresponding arrays for keys and values, this is quick way:
keys = ["a", "b", "c"]
values = [1, 2, 3]
hash = Hash[keys.zip(values)]
# => {"a"=>1, "b"=>2, "c"=>3}
# for symbols
hash = Hash[keys.map(&:to_sym).zip(values)]
# => {:a=>1, :b=>2, :c=>3}

If you want to make a new hash from two hashes (one contains keys for new hash and other contains values with respect to keys in first hash then only)
you can use something like below :
keys = ['k1', 'k2', 'k3']
values = ['b1', 'b2']
h = {}
keys.zip(values) { |a,b| h[a.to_sym] = b }
# => nil
p h
# => {:k1=>"b1", :k2=>"b2", :k3=>nil}
Keep in mind that if the keys are more and values are less in number then key will have nil value as mentioned in the e.g. but if the keys are less as compare to values then it will now consider the remaining values for e.g.
keys =['b1', 'b2']
=> ["b1", "b2"]
values = ['k1', 'k2', 'k3']
=> ["k1", "k2", "k3"]
h = {}
=> {}
keys.zip(values) { |a,b| h[a.to_sym] = b }
=> nil
p h
{:b1=>"k1", :b2=>"k2"}

Related

How to replace missing key/value with zero using slice

I have a hash. I need to extract some key/value pairs, but some desired keys are missing.
How can I replace the missing pairs with "key" => 0.0 when I call attributes.slice on the record and keys as follows:
record = Model.last
record.attributes.slice('k1','k2','k3','k4','k5') # this returns
=> {"k1"=> 343, k3=> 0.0}
If some keys are missing then they won't appear in the result. How can I get the remaining missing keys assigned with 0.0?
Suppose
h = { 'k2'=>2, 'k1'=>1 }
and
all_keys = ['k1', 'k2', 'k3', 'k4']
then
all_keys.map { |k| h.fetch(k,0.0) }
#=> [1, 2, 0.0, 0.0]
See Hash#fetch.
We can take advantage of the fact that a hash can be overwritten with new key/value pairs:
{a: 0, b: 0}.merge(a: 2) # => {:a=>2, :b=>0}
Knowing that, we can do something like this:
desired_keys = [:a, :b]
foo = {a: 1}
desired_keys.zip([0] * desired_keys.size).to_h.merge(foo.slice(*desired_keys))
# => {:a=>1, :b=>0}
desired_keys is a predefined list of the key/value pairs we want, foo is the actual hash the real values are coming from.
[0] * 2 # => [0, 0] creates an array of a given size.
desired_keys.zip([0] * desired_keys.size).to_h creates a temporary hash of the values being used as filler.
merge(foo.slice(*desired_keys)) grabs the pairs we wanted. In this situation, * AKA "splat" explodes the array into its individual elements, so they're passed as separate parameters to slice. Here's what's happening:
def bar(*a)
a
end
bar(%w[a b]) # => [["a", "b"]]
bar(*%w[a b]) # => ["a", "b"]
Notice that the first call is passing in an array, whereas the second passes separate values.
Breaking it down a little to make it a bit more apparent:
desired_keys.zip([0] * desired_keys.size).to_h # => {:a=>0, :b=>0}
.merge(foo.slice(*desired_keys)) # => {:a=>1, :b=>0}
Because we know the record fields we're retrieving, it's easy to create that temporary hash once, in advance, then reuse it every time, resulting in very fast code:
DESIRED_KEYS = [:a, :b]
ZERO_HASH = DESIRED_KEYS.zip([0] * DESIRED_KEYS.size).to_h # => {:a=>0, :b=>0}
foo = {a: 1}
ZERO_HASH.merge(foo.slice(*DESIRED_KEYS))
# => {:a=>1, :b=>0}
All the methods, including * are part of Array or Hash.
How to use 0.0 instead of 0 is left as an exercise for the reader.
I think this should work in your case
Model.slice('k1','k2','k3','k4','k5').transform_values! { |v| v ? v : 0.0 }

Remove specific key set which is an array from a hash

I am trying to remove the elements in an array from a hash and the array forms part of the 'keys' in a hash. Here is an illustration.
hash = {'a' = 1, 'b' = 2, 'c' =3, 'd' = 4}
arr = ["a","d"] #Now I need to remove the elements from this array from the above hash
Resultant hash should be as below
new_hash = {'b' = 2,'c' =3}
This is what I tried unfortunately it doesn't seem to work
for i in 0..hash.length-1
arr.each do |key_to_del|
hash.delete key_to_del unless h.nil?
end
end
Your hash isn't correct format. It should be like this.
hash={"a"=>1, "b"=>2, "c"=>3, "d"=>4}
arr=["a","d"]
Solution 1
hash.reject! {|k, v| arr.include? k }
Solution 2
arr.each{|v| hash.delete(v)}

how do I invert my hash (switch key/value) and group by value

I have a hash in rails like so:
{"unique_id" => "1",
"unique_id2" => "2",
"unique_id3" => "n"}
Each unique key has a count that can be a number 1-20. What I would like to do is have a hash that looks like this:
{"1" => ["unique_id", "unique_id2"],
"2" => ["unique_id3"],
"3" => ["unique_id4", "unique_id5", "uniqueid6"]}
How would I go about doing that with a hash?
Not too hard!
hash = { "unique_id" => "1",
"unique_id2" => "2",
"unique_id3" => "n"
}
new_hash = hash.each_with_object({}) { |(k,v), h| (h[v] ||= []) << k }
each_with_object({}) is just an each loop with a blank hash
||= [] means if the hash doesn't have a value for v, set it equal to an empty array
<< k pushes the key onto the array
Try this:
h.group_by{|k,v| v }.each{|k,v| v.map!(&:first) }
group_by takes your hash and groups it by value
each iterates over the result hash
map! maps the first elements of the result value arrays, since group_by on a Hash returns two-dimensional Arrays with the structure [key, value]

Ruby mixed array to nested hash

I have a Ruby array whose elements alternate between Strings and Hashes. For example-
["1234", Hash#1, "5678", Hash#2]
I would like to create a nested hash structure from this. So,
hash["1234"]["key in hash#1"] = value
hash["5678"]["key in hash#2"] = value
Does anyone have/now a nice way of doing this? Thank you.
Simply use
hsh = Hash[*arr] #suppose arr is the array you have
It will slice 2 at a time and convert into hash.
I don't think there is a method on array to do this directly. The following code works and is quite easy to read.
hsh = {}
ary.each_slice(2) do |a, b|
hsh[a] = b
end
# Now `hsh` is as you want it to be
Guessing at what you want, since "key in hash#1" is not clear at all, nor have you defined what hash or value should be:
value = 42
h1 = {a:1}
h2 = {b:2}
a = ["1234",h1,"5678",h2]
a.each_slice(2).each{ |str,h| h[str] = value }
p h1, #=> {:a=>1, "1234"=>42}
h2 #=> {:b=>2, "5678"=>42}
Alternatively, perhaps you mean this:
h1 = {a:1}
h2 = {b:2}
a = ["1234",h1,"5678",h2]
hash = Hash[ a.each_slice(2).to_a ]
p hash #=> {"1234"=>{:a=>1}, "5678"=>{:b=>2}}
p hash["1234"][:a] #=> 1
let's guess, using facets just for fun:
require 'facets'
xs = ["1234", {:a => 1, :b => 2}, "5678", {:c => 3}]
xs.each_slice(2).mash.to_h
#=> {"1234"=>{:a=>1, :b=>2}, "5678"=>{:c=>3}}

Creating a new hash with default keys

I want to create a hash with an index that comes from an array.
ary = ["a", "b", "c"]
h = Hash.new(ary.each{|a| h[a] = 0})
My goal is to start with a hash like this:
h = {"a"=>0, "b"=>0, "c"=>0}
so that later when the hash has changed I can reset it with h.default
Unfortunately the way I'm setting up the hash is not working... any ideas?
You should instantiate your hash h first, and then fill it with the contents of the array:
h = {}
ary = ["a", "b", "c"]
ary.each{|a| h[a] = 0}
Use the default value feature for the hash
h = Hash.new(0)
h["a"] # => 0
In this approach, the key is not set.
h.key?("a") # => false
Other approach is to set the missing key when accessed.
h = Hash.new {|h, k| h[k] = 0}
h["a"] # => 0
h.key?("a") # => true
Even in this approach, the operations like key? will fail if you haven't accessed the key before.
h.key?("b") # => false
h["b"] # => 0
h.key?("b") # => true
You can always resort to brute force, which has the least boundary conditions.
h = Hash.new.tap {|h| ["a", "b", "c"].each{|k| h[k] = 0}}
h.key?("b") # => true
h["b"] # => 0
You can do it like this where you expand a list into zero-initialized values:
list = %w[ a b c ]
hash = Hash[list.collect { |i| [ i, 0 ] }]
You can also make a Hash that simply has a default value of 0 for any given key:
hash = Hash.new { |h, k| h[k] = 0 }
Any new key referenced will be pre-initialized to the default value and this will avoid having to initialize the whole hash.
This may not be the most efficient way, but I always appreciate one-liners that reveal a little more about Ruby's versatility:
h = Hash[['a', 'b', 'c'].collect { |v| [v, 0] }]
Or another one-liner that does the same thing:
h = ['a', 'b', 'c'].inject({}) {|h, v| h[v] = 0; h }
By the way, from a performance standpoint, the one-liners run about 80% of the speed of:
h = {}
ary = ['a','b','c']
ary.each { |a| h[a]=0 }
Rails 6 added index_with on Enumerable module. This will help in creating a hash from an enumerator with default or fetched values.
ary = %w[a b c]
hash = ary.index_with(0) # => {"a"=>0, "b"=>0, "c"=>0}
Another option is to use the Enum#inject method which I'm a fan of for its cleanliness. I haven't benchmarked it compared to the other options though.
h = ary.inject({}) {|hash, key| hash[key] = 0; hash}
Alternate way of having a hash with the keys actually added
Hash[[:a, :b, :c].zip([])] # => {:a=>nil, :b=>nil, :c=>nil}
Hash[[:a, :b, :c].zip(Array.new(3, 0))] # => {:a=>0, :b=>0, :c=>0}

Resources