Adding a count/tally to an array of hashes [duplicate] - arrays

I have an array of hashes:
[{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3>}, {"Dry Goods"=>2}]
I need to use inject here I think but I've really been struggling.
I want a new hash that reflects the sum of the previous hash's duplicate keys:
[{"Vegetable"=>15}, {"Dry Goods"=>5}]
I'm in control of the code that outputs this hash so I can modify it if necessary. The results were mainly hashes because this could end up nested any number of levels deep and then it's easy to call flatten on the array but not flatten the keys/values of the hash too:
def recipe_pl(parent_percentage=nil)
ingredients.collect do |i|
recipe_total = i.recipe.recipeable.total_cost
recipe_percentage = i.ingredient_cost / recipe_total
if i.ingredientable.is_a?(Purchaseitem)
if parent_percentage.nil?
{i.ingredientable.plclass => recipe_percentage}
else
sub_percentage = recipe_percentage * parent_percentage
{i.ingredientable.plclass => sub_percentage}
end
else
i.ingredientable.recipe_pl(recipe_percentage)
end
end
end

ar = [{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3}, {"Dry Goods"=>2}]
p ar.inject{|memo, el| memo.merge( el ){|k, old_v, new_v| old_v + new_v}}
#=> {"Vegetable"=>15, "Dry Goods"=>5}
Hash.merge with a block runs the block when it finds a duplicate; inject without a initial memo treats the first element of the array as memo, which is fine here.

Simply use:
array = [{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3}, {"Dry Goods"=>2}]
array.inject{|a,b| a.merge(b){|_,x,y| x + y}}

ar = [{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3}, {"Dry Goods"=>2}]
While the Hash.merge technique works fine, I think it reads better with an inject:
ar.inject({}) { |memo, subhash| subhash.each { |prod, value| memo[prod] ||= 0 ; memo[prod] += value } ; memo }
=> {"Dry Goods"=>5, "Vegetable"=>15}
Better yet, if you use Hash.new with a default value of 0:
ar.inject(Hash.new(0)) { |memo, subhash| subhash.each { |prod, value| memo[prod] += value } ; memo }
=> {"Dry Goods"=>5, "Vegetable"=>15}
Or if inject makes your head hurt:
result = Hash.new(0)
ar.each { |subhash| subhash.each { |prod, value| result[prod] += value } }
result
=> {"Dry Goods"=>5, "Vegetable"=>15}

I'm not sure that a hash is what you want here, because I don't multiple entries in each hash. so I'll start by changing your data representation a little.
ProductCount=Struct.new(:name,:count)
data = [ProductCount.new("Vegetable",10),
ProductCount.new("Vegetable",5),
ProductCount.new("Dry Goods",3),
ProductCount.new("Dry Goods",2)]
If the hashes can have multiple key-value pairs, then what you probably want to do is
data = [{"Vegetable"=>10}, {"Vegetable"=>5}, {"Dry Goods"=>3>}, {"Dry Goods"=>2}]
data = data.map{|h| h.map{|k,v| ProductCount.new(k,v)}}.flatten
Now use the facets gem as follows
require 'facets'
data.group_by(&:name).update_values{|x| x.map(&:count).sum}
The result is
{"Dry Goods"=>5, "Vegetable"=>15}

If have two hashes with multiple keys:
h1 = { "Vegetable" => 10, "Dry Goods" => 2 }
h2 = { "Dry Goods" => 3, "Vegetable" => 5 }
details = {}
(h1.keys | h2.keys).each do |key|
details[key] = h1[key].to_i + h2[key].to_i
end
details

Related

Converting Ruby array of array into a hash

I have an array of arrays as below :
[
["2021-07-26T11:38:42.000+09:00", 1127167],
["2021-08-26T11:38:42.000+09:00", 1127170],
["2021-09-26T11:38:42.000+09:00", 1127161],
["2021-07-25T11:38:42.000+09:00", 1127177],
["2021-08-27T11:38:42.000+09:00", 1127104]
]
What i want to have as the output :
{
"2021-July" => [["2021-07-26T11:38:42.000+09:00", 1127167],["2021-07-25T11:38:42.000+09:00", 1127177]],
"2021-August" => [["2021-08-26T11:38:42.000+09:00", 112717],["2021-08-27T11:38:42.000+09:00", 112710]],
"2021-September" => ["2021-09-26T11:38:42.000+09:00", 112716]
}
I want to create the hash key year-month format based on the date value in each array element. What would be the easiest way to do this?
use group_by
date_array = [["2021-07-26T11:38:42.000+09:00", 1127167],["2021-08-26T11:38:42.000+09:00", 112717],["2021-09-26T11:38:42.000+09:00", 112716],["2021-07-25T11:38:42.000+09:00", 1127177],["2021-08-27T11:38:42.000+09:00", 112710]]
result = date_array.group_by{ |e| Date.parse(e.first).strftime("%Y-%B") }
date_array = [["2021-07-26T11:38:42.000+09:00", 1127167],["2021-08-26T11:38:42.000+09:00", 112717],["2021-09-26T11:38:42.000+09:00", 112716],["2021-07-25T11:38:42.000+09:00", 1127177],["2021-08-27T11:38:42.000+09:00", 112710]]
result_date_hash = Hash.new([])
date_array.each do |date|
formatted_date = Date.parse(date.first).strftime("%Y-%B")
result_date_hash[formatted_date] += date
end
Output:
puts result_date_hash
=>
{"2021-July"=>["2021-07-26T11:38:42.000+09:00", 1127167, "2021-07-25T11:38:42.000+09:00", 1127177],
"2021-August"=>["2021-08-26T11:38:42.000+09:00", 1127170, "2021-08-27T11:38:42.000+09:00", 1127104],
"2021-September"=>["2021-09-26T11:38:42.000+09:00", 1127161]}

How do I replace one element in an array with potentially multiple elements?

I want to replace an item in an array:
arr = ["55", "4.ARTHUR", "masddf"]
with potentially multiple items based on whether it matches a regular expression. I would like to have the result:
["55", "4.", "ARTHUR", "masddf"]
I tried:
arr.map { |o| o =~ /\d+\./ ? o.split(/^(\d+\.)/).reject { |c| c.empty? } : o }
# => ["55", ["4.", "ARTHUR"], "masddf"]
arr.map { |o| o =~ /\d+\./ ? o.split(/^(\d+\.)/).reject { |c| c.empty? }.flatten : o }
# => ["55", ["4.", "ARTHUR"], "masddf"]
I can't seem to get the elements outside of the array they got split into.
Any ideas?
Use flat_map instead:
arr = ["55", "4.ARTHUR", "masddf"]
arr.flat_map { |o| o =~ /\d+\./ ? o.split(/^(\d+\.)/).reject { |c| c.empty? } : o }
# => ["55", "4.", "ARTHUR", "masddf"]
See it on repl.it: https://repl.it/F90V
By the way, a simpler way to solve this problem is to use String#scan:
arr.flat_map {|o| o.scan(/^\d+\.|.+/) }
See it on repl.it: https://repl.it/F90V/1

Iterate through nested hash to create an array while adding items to it

I have a nested hash and I would like to rearrange the key/val pairs. The example below shows a hash of styles that points to hash of languages, which then points to a hash of the type of language it is. I want to reformat it to look like the new_hash example. I understand to structure it by iterating through the hash through different levels and creating the hash like that, however, the part I'm concerned/confused about is creating the array that :style points to and then pushing the correct style to it.
I assumed the code snippet would work as I expect it to. My new_hash will have a key of :language which points to another hash. This hash has a key of :style that points to an array in which I will store all the styles associated with each respective language. The :javascript hash should have two styles in its array since it exists twice in the original hash, however, when running this code snippet, the array is not adding both styles. It seems that during one iteration when assigning the hash, :javascript is assigned the style of :oo but in another iteration, it gets replaced with :functional. I'm not sure of the syntax to initialize the array and add multiple items to it while iterating through the hash.
hash = {
:oo => {
:ruby => {:type => "Interpreted"},
:javascript => {:type => "Interpreted"},
},
:functional => {
:scala => {:type => "Compiled"},
:javascript => {:type => "Interpreted"}
}
}
new_hash = {
:ruby => {
:type => "Interpreted", :style => [:oo]
},
:javascript => {
:type => "Interpreted", :style => [:oo, :functional]
},
:scala => {
:type => "Compiled", :style => [:functional]
}
}
hash.each do |style, programming_language|
programming_language.each do |language, type|
type.each do |key, value|
new_hash[language] = {:style => [style]}
end
end
end
You could use the forms of Hash#update (aka merge!) and Hash#merge that employ a hash to determine the values of keys that are present in both hashes being merged. See the docs for details.
hash.each_with_object({}) do |(style,language_to_type_hash),h|
language_to_type_hash.each do |language,type_hash|
h.update(language=> { type: type_hash[:type], style: [style] }) do |_,o,_|
o.merge(style: [style]) { |_,ostyle_arr,nstyle_arr| ostyle_arr + nstyle_arr }
end
end
end
#=> {:ruby =>{:type=>"Interpreted", :style=>[:oo]},
# :javascript=>{:type=>"Interpreted", :style=>[:oo, :functional]},
# :scala =>{:type=>"Compiled", :style=>[:functional]}}
Hash::new allows you to specify a default value for a non existent key so in your case the default value would be {type: nil, style: []}
This functionality will allow you to loop only once and implement as follows
programming_languages = {
:oo => {
:ruby => {:type => "Interpreted"},
:javascript => {:type => "Interpreted"},
},
:functional => {
:scala => {:type => "Compiled"},
:javascript => {:type => "Interpreted"}
}
}
programming_languages.each_with_object(Hash.new {|h,k| h[k] = {type: nil, style: []}}) do |(style,languages),obj|
languages.each do |language,type_hash|
obj[language][:style] << style
obj[language][:type] = type_hash[:type]
end
end
Output:
#=> {:ruby=>{:type=>"Interpreted", :style=>[:oo]},
:javascript=>{:type=>"Interpreted", :style=>[:oo, :functional]},
:scala=>{:type=>"Compiled", :style=>[:functional]}}
Realized that this can be solved iterating the hash twice. Once to initialize the array, and the second time to then add the necessary items to it. Though not sure if this can done only iterating the hash once.
new = {}
languages.each do |style, programming_language|
programming_language.each do |language, type|
type.each do |key, value|
new[language] = {:type => nil , :style => []}
end
end
end
languages.each do |style, programming_language|
programming_language.each do |language, type|
type.each do |key, value|
new[language][:type] = value
new[language][:style] << style
end
end
end
new
Once we give the hashes better names, it becomes a bit easier to work it out. I've also made use of sets so we don't have to worry about duplicates.
require 'set'
# Our new hash of language info. _new to differentiate between
# the hash of languages under the hash of styles.
languages_new = {}
# For each style...
styles.each do |style, languages|
# For each language in that style...
languages.each do |language, info|
# Add a new hash for that language if there isn't one already
languages_new[language] ||= {}
# For each bit of info about that language...
info.each do |key, val|
# Add a new set for that info if there isn't one already
# The `var = hash[key] ||= new_var` pattern allows
# conditional initialization while also using either the
# new or existing set.
set = languages_new[language][key] ||= Set.new
# Add the info to it
set.add(val)
end
# Handle the special case of style.
set = languages_new[language][:style] ||= Set.new
set.add(style)
end
end
Note that rather than hard coding the initialization of hashes and sub-hashes, I've done it in each level of looping. This means I don't have to list out all the keys, and it will handle new and unexpected keys.
By using sets for the values I make no assumptions about how many values a bit of language information can have.

Correct way to search an Array instead of where

How to query search an Array with where? What's the correct way?
U have an association like this:
# Foo has many bars
Foo.first.bars
Controller:
def index
#bars = []
#datas = Foo.where(email: current_user.email)
#datas.map { |d| #bars.push(d.bar).where("name like ?", "%#{params[:email]}%") }
respond_to do |format|
format.html
format.json { render json: #bars }
end
end
Instead of where for Array, what is the correct query term?
You can use the select method to filter out values in an array given a certain condition.
[1,2,3,4,5].select { |num| num.even? } #=> [2, 4]
Or for your particular example:
#bars = #datas.map { |d| d.bars }.select { |b| b.name.include? params[:email] }
However, since you actually don't have an array of bars and would have to create it, this is simply an unnecessary step and a more straightforward solution would be:
#datas = Foo.where(email: current_user.email)
# #bars is an array
#bars = #datas.map { |d| d.bars.where("name like ?", "%#{params[:email]}%") }
or
# #bars is an ActiveRecord object
#bars = Bar.where(id: #datas.pluck(:id)).where("name like ?", "%#{params[:email]}%")

Merge nested hash without overwritting in Ruby

After checking this Ruby convert array to nested hash and other sites I am not able to achieve the following convertion:
I have this:
{"a"=>"text"}
{"b"=>{"x"=>"hola"}}
{"b"=>{"y"=>"pto"}
}
and I want to obtain:
{"a"=>text,
b=>{"x" => "hola",
"y" => "pto"
}
}
Until now the code seems like this:
tm =[[["a"],"text"],[["b","x"],"hola"],[["b","y"],"pto"]]
q = {}
tm.each do |l|
q = l[0].reverse.inject(l[1]) { |p, n| { n => p } }
i += 1
end
I tried with merge, but it overwrites the keys!. I tried also this How can I merge two hashes without overwritten duplicate keys in Ruby? but it keeps overwritting.
Update:
How can I do it for an undefined nested hash (level) ? hash[key1][key2][key3]... = "value"
{"a"=>"text"},
{"b"=>{"x"=>"hola"},
{"b"=>{"y"=>"pto"},
{"c"=>{"g"=>{"k" => "test1"}},
...
}
You could use merge with a block to tell Ruby how to handle duplicate keys:
a = {"a"=>"text"}
b = {"b"=>{"x"=>"hola"}}
c = {"b"=>{"y"=>"pto"}}
a.merge(b).merge(c) { |key, left, right| left.merge(right) }
#=> {"a"=>"text", "b"=>{"x"=>"hola", "y"=>"pto"}}
For Rails there is the deep_merge function for ActiveSupport that does exactly what you ask for.
You can implement the same for yourself as follows:
class ::Hash
def deep_merge(second)
merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
self.merge(second, &merger)
end
end
Now,
h1 = {"a"=>"text"}
h2 = {"b"=>{"x"=>"hola"}}
h3 = {"b"=>{"y"=>"pto"}}
h1.deep_merge(h2).deep_merge(h3)
# => {"a"=>"text", "b"=>{"x"=>"hola", "y"=>"pto"}}

Resources