How to merge same hashes in array? - ruby-on-rails

If I have:
array = [{:external_product_id=>"A", :quantity=>1}, {:external_product_id=>"A", :quantity=>2}, {:external_product_id=>"B", :quantity=>1}]
and want to transform it into:
array = [{:external_product_id=>"A", :quantity=>3}, {:external_product_id=>"B", :quantity=>1}]
i.e., merging products with the same id ("A") together. Is there any easier way to do this than using map, select, etc?

anything like this?
array.group_by { |item| item[:external_product_id] }
.map do |external_product_id, items|
{
external_product_id: external_product_id,
quantity: items.sum { |item| item[:quantity] }
}
end
=> [{:external_product_id=>"A", :quantity=>3}, {:external_product_id=>"B", :quantity=>1}]

Related

Sorting select results

I'm trying to sort c.description alphabetically but I'm not familiar with Ruby and can't seem to get .sort to work.
cakes.select do |cake|
cake.categories.pluck(:id).any? do |ca|
category.self_and_descendant_ids.include? ca
end.map { |c| { id: c.form_descriptor, name: "#{c.description}" } }
end
I don't believe the code you wrote would function if you ran it in IRB. Could we have a better example of your data structure? You probably want to stay in the database for this. It's not clear where your "category" variable is coming from. The internal any? followed by a map won't work (map operates on an enumerable, any? returns a boolean).
Your pluck(:id) is an N+1 and will round-trip the to the database for every cake, not including whatever self_and_descendant_ids is doing - seems like a modeling problem since you could just associate categories with cakes and be done with any notion of descendants but this all conjecture without knowing more about your data or what you're modeling.
Categories.joins(:cakes)
.where(cakes: { id: cake_ids })
.order(description: :asc)
.pluck(:form_descriptor, :description)
.map { |id, name| { id: id, name: name } }
If I were to sort the descriptions alphabetically, I could use
sorted_cakes = cakes.sort { |cake_a, cake_b| cake_a.description <=> cake_b.description }
If you wanted them in reverse alphabetical order, you can change it to cake_b.description <=> cake_a.description
Why not sort it before traversing? You can do like
cakes.select do |cake|
cake.categories.order('categories.description ASC').pluck(:id).any? do |ca|
category.self_and_descendant_ids.include? ca
end.map { |c| { id: c.form_descriptor, name: "#{c.description}" } }
end

Convert array of Model into an array of the specified Model.attribute

cars = Car.find(data).find_all{ |car| car.model == "Honda" }
this returns a list of Car's--I'd like to convert this list to a list that only contains the car.id's. How would I do it in a Ruby like way?
Simply call:
cars = Car.find(data).find_all{ |car| car.model == "Honda" }.map{ |car| car.id }
http://corelib.rubyonrails.org/classes/Array.html#M000427
You can do this:
car_ids = Car.find(data).find_all{ |car| car.model=="Honda" }.map{ |car| car.id }
Essentially, array.map { |x| f(x) } returns a new array of identical size, which contains the result of calling f on each of the original array's entries, in the same order.
I'd do it like that:
cars = Car.where(id: data, model: 'Honda').pluck(:id)
assuming that data is an array of car id's

Ruby way to group anagrams in string array

I implemented a function to group anagrams.
In a nutshell:
input: ['cars', 'for', 'potatoes', 'racs', 'four','scar', 'creams', scream']
output: [["cars", "racs", "scar"], ["four"], ["for"], ["potatoes"],["creams", "scream"]]
I would like to know if there is a better way to do this.
I really think I used too much repetition statements: until, select,
delete_if.
Is there any way to combine the select and delete_if statement? That
means, can selected items be automatically deleted?
Code:
def group_anagrams(words)
array = []
until words.empty?
word = words.first
array.push( words.select { |match| word.downcase.chars.sort.join.eql?(match.downcase.chars.sort.join ) } )
words.delete_if { |match| word.downcase.chars.sort.join.eql?(match.downcase.chars.sort.join ) }
end
array
end
Thanks in advance,
Like that:
a = ['cars', 'for', 'potatoes', 'racs', 'four','scar', 'creams', 'scream']
a.group_by { |element| element.downcase.chars.sort }.values
Output is:
[["cars", "racs", "scar"], ["for"], ["potatoes"], ["four"], ["creams", "scream"]]
If you want to you can turn this one-liner to a method of course.
You could use the partition function instead of select, implemented in Enumerable. It splits the entries within the array according to the decision-function into two arrays.
def group_anagrams(words)
array = []
until words.empty?
word = words.first
delta, words = words.partition { |match| word.downcase.chars.sort.join.eql?(match.downcase.chars.sort.join ) } )
array += delta
end
array
end
(untested)

Pulling from two arrays a non-matching object

I have two arrays. If an object in one array has no matching email attribute in the other, I want to build an array out of all those objects..
My attempts at attacking the dragon :
CardReferral.all.map(&:email) - CardSignup.all.map(&:email)
That almost does what I need! Unfortunately, it only supplies an email in an array. And I want the whole object.
t = CardSignup.all.map(&:email)
result = CardReferral.all.reject { |i| t.include? i.email }
Simplified example:
a = [:x, :y, :z]
b = [:a, :y, :b]
a.select { |e| ! b.include? e }
=> [:x, :z]
So I guess in your case it goes something like:
CardReferral.all.select { |e| ! CardSignup.all.include? e.email }
Or, incorporating feedback :-) ...
t = CardSignup.all
CardReferral.all.reject { |e| t.include? e.email }

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