how to group by in a array of hashes - ruby-on-rails

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)} }

Related

build a new array of hash from multiple array of hashes

I have following three array of hashes.
customer_mapping = [
{:customer_id=>"a", :customer_order_id=>"g1"},
{:customer_id=>"b", :customer_order_id=>"g2"},
{:customer_id=>"c", :customer_order_id=>"g3"},
{:customer_id=>"d", :customer_order_id=>"g4"},
{:customer_id=>"e", :customer_order_id=>"g5"}
]
customer_with_products = [
{:customer_order_id=>"g1", :product_order_id=>"a1"},
{:customer_order_id=>"g2", :product_order_id=>"a2"},
{:customer_order_id=>"g3", :product_order_id=>"a3"},
{:customer_order_id=>"g4", :product_order_id=>"a4"},
{:customer_order_id=>"g5", :product_order_id=>"a5"}
]
product_mapping = [
{:product_id=>"j", :product_order_id=>"a1"},
{:product_id=>"k", :product_order_id=>"a2"},
{:product_id=>"l", :product_order_id=>"a3"}
]
What i want is a new hash with only customer_id and product_id
{:product_id=>"j", :customer_id=>"a"},
{:product_id=>"k", :customer_id=>"b"},
{:product_id=>"l", :customer_id=>"c"}
I tried to loop over product_mapping and select the customer_order_id that match product_order_id in customer_with_products and then thought of looping over customer_mapping but not able to get desired output from the first step.
How can i achieve this?
Using
def merge_by(a,b, key)
(a+b).group_by { |h| h[key] }
.each_value.map { |arr| arr.inject(:merge) }
end
merge_by(
merge_by(customer_mapping, customer_with_products, :customer_order_id),
product_mapping,
:product_order_id
).select { |h| h[:product_id] }.map { |h| h.slice(:product_id, :customer_id) }
#=>[{:product_id=>"j", :customer_id=>"a"},
# {:product_id=>"k", :customer_id=>"b"},
# {:product_id=>"l", :customer_id=>"c"}]
Definitely not the cleanest solution, if your initial arrays come from SQL queries, I think those queries could be modified to aggregate your data properly.
merge_by(customer_mapping, customer_with_products, :customer_order_id)
# => [{:customer_id=>"a", :customer_order_id=>"g1", :product_order_id=>"a1"},
# {:customer_id=>"b", :customer_order_id=>"g2", :product_order_id=>"a2"},
# {:customer_id=>"c", :customer_order_id=>"g3", :product_order_id=>"a3"},
# {:customer_id=>"d", :customer_order_id=>"g4", :product_order_id=>"a4"},
# {:customer_id=>"e", :customer_order_id=>"g5", :product_order_id=>"a5"}]
Then merge it similarly with your last array and cleanup the result selecting only the elements for which :product_id was found, slicing wanted keys.
Alternatively, a much more readable solution, depending on your array sizes might be slower as it keeps iterating over the hashes:
product_mapping.map do |hc|
b_match = customer_with_products.detect { |hb| hb[:product_order_id] == hc[:product_order_id] }
a_match = customer_mapping.detect { |ha| ha[:customer_order_id] == b_match[:customer_order_id] }
[hc, a_match, b_match].inject(:merge)
end.map { |h| h.slice(:product_id, :customer_id) }
Following your handling of the problem the solution would be the following:
result_hash_array = product_mapping.map do |product_mapping_entry|
customer_receipt = customer_with_products.find do |customer_with_products_entry|
product_mapping_entry[:product_order_id] == customer_with_products_entry[:product_order_id]
end
customer_id = customer_mapping.find do |customer_mapping_entry|
customer_receipt[:customer_order_id] == customer_mapping_entry[:customer_order_id]
end[:customer_id]
{product_id: product_mapping_entry[:product_id], customer_id: customer_id}
end
Output
results_hash_array => [{:product_id=>"j", :customer_id=>"a"},
{:product_id=>"k", :customer_id=>"b"},
{:product_id=>"l", :customer_id=>"c"}]
Other option, starting from customer_mapping, one liner (but quite wide):
customer_mapping.map { |e| {customer_id: e[:customer_id], product_id: (product_mapping.detect { |k| k[:product_order_id] == (customer_with_products.detect{ |h| h[:customer_order_id] == e[:customer_order_id] } || {} )[:product_order_id] } || {} )[:product_id] } }
#=> [{:customer_id=>"a", :product_id=>"j"},
# {:customer_id=>"b", :product_id=>"k"},
# {:customer_id=>"c", :product_id=>"l"},
# {:customer_id=>"d", :product_id=>nil},
# {:customer_id=>"e", :product_id=>nil}]
cust_order_id_to_cust_id =
customer_mapping.each_with_object({}) do |g,h|
h[g[:customer_order_id]] = g[:customer_id]
end
#=> {"g1"=>"a", "g2"=>"b", "g3"=>"c", "g4"=>"d", "g5"=>"e"}
prod_order_id_to_cust_order_id =
customer_with_products.each_with_object({}) do |g,h|
h[g[:product_order_id]] = g[:customer_order_id]
end
#=> {"a1"=>"g1", "a2"=>"g2", "a3"=>"g3", "a4"=>"g4", "a5"=>"g5"}
product_mapping.map do |h|
{ product_id: h[:product_id], customer_id:
cust_order_id_to_cust_id[prod_order_id_to_cust_order_id[h[:product_order_id]]] }
end
#=> [{:product_id=>"j", :customer_id=>"a"},
# {:product_id=>"k", :customer_id=>"b"},
# {:product_id=>"l", :customer_id=>"c"}]
This formulation is particularly easy to test. (It's so straightforward that no debugging was needed).
I would recommended to rather take a longer but more readable solution which you also understand in some months from now by looking at it. Use full names for the hash keys instead of hiding them behind k, v for more complexe lookups (maybe its just my personal preference).
I would suggest somethink like:
result = product_mapping.map do |mapping|
customer_id = customer_mapping.find do |hash|
hash[:customer_order_id] == customer_with_products.find do |hash|
hash[:product_order_id] == mapping[:product_order_id]
end[:customer_order_id]
end[:customer_id]
{ product_id: mapping[:product_id], customer_id: customer_id }
end

Simplify code that adds dictionary value with highest count to an array

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

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

What is an elegant way to replace an element of an array based on a match criteria?

I am using the following logic to update a list item based on a criteria.
def update_orders_list(order)
#orders.delete_if{|o| o.id == order.id}
#orders << order
end
Ideally, I would have preferred these approaches:
array.find_and_replace(obj) { |o| conditon }
OR
idx = array.find_index_of { |o| condition }
array[idx] = obj
Is there a better way?
array.map { |o| if condition(o) then obj else o }
maybe?
As of 1.8.7, Array#index accepts a block. So your last example should work just fine with a minor tweak.
idx = array.index { |o| condition }
array[idx] = obj

Resources