jklina
jklina

Reputation: 3417

Trying to create nested loops dynamically in Ruby

I currently have the following method:

def generate_lineups(max_salary)
  player_combos_by_position = calc_position_combinations
  lineups = []

  player_combos_by_position[:qb].each do |qb_set|
    unless salary_of(qb_set) > max_salary
      player_combos_by_position[:rb].each do |rb_set|
        unless salary_of(qb_set, rb_set) > max_salary
          lineups << create_team_from_sets(qb_set, rb_set)
        end
      end
    end
  end
  return lineups
end

player_combos_by_position is a hash that contains groupings of players keyed by position:

{ qb: [[player1, player2], [player6, player7]], rb: [[player3, player4, player5], [player8, player9, player10]] }

salary_of() takes the sets of players and calculates their total salary.

create_team_from_sets() takes sets of players and returns a new Team of the players

Ideally I want to remove the hardcoded nested loops as I do not know which positions will be available. I think recursion is the answer, but I'm having a hard time wrapping my head around the solution. Any ideas would be greatly appreciated.

Some answers have recommended the use of Array#product. This is normally an elegant solution however I'm dealing with very large sets of data (there's about 161,000 combinations of WRs and about 5000 combinations of RBs to form together alone). In my loops I use the unless salary_of(qb_set, rb_set) > max_salary check to avoid making unnecessary calculations as this weeds out quite a few. I cannot do this using Array#product and therefore the combinations take very long times to put together. I'm looking for away to rule out combinations early and save on computer cycles.

Upvotes: 0

Views: 493

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110665

After reading your edit, I see your problem. Here I've modified my code to show you how you could impose a salary limit for each combination for each position group, as well as for the entire team. Does this help? You may want to consider putting your data in a database and using Rails.

team_max_salary = 300
players = {player1: {position: :qb, salary: 15, rating: 9}, player2: {postion: :rb, salary: 6, rating: 6},...}
group_info = {qb: {nplayers: 2, max_salary: 50}, rb: {nplayers: 2, max_salary: 50}, ... } 

groups = group_info.keys
players_by_group = {}
groups.each {|g| players_by_group[g] = []}
players.each {|p| players_by_group[p.position] << p} 
combinations_for_team = []
groups.each do |g|
  combinations_by_group = players_by_group[g].combinations(group_info[g][:nplayers]).select {|c| salary(c) <= group_info[g][:max_salary]} 
  # Possibly employ other criteria here to further trim combinations_by_group
  combinations_for_team = combinations_for_team.product(combinations_by_group).flatten(1).select {|c| salary(c) <= team_max_salary}
end

I may be missing a flatten(1). Note I've made the player keys symbols (e.g., :AaronRogers`), but you could of course use strings instead.

Upvotes: 1

tihom
tihom

Reputation: 8003

You can use Array#product to get all the possible lineups and then select the ones that are within budget. This allows for variable number of positions.

first_pos, *rest = player_combos_by_position.values

all_lineups =  first_pos.product(*rest)
#=> all possible lineups

lineups = all_lineups.
          # select lineups within budget
          select{|l| salary_of(*l) <= max_salary}.
          # create teams from selected lineups
          map{|l| create_team_from_sets(*l) }

Other option: Recursive Method (not tested but should get you started)

def generate_lineups(player_groups,max_salary)

   first, *rest = player_groups

   lineups = []
   first.each do |player_group|
        next if salary_of(player_group) > max_salary
        if rest.blank?
           lineups << player_group
        else
           generate_lineups(rest,max_salary).each do |lineup|
               new_lineup = create_team_from_sets(player_group, *lineup)
               lineups << new_lineup unless salary_of(*new_lineup) > max_salary
           end
        end
   end
  return lineups
end

Usage:

lineups = generate_lineups(player_combos_by_position.values,max_salary)

Upvotes: 1

Related Questions