I have an array of instances of model Foo. Foo is an Ohm based data store. Each instance of Foo has an_id and integer attributes such as follows, likes. If there are two instances of Foo with the same an_id, I'd love to add the follows and likes attributes together. The solution I had was to create a hash where each key is an an_id of the array, and keep the state there. If the array is large enough, this is not efficient as I need each object back into an array. I'd love to group the array by Foo#an_id and merge/add the counter attributes together and pop that back into the array. Is something like this currently supported?
group_by, sum
As a start, something like this:
grouped_hash = your_array.group_by(&:an_id)
sums_by_id = {}
grouped_hash.each do |id,values|
sums_by_id[id] = {}
# you could also just iterate over values once and += :follows and :likes
sums_by_id[id][:follows] = values.sum(&:follows)
sums_by_id[id][:likes] = values.sum(&:likes)
end
Example output:
sums_by_id => {1 => {:follows => 2, :likes => 4}, 2 => ...
Additionally, take a look at:
inject
(5..10).inject {|sum, n| sum + n } # 45
You can use inject to get a sum of values:
array = *array of Foo*
total = array.inject { |sum, x| sum + x.likes }
Related
So, I have an after_save hook on review model which calls calculate_specific_rating function of product model. The function goes like this:
def calculate_specific_rating
ratings = reviews.reload.all.pluck(:rating)
specific_rating = Hash.new(0)
ratings.each { |rating| specific_rating[rating] += 1 }
self.specific_rating = specific_rating
save
end
Right now, it returns
specific_rating => {
"2"=> 3, "4"=> 1
}
I want it to return like:
specific_rating => {
"1"=> 0, "2"=>3, "3"=>0, "4"=>1, "5"=>0
}
Also, is it okay to initialize a new hash everytime a review is saved? I want some alternative. Thanks
You can create a range from 1 until the maximum value in ratings plus 1 and start iterating through it, yielding an array where the first element is the current one, and the second element is the total of times the current element is present in ratings. After everything the result is converted to a hash:
self.specific_rating = (1..ratings.max + 1).to_h { |e| [e.to_s, ratings.count(e)] }
save
You could also do something like this -
def calculate_specific_rating
ratings = [1,2,3,4,5]
existing_ratings = reviews.group_by(&:rating).map{|k,v| [k, v.count]}.to_h
Hash[(ratings - existing_ratings.keys).map {|x| [x, 0]}].merge(existing_ratings)
end
which gives
{3=>0, 4=>0, 5=>0, 2=>3, 1=>1}
I have an array of rooms: rooms_array = [room1...roomn] and each room is a hash with respective details. Each room hash has an offers hash.
room1 = {...., offers=> {...},...}
Now I have another array of offers hashes.
avg_array = [[{offer1},{offer2}],[{offer4},{offer3}],....]
Length of both the hashes is same, so first array of avg_array is for room1, second for room2 and so on...
My problem is how do I add each array of avg_array into corresponding offers hash of rooms_array.
My attempt:
_rooms.values.map do |room|
if room[:offers].count > 1
i=0
room[:offers] = rooms_hash[i]
i = i + 1
end
end
Looks like you might be able to do something using Array.zip
rooms.zip(avg_array).map do |room,avg|
room[:offers] = avg
room
end
If you want to append to an existing array:
rooms.zip(avg_array).map do |room,avg|
room[:offers] ||= []
room[:offers].concat avg
room
end
see:
What's the 'Ruby way' to iterate over two arrays at once
I have a array which is inside a hash. I want know the result of the student (pass/fail) using the following array. First I have to match them with particular standard and compare their marks with the hash pass and fails. And I want to get the key pass or fail based on their mark. How to achieve this using Ruby?
array = [
{
:standard =>1
:pass=>{:tamil=>30,:eng=>25,:math=>35},
:fail=>{:tamil=>10,:eng=>15,:maths=>20}
},
{
:standard =>2,
:pass=>{:tamil=>40,:eng=>35,:math=>45},
:fail=>{:tamil=>20,:eng=>25,:maths=>30}
}
]
#student is assumed to be defined
standard = array.select {|standard| standard[:standard] == #student.standard}
eng_pass = #student.eng_mark >= standard[:pass][:eng]
eng_fail = #student.eng_mark <= standard[:fail][:eng]
return [eng_pass, eng_fail, whatever_else_you_want]
So on and forth for various topics.
The syntax in reading values from this structure is something like:
array[0][:pass][:eng]
and accordingly you can do the comparison as usual in batch:
for i in 0..#students_array.length
num = # student's score
standard = # something like array[0][:pass][:eng]
if num > standard
# something like 'put "You passed!"'
end
end
What is the best way to incrementally iterate through a pair of hashes in Ruby? Should I convert them to arrays? Should I go an entirely different direction? I am working on a problem where the code is supposed to determine what to bake, and in what quantities, for a bakery given 2 inputs. The number of people to be fed, and their favorite food. They bake 3 things (keys in my_list) and each baked item feeds a set number of people (value in my_list).
def bakery_num(num_of_people, fav_food)
my_list = {"pie" => 8, "cake" => 6, "cookie" => 1}
bake_qty = {"pie_qty" => 0, "cake_qty" => 0, "cookie_qty" => 0}
if my_list.has_key?(fav_food) == false
raise ArgumentError.new("You can't make that food")
end
index = my_list.key_at(fav_food)
until num_of_people == 0
bake_qty[index] = (num_of_people / my_list[index])
num_of_people = num_of_people - bake_qty[index]
index += 1
end
return "You need to make #{pie_qty} pie(s), #{cake_qty} cake(s), and #{cookie_qty} cookie(s)."
end
The goal is to output a list for the bakery that will result in no uneaten food. When doing the math, the modulo would then be divided into the next food item.
Thanks for the help.
What is the best way to incrementally iterate through a pair of hashes in Ruby?
Since the keys of bake_qty conveniently have a '_qty' appended to them from their corresponding keys in my_list, you can use this to your advantage:
max_value = my_list[fav_food]
my_list.each do |key,value|
next if max_value < value
qty = bake_qty[key+'_qty']
...
end
You could use 'inject' method.
until num_of_people == 0
num_of_people = my_list.inject(num_of_people) do |t,(k,v)|
if num_of_people > 0
bake_qty["#{key}_qty"] += num_of_people/v
t - v
end
end
You can sort your hash at the beginning to ensure that your first food is the fav food
I am in a broken spot. I was able to get the array from into #set1 and now need to compare #set1 with #set2 and see how many matches there are. I can get the #array1 to work correctly if I have static numbers in an array in #array2 but not when I make it dynamic.
I need a way to compare these two arrays and am at a loss now!
def show
#set1 = Set1.find(params[:id])
#set2 = Set2.where(:date => #set1.date)
#array1 = [Set1.find(params[:id]).let1, Set1.find(params[:id]).let2]
#array2 = [Winnings.where(:date => #set1.date).let1, Winnings.where(:date => #set1.date).let2]
#intersection = #array1 & #array2
end
I think part of the problem here is that you can make new objects with the same attributes but that do not respond properly to the comparisons that the intersection operator :& uses.
Example:
class Thing
attr_reader :first,:last
def initialize(first,last)
#first = first
#last = last
end
end
thing1 = Thing.new("John","Smith")
thing2 = Thing.new("John","Smith")
thing1 == thing2
# => false
[thing1] & [thing2]
# => []
You might consider mapping each array to some identifying value (maybe id) and finding the intersection of those arrays. That is to say
#set1 = Set1.find(params[:id])
#set2 = Set2.where(:date => #set1.date)
#array1 = [Set1.find(params[:id]).let1, Set1.find(params[:id]).let2]
#array2 = [Winnings.where(:date => #set1.date).let1, Winnings.where(:date => #set1.date).let2]
#array1.map{|obj| obj.id} & #array2.map{|obj| obj.id}
# => an array of unique object ids that are in both #array1 and #array2
Or, if you want the objects themselves...
(#array1.map{|obj| obj.id} & #array2.map{|obj| obj.id}).map{ |id| Set.find(id) }