0

I have two arrays of hashes. Here's the structure of the arrays:

array_1 = [{:address=>"123 Main St", :locality=>"New York",
            :neighbourhood=>"Main", :lot_front=>18.0, :lot_depth=>37.0,
            :property_type=>"Detached", :asking_price=>156000}]

array_2 = [{:address=>"121 Orchard Heights Dr", :locality=>"Clarington",
            :neighbourhood=>"Newcastle", :lot_front=>19.0, :lot_depth=>46.0,
            :property_type=>"Detached", :closed_price=>270000,
            :sold_date=>"2013-04-02"}]

My goal is to do the following:

  • For every item in array_1, find all items in array_2 that have the same values for :locality and the :neighbourhood. Then....find the average of all the values of :closed_price of the items from the search.

I'd like the code to loop through all the items in array_1 and execute the above logic.

I'm a beginner and have tried everything I know. Nothing has worked.

Thanks for your help.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
itsmemrit
  • 15
  • 5
  • I formatted your code and changed the labels for the arrays to variables (so readers can refer to the variables in code they may suggest without having to define them). Each array constains a single element (a hash), and you forgot the closing brackets (`]`). The example would be better if the arrays contained more than a single element. – Cary Swoveland Dec 16 '20 at 00:44

3 Answers3

0

Try the following:

array_1.each do |el|
    close_addresses = array_2.filter { |sub_el| el[:locality] == sub_el[:locality] && el[:neighbourhood] == sub_el[:neighbourhood] }

    if close_addresses.any?
        el[:expected_price] = close_addresses.instance_eval { reduce(0) { |sum, sub_el| sum + sub_el[:closed_price] } / size }
    end
end

Added the :expected_price key to the array_1 elements to save the average :closed_price values.

Edgar Marques
  • 46
  • 1
  • 2
  • Can you explain what the .instance_eval does? – itsmemrit Dec 16 '20 at 04:38
  • instance_eval evaluates a given block with the contect of the receiver object. In this example it would be the same as writing: close_addresses.reduce(0) { ... } / close_addresses.size }. Its a way to avoid calling the close_addresses variable twice. – Edgar Marques Dec 16 '20 at 12:22
0
array_1 = [{:address=>"123 Main St", :locality=>"New York",
            :neighbourhood=>"Main", :lot_front=>18.0, :lot_depth=>37.0,
            :property_type=>"Detached", :asking_price=>156000},
           {:address=>"321 Niam St", :locality=>"New York",
            :neighbourhood=>"Niam", :lot_front=>18.0, :lot_depth=>37.0,
            :property_type=>"Unattached", :asking_price=>100000}]

array_2 = [{:address=>"121 Orchard Heights Dr", :locality=>"New York",
            :neighbourhood=>"Main", :lot_front=>19.0, :lot_depth=>46.0,
            :property_type=>"Detached", :closed_price=>270000,
            :sold_date=>"2013-04-02"},
           {:address=>"121 Orchard Heights Dr", :locality=>"New York",
            :neighbourhood=>"Niam", :lot_front=>19.0, :lot_depth=>46.0,
            :property_type=>"Detached", :closed_price=>600000,
            :sold_date=>"2013-04-02"},
           {:address=>"121 Orchard Heights Dr", :locality=>"New York",
            :neighbourhood=>"Main", :lot_front=>19.0, :lot_depth=>46.0,
            :property_type=>"Detached", :closed_price=>400000,
            :sold_date=>"2013-04-02"}]

First construct a hash h2 whose keys are distinct pairs of the values of :locality and neighborhourhood in array_2 and whose values are the elements of h2 (hashes) whose values for those keys correspond to the elements of the key:

h2 = array_2.each_with_object(Hash.new { |h,k| h[k] = [] }) do |g,h|      
  h[[g[:locality], g[:neighbourhood]]] << g
end
  #=> {["New York", "Main"]=>[
  #      {:address=>"121 Orchard Heights Dr", :locality=>"New York",
  #       :neighbourhood=>"Main", :lot_front=>19.0, :lot_depth=>46.0,
  #       :property_type=>"Detached", :closed_price=>270000,
  #       :sold_date=>"2013-04-02"},
  #      {:address=>"121 Orchard Heights Dr", :locality=>"New York",
  #       :neighbourhood=>"Main", :lot_front=>19.0, :lot_depth=>46.0,
  #       :property_type=>"Detached", :closed_price=>400000,
  #       :sold_date=>"2013-04-02"}
  #    ],
  #    ["New York", "Niam"]=>[
  #      {:address=>"121 Orchard Heights Dr", :locality=>"New York",
  #       :neighbourhood=>"Niam", :lot_front=>19.0, :lot_depth=>46.0,
  #       :property_type=>"Detached", :closed_price=>600000,
  #       :sold_date=>"2013-04-02"}
  #    ]
  #   } 

See the form of Hash::new that takes a block.

Now merge each element (hash) of array_1 with a hash having the single key :closed_price whose value is the average of the values of :close_price taken over all elements in h2 for which the values of :locality and neighborhourhood are the same as those for the element of array_1 being matched:

array_1.map do |g|
  a2s = h2[ [g[:locality], g[:neighbourhood]] ]
  g.merge( closed_avg: a2s.sum { |g| g[:closed_price] }.fdiv(a2s.size) )
end
  [{:address=>"123 Main St", :locality=>"New York",
   :neighbourhood=>"Main", :lot_front=>18.0, :lot_depth=>37.0,
   :property_type=>"Detached", :asking_price=>156000, :closed_avg=>335000.0},
   {:address=>"321 Niam St", :locality=>"New York",
    :neighbourhood=>"Niam", :lot_front=>18.0, :lot_depth=>37.0,
    :property_type=>"Unattached", :asking_price=>100000, :closed_avg=>600000.0}]

See Hash#merge and Numeric#fdiv. merge does not mutate (modify) any of the elements (hashes) of array_1.

The construction of h2 is effectively the same as the following:

h2 = {}
array_2.each do |g|
  arr = [g[:locality], g[:neighbourhood]]
  h2[arr] = [] unless h2.key?(arr)
  h2[arr] << g
end
h2      
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
0

Try this :)

I tried to be as simple and readable as possible on this solution.

def average(array)
  array.sum.to_f / array.size.to_f
end

array_1.map do |a|
  closed_prices = array_2.select do |b|
    a[:locality] == b[:locality] and a[:neighbourhood] == b[:neighbourhood]
  end.map do |matched|
    matched[:closed_price]
  end

  average(closed_prices)
end

I used the following hashes to test this code:

I removed the extra keys from the hashes to simplify, removing noises.

array_1 = [
  {
    :locality => "New York",
    :neighbourhood => "Main"
  },
  {
    :locality => "Clarington",
    :neighbourhood => "Newcastle"
  },
]

array_2 = [
  {
    :locality => "Clarington",
    :neighbourhood => "Newcastle",
    :closed_price => 270000
  },
  {
    :locality => "New York",
    :neighbourhood => "Main",
    :closed_price => 100000
  },
  {
    :locality => "New York",
    :neighbourhood => "Main",
    :closed_price => 200000
  }
]

This would return the array

[150000.0, 270000.0]

Which is the average of the :closed_price values for all the items of the array_2 that have the same :locality and :neighbourhood for each item of the array_1. This resulting array has two elements on this case because the array_1 has two elements.

You can also monkey patch the Array class to implement the avg() method right there if you want on this solution!

This will make the code much more simple.

Like this:

array_1.map do |a|
  array_2.select do |b|
    a[:locality] == b[:locality] and a[:neighbourhood] == b[:neighbourhood]
  end.map do |matched|
    matched[:closed_price]
  end.avg
end

I explained more about monkey patching right here if you desire: How do I create an average from a Ruby array?

Victor
  • 1,904
  • 18
  • 18
  • 1
    Thank you, Victor! will try thiss hortly – itsmemrit Dec 18 '20 at 03:21
  • U're welcome! Tell me later if this solution worked for your case :) – Victor Dec 18 '20 at 05:13
  • You can also use `.reject(&:nan?)` on the resulting array if you want to remove `NaN` values. The `NaN` values will happen if it was not possible to find items on the `array_2` that have the same values for `:locality` and the `:neighbourhood` as `array_1`. – Victor Dec 18 '20 at 05:21
  • Nice!! So, can you please accept this answer as a correct answer? It will help other people with similar doubts! Thanks dude! – Victor Feb 16 '21 at 01:16