What's the difference `{'x'=> 3}` and `{x: 3}`? - ruby

I have this:
a = {'x' => 3}
b = {'x': 3}
c = {x: 3}
d = {:x => 3}
e = {:'x' => 3}
So, I have that b = c = d = e = {:x => 3}, meanwhile a = {"x" => 3} but a.class == b.class.
I don't understand what the difference is between a and the rest of variables.

In b,c,d, and e, the key is a Symbol.
In a, the key is a String.
a = { 'x' => 3 } #=> { "x" => 3 }
b = { 'x': 3 } #=> { :x => 3 }
c = { x: 3 } #=> { :x => 3 }
d = { :x => 3 } #=> { :x => 3 }
e = { :'x' => 3 } #=> { :x => 3 }

Your variable a hash has "x" key as a string, while other variables have that key as symbol.
Calling class on an object in Ruby returns its class, in your example it is Hash. In other words, the constructor of all hash instances, such as {x: 3} is Hash object.

There is a significant difference between String and Symbol classes in ruby:
explanation on SO;
good article on the topic.
By convention, all but the very first hash notations cast keys to the Symbol instance, while the first one uses the key (String instance in this particular case) as is. (To be more precise: b and c cast key to the Symbol instance, d and e do not cast anything, but keys given in these cases are Symbol instances already.)
Since ('x' == :x) == false, a hash differs from the latters.

Related

hash.merge with block to add up sub-hash keys?

I have read something about hash merge with a block and this is working fine for simple, non-nested hashes in plain ruby. The following code results in {1=>2, 2=>4, 4=>6} as expected:
a = {1 => 1, 2 => 2, 4 => 3}
b = {1 => 1, 2 => 2, 4 => 3}
a.merge(b) { |key, value_a, value_b | value_a + value_b }
But the merge is not working for a nested hash structure, I get a NoMethodError (undefined method '+' for {1=>1, 2=>2}:Hash)
a = { "2018" => {1 => 1, 2 => 2, 4 => 3} }
b = { "2019" => {1 => 1, 2 => 2, 4 => 3} }
c = a.merge(b) { |key, value_a, value_b | value_a + value_b }
I have read about each_with_object and I am unsure how to use it. Is there a smart way to accomplish the merge of the values of the sub-hash? What do you think is the easiest way?
You can use Hash#deep_merge from active support to do this.
require 'active_support/all'
a = { k1: { k2: 1 } }
b = { k1: { k2: 2 } }
a.deep_merge(b) { |k, v1, v2| v1 + v2 }
# => { l1: { k2: 3 } }
Nested Hash, nested Hash#merge?
I changed the key of b to "2018"
a = { "2018" => {1 => 1, 2 => 2, 4 => 3} }
b = { "2018" => {1 => 1, 2 => 2, 4 => 3} }
c = a.merge(b) { |k, v1, v2| v1.merge(v2) { |kk, aa, bb | aa + bb } }
#=> {"2018"=>{1=>2, 2=>4, 4=>6}}
For your original values:
a = { "2018" => {1 => 1, 2 => 2, 4 => 3} }
b = { "2019" => {1 => 1, 2 => 2, 4 => 3} }
The result is
#=> {"2018"=>{1=>1, 2=>2, 4=>3}, "2019"=>{1=>1, 2=>2, 4=>3}}

Combine hash keys and values if keys are identical except for case

Let's say I have a Ruby Hash where at least two keys are identical, except for their case, for instance:
{ 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
Is there a way I can combine like keys (except for their case) such that the resulting Hash might look like this?
{ 'foo' => 3, 'bar' => 3 }
Thank you!
You can build a new hash:
new_hash = Hash.new(0)
old_hash.each_pair { |k, v| new_hash[k.downcase] += v }
You can use inject to loop all the hash items and build a new hash.
hash = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
hash.inject({}) do |result, (key, value)|
key = key.downcase
result[key] = result[key] ? result[key] + value : value
result
end
Here is one more way of doing this
h = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
p h.collect{|k, v| {k.downcase => v}}.reduce { |a, v| a.update(v) {|k, o, n| o + n } }
#=> {"foo"=>3, "bar"=>3}
h = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
h.each_with_object({}) { |(k,v),g| g[k.downcase] = g[k.downcase].to_i + v }
#=> {"foo"=>3, "bar"=>3}
This makes use of the fact that if g does not have a key e, g[e] on the right side will equal nil and nil.to_i #=> 0. On the other hand, if g has a key e, h[e].to_i will equal h[e].
Another way:
h.each_with_object({}) { |(k,v),g| g.update(k.downcase=>v) { |_,o,v| o+v } }
#=> {"foo"=>3, "bar"=>3}

ruby use array tvalues to index nested hash of hash [duplicate]

This question already has answers here:
What is the most ruby-ish way of accessing nested hash values at arbitrary depths? [duplicate]
(4 answers)
How to avoid NoMethodError for missing elements in nested hashes, without repeated nil checks?
(16 answers)
Closed 7 years ago.
In Ruby, I want to do something like this,
I have a hash of hash built like this.
h = {1 => {2 => {3 => "three"}},'a' => { 'b' => { 'c' => "basd"}}}
=> {"a"=>{"b"=>{"c"=>"basd"}}, 1=>{2=>{3=>"three"}}}
If I have an array with values like this.
a = [1, 2, 3]
I want to have a method which will use the array values to index nested keys in my hash and return the value pointed by last key (as guided by previous array/keys)
for eg.
getHashValue([1,2,3]) should return "three" => h[1][2][3]
if a = ['a','b', 'c'] then the return value should be basd.
How to get this done?
And then there's:
keys.inject(hash, :fetch)
or for earlier Ruby versions:
keys.inject(hash) {|h, k| h[k]}
If you did want to use recursion, a more Rubyesque approach would be:
def get_value(obj, keys)
keys.empty? ? obj : get_value(obj[keys[0]], keys[1..-1])
end
Simple recursion
def getValue(hash, keys, i = 0)
if i + 1 < keys.length
getValue(hash[keys[i]], keys, i + 1)
else
hash[keys[i]]
end
end
getValue(h, [1,2,3]) => "three"
getValue(h, ['a','b','c']) => "basd"
Ruby 2.3.0 introduced a new method called dig on both Hash and Array that solves this problem entirely.
It returns nil if an element is missing at any level of nesting.
h1 = {}
h2 = { a: {} }
h3 = { a: { b: {} } }
h4 = { a: { b: { c: 100 } } }
h1.dig(:a, :b, :c) # => nil
h2.dig(:a, :b, :c) # => nil
h3.dig(:a, :b, :c) # => nil
h4.dig(:a, :b, :c) # => 100
h = {1 => {2 => {3 => "three"}},'a' => { 'b' => { 'c' => "basd"}}}
a = ['a','b', 'c']
a.inject(h, :[]) # => "basd"
h = {1 => {2 => {3 => "three"}},'a' => { 'b' => { 'c' => "basd"}}}
a = [1, 2, 3]
a.inject(h, :[]) # => "three"

Do average on hashes

lets say i got 3 hashes:
hash1={
a => 1,
b => 2,
c => 4
}
hash2={
b => 1,
c => 3
}
hash3={
a => 2,
b => 1,
c => 3,
d => 4
}
I want to average stuff to a new hash according to keys, so new hash will be
result={a=>1.5,b=>4/3,c=>10/3,d=>4}
meaning if key not exists in one hash we don't count it as 0.
is there an elegant way?
result = {}
[hash1, hash2, hash3].each{|h| h.each{|k, v| (result[k] ||= []).push(v)}}
result.each{|k, v| result[k] = v.inject(:+).to_f/v.length}
result # =>
# {
# a => 1.5,
# b => 1.3333333333333333,
# c => 3.3333333333333335,
# d => 4.0
# }

can't convert Array into Integer

I'm trying to iterate through an array, #chem_species = ["H2", "S", "O4"] and multiply a constant times the amount of constants present: H = 1.01 * 2, S = 32.1 * 1 and so on. The constants are of course defined within the class, before the instance method.
The code I've constructed to do this does not function:
def fw
x = #chem_species.map { |chem| chem.scan(/[A-Z]/)}
y = #chem_species.map { |chem| chem.scan({/\d+/)}
#mm = x[0] * y[0]
end
yields -> TypeError: can't convert Array into Integer
Any suggestions on how to better code this? Thank you for your insight in advance.
How about doing it all in one scan & map? The String#scan method always returns an array of the strings it matched. Look at this:
irb> "H2".scan /[A-Z]+|\d+/i
=> ["H", "2"]
So just apply that to all of your #chem_species using map:
irb> #chem_species.map! { |chem| chem.scan /[A-Z]+|\d+/i }
=> [["H", "2"], ["S"], ["O", "4"]]
OK, now map over #chem_species, converting each element symbol to the value of its constant, and each coefficient to an integer:
irb> H = 1.01
irb> S = 32.01
irb> O = 15.99
irb> #chem_species.map { |(elem, coeff)| self.class.const_get(elem) * (coeff || 1).to_i }
=> [2.02, 32.01, 63.96]
There's your molar masses!
By the way, I suggest you look up the molar masses in a single hash constant instead of multiple constants for each element. Like this:
MASSES = { :H => 1.01, :S => 32.01, :O => 15.99 }
Then that last map would go like:
#chem_species.map { |(elem, coeff)| MASSES[elem.to_sym] * (coeff || 1).to_i }
You have a syntax error in your code: Maybe it should be:
def fw
x = #chem_species.map { |chem| chem.scan(/[A-Z]/)}
y = #chem_species.map { |chem| chem.scan(/\d+/)}
#mm = x[0] * y[0]
end
Have you looked at the output of #chem_species.map { |chem| chem.scan(/[A-Z]/)} (or the second one for that matter)? It's giving you an array of arrays, so if you really wanted to stick with this approach you'd have to do x[0][0].
Instead of mapping, do each
#chem_species.each { |c| c.scan(/[A-Z]/) }
Edit: just realized that that didn't work at all how I had thought it did, my apologies on a silly answer :P
Here's a way to multiply the values once you have them. The * operator won't work on arrays.
x = [ 4, 5, 6 ]
y = [ 7, 8, 9 ]
res = []
x.zip(y) { |a,b| res.push(a*b) }
res.inject(0) { |sum, v| sum += v}
# sum => 122
Or, cutting out the middle man:
x = [ 4, 5, 6 ]
y = [ 7, 8, 9 ]
res = 0
x.zip(y) { |a,b| res += (a*b) }
# res => 122
(one-liners alert, off-topic alert)
you can parse the formula directly:
"H2SO4".scan(/([A-Z][a-z]*)(\d*)/)
# -> [["H", "2"], ["S", ""], ["O", "4"]]
calculate partial sums:
aw = { 'H' => 1.01, 'S' => 32.07, 'O' => 16.00 }
"H2SO4".scan(/([A-Z][a-z]*)(\d*)/).collect{|e,x| aw[e] * (x==""?1:x).to_i}
# -> [2.02, 32.07, 64.0]
total sum:
"H2SO4".scan(/([A-Z][a-z]*)(\d*)/).collect{|e,x| aw[e] * (x==""?1:x).to_i}.inject{|s,x| s+x}
# -> 98.09

Resources