Home > OS >  Switch in different hash values to a combination array, and perform a calculation that ranks the fir
Switch in different hash values to a combination array, and perform a calculation that ranks the fir

Time:01-29

I have a few arrays that contain the same value, but have different keys associated to them. In this case, I have names of Teams and Drivers that have an associated Price and Points value. I have used 4 for each but there could be any number in either, both being decimals.

# DRIVER AND TEAM ARRAYS
driver_points = { "john" => 20.1, "mike" => 19.3, "paul" => 15.6, "mark" => 1.1 }
driver_price = { "john" => 4.0, "mike" => 5.0, "paul" => 6.0, "mark" => 2.1 }
team_points = { "cowboys" => 20.1, "bears" => 19.3, "lions" => 15.6, "united" => 2.8 }
team_price = { "cowboys" => 1.0, "bears" => 2.0, "lions" => 3.0, "united" => 2.4 }

I have turned the driver_price and team_price hashes into an array that gives me all the combinations less than or equal to a target value. With the condition that it can only contain 1 team and 3 drivers. I now want to rank those price combinations, by highest points. So I'm hoping there is a way that, in my current combinations I can substitute in the equivalent points value for each key, sum the points, and then rank the combinations by the points. I'd also like that rather than my combinations containing numbers that the Keys are used instead.

Here is my current code (giving me all the combinations by the target). And also a line at the bottom which is totalling each combination.

 # ALL COMBINATIONS OF DRIVERS AND TEAMS BASED ON BUDGET AND ARRAY
team = team_price.values.permutation(1).to_a
driver = driver_price.values.permutation(3).to_a
target = 13.5
array = team.product(driver)
res = array.select {|i| i.map(&:sum).sum <= target}.compact
t1 = res.map {|i| i[0]}
d2 = res.map {|i| i[1].flatten.sort}
combo = t1.zip(d2).uniq
full_combo = combo.flatten.each_slice(4).to_a

# TOTAL COST OF COMBINATION
total_cost = combo.map {|budget| budget.map(&:sum).sum}

# OUTPUT
@test1 = full_combo, total_cost

Which is outputting (All price combinations and totalling combo):

[[[1.0, 2.1, 4.0, 5.0], [1.0, 2.1, 4.0, 6.0], [2.0, 2.1, 4.0, 5.0], [2.4, 2.1, 4.0, 5.0]], [12.1, 13.1, 13.1, 13.5]]

I would like to rank these combinations, but by the equivalent points value. So hoping I can switch in the points, sum it up, and then rank the combos highest to lowest based on this and also display the Keys rather than values. So something like this:

  1. Convert current combo to key equivalent:

[[cowboys, mark, john, mike], [cowboys, mark, john, paul], [bears, mark, john, mike], [united, mark, john, mike]]

  1. Use this to switch in the points hash values and sum each combo:

[[60.6], [56.9], [59.8], [43.4]]

  1. Then use the Points Sums to rank each combo. Ideal output:
Combo Total Price Total Points
cowboys, mark, john, mike 12.1 60.6
bears, mark, john, mike 13.1 59.8
cowboys, mark, john, paul 13.1 56.9
united, mark, john, mike 13.5 43.4

CodePudding user response:

We are given the hashes team_price, driver_price, team_points and driver_points and the maximum price of a combination, which consists of one team and three drivers:

target = 13.5 

We first need variables holding the teams and the drivers.

teams = team_price.keys
  #=> ["cowboys", "bears", "lions", "united"]
drivers = driver_price.keys
  #=> ["john", "mike", "paul", "mark"]

Looking ahead, we will be needing to sum both prices and points for given combinations. It therefore makes sense to create a method that does that.

def add_up(combo, ht, hd)
  t, d = combo
  ht[t]   hd.values_at(*d).sum
end

If, for example,

combo = ["cowboys", ["john", "mike", "paul"]]

then the total of prices for that combo is

add_up(combo, team_price, driver_price)
  #=> 16.0

See Hash#values_at and Array#sum.


We need to compute an array of all combinations for which the sum of prices does not exceed target. First compute all combinations of three drivers.

all_driver_combos = drivers.combination(3).to_a
  #=> [["john", "mike", "paul"], ["john", "mike", "mark"],
  #    ["john", "paul", "mark"], ["mike", "paul", "mark"]]

See Array#combination.

Next construct all combinations.

all_combos = teams.product(all_driver_combos)
  #=> [["cowboys", ["john", "mike", "paul"]],
  #    ["cowboys", ["john", "mike", "mark"]],
  #    ["cowboys", ["john", "paul", "mark"]],
  #    ["cowboys", ["mike", "paul", "mark"]],
  #    ["bears", ["john", "mike", "paul"]],
  #    ["bears", ["john", "mike", "mark"]],
  #    ["bears", ["john", "paul", "mark"]],
  #    ["bears", ["mike", "paul", "mark"]],
  #    ["lions", ["john", "mike", "paul"]],
  #    ["lions", ["john", "mike", "mark"]],
  #    ["lions", ["john", "paul", "mark"]],
  #    ["lions", ["mike", "paul", "mark"]],
  #    ["united", ["john", "mike", "paul"]],
  #    ["united", ["john", "mike", "mark"]],
  #    ["united", ["john", "paul", "mark"]],
  #    ["united", ["mike", "paul", "mark"]]]

See Array#product.

Next extract from all_combos those for which the sum of prices does not exceed target.

valid_combos = all_combos.select do |c|
  add_up(c, team_price, driver_price) <= target
end
  #=> [["cowboys", ["john", "mike", "mark"]],
  #    ["cowboys", ["john", "paul", "mark"]],
  #    ["bears", ["john", "mike", "mark"]],
  #    ["united", ["john", "mike", "mark"]]]

If desired we could substitute out all_driver_combos and chain all_combos:

valid_combos = teams.product(drivers.combination(3).to_a)
                    .select do |c|
                       add_up(c, team_price, driver_price)<=target
                     end

Lastly, we wish to sort the valid_combos by the sum of points for each element of valid_combos.

ordered = valid_combos.sort_by do |c|
  -add_up(c, team_points, driver_points)
end
  #=> [["cowboys", ["john", "mike", "mark"]],
  #    ["bears", ["john", "mike", "mark"]],
  #    ["cowboys", ["john", "paul", "mark"]],
  #    ["united", ["john", "mike", "mark"]]]

​See Enumerable#sort_by.

Notice that I have negated the value returned by add_up so that the sort is from largest to smallest. One could alternatively write

ordered = valid_combos.sort_by do |c|
  add_up(c, team_points, driver_points)
end.reverse

We could examine the totals of prices and points for each element of ordered as follows.

ordered.map do |c|
  { c=>{ price: add_up(c, team_price, driver_price),
         points: add_up(c, team_points, driver_points) } }  
end
  #=> [{["cowboys", ["john", "mike", "mark"]]=>
  #        {:price=>12.1, :points=>60.6}},
  #    {["bears", ["john", "mike", "mark"]]=>
  #        {:price=>13.1, :points=>59.8}},
  #    {["cowboys", ["john", "paul", "mark"]]=>
  #        {:price=>13.1, :points=>56.9}},
  #    {["united", ["john", "mike", "mark"]]=>
  #        {:price=>13.5, :points=>43.3}}]
  • Related