Ruby: get the max occurrences of many hashes depending their content - ruby-on-rails

I usually count the max occurrences of an array of items (int) like this:
specialties_with_frequency = specialties.inject(Hash.new(0)) { |h,v| h[v] += 1; h }
#reference.specialty_id = specialties.max_by { |v| specialties_with_frequency[v] }
Today, I need to count the max occurrences of hashes content.
varietal is a database object containing these fields:
id, grape_id, percent
My duplicate object can have multiple varietals.
#duplicates.each do |duplicate|
duplicate.varietals.each do |varietal|
end
end
For example, browsings duplicates, I will have:
duplicate 1: varietals => {grape_id => 1}, {grape_id => 2}
duplicate 2: varietals => {grape_id => 3}
duplicate 3: varietals => {grape_id => 1}, {grape_id => 2}
duplicate 4: varietals => {grape_id => 3}, {grape_id => 5}
In this case, the accepted data will be:
{grape_id => 1}, {grape_id => 2}
because there are 2 occurrences browsing all duplicates.
I have no idea of how to explore the same values in all occurrences.
Thanks,
Alexandre

You can use the exact same code as before, only with varietals arrays as keys in your frequency hash. Just make sure the arrays are sorted so that the keys will be equal for the same content.
If the grape_id is the only field used for occurence checking, you can simplify a bit by mapping the array of varietals to an array of numbers, in which case your frequency builder will look like this:
specialties_with_frequency = #duplicates.inject(Hash.new(0)) do |h, duplicate|
grape_ids = duplicate.varietals.map { |v| v[:grape_id] }.sort
h[grape_ids] += 1; h
end
Given the example you provided, the value should now be:
{[1, 2]=>2, [3]=>1, [3, 5]=>1}

For arrays and hashes it’s better to use Enumerable#group_by:
with_freq = whatever.group_by { |v| v } # (&:itself) for ruby2.3
.map { |k, v| [k, v.count] }
.to_h
If you need some sophisticated algorithm for grouping, change { |v| v } to use this algorithm.

Related

Search by array of values in .select query method

I'm having this type of search:
values = ModelName.find(:all, :conditions => ['attr_id IN (SELECT attr_id FROM srv_type_attr WHERE id IN (?))', serv_objt_attr.collect(&:stya_id)])
Witch returns me an array of needed values:
[33458, 33438]
Next i need to check if record exists with select:
serv_objt_attr.select {|array| array.stya_id == values.collect(&:attr_id).uniq}
This is an example what i'm thinking off.
So how to do it with select, so he would walk through all values witch i'm getting from values.
I know that i could to something like
values.collect(&:attr_id).uniq do |val|
serv_objt_attr.select {|array| array.stya_id == val}
end
But i do not thing that this is a good option.
Ruby 1.8.7
Rails 2.3.4
This is a good case for the set intersection operator:
values = ModelName.find(:all, :conditions => ['attr_id IN (SELECT attr_id FROM srv_type_attr WHERE id IN (?))', serv_objt_attr.collect(&:stya_id)])
values & Set.new(serv_objt_attr.map(&:stya_id)
Here's what the & does:
>> values = [1,2,3]
=> [1, 2, 3]
>> other_array = [1,5,9,3]
=> [1, 5, 9, 3]
>> values & other_array
=> [1, 3]

Convert group count from activerecord into hash with multiple group stages

I'm trying to get some statistics.
Model.where(status:#statuses).group(:sub_group, :status).count
This returns me
{
["sub_group1", "status1"] => 3},
["sub_group2", "status3"] => 7,
["sub_group1", "status2"] => 5, ....etc }
I want to merge them so each element has a unique subgroup.
e.g. a hash like:
{
"sub_group1" => {"status1" => 3, "status2" => 5,
"sub_group2" => {"status3" => 7},
}
or an array
[
["subgroup1", {"status1" = 3, "status2" => 5}],
["subgroup2".....
]
i.e. I want all those terms to be merged with sub_group as the primary header so that I can get the results for subgroup in one item.
Brain not working today....
You can try with merge!:
result = Model.where(status: #statuses).group(:sub_group, :status).count
result.reduce({}) do |collection, (attributes, count)|
collection.merge!(attributes[0] => { attributes[1] => count }) do |_, prev_value, next_value|
prev_value.merge!(next_value)
end
end
Demonstration
I suppose you could do something like this:
res = Model.where(status:#statuses).group(:sub_group, :status).count
nice_hash = {}
res.each do |key, count|
nice_hash[key[0]] = {} unless nice_hash[key[0]]
nice_hash[key[0]][key] = count
end
After this nice_hash should be on the desired format
Please try with below code.
a = Model.where(status:#statuses).group(:sub_group, :status).count
res = {}
a.each do |k,v|
c={}
if res.has_key?(k[0])
c[k[1]]=v
res[k[0]]=res[k[0]].merge(c)
else
c[k[1]]=v
res[k[0]]=c
end
end
I believe the answer to your question should be this or this should guide in the right direction:
Model.where(status:#statuses).group_by(&:sub_group)
If you need the only the statuses as you mentioned, you could do:
Model.where(status:#statuses).select(:status, :sub_group).group_by(&:sub_group)

Ruby - how to replace key in a hash by a key from different one?

I have this hash:
CARS = {"Audi" => 0,
"BMW" => 1,
...}
And this output from ActiveRecord (#top_cars):
{1=>18, 0=>17, 3=>13, 5=>10, 2=>5, 4=>1}
How do I replace the keys from #top_cars by the car names from CARS?
Thank you
EDIT:
So the desired output would be like {"BMW"=>18, "Audi"=>17, "Renault"=>13, "Mercedes"=>10, "Ford"=>5, "Porsche"=>1}
This would do the trick:
#top_cars.map {|key, value| [CARS.key(key), value]}.to_h
possible solution:
#top_cars.inject({}) {|memo, (key,value)| memo.merge(CARS.key(key) => value)}
You could merge cars with itself:
cars = { "Audi" => 0,
"Mercedes" => 1,
"Ford" => 2,
"Renault" => 3,
"BMW" => 4,
"Porsche" => 5
}
top_cars = {1=>18, 0=>17, 3=>13, 5=>10, 2=>5, 4=>1}
cars.merge(cars) { |*,n| top_cars[n] }
#=> {"Audi"=>17, "Mercedes"=>18, "Ford"=>5, "Renault"=>13, "BMW"=>1, "Porsche"=>10}
This uses the form of Hash#merge where a block is employed to determine the values of keys that are present in both hashes being merged, which here is all the keys.

How to use has_key with a collection?

I Have an collection:
[a, b, c]
and i want verify if a hash contains some key of this collection
I try:
col = [a, b, c]
my_hash = {c => 1, f => 2, h => 3}
my_hash.has_key? col
=> false
but not work.
Can anybody help me?
Thanks.
1. Iterate over col and check each.
No explanation required.
2. Use existing library functionality to do the same:
keys = [:a, :b, :c]
h = { c: 1, f: 2, h: 3 }
h.any? { |key, val| keys.include? key }
=> true
3. Set math:
h.keys & keys
=> [:c]
Then wrap it up to return true/false depending on which way you want things to read.
Try this:
my_hash.keys & col
# => [c]
& intersects the list of keys with the col array, returning only item in col which appear as keys in my_hash.
Another option - values_at:
my_hash.values_at(*col).compact
# => [1]
But you can do:
my_hash.any? { |key,_| col.include?(key) }
Read it like - Any key from my_hash included in col array.

Intersect array of hashes with array of ids

I have an array of hashes, this is not an active record model. This array is of objects of type Person with properties of id, name, age. I have a second array of strings, ["john", "james", "bill"].
I am attempting to remove all objects in the array of hashes except for the ones who have names in the second array, essentially performing an intersect, but I'm having quite a few problems. Any suggestions? I'm not sure if my syntax is just off or if I'm thinking about this the wrong way. Obviously I can just iterate through but this seems like its probably not the best way to handle the situation.
http://www.ruby-doc.org/core-1.9.2/Array.html#method-i-select
arr1 = [{:id => 1, :name => "John"}, {:id => 2, :name => "Doe"}];
arr2 = ["Doe"];
intersect = arr1.select {|o| arr2.include? o[:name]} # you can also use select!
p intersect # outputs [{:name=>"Doe", :id=>2}]
Late to the party, but if arr1 :name is an array this works nicely:
arr1 = [{:id => 1, :name => ["John", "Doe"]}, {:id => 2, :name => ["Doe"]}];
arr2 = ["Doe"]
> intersect = arr1.reject{|o| (arr2 & o[:name]).empty?}
=> [{:id=>1, :name=>["John", "Doe"]}, {:id=>2, :name=>["Doe"]}] #output
> arr2 = ["John"]
> intersect = arr1.reject{|o| (arr2 & o[:name]).empty?}
=> [{:id=>1, :name=>["John", "Doe"]}] #output
or use select:
intersect = arr1.select{|o| !(arr2 & o[:name]).empty?}
To remove all objects in the array of hashes except for the ones who have names in the second array, you can do:
arr1.reject!{|o| (arr2 & o[:name]).empty?}

Resources