Simplify code that adds dictionary value with highest count to an array - ruby-on-rails

I have an array of individual animals, and an array of relevant species. I want to add the higher of cats and dogs to my array of relevant species. The array of individual animals may not have the requested species, in which case species_count returns {}.
species_count takes in an array of animals and groups them by species. Example:
animals = ['chihuaha', 'german_shepherd', 'golden_retriever', 'tabby cat', 'siamese cat'}
species_count(animals, DOG) = { species: 'dog', count: 3 }
species_count(animals, CAT) = { species: 'cat', count: 3 }
species_count(animals, MOUSE) = {}
The below can be improved, I think. Ruby has all sorts of magical methods that surprise me.
dogs = species_count(animals, DOG)
dog_count = dogs.fetch(:count, 0)
cats = species_count(animals, CAT)
cat_count = cats.fetch(:count, 0)
if dog_count >= cat_count && dog_count >= 3
relevant_species << dogs
elsif cat_count >= 3
relevant_species << cats
end

Something like this will probably be the way to do it:
Simplified code:
relevant_species = [DOG, CAT, MOUSE]
.map { |animal| species_count(animals, animal) }
.sort { |a, b| a[:count].to_i <=> b[:count].to_i }
.last
OR step by step:
# returns array of [{ species: 'dog', count: 3 }, ... ]
species_counts = [DOG, CAT, MOUSE].map { |animal| species_count(animals, animal) }
# sorts the array based on the count value. to_i is to account for nils
sorted_species_counts = species_counts.sort { |a, b| a[:count].to_i <=> b[:count].to_i }
# returns the last element (with the highest count value) to be assigned to relevant species
relevant_species = sorted_species_counts.last

Related

ruby hash searching for two criterias

grades = [
{:student=>"James", :age=>19, :score=>85},
{:student=>"Kate", :age=>19, :score=>92},
{:student=>"Sara", :age=>20, :score=>74},
{:student=>"Riley", :age=>20, :score=>85},
{:student=>"patrick", :age=>20, :score=>96},
{:student=>"luke", :age=>21, :score=>88},
{:student=>"susie", :age=>21, :score=>90}
]
I am trying to get the student with the highest score that is the age 20 but only can sort the highest by all the students. Does any one know how to limit the max_by to only students that are 20 from the above hash ?
filter for people with age 20
find the maximum score
grades.select { |person| person[:age] == 20 }.max_by { |person| person[:score] }
My objective is to make a single pass through the hashes.
Initially I posted the following, but it was incorrect because it returns the name of a student who is not 20 if there are no students of age 20.
grades.max { |h| h[:age] == 20 ? h[:score] :
-Float::INFINITY }[:student]
Here is my revised answer:
def best20(grades)
student = nil
highest = -Float::INFINITY
grades.each do |h|
if h[:age] == 20 && h[:score] > highest
highest = h[:score]
student = h[:student]
end
end
student
end
best20 grades
#=> "patrick"
best20 [{:student=>"James", :age=>19, :score=>85}]
#=> nil
Just as idea.
Suppose we have several 20-year-old students with the same maximum score.
In this case variants of Cary and Ursus return only first person.
Therefore, such a thought occurred to me:
grades = [
{:student=>"James", :age=>19, :score=>85},
{:student=>"Kate", :age=>19, :score=>92},
{:student=>"Sara", :age=>20, :score=>74},
{:student=>"Riley", :age=>20, :score=>85},
{:student=>"Patrick", :age=>20, :score=>96},
{:student=>"Vladimir", :age=>20, :score=>96},
{:student=>"Luke", :age=>21, :score=>88},
{:student=>"Susie", :age=>21, :score=>90}
]
def best20(grades)
students20 = grades.select { |r| r[:age] == 20 }
return nil if students20.empty?
max_score20 = students20.max_by { |r| r[:score] }[:score]
students20.select { |r| r[:score] == max_score20 }.map { |r| r[:student] }
end
best20 grades
# => ["Patrick", "Vladimir"]
best20 [{:student=>"James", :age=>19, :score=>85}]
# => nil
Do not judge strictly, this is just an idea.

Assign each array element attribute with a value from another array respectively

I am trying to assign each person an age value from a list with same size.
class Person
attr_accessor :age
end
a = [person1, person2, person3, person4, person5]
b = [1,2,3,4,5]
How can I do the assignment below using a neat way(without using index i)?
i = 0
a.each do |p|
p.age = b[i]
i += 1
end
If they are guaranteed to be the same length, then you can use zip:
a.zip(b).each do |p, age|
p.age = age
end
As #ardavis pointed out, zip takes a block so you can remove the .each.
I know you asked for a solution without an index, but note that your code can be made neater even with an index. In Ruby, you don't need to define and increment your own index. Instead, you can use with_index like so:
a.each.with_index do |p, i|
p.age = b[i]
end
You can use index (as each Person instance is going to be unique):
a.each { |ai| ai.age = b[a.index(ai)] }
Demonstration
P.S. I would go with the approach introduced by #ardavis, using just zip:
a.zip(b) { |a, b| a.age = b }

Ruby on rails How to find first item in array that matches items in another array

I have a leaderboard array that looks something like this:
[{:member=>"1", :score=>7.0, :rank=>1}, {:member=>"5", :score=>6.0, :rank=>2}, {:member=>"4", :score=>5.0, :rank=>3}, {:member=>"3", :score=>4.0, :rank=>4}, {:member=>"2", :score=>3.0, :rank=>5}]
I also have an array of active user ids [3,5].
How can I get the member number of the highest ranked active user and assign that to a variable? The leaderboard array will always be in order of rank.
One method would be to reduce your array to only entries who's :member is also in the active user IDs array, and then take the first element of that array:
leaderboard = [...]
active_user_ids = [3,5]
leaderboard.take_while{ |m| active_user_ids.include?(m[:member].to_i) }.first
leaderboard = [{:member=>"1", :score=>7.0, :rank=>1},
{:member=>"5", :score=>6.0, :rank=>2},
{:member=>"4", :score=>5.0, :rank=>3},
{:member=>"3", :score=>4.0, :rank=>4},
{:member=>"2", :score=>3.0, :rank=>5}]
active_members = [3,5]
highest_ranked_active_member = leaderboard.
select { |h| active_members.include? h[:member].to_i }.
min_by { |h| h[:rank] }[:member]
#=> "5"
This is a case where I love creating reusable lambdas to reuse code when querying the data.
active_members = [3,5]
active_member = -> member { active_members.include? member[:member].to_i }
member_score = -> member { member[:score] }
leader_board = ...
# Find first active_member:
p leader_board.find(&active_member) #=> {:member=>"5", :score=>6.0, :rank=>2}
# Find active member with lowest and highest score:
p leader_board.select(&active_member).minmax_by(&member_score) #=> [{:member=>"3", :score=>4.0, :rank=>4}, {:member=>"5", :score=>6.0, :rank=>2}]
list = [
{:member=>"1", :score=>7.0, :rank=>1},
{:member=>"5", :score=>6.0, :rank=>2},
{:member=>"4", :score=>5.0, :rank=>3},
{:member=>"3", :score=>4.0, :rank=>4},
{:member=>"2", :score=>3.0, :rank=>5}
]
list.select { |item| [3,5].include? item[:member].to_i }.max { |item| item[:rank] }[:member] => "3"

ActiveRecord query from an array of hash

I have an array of hash.
eg) array_of_hash = [ { id: 20, name: 'John' }, { id: 30, name: 'Doe'} ]
I would like to get records which match all the criteria in a particular hash.
So the query I want to get executed is
SELECT persons.* FROM persons WHERE persons.id = 20 AND persons.name = 'John' OR persons.id = 30 AND persons.name = 'Doe'
What is the best way to construct this query from an array of hash?
I think this is okay:
ids = array_of_hash.map { |h| h[:id] }
names = array_of_hash.map { |h| h[:name] }
Person.where(id: ids, name: names)
(although it's not super generic)
other attempt:
people = Person.all
array_of_hash.each do |h|
people = people.where(h)
end
people # => will generate a long long query.
Try to map all conditions to your ActiveRecord model independently and flatten the result array afterwards:
array_of_hash.map{ |where_clause| Person.where(where_clause) }.flatten

how to group by in a array of hashes

I have a array of hashes
and I need to be able to group them by users.
order = Order.find(options[:order_id])
shipments = order.shipments.select {|shipment| shipment["roundtrip_shipment"] == nil }.collect {|o| {
id: o.id,
user_id: o.user_id,
...
} }
I am having some problems in making a group by in the array.
I have tried to do shipments = order.shipments.group_by { |shipment| shipment[:user_id] }.count
but this always returns 1 when I know that have 2 users here
Thanks for all the help
You can simply do as follow:
order = Order.find(options[:order_id])
shipments = order.shipments.where.not(roundtrip_shipment: nil).group_by(&:user_id).collect {|x,y| {x => y.map(&:id)} }

Resources