2

I have two arrays of hashes

sent_array = [{:sellersku=>"0421077128", :asin=>"B00ND80WKY"},
{:sellersku=>"0320248102", :asin=>"B00WTEF9FG"}, 
{:sellersku=>"0324823180", :asin=>"B00HXZLB4E"}]

active_array = [{:price=>39.99, :asin1=>"B00ND80WKY"}, 
{:price=>7.99, :asin1=>"B00YSN9QOG"}, 
{:price=>10, :asin1=>"B00HXZLB4E"}]

I want to loop through sent_array, and find where the value in :asin is equal to the value in :asin1 in active_array, then copy the key & value of :price to sent_array. Resulting in this:

final_array = [{:sellersku=>"0421077128", :asin=>"B00ND80WKY", :price=>39.99},
{:sellersku=>"0320248102", :asin=>"B00WTEF9FG"}, 
{:sellersku=>"0324823180", :asin=>"B00HXZLB4E", :price=>10}]

I tried this, but I get a TypeError - no implicit conversion of Symbol into Integer (TypeError)

sent_array.each do |x|
 x.detect { |key, value| 
  if value == active_array[:asin1]
    x[:price] << active_array[:price]
  end
 }
end
ToddT
  • 3,084
  • 4
  • 39
  • 83

3 Answers3

2

For reasons of both efficiency and readability, it makes sense to first construct a lookup hash on active_array:

h = active_array.each_with_object({}) { |g,h| h[g[:asin1]] = g[:price] } 
  #=> {"B00ND80WKY"=>39.99, "B00YSN9QOG"=>7.99, "B00HXZLB4E"=>10}

We now merely step through sent_array, updating the hashes:

sent_array.each { |g| g[:price] = h[g[:asin]] if h.key?(g[:asin]) }
  #=> [{:sellersku=>"0421077128", :asin=>"B00ND80WKY", :price=>39.99},
  #    {:sellersku=>"0320248102", :asin=>"B00WTEF9FG"}, 
  #    {:sellersku=>"0324823180", :asin=>"B00HXZLB4E", :price=>10}] 

Retrieving a key-value pair from a hash (h) is much faster, of course, than searching for a key-value pair in an array of hashes.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • Thanks Cary! Worked like a charm.. and I took some time to figure out what the heck each_with_object does.. And correct me if I'm wrong, but first it creates a new hash, and in stepping thru active_array creates keys from the values in :asin1 and assigns :price as its value. – ToddT Jun 25 '15 at 14:42
  • Yes, and [Enumerable#each_with_object](http://ruby-doc.org/core-2.2.2/Enumerable.html#method-i-max_by) does one more thing:it returns the object. It therefore saves two steps over: `h={}; active_array.each { |g| ... }; h`. That method made its debut in v1.9. That's before my time--I was just a kid then--but I'm told that pre-1.9, [Enumerable#reduce](http://ruby-doc.org/core-2.2.2/Enumerable.html#method-i-reduce) (aka `inject`) would have been used here. (btw `reduce` still has many wondrous uses) – Cary Swoveland Jun 25 '15 at 17:06
0

This does the trick. Iterate over your sent array and attempt to find a record in your active_array that has that :asin. If you find something, set the price and you are done.

Your code I believe used detect/find incorrectly. What you want out of that method is the hash that matches and then do something with that. You were trying to do everything inside of detect.

sent_array.each do |sent|
  item = active_array.find{ |i| i.has_value? sent[:asin] }
  sent[:price] = item[:price] if item
end

 => [{:sellersku=>"0421077128", :asin=>"B00ND80WKY", :price=>39.99}, {:sellersku=>"0320248102", :asin=>"B00WTEF9FG"}, {:sellersku=>"0324823180", :asin=>"B00HXZLB4E", :price=>10}] 
Jeff Price
  • 3,229
  • 22
  • 24
0

I am assuming second element of both sent_array and active_array has B00WTEF9FG as asin and asin1 respectively. (seeing your final result)

Now:

a = active_array.group_by{|a| a[:asin1]}
b = sent_array.group_by{|a| a[:asin]}
a.map { |k,v|
   v[0].merge(b[k][0])
}

#  => [{:price=>39.99, :asin1=>"B00ND80WKY", :sellersku=>"0421077128", :asin=>"B00ND80WKY"}, {:price=>7.99, :asin1=>"B00WTEF9FG", :sellersku=>"0320248102", :asin=>"B00WTEF9FG"}, {:price=>10, :asin1=>"B00HXZLB4E", :sellersku=>"0324823180", :asin=>"B00HXZLB4E"}] 

Why were you getting TypeError?

You are doing active_array[:asin1]. Remember active_array itself is an Array. Unless you iterate over it, you cannot look for keys.

Another issue with your approach is, you are using Hash#detect

find is implemented in terms of each. And each, when called on a Hash, returns key-value pairs in form of arrays with 2 elements each. That's why find returns an array.

source

Same is true for detect. So x.detect { |key, value| .. } is not going to work as you are expecting it to.

Solution without assumption

a.map { |k,v|
   b[k] ? v[0].merge(b[k][0]) : v[0]
}.compact
# => [{:price=>39.99, :asin1=>"B00ND80WKY", :sellersku=>"0421077128", :asin=>"B00ND80WKY"}, {:price=>7.99, :asin1=>"B00YSN9QOG"}, {:price=>10, :asin1=>"B00HXZLB4E", :sellersku=>"0324823180", :asin=>"B00HXZLB4E"}] 

Here since asin1 => "B00ND80WKY" has no match, it cannot get sellersku from other hash.

Community
  • 1
  • 1
shivam
  • 16,048
  • 3
  • 56
  • 71
  • @Jeff Price.. Shivam, good points. thank you. Yes I'm still getting the TypeError in "Jeff Price's" solution. But your solution doesn't work either because sent_array is a subset of active_array.. any other ideas? – ToddT Jun 24 '15 at 20:31
  • @ToddT thats because your input is incorrect. I already mentioned that in the first line of my answer. Im making an assumption. – shivam Jun 24 '15 at 20:38
  • @ToddT I have updated my solution for the given case too. Please check – shivam Jun 24 '15 at 20:45
  • Shoot.. that doesn't work either.. I'm trying to figure this out, but I'm lost.. basically the results of the group_by is exactly the same as the result from the map. It looks like this: B0039UTCYI"=>[{:price=>"79", :asin1=>"B0039UTCYI"}] so the missing piece now is :sellersku – ToddT Jun 25 '15 at 01:48