In the following nested hash,
hash = {a: 2, b: 4, c: {name: "John", id: 12, age: 33}}
I want to return the values that are hash:
{:name => "John", :id => 12, :age => 33}
I want to returned a hash. I thought the following code will do the job:
hash.select! {|_k, v| v.is_a?(Hash)}
# => {:c => {:name => "John", :id => 12, :age => 33}}
but I get both k/v pairs. Did I miss anything on the code? How can I achieve the return value as mentioned?
I would do something like:
hash.values.find(&Hash.method(:===))
#=> {:name=>"John", :id=>12, :age=>33}
select returns the key and value that matched. Add .values to get just the values without the keys:
hash.select! { |_k, v| v.is_a?(Hash) }.values
This will return an array of the values that were matched by select:
[{:name=>"John", :id=>12, :age=>33}]
If you know there will only ever be one result, you can get the desired value by calling first:
hash.select! { |_k, v| v.is_a?(Hash) }.values.first
{:name=>"John", :id=>12, :age=>33}
Related
Tell me how to do this, where you can read about it, because I do not understand at all how to implement it. Thanks.
def initialize
#arr = []
end
def items(init)
arrfinish = init - #arr
#arr = (#arr + init).uniq
yield arrfinish
end
def idefine(find_text)
end
The class has a method(items) that connects arrays by removing duplicate elements. I need to make sure that the idefine
method receives the key by which filtering will be performed when adding new elements, I will give an example below.
app_handler.idefine('id')
app_handler.items([{'id' => 1}, {'id' => 1, 'test_key' => 'Some data'}, {'id' => 2}])
From this example, the second element with id = 1 should be ignored.
Ok, putting aside the class definition, what I understand of the question is:
In an array of hashes, remove the hashes that contain a duplicate value of a given key.
The following function filter the hashes and copy the content of the selected ones in a new array:
require 'set'
def no_dup_val key, arr
previous_values = Set[]
arr.each_with_object([]) do |hash,result|
next unless hash.has_key?(key)
next if previous_values.include?(hash[key])
previous_values << hash[key]
result << hash.dup
end
end
Which gives you:
no_dup_val 'id', [{'id' => 1}, {'id' => 1, 'key' => 'data'}, {'id' => 2}, {'stock' => 3}, {'e-stock'=>0}]
#=> [{"id"=>1}, {"id"=>2}]
Note that the hashes that don't contain the key are also removed, that's my choice, which leads to the following questions:
What happens when the key is not present in a hash?
What happens when the item function is called more than once? Do you take into account the hashes already in #arr?
What happens when you call idefine with a new key? Do you filter the existing elements of #arr with the new key?
As you can see, you need to be a little more specific about what you want to do.
Update
If you don't care about copying the contents of the hashes then these may fit your needs.
Hashes without the id key are removed:
def no_dup_val key, arr
arr.filter{ |h| h.has_key?(key) }.uniq{ |h| h[key] }
end
no_dup_val 'id', [{'id' => 1}, {'id' => 1, 'key' => 'data'}, {'id' => 2}, {'stock' => 3}, {'e-stock'=>0}]
#=> [{"id"=>1}, {"id"=>2}]
Hashes without the id key are treated as having "id" => nil (so the first will be kept):
def no_dup_val key, arr
arr.uniq{ |h| h[key] }
end
no_dup_val 'id', [{'id' => 1}, {'id' => 1, 'key' => 'data'}, {'id' => 2}, {'stock' => 3}, {'e-stock'=>0}]
#=> [{"id"=>1}, {"id"=>2}, {"stock"=>3}]
All the hashes without the id key are kept:
How to merge hashes within array with same key, value pair? I have following array,
arry = [{"id" => 1, "val1" => 123},
{"id" => 2, "val1" => 234},
{"id" => 1, "val2" => 321},
{"id" => 1, "val3" => 445},
{"id" => 3, "val3" => 334}]
Want to get
arry = [{"id" => 1, "val1" => 123, "val2" => 321, "val3" => 445},
{"id" => 2, "val1" => 234},
{"id" => 3, "val3" => 334}]
is there ruby way to do it? Tried few ways but no success so far.
The arry you have posted is not a valid ruby array in the first place (I fixed it in my edit.)
arry.
group_by { |h| h["id"] }.
values.
map { |a| a.reduce(&:merge) }
#⇒ [{"id"=>1, "val1"=>123, "val2"=>321, "val3"=>445},
# {"id"=>2, "val1"=>234}, {"id"=>3, "val3"=>334}]
If your input might have same keys within the same "id" (like {"id" => 1, "val1" => 123}, {"id" => 1, "val1" => 456},) you are to decide how to merge them. In any case, Hash#merge with a block would be your friend there.
arry.each_with_object({}) { |g,h| h.update(g["id"]=>g) { |_,o,n| o.merge(n) } }.values
#=> [{"id"=>1, "val1"=>123, "val2"=>321, "val3"=>445},
# {"id"=>2, "val1"=>234},
# {"id"=>3, "val3"=>334}]
Note the receiver of values is:
{1=>{"id"=>1, "val1"=>123, "val2"=>321, "val3"=>445},
2=>{"id"=>2, "val1"=>234},
3=>{"id"=>3, "val3"=>334}}
This uses the form of Hash#update (aka merge) that employs the block { |_,o,n| o.merge(n) } to determine the values of keys that are present in both hashes being merged. See the doc for descriptions of the three block variables. (I've used an underscore for the first, the common key, to signify that it is not used in the block calculation.)
This should work too,
arry.group_by { |a| a['id'] }.map{|_, ar| ar.reduce(:merge)}
This would return,
[{"id"=>1, "val1"=>123, "val2"=>321, "val3"=>445}, {"id"=>2, "val1"=>234}, {"id"=>3, "val3"=>334}]
I am trying to merge an array of hashes based on a particular key/value pair.
array = [ {:id => '1', :value => '2'}, {:id => '1', :value => '5'} ]
I would want the output to be
{:id => '1', :value => '7'}
As patru stated, in sql terms this would be equivalent to:
SELECT SUM(value) FROM Hashes GROUP BY id
In other words, I have an array of hashes that contains records. I would like to obtain the sum of a particular field, but the sum would grouped by key/value pairs. In other words, if my selection criteria is :id as in the example above, then it would seperate the hashes into groups where the id was the same and the sum the other keys.
I apologize for any confusion due to the typo earlier.
Edit: The question has been clarified since I first posted my answer. As a result, I have revised my answer substantially.
Here are two "standard" ways of addressing this problem. Both use Enumerable#select to first extract the elements from the array (hashes) that contain the given key/value pair.
#1
The first method uses Hash#merge! to sequentially merge each array element (hashes) into a hash that is initially empty.
Code
def doit(arr, target_key, target_value)
qualified = arr.select {|h|h.key?(target_key) && h[target_key]==target_value}
return nil if qualified.empty?
qualified.each_with_object({}) {|h,g|
g.merge!(h) {|k,gv,hv| k == target_key ? gv : (gv.to_i + hv.to_i).to_s}}
end
Example
arr = [{:id => '1', :value => '2'}, {:id => '2', :value => '3'},
{:id => '1', :chips => '4'}, {:zd => '1', :value => '8'},
{:cat => '2', :value => '3'}, {:id => '1', :value => '5'}]
doit(arr, :id, '1')
#=> {:id=>"1", :value=>"7", :chips=>"4"}
Explanation
The key here is to use the version of Hash#merge! that uses a block to determine the value for each key/value pair whose key appears in both of the hashes being merged. The two values for that key are represented above by the block variables hv and gv. We simply want to add them together. Note that g is the (initially empty) hash object created by each_with_object, and returned by doit.
target_key = :id
target_value = '1'
qualified = arr.select {|h|h.key?(target_key) && h[target_key]==target_value}
#=> [{:id=>"1", :value=>"2"},{:id=>"1", :chips=>"4"},{:id=>"1", :value=>"5"}]
qualified.empty?
#=> false
qualified.each_with_object({}) {|h,g|
g.merge!(h) {|k,gv,hv| k == target_key ? gv : (gv.to_i + hv.to_i).to_s}}
#=> {:id=>"1", :value=>"7", :chips=>"4"}
#2
The other common way to do this kind of calculation is to use Enumerable#flat_map, followed by Enumerable#group_by.
Code
def doit(arr, target_key, target_value)
qualified = arr.select {|h|h.key?(target_key) && h[target_key]==target_value}
return nil if qualified.empty?
qualified.flat_map(&:to_a)
.group_by(&:first)
.values.map { |a| a.first.first == target_key ? a.first :
[a.first.first, a.reduce(0) {|tot,s| tot + s.last}]}.to_h
end
Explanation
This may look complex, but it's not so bad if you break it down into steps. Here's what's happening. (The calculation of qualified is the same as in #1.)
target_key = :id
target_value = '1'
c = qualified.flat_map(&:to_a)
#=> [[:id,"1"],[:value,"2"],[:id,"1"],[:chips,"4"],[:id,"1"],[:value,"5"]]
d = c.group_by(&:first)
#=> {:id=>[[:id, "1"], [:id, "1"], [:id, "1"]],
# :value=>[[:value, "2"], [:value, "5"]],
# :chips=>[[:chips, "4"]]}
e = d.values
#=> [[[:id, "1"], [:id, "1"], [:id, "1"]],
# [[:value, "2"], [:value, "5"]],
# [[:chips, "4"]]]
f = e.map { |a| a.first.first == target_key ? a.first :
[a.first.first, a.reduce(0) {|tot,s| tot + s.last}] }
#=> [[:id, "1"], [:value, "7"], [:chips, "4"]]
f.to_h => {:id=>"1", :value=>"7", :chips=>"4"}
#=> {:id=>"1", :value=>"7", :chips=>"4"}
Comment
You may wish to consider makin the values in the hashes integers and exclude the target_key/target_value pairs from qualified:
arr = [{:id => 1, :value => 2}, {:id => 2, :value => 3},
{:id => 1, :chips => 4}, {:zd => 1, :value => 8},
{:cat => 2, :value => 3}, {:id => 1, :value => 5}]
target_key = :id
target_value = 1
qualified = arr.select { |h| h.key?(target_key) && h[target_key]==target_value}
.each { |h| h.delete(target_key) }
#=> [{:value=>2}, {:chips=>4}, {:value=>5}]
return nil if qualified.empty?
Then either
qualified.each_with_object({}) {|h,g| g.merge!(h) { |k,gv,hv| gv + hv } }
#=> {:value=>7, :chips=>4}
or
qualified.flat_map(&:to_a)
.group_by(&:first)
.values
.map { |a| [a.first.first, a.reduce(0) {|tot,s| tot + s.last}] }.to_h
#=> {:value=>7, :chips=>4}
Say we have
[{:name=>"Joe", :age => 15},{:name=>"Josh", :age => 83},{:name=>"Jim", :age => 1203}]
What is the best way way to get the hash where the name is "Joe"? To return {:name=>"Joe", :age=>15}?
array.select will return an array of values (even if there is only one match):
my_array.select { |item| item[:name] == "Joe" }
# => [{:name => "Joe", :age => 15}]
You can use Enumerable's find method instead to return the first instance (as a hash):
my_array.find { |item| item[:name] == "Joe" }
# => {:name => "Joe", :age => 15}
array.select { |e| e[:name] == 'Joe' }.first
If this is an operation you need to perform frequently, then store the set of hashes as a hash of hashes, with the value for the name key as the key to each hash. That way you can look up your hashes in O(1) time.
Is there a simple way of remapping a hash in ruby the following way:
from:
{:name => "foo", :value => "bar"}
to:
{"foo" => "bar"}
Preferably in a way that makes it simple to do this operation while iterating over an array of this type of hashes:
from:
[{:name => "foo", :value => "bar"}, {:name => "foo2", :value => "bar2"}]
to:
{"foo" => "bar", "foo2" => "bar2"}
Thanks.
arr = [ {:name=>"foo", :value=>"bar"}, {:name=>"foo2", :value=>"bar2"}]
result = {}
arr.each{|x| result[x[:name]] = x[:value]}
# result is now {"foo2"=>"bar2", "foo"=>"bar"}
A modified version of Vanson Samuel's code does the intended.
It's a one-liner, but quite a long one.
arr = [{:name=>"foo", :value=>"bar"}, {:name=>"foo2", :value=>"bar2"}]
arr.inject({}){|r,item| r.merge({item['name'] => item['value']})}
# result {"foo" => "bar", "foo2" => "bar2"}
I wouldn't say that it's prettier than Gishu's version, though.
As a general rule of thumb, if you have a hash of the form {:name => "foo", :value => "bar"}, you're usually better off with using a tuple of ["foo", "bar"].
arr = [["foo", "bar"], ["foo2", "bar2"]]
arr.inject({}) { |accu, (key, value)| accu[key] = value; accu }
I know this is old, but the neatest way to achieve this is to map the array of hashes to an array of tuples, then use Hash[] to build a hash from that, as follows:
arr = [{:name => "foo", :value => "bar"}, {:name => "foo2", :value => "bar2"}]
Hash[ array.map { |item| [ item[:name], item[:value] ] } ]
# => {"foo"=>"bar", "foo2"=>"bar2"}
a bit late but:
[{ name: "foo", value: "bar" },{name: "foo2", value: "bar2"}].map{ |k| k.values }.to_h