Get single hash value inside array of hashes - ruby

I have an array of hashes:
array = [
{
a: 1,
b: 2,
c: [3]
},
{
a: 1,
b: 2,
c: [3, 4, 5]
},
]
and would like to target the value 3 inside the array value for the c: key in the first hash. I assume something would be added to array[0] to capture that specific value.

In the Hash, c: [3] is syntax sugar for :c => [3]. So, to access the value 3:
array[0][:c][0]
#=> 3

Related

Ruby: returning a hash of key value pairs up to a given length of key value pairs

Given a hash of arbitrary length, how do i return only a number of key value pairs (as a new hash) up to a certain point given as an integer?
For example:
hash = {a: 6, b: 2, c: 1, d: 5, e: 3, f: 4}
desiredlength = 3
desiredoutput = hash = {a: 6, b: 2, c: 1}
(In my example I deliberately gave the hash values as random numbers, since I'd like a method that does not rely on the content of the values like .sort, just the order in which they appear in the hash)
This seems like it should be simple but I haven't found a good method yet.
I did come up with this method:
Hash[hash.to_a[0, desiredlength]]
This takes the original hash, turns it into an array, and converts the range of 0 to desiredlength back into a hash
... but it feels super clunky and rubocop doesn't like it
As with many things in ruby, There's More Than One Way To Do It.
Here's one:
hash = {a: 6, b: 2, c: 1, d: 5, e: 3, f: 4}
hash.first(3).to_h
#=> {:a=>6, :b=>2, :c=>1}
# Or, similarly:
Hash[hash.first(3)]
See: Enumberable#first.
hash = {a: 6, b: 2, c: 1, d: 5, e: 3, f: 4}
desiredlength = 3
hash.select { (desiredlength -= 1) >= 0 }
#=> {a: 6, b: 2, c: 1 }
See Hash#select.
Another way:
hash.slice(*hash.keys.first(desiredlength))
#=> {a: 6, b: 2, c: 1 }
See Hash#slice.
Here is another way it can be done:
hash = {a: 6, b: 2, c: 1, d: 5, e: 3, f: 4}
desiredlength = 3
hash.take(desiredlength).to_h

Join an array with a block natively

Is there a native way to join all elements of an array into a unique element like so:
[
{a: "a"},
{b: "b"}
].join do | x, y |
x.merge(y)
end
To output something like:
{
a: "a",
b: "b"
}
The fact that I used hashes into my array is an example, I could say:
[
0,
1,
2,
3
].join do | x, y |
x + y
end
Ends up with 6 as a value.
Enumerable#inject covers both of these cases:
a = [{a: "a"}, {b: "b"}]
a.inject(:merge) #=> {:a=>"a", :b=>"b"}
b = [0, 1, 2, 3]
b.inject(:+) #=> 6
inject "sums" an array using the provided method. In the first case, the "addition" of the sum and the current element is done by merging, and in the second case, through addition.
If the array is empty, inject returns nil. To make it return something else, specify an initial value (thanks #Hellfar):
[].inject(0, :+) #=> 0
[
{a: "a"},
{b: "b"}
].inject({}){|sum, e| sum.merge e}

Hash of hashes from an array

From an array:
this = [1, 2, 3, 4, 5]
I am trying to create a hash of hashes:
{{num: 1}, {num: 2}, {num: 3}, {num: 4}, {num: 5}}
But I'm getting an empty hash:
Hash.new(this.each do |num| Hash.new(num: num) end)
# => {}
What am I doing wrong?
First, your desired result in your question doesn't make sense since you're using the Hash {} syntax, but there are no keys. It seems as though you want your result to be an array of hashes.
Second, you're confusing each with map. each simply iterates through an array, passing each item to the block. The return value of arr.each is just arr. map, on the other hand, returns a new array based on the return value of the block:
[1, 2, 3, 4, 5].map { |item| { num: item } }
You are setting the default value (furthermore with a block that does not do anything meaningful) without setting any key-value pairs.

How to merge two hashes that have same keys in ruby

I have a two hashes that should have same keys like:
a = {a: 1, b: 2, c: 3}
b = {a: 2, b: 3, c: 4}
And I want to sum up each values like this:
if a.keys == b.keys
a.values.zip(b.values).map{|a, b| a+b}
end
But this code doesn't work if the order of keys are different like b = {a: 2, c: 4, b: 3}.
How can I write the code taking into account about order of keys?
Use Hash#merge or Hash#merge!:
a = {a: 1, b: 2, c: 3}
b = {a: 2, c: 4, b: 3}
a.merge!(b) { |k, o, n| o + n }
a # => {:a=>3, :b=>5, :c=>7}
The block is called with key, old value, new value. And the return value of the block is used as a new value.
If you're using Active Support (Rails), which adds Hash#transform_values, I really like this easy-to-read solution when you have n hashes:
hashes = [hash_1, hash_2, hash_3] # any number of hashes
hashes.flat_map(&:to_a).group_by(&:first).transform_values { |x| x.sum(&:last) }

why Hash value gets affected in Ruby

mainhash = { 'A' => [ 0,1,2,3,4 ] , 'B' => [ 0 ,1,2 ,3 ] }
ahash = mainhash['A']
indval = ahash.shift
ahash become as follows
[1, 2, 3, 4]
and mainhash become as follows
{"A"=>[1, 2, 3, 4], "B"=>[0, 1, 2, 3]}
I am manipulating ahash variable by shifting some values from ahash, When I do this operation it affects the mainhash value. Why it is happening?
Am I missing any conceptual understanding?
It's because ahash and mainhash both have references to the same Array instance. If you modify this through ahash, referenced object is being modified, so no wonder it changes also in mainhash.
To operate on copy (shallow copy, to be precise) of the object instead of the same object, you should use dup method:
ahash = mainhash['A'].dup
Look Array#shift
Removes the first element of self and returns it (shifting all other elements down by one). Returns nil if the array is empty.
mainhash = { 'A' => [ 0,1,2,3,4 ] , 'B' => [ 0 ,1,2 ,3 ] }
ahash = mainhash['A']
p ahash.object_id # => 8577888
p mainhash['A'].object_id # => 8577888
p indval = ahash.shift # => 0
As above seen, ahash and mainhash['A'] refer to the same Array object [ 0,1,2,3,4], thus changing ahash#shift causes 0 to be removed from ahash which also causes 0 to be removed from mainhash['A'].
Said that your Hash becomes as below :
mainhash
# => {"A"=>[1, 2, 3, 4], "B"=>[0, 1, 2, 3]}
All operations are legitimate and happened as documented to the link,I have given above.
How can I avoid affecting the mainhash
As #Marek Lipka said :
you should use dup method: ahash = mainhash['A'].dup.
mainhash = { 'A' => [ 0,1,2,3,4 ] , 'B' => [ 0 ,1,2 ,3 ] }
ahash = mainhash['A'].dup
ahash.object_id # => 8577516
mainhash['A'].object_id # => 8577600
indval = ahash.shift # => 0
ahash # => [1, 2, 3, 4]
mainhash['A'] # => [0, 1, 2, 3, 4]
Why it is happening ?.
arr = [1, 2, 3]
x = arr
arr.shift
p arr
p x
--output:--
[2, 3]
[2, 3]
arr and x both refer to the same array. Assignment ('=') does not create a copy.
Now look at this code:
arr = [1, 2, 3]
x = arr.dup
arr.shift
p arr
p x
--output:--
[2, 3]
[1, 2, 3]
And by the way, the name 'ahash' is a terrible name for an array.

Resources