Ruby - Merge an Array into a Hash - ruby

I have an array which looks like this:
array = [[:foo, :bar], [:foo, :baz], [:baz, {a: 1, b: 2}], [:baz, {c: 1, d:2}]]
and I need to turn it into a hash which looks like this:
{:foo =>[:bar, :baz], :baz => {a: 1, b: 2, c: 1, d: 2}}
This is the code I have so far:
def flatten(array)
h = {}
array.each_with_object({}) do |(k, v), memo|
if v.is_a?(Hash)
memo[k] = h.merge!(v)
else
# What goes here?
end
end
end
When used like so:
flatten(array)
outputs:
{baz => {:a => 1, :b => 2, :c => 1, :d => 2}}
May someone please point me in the right direction? Help appreciated.

def convert(arr)
arr.each_with_object({}) do |a,h|
h[a.first] =
case a.last
when Hash
(h[a.first] || {}).update(a.last)
else
(h[a.first] || []) << a.last
end
end
end
convert array
#=> {:foo=>[:bar, :baz], :baz=>{:a=>1, :b=>2, :c=>1, :d=>2}}

Hash[ array.group_by(&:first).map{ |k,a| [k,a.map(&:last)] } ]

Here is my attempt at solving this problem. I have to make assumption that in the input array, entries like the ones similar to :baz will always be paired with Hash objects. The solution will not work if you have one :baz with a symbol and another with hash.
array = [[:foo, :bar], [:foo, :baz], [:baz, {a: 1, b: 2}], [:baz, {c: 1, d:2}]]
h = Hash.new
array.each do |n1, n2|
if n2.class == Hash
h[n1] = (h[n1] || {}).merge(n2)
else
h[n1] = (h[n1] || []) << n2
end
end
p h
Output
{:foo=>[:bar, :baz], :baz=>{:a=>1, :b=>2, :c=>1, :d=>2}}
[Finished in 0.1s]

Related

Easily map over Hash like Array#map

Sometimes I want to map over a collection.
If it's an array it's easy:
foo = [1,2,3]
foo.map {|v| v + 1}
#=> [2, 3, 4]
But a hash doesn't work the same way:
bar = {a: 1, b: 2, c: 3}
bar.map{|k,v| v+1}
#=> [2, 3, 4]
What I'd really like is something like:
bar = {a: 1, b: 2, c: 3}
bar.baz{|k,v| v+1}
#=> {:a=>2, :b=>3, :c=>4}
where Hash#baz is some method. Is there an easy way to get a "map-like" experience for a hash?
In Ruby 2.4 you can use the built-in Hash#transform_values:
bar = {a: 1, b: 2, c: 3}
# => {:a=>1, :b=>2, :c=>3}
bar.transform_values {|v| v+1 }
# => {:a=>2, :b=>3, :c=>4}
Just to point out the obvious and most common solution to address this need:
bar = {a: 1, b: 2, c: 3}
# => {:a=>1, :b=>2, :c=>3}
bar.map { |k, v| [k, v + 1] }.to_h
# => {:a=>2, :b=>3, :c=>4}
From the ruby-forum, you can use Hash#merge to merge the hash with itself:
bar = {a: 1, b: 2, c: 3}
#=> {:a=>1, :b=>2, :c=>3}
bar.merge(bar){|k,v| v+1}
#=> {:a=>2, :b=>3, :c=>4}
One of the great things about Ruby is if you don't like what's in the Ruby core you can always go and extend it to fill in the missing pieces:
class Hash
def map_values
map do |k,v|
[ k, yield(k, v) ]
end.to_h
end
end
Which gives you the thing you wanted:
bar = {a: 1, b: 2, c: 3}
# => {:a=>1, :b=>2, :c=>3}
bar.map_values{ |k,v| v+1 }
# => {:a=>2, :b=>3, :c=>4}
I'm surprised this hasn't been introduced into core Ruby, but it might be in the future.
Update: As Eric points out, transform_values is now in Ruby 2.4.0. This is also in ActiveSupport if you're using Rails 4.2 or later.
bar = {a: 1, b: 2, c: 3}
# => {:a=>1, :b=>2, :c=>3}
bar.transform_values{ |v| v+1 }
# => {:a=>2, :b=>3, :c=>4}
bar = {a: 1, b: 2, c: 3}
bar.merge(bar) { |*,v| v+1 }
#=> {:a=>2, :b=>3, :c=>4}
This uses the form of Hash.merge that employs a block to return the values of keys that are present in both hashes being merged, which here is all keys.
Another way:
bar.keys.each { |k| bar[k] += 1 }
bar
#=> {:a=>2, :b=>3, :c=>4}
which can be written in one line using Object#tap:
bar.tap { |h| h.keys.each { |k| h[k] += 1 } }
#=> {:a=>2, :b=>3, :c=>4}

Get key/value pairs as arrays

I've been struggling with this: Complete the keysAndValues function so that it takes in an object and returns the keys and values as separate arrays.
I've added several versions so you can see how i'm thinking through the problem.
def keysAndValues(data)
data.each do |data|
data.split(key.to_a, value.to_a)
end
data
end
keysAndValues ({a: 1, b: 2, c: 3})
def keysAndValues(data)
data.each do |data|
data.split([key], [value])
end
data
end
keysAndValues ({a: 1, b: 2, c: 3})
def keysAndValues(data)
data.each do |data|
data.slice([key], [value])
end
data
end
keysAndValues ({a: 1, b: 2, c: 3})
def keysAndValues(data)
data.each do |data|
data.slice_to.a(2)([ :a ], [ ' ' ])
end
data
end
keysAndValues ({a: 1, b: 2, c: 3})
def keysAndValues(data)
data.each.slice_to.a(2) { |x, y| [x], [y] }
end
data
end
keysAndValues ({a: 1, b: 2, c: 3})
def keysAndValues(data)
[data.keys, data.values]
end
keys, values = keysAndValues({a: 1, b: 2, c: 3})
keys
# => [:a, :b, :c]
values
# => [1, 2, 3]
Please note that keysAndValues is not following the Ruby naming convention. The correct name should be keys_and_values.
There are built-in methods for that:
irb(main):001:0> {a: 1, b: 2, c: 3}.keys
=> [:a, :b, :c]
irb(main):002:0> {a: 1, b: 2, c: 3}.values
=> [1, 2, 3]
Or, maybe you want this? (Sorry, your question is not clear to me)
irb(main):003:0> Array({a: 1, b: 2, c: 3})
=> [[:a, 1], [:b, 2], [:c, 3]]
def keys_and_values(data)
return data.keys, data.values
end
thus:
data_hash = {a: 1, b: 2, c: 3, d: 4}
keys_and_values(data_hash)
returns:
[[a:, b:, c:, d:], [1, 2, 3, 4]]

ruby: remove a value in an array that is in a hash

I have a hash like this:
a = { a: 1, b: 2, c: [9, 8, 7]}
I need to write a method that given a pair key and value, removes the occurrences of such couple from the hash.
for example, if I pass the couple (:a, 1) I obtain the hash:
a = { b: 2, c: [9, 8, 7]}
if I pass the couple (:c, 8) I obtain the hash:
a = { a: 1, b: 2, c: [9, 7]}
if I pass the couple (:a, 3) I obtain the (unchanged) hash:
a = { a: 1, b: 2, c: [9, 8, 7]}
I'm not sure how to do this, here's what I got so far:
def remove_criterion (key, value)
all_params = params.slice(key)
if all_params[key].class == Array
else
params.except(key)
end
end
which obviously is incomplete.
thanks for any help,
Here's one solution:
def remove_criterion key, value
params.each_with_object({}) do |pair, h|
k, v = *pair
if k == key
case v
when Array
nv = v.reject { |each| each == value }
h[k] = nv unless nv.empty?
else
h[k] = v unless v == value
end
else
h[k] = v
end
end
end
Testing it out in irb:
irb(main):007:0> remove_criterion :a, 1
=> {:b=>2, :c=>[9, 8, 7]}
irb(main):008:0> remove_criterion :c, 8
=> {:a=>1, :b=>2, :c=>[9, 7]}
irb(main):009:0> remove_criterion :a, 3
=> {:a=>1, :b=>2, :c=>[9, 8, 7]}
def remove_criterion(key, value)
params.each do |k,v|
if k == key and v == value
params.delete(key)
elsif v.class == Array and v.include?(value)
v.delete(value)
end
end
params
end
I'd do it like this:
def doit(h,k,v)
return h unless h.include?(k)
if h[k] == v
h.delete(k)
elsif h[k].is_a? Array
h[k].delete(v)
end
h
end
h = {a: 1, b: 2, c: [9, 8, 7]}
doit(h,:b,2) # => {:a=>1, :c=>[9, 8, 7]}
doit(h,:b,3) # => {:a=>1, :b=>2, :c=>[9, 8, 7]}
doit(h,:c,8) # => {:a=>1, :b=>2, :c=>[9, 7]}
doit(h,:c,6) # => {:a=>1, :b=>2, :c=>[9, 8, 7]}
doit(h,:d,1) # => {:a=>1, :b=>2, :c=>[9, 8, 7]}

Ruby creating Hash custom invert function in Ruby

Ruby class Hash has method "invert" which make "reversal" between keys and values and delete same keys (in our case its: "1=>:a").
h = {a: 1, b: 2, c: 1}
=> {:a=>1, :b=>2, :c=>1}
h.invert
=> {1=>:c, 2=>:b}
How implement custom Hash method "c_invert", which will return very first (not last) pair of duplicated key => value? Exapmle:
> h = {a: 1, b: 2, c: 1}
=> {:a=>1, :b=>2, :c=>1}
> h.c_invert
=> {1=>:a, 2=>:b}
class Hash
def c_invert
Hash[to_a.reverse].invert
end
end
or
class Hash
def c_invert
Hash[to_a.reverse.map(&:reverse)]
end
end
h = {:d =>1,:a=>1, :b=> 2, :c=>1}
Hash[h.map(&:reverse).reverse]
# => {1=>:d, 2=>:b}
h = {a: 1, b: 2, c: 1}
Hash[h.map(&:reverse).reverse]
# => {1=>:a, 2=>:b}

How do I map an array of hashes?

I have an array of hashes:
arr = [ {:a => 1, :b => 2}, {:a => 3, :b => 4} ]
What I want to achieve is:
arr.map{|x| x[:a]}.reduce(:+)
but I think it's a bit ugly, or at least not that elegant as:
arr.map(&:a).reduce(:+)
The later one is wrong because there is no method called a in the hashes.
Are there any better ways to write map{|x| x[:a]}?
You could make actual Objects, possibly with a Struct:
MyClass = Struct.new :a, :b
arr = [MyClass.new(1, 2), MyClass.new(3, 4)]
arr.map(&:a).reduce(:+) #=> 4
Or for more flexibility, an OpenStruct:
require 'ostruct'
arr = [OpenStruct.new(a: 1, b: 2), OpenStruct.new(a: 3, b: 4)]
arr.map(&:a).reduce(:+) #=> 4
Of course either of these can be constructed from existing hashes:
arr = [{ :a => 1, :b => 2 }, { :a => 3, :b => 4 }]
ss = arr.map { |h| h.values_at :a, :b }.map { |attrs| MyClass.new(*attrs) }
ss.map(&:a).reduce(:+) #=> 4
oss = arr.map { |attrs| OpenStruct.new attrs }
oss.map(&:a).reduce(:+) #=> 4
Or, for a more creative, functional approach:
def hash_accessor attr; ->(hash) { hash[attr] }; end
arr = [{ :a => 1, :b => 2 }, { :a => 3, :b => 4 }]
arr.map(&hash_accessor(:a)).reduce(:+) #=> 4
It is unclear what you mean as "better" and why you think the correct version is ugly.
Do you like this "better"?
arr.inject(0) { |sum, h| sum + h[:a] }
There's a way, extending the Symbol.
lib/core_extensions/symbol.rb (credit goes here)
# frozen_string_literal: true
class Symbol
def with(*args, &)
->(caller, *rest) { caller.send(self, *rest, *args, &) }
end
end
Then, given:
arr = [ {:a => 1, :b => 2}, {:a => 3, :b => 4} ]
you can do this:
arr.map(&:[].with(:a)).reduce(:+)
Explanation: to access hash value under any key, you call Hash#[] method. When passed as a :[] (extended) symbol to the Array#map, you can then call .with(*args) on this symbol, effectively passing the parameter (hash key) down to the :[] method. Enjoy.

Resources