How to find the max value in an array of hashes? - ruby-on-rails

Given this array of hashes here:
arr = [{:question_type=>"Fire", :total=>0.0}, {:question_type=>"Water", :total=>0.0}, {:question_type=>"Metal", :total=>0.0}, {:question_type=>"Earth", :total=>0.0}, {:question_type=>"Wood", :total=>100.0}]
I would like to pick the hash with the highest value for the total key.
So the code bellow seems to do the work
max = arr.max_by{|x| x[:total]}
puts max[:question_type]
#=> Wood
However if I have 2 hashes with the same value it will return the first one only
arr2 = [{:question_type=>"Fire", :total=>0.0}, {:question_type=>"Water", :total=>0.0}, {:question_type=>"Metal", :total=>0.0}, {:question_type=>"Earth", :total=>50.0}, {:question_type=>"Wood", :total=>50.0}]
max = arr2.max_by{|x| x[:total]} #it should be arr2
puts max[:question_type]
#=> Earth
What would be the best way to get it to return Earth and Wood in case both are the highest values?

You can do this with group_by and max:
arr.group_by { |x| x[:total] }.max.last

You could do this in tow steps this way:
max = arr.max_by{|x| x[:total]}
max = arr.select{ |x| x[:total] == max[:total }

You can always just take that max value and select.
arr = [{:question_type=>"Fire", :total=>0.0}, {:question_type=>"Water", :total=>0.0}, {:question_type=>"Metal", :total=>0.0}, {:question_type=>"Earth", :total=>50.0}, {:question_type=>"Wood", :total=>50.0}]
max = arr.max_by{|x| x[:total]}
max_values = arr.select{|hash| hash[:total] == max[:total]}

An alternative approach to some solid answers already posted here is to roll your own method to retrieve the max values, including multiples if there are ties:
def get_max(arr)
result = []
current_max = 0.0
arr.each do |hash|
if hash[:total] > current_max
result = [hash[:question_type]]
current_max = hash[:total]
elsif hash[:total] == current_max
result.push(hash[:question_type])
end
end
result
end
arr = [{:question_type=>"Fire", :total=>0.0}, {:question_type=>"Water", :total=>0.0}, {:question_type=>"Metal", :total=>0.0}, {:question_type=>"Earth", :total=>50.0}, {:question_type=>"Wood", :total=>50.0}]
puts get_max(arr)
# => ["Earth", "Wood"]
It may not be as succinct as using something like #max_by and #select, but the benefit of the above approach is you only iterate through the array once.
Hope it helps!

Related

How to loop through arrays of different length in Ruby?

Let's say i have two relation arrays of a user's daily buy and sell.
how do i iterate through both of them using .each and still let the the longer array run independently once the shorter one is exhaused. Below i want to find the ratio of someone's daily buys and sells. But can't get the ratio because it's always 1 as i'm iterating through the longer array once for each item of the shorter array.
users = User.all
ratios = Hash.new
users.each do |user|
if user.buys.count > 0 && user.sells.count > 0
ratios[user.name] = Hash.new
buy_array = []
sell_array = []
date = ""
daily_buy = user.buys.group_by(&:created_at)
daily_sell = user.sells.group_by(&:created_at)
daily_buy.each do |buy|
daily_sell.each do |sell|
if buy[0].to_date == sell[0].to_date
date = buy[0].to_date
buy_array << buy[1]
sell_array << sell[1]
end
end
end
ratio_hash[user.name][date] = (buy_array.length.round(2)/sell_array.length)
end
end
Thanks!
You could concat both arrays and get rid of duplicated elements by doing:
(a_array + b_array).uniq.each do |num|
# code goes here
end
Uniq method API
daily_buy = user.buys.group_by(&:created_at)
daily_sell = user.sells.group_by(&:created_at
buys_and_sells = daily_buy + daily_sell
totals = buys_and_sells.inject({}) do |hsh, transaction|
hsh['buys'] ||= 0;
hsh['sells'] ||= 0;
hsh['buys'] += 1 if transaction.is_a?(Buy)
hsh['sells'] += 1 if transaction.is_a?(Sell)
hsh
end
hsh['buys']/hsh['sells']
I think the above might do it...rather than collecting each thing in to separate arrays, concat them together, then run through each item in the combined array, increasing the count in the appropriate key of the hash returned by the inject.
In this case you can't loop them with each use for loop
this code will give you a hint
ar = [1,2,3,4,5]
br = [1,2,3]
array_l = (ar.length > br.length) ? ar.length : br.length
for i in 0..array_l
if ar[i] and br[i]
puts ar[i].to_s + " " + br[i].to_s
elsif ar[i]
puts ar[i].to_s
elsif br[i]
puts br[i].to_s
end
end

creating an array using find method in rails

input = {"color"=>["red"],"size"=>["s","l"]}
json_obj = [{"color":"red","id":"123","size":"s","name":"test"},
{"color":"yellow","id":"124","size":"s","name":"test"},
{"color":"red","id":"125","size":"l","name":"test"}]
Output should be
output["red_s"] = {"color":"red","id":"123","size":"s","name":"test"}
output["red_l"] = {"color":"red","id":"125","size":"l","name":"test"}
output is the combinations of the input and a find on the json_obj.
How to get the output in rails?
I have the below script to get the combinations ie.red_s and red_l,
ary = input.map {|k,v| [k].product v}
output = ary.shift.product(*ary).map {|a| Hash[a]}
And
output[red_s]=json_obj.find{|h| h["color"] == "red" and h["size"] == "S"}
I don't want to have any hardcodings in code like color and size as above.
I think this should get you close to what you want.
Note the "ticks" around your json array object (what you had is not valid ruby)
The other issue is you would have to figure a better way to create the output hash key.
require 'json'
input = {"color"=>["red"],"size"=>["s","l"]}
output = {}
json_obj = '[{"color":"red","id":"123","size":"s","name":"test"},
{"color":"yellow","id":"124","size":"s","name":"test"},
{"color":"red","id":"125","size":"l","name":"test"}]'
found = JSON.parse json_obj
input.each_key do |key|
found = found.select { |item| input[key].include?(item[key]) }
end
puts found
found.each do |item|
output_key = ""
input.each_key do |key|
output_key = "#{item[key]}_" + output_key
end
output["#{output_key}"] = item.to_json
end
puts output

Creating a new hash with default keys

I want to create a hash with an index that comes from an array.
ary = ["a", "b", "c"]
h = Hash.new(ary.each{|a| h[a] = 0})
My goal is to start with a hash like this:
h = {"a"=>0, "b"=>0, "c"=>0}
so that later when the hash has changed I can reset it with h.default
Unfortunately the way I'm setting up the hash is not working... any ideas?
You should instantiate your hash h first, and then fill it with the contents of the array:
h = {}
ary = ["a", "b", "c"]
ary.each{|a| h[a] = 0}
Use the default value feature for the hash
h = Hash.new(0)
h["a"] # => 0
In this approach, the key is not set.
h.key?("a") # => false
Other approach is to set the missing key when accessed.
h = Hash.new {|h, k| h[k] = 0}
h["a"] # => 0
h.key?("a") # => true
Even in this approach, the operations like key? will fail if you haven't accessed the key before.
h.key?("b") # => false
h["b"] # => 0
h.key?("b") # => true
You can always resort to brute force, which has the least boundary conditions.
h = Hash.new.tap {|h| ["a", "b", "c"].each{|k| h[k] = 0}}
h.key?("b") # => true
h["b"] # => 0
You can do it like this where you expand a list into zero-initialized values:
list = %w[ a b c ]
hash = Hash[list.collect { |i| [ i, 0 ] }]
You can also make a Hash that simply has a default value of 0 for any given key:
hash = Hash.new { |h, k| h[k] = 0 }
Any new key referenced will be pre-initialized to the default value and this will avoid having to initialize the whole hash.
This may not be the most efficient way, but I always appreciate one-liners that reveal a little more about Ruby's versatility:
h = Hash[['a', 'b', 'c'].collect { |v| [v, 0] }]
Or another one-liner that does the same thing:
h = ['a', 'b', 'c'].inject({}) {|h, v| h[v] = 0; h }
By the way, from a performance standpoint, the one-liners run about 80% of the speed of:
h = {}
ary = ['a','b','c']
ary.each { |a| h[a]=0 }
Rails 6 added index_with on Enumerable module. This will help in creating a hash from an enumerator with default or fetched values.
ary = %w[a b c]
hash = ary.index_with(0) # => {"a"=>0, "b"=>0, "c"=>0}
Another option is to use the Enum#inject method which I'm a fan of for its cleanliness. I haven't benchmarked it compared to the other options though.
h = ary.inject({}) {|hash, key| hash[key] = 0; hash}
Alternate way of having a hash with the keys actually added
Hash[[:a, :b, :c].zip([])] # => {:a=>nil, :b=>nil, :c=>nil}
Hash[[:a, :b, :c].zip(Array.new(3, 0))] # => {:a=>0, :b=>0, :c=>0}

Split a string into an array of numbers

My string:
>> pp params[:value]
"07016,07023,07027,07033,07036,07060,07062,07063,07065,07066,07076,07081,07083,07088,07090,07092,07201,07202,07203,07204,07205,07206,07208,07901,07922,07974,08812,07061,07091,07207,07902"
How can this become an array of separate numbers like :
["07016", "07023", "07033" ... ]
result = params[:value].split(/,/)
String#split is what you need
Try this:
arr = "07016,07023,07027".split(",")
Note that what you ask for is not an array of separate numbers, but an array of strings that look like numbers. As noted by others, you can get that with:
arr = params[:value].split(',')
# Alternatively, assuming integers only
arr = params[:value].scan(/\d+/)
If you actually wanted an array of numbers (Integers), you could do it like so:
arr = params[:value].split(',').map{ |s| s.to_i }
# Or, for Ruby 1.8.7+
arr = params[:value].split(',').map(&:to_i)
# Silly alternative
arr = []; params[:value].scan(/\d+/){ |s| arr << s.to_i }

ruby looping question

I want to make a loop on a variable that can be altered inside of the loop.
first_var.sort.each do |first_id, first_value|
second_var.sort.each do |second_id, second_value_value|
difference = first_value - second_value
if difference >= 0
second_var.delete(second_id)
else
second_var[second_id] += first_value
if second_var[second_id] == 0
second_var.delete(second_id)
end
first_var.delete(first_id)
end
end
end
The idea behind this code is that I want to use it for calculating how much money a certain user is going to give some other user. Both of the variables contain hashes. The first_var is containing the users that will get money, and the second_var is containing the users that are going to pay. The loop is supposed to "fill up" a user that should get money, and when a user gets full, or a user is out of money, to just take it out of the loop, and continue filling up the rest of the users.
How do I do this, because this doesn't work?
Okay. What it looks like you have is two hashes, hence the "id, value" split.
If you are looping through arrays and you want to use the index of the array, you would want to use Array.each_index.
If you are looping through an Array of objects, and 'id' and 'value' are attributes, you only need to call some arbitrary block variable, not two.
Lets assume these are two hashes, H1 and H2, of equal length, with common keys. You want to do the following: if H1[key]value is > than H2[key]:value, remove key from H2, else, sum H1:value to H2:value and put the result in H2[key].
H1.each_key do |k|
if H1[k] > H2[k] then
H2.delete(k)
else
H2[k] = H2[k]+H1[k]
end
end
Assume you are looping through two arrays, and you want to sort them by value, and then if the value in A1[x] is greater than the value in A2[x], remove A2[x]. Else, sum A1[x] with A2[x].
b = a2.sort
a1.sort.each_index do |k|
if a1[k] > b[k]
b[k] = nil
else
b[k] = a1[k] + b[k]
end
end
a2 = b.compact
Based on the new info: you have a hash for payees and a hash for payers. Lets call them ees and ers just for convenience. The difficult part of this is that as you modify the ers hash, you might confuse the loop. One way to do this--poorly--is as follows.
e_keys = ees.keys
r_keys = ers.keys
e = 0
r = 0
until e == e_keys.length or r == r_keys.length
ees[e_keys[e]] = ees[e_keys[e]] + ers[r_keys[r]]
x = max_value - ees[e_keys[e]]
ers[r_keys[r]] = x >= 0 ? 0 : x.abs
ees[e_keys[e]] = [ees[e_keys[e]], max_value].min
if ers[r_keys[r]] == 0 then r+= 1 end
if ees[e_keys[e]] == max_value then e+=1 end
end
The reason I say that this is not a great solution is that I think there is a more "ruby" way to do this, but I'm not sure what it is. This does avoid any problems that modifying the hash you are iterating through might cause, however.
Do you mean?
some_value = 5
arrarr = [[],[1,2,5],[5,3],[2,5,7],[5,6,2,5]]
arrarr.each do |a|
a.delete(some_value)
end
arrarr now has the value [[], [1, 2], [3], [2, 7], [6, 2]]
I think you can sort of alter a variable inside such a loop but I would highly recommend against it. I'm guessing it's undefined behaviour.
here is what happened when I tried it
a.each do |x|
p x
a = []
end
prints
1
2
3
4
5
and a is [] at the end
while
a.each do |x|
p x
a = []
end
prints nothing
and a is [] at the end
If you can I'd try using
each/map/filter/select.ect. otherwise make a new array and looping through list a normally.
Or loop over numbers from x to y
1.upto(5).each do |n|
do_stuff_with(arr[n])
end
Assuming:
some_var = [1,2,3,4]
delete_if sounds like a viable candidate for this:
some_var.delete_if { |a| a == 1 }
p some_var
=> [2,3,4]

Resources