For example, I have array of single hashes
a = [{a: :b}, {c: :d}]
What is best way to convert it into this?
{a: :b, c: :d}
You may use
a.reduce Hash.new, :merge
which directly yields
{:a=>:b, :c=>:d}
Note that in case of collisions the order is important. Latter hashes override previous mappings, see e.g.:
[{a: :b}, {c: :d}, {e: :f, a: :g}].reduce Hash.new, :merge # {:a=>:g, :c=>:d, :e=>:f}
You can use .inject:
a.inject(:merge)
#=> {:a=>:b, :c=>:d}
Demonstration
Which initiates a new hash on each iteration from the two merged. To avoid this, you can use destructive :merge!( or :update, which is the same):
a.inject(:merge!)
#=> {:a=>:b, :c=>:d}
Demonstration
These two are equivalent (reduce/inject are the same method):
total_hash = hs.reduce({}) { |acc_hash, hash| acc_hash.merge(hash) }
total_hash = hs.reduce({}, :merge)
Note that Hash#merge creates a new hash on each iteration, which may be a problem if you are building a big one. In that case, use update instead:
total_hash = hs.reduce({}, :update)
Alternatively, you can convert the hashes to pairs and then build the final hash:
total_hash = hs.flat_map(&:to_a).to_h
I came across this answer and I wanted to compare the two options in terms of performance to see which one is better:
a.reduce Hash.new, :merge
a.inject(:merge)
using the ruby benchmark module, it turns out that option (2) a.inject(:merge) is faster.
code used for comparison:
require 'benchmark'
input = [{b: "c"}, {e: "f"}, {h: "i"}, {k: "l"}]
n = 50_000
Benchmark.bm do |benchmark|
benchmark.report("reduce") do
n.times do
input.reduce Hash.new, :merge
end
end
benchmark.report("inject") do
n.times do
input.inject(:merge)
end
end
end
the results were
user system total real
reduce 0.125098 0.003690 0.128788 ( 0.129617)
inject 0.078262 0.001439 0.079701 ( 0.080383)
Just use
a.reduce(:merge)
#=> {:a=>:b, :c=>:d}
Try this
a.inject({}){|acc, hash| acc.merge(hash)} #=> {:a=>:b, :c=>:d}
You can transform it to array [[:a, :b]] and after that translate everything to hash {:a=>:b}
# it works like [[:a, :b]].to_h => {:a=>:b}
[{a: :b}, {c: :d}].map { |hash| hash.to_a.flatten }.to_h
# => {:a=>:b, :c=>:d}
Related
I want to convert:
[:one, :two, :three]
to:
{one: :one, two: :two, three: three}
So far I'm using this:
Hash[[:basic, :silver, :gold, :platinum].map { |e| [e, e] }]
But I would like to know if it's possible by some other way?
This is to use in a Rails enum definition in model, to save values as strings in db.
Array#zip:
a = [:one, :two, :three]
a.zip(a).to_h
#=> {:one=>:one, :two=>:two, :three=>:three}
Array#transpose:
[a, a].transpose.to_h
#=> {:one=>:one, :two=>:two, :three=>:three}
I admit to a hangup: given the choice, I prefer constructing hashes from scratch rather than creating an array and converting it to a hash.
[:one, :two, :three].each_with_object({}) { |e,h| h[e]=e }
#=> {:one=>:one, :two=>:two, :three=>:three}
Here is another way with map:
>> [:one, :two, :three].map { |x| [x,x] }.to_h
=> {:one=>:one, :two=>:two, :three=>:three}
I'm looking for a simple way to copy/reduce a hash, but only include the keys/values specified in an array of keys.
original_hash = { one: 1, two: 'too', three: 3 }
wanted_keys = [:one, :three]
new_hash = # do something with the hash
expect(new_hash).to eq({ one: 1, three: 3 })
If the hash has a very large number of keys and/or the array of wanted keys is very large (improbable as that may be),
original_hash.select { |k, v| wanted_keys.include?(k) }
would be relatively inefficient because a linear search of wanted_keys is required for each of original_hash's keys. Here are two ways to speed things up. (#Lucas' solution is a third way.)
Convert wanted_keys to a set
require 'set'
wanted_keys_set = wanted_keys.to_set
original_hash.select { |k, v| wanted_keys_set.include?(k) }
#=> {:one=>1, :three=>3}
Match wanted_keys with the values of those keys in original_hash and then convert the resulting array to a hash
wanted_keys.zip(original_hash.values_at(*wanted_keys)).to_h
#=> {:one=>1, :three=>3}
Prior to Ruby v2.0, when Array#to_h made its debut, this would be written
Hash[wanted_keys.zip(original_hash.values_at(*wanted_keys))]
this is for your spec passing :)
original_hash.slice(*wanted_keys)
If you happen to be using Rails, you could use Hash#slice
require "active_support/core_ext/hash"
original_hash = { one: 1, two: 'too', three: 3 }
wanted_keys = [:one, :three]
new_hash = original_hash.slice *wanted_keys
#=> {:one=>1, :three=>3}
Implementation of Hash#slice method is present in Active support core extensions code rails/activesupport/lib/active_support/core_ext/hash/slice.rb
if you dont wanna to iterate whole Hash, you can use each_with_object
original_hash = {one: 1, two: 'too', three: 3}
wanted_keys = [:one, :three]
# iterate only array of keys
new_hash = wanted_keys.each_with_object({}) do |key, exp|
exp[key] = original_hash[key] if original_hash[key]
end
You could try something like (inefficient solution below)
original_hash.select{|k,v| wanted_keys.include? k }
I'm not entirely up on my Ruby-foo so I'm not sure if this returns a list or a Hash.
This question already has answers here:
Turning a Hash of Arrays into an Array of Hashes in Ruby
(7 answers)
Closed 9 years ago.
Summary
Given a Hash where some of the values are arrays, how can I get an array of hashes for all possible combinations?
Test Case
options = { a:[1,2], b:[3,4], c:5 }
p options.self_product
#=> [{:a=>1, :b=>3, :c=>5},
#=> {:a=>1, :b=>4, :c=>5},
#=> {:a=>2, :b=>3, :c=>5},
#=> {:a=>2, :b=>4, :c=>5}]
When the value for a particular key is not an array, it should simply be included as-is in each resulting hash, the same as if it were wrapped in an array.
Motivation
I need to generate test data given a variety of values for different options. While I can use [1,2].product([3,4],[5]) to get the Cartesian Product of all possible values, I'd rather use hashes to be able to label both my input and output so that the code is more self-explanatory than just using array indices.
I suggest a little pre-processing to keep the result general:
options = { a:[1,2], b:[3,4], c:5 }
options.each_key {|k| options[k] = [options[k]] unless options[k].is_a? Array}
=> {:a=>[1, 2], :b=>[3, 4], :c=>[5]}
I edited to make a few refinements, principally the use of inject({}):
class Hash
def self_product
f, *r = map {|k,v| [k].product(v).map {|e| Hash[*e]}}
f.product(*r).map {|a| a.inject({}) {|h,e| e.each {|k,v| h[k]=v}; h}}
end
end
...though I prefer #Phrogz's '2nd attempt', which, with pre-processing 5=>[5], would be:
class Hash
def self_product
f, *r = map {|k,v| [k].product(v)}
f.product(*r).map {|a| Hash[*a.flatten]}
end
end
First attempt:
class Hash
#=> Given a hash of arrays get an array of hashes
#=> For example, `{ a:[1,2], b:[3,4], c:5 }.self_product` yields
#=> [ {a:1,b:3,c:5}, {a:1,b:4,c:5}, {a:2,b:3,c:5}, {a:2,b:4,c:5} ]
def self_product
# Convert array values into single key/value hashes
all = map{|k,v| [k].product(v.is_a?(Array) ? v : [v]).map{|k,v| {k=>v} }}
#=> [[{:a=>1}, {:a=>2}], [{:b=>3}, {:b=>4}], [{:c=>5}]]
# Create the product of all mini hashes, and merge them into a single hash
all.first.product(*all[1..-1]).map{ |a| a.inject(&:merge) }
end
end
p({ a:[1,2], b:[3,4], c:5 }.self_product)
#=> [{:a=>1, :b=>3, :c=>5},
#=> {:a=>1, :b=>4, :c=>5},
#=> {:a=>2, :b=>3, :c=>5},
#=> {:a=>2, :b=>4, :c=>5}]
Second attempt, inspired by #Cary's answer:
class Hash
def self_product
first, *rest = map{ |k,v| [k].product(v.is_a?(Array) ? v : [v]) }
first.product(*rest).map{ |x| Hash[x] }
end
end
In addition to being more elegant, the second answer is also about 4.5x faster than the first when creating a large result (262k hashes with 6 keys each):
require 'benchmark'
Benchmark.bm do |x|
n = *1..8
h = { a:n, b:n, c:n, d:n, e:n, f:n }
%w[phrogz1 phrogz2].each{ |n| x.report(n){ h.send(n) } }
end
#=> user system total real
#=> phrogz1 4.450000 0.050000 4.500000 ( 4.502511)
#=> phrogz2 0.940000 0.050000 0.990000 ( 0.980424)
While working in Ruby I often find myself in a conflict between using methods with a ! or using a normal method as assign the value back. I am not sure when to use what.
For example,
I have 2 hashes (h1 and h2) and I want to merge them and store the value back in hash h1, now should I be using
h1.merge!(h2)
or
h1 = h1.merge(h2)?
Is there any difference between the two?
Most of the time there is very little difference between h1.merge!(h2) and h1 = h1.merge(h2).
However, note that:
Since merge! modifies the old hash, you might be unintentionally affecting some other object in your program that holds a reference to that same hash. It is bad practice to modify a hash that you received as a method parameter because the caller usually does not expect it.
Using merge! is not functional programming, if you are a fan of that.
Using merge! is probably more efficient since it does not create a new hash, especially for large hashes.
I would use merge most of the time and only use merge! if I determine that it is safe and better.
Is there any difference between the two?
Yes,of-course there is.
You should use !(bang) version of Hash#merge,when you want to change the receiver itself.
Example
h1 = {a: 1}
h2 = {b: 2}
h3 = h1.merge(h2) # => {:a=>1, :b=>2}
h1 # => {:a=>1}
Now see :
h1 = {a: 1}
h2 = {b: 2}
h1.merge!(h2) # => {:a=>1, :b=>2}
h1 # => {:a=>1, :b=>2}
h1 = h1.merge(h2) gives the same answer
Humm,that is because,you are assinging the new hash to h1 after applying the Hash#merge method:
h1 = {a: 1}
h2 = {b: 2}
h1.object_id # => 69435570
h1 = h1.merge(h2) # => {:a=>1, :b=>2}
h1 # => {:a=>1, :b=>2}
h1.object_id # => 69434820
As you said I want to merge them and store the value back in hash h1,Then I would recommend to use Hash#merge!.Because h1 = h1.merge(h2) is same as h1.merge!(h2)(will save the new hash creation,and assign back it to the h1).
While working in Ruby I often find myself in a conflict between using
methods with a ! or using a normal method.
You should be thinking about more important things, so just adopt the rule: I will never use bang methods. Now be free and soar...
require 'benchmark'
n = 1_000_000
h1 = {a: 1, b: 2}
h2 = {b: 3, c: 4}
Benchmark.bm(20) do |b|
b.report("no-bang-hash-merge") do
n.times { h1 = h1.merge h2 }
end
b.report("bang-hash-merge") do
n.times { h1.merge! h2 }
end
end
--output:--
user system total real
no-bang-hash-merge 2.750000 0.050000 2.800000 ( 2.817345)
bang-hash-merge 0.400000 0.000000 0.400000 ( 0.406870)
.
require 'benchmark'
hash_size = 10_000
#Keys overlap:
key1 = 'a'
key2 = nil
h1 = {}
hash_size.times do |i|
h1[key1] = i
key2 = key1.dup if i == hash_size/2
key1.succ!
end
h2 = {}
hash_size.times do |i|
h2[key2] = i
key2.succ!
end
=begin
#No overlap:
key = 'a'
h1 = {}
hash_size.times do |i|
h1[key] = i
key.succ!
end
h2 = {}
hash_size.times do |i|
h2[key] = i
key.succ!
end
=end
n = 100_000
puts "50% of keys overlap, hash size #{hash_size}:"
Benchmark.bm(20) do |b|
b.report("no-bang-hash-merge") do
n.times { h1 = h1.merge h2 }
end
b.report("bang-hash-merge") do
n.times { h1.merge! h2 }
end
end
--some test runs:---
50% of keys overlap, hash size 10000:
user system total real
no-bang-hash-merge 1500.570000 74.520000 1575.090000 (1695.523240)
bang-hash-merge 255.910000 0.940000 256.850000 (269.957178)
No keys overlap, hash size 10000:
user system total real
no-bang-hash-merge 1906.070000 109.340000 2015.410000 (2151.865636)
bang-hash-merge 162.680000 0.190000 162.870000 (163.369607)
So if you need the speed, bang away. Otherwise, don't risk it.
number = {:a => 1, :b => 2, :c => 3, :d => 4}
upon evaluation of certain condition i want to delete key-value pair of a,b,c
number.delete "A"
number.delete "B"
number.delete "C"
Or, less performant but more terse:
number.reject! {|k, v| %w"A B C".include? k }
or, more performant than second Chris' solution but shorter than first:
%w"A B C".each{|v| number.delete(v)}
ActiveSupport that is a part of Rails comes with several built-in methods can help you to achieve your goal.
If you just want to delete some key-value pairs, you can use Hash#except!
number.except!(:a, :b, :c)
If you want to keep the original hash, then use Hash#except
new_hash = number.except!(:a, :b, :c)
new_hash # => {:d=>4}
number # => {:a=>1, :b=>2, :c=>3, :d=>4}
You also can go with Rails-free way:
new_hash = number.dup.tap do |hash|
%i[a b c].each {|key| hash.delete(key)}
end
new_hash # => {:d=>4}
number # => {:a=>1, :b=>2, :c=>3, :d=>4}
P.S.: the last code example is very slow, I'm just providing it as an alternative.