0

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.

1
  • I think I understand. In your example, the lineup will include two quarterbacks (one a backup, say) and three running backs. create_team_from_sets returns all the players in qb_set and in rb_set, and maybe other information, such as total salary for those five players. Presumably, you'll be adding other groupings, such as linemen, punters and so on, and impose a maximum total salary restriction for the entire team. Correct? Commented Sep 27, 2013 at 21:11

2 Answers 2

1

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)
Sign up to request clarification or add additional context in comments.

2 Comments

This is good and would normally work, however it forces me to run through all the combinations as opposed to ruling ones out early that do not meet the criteria. I updated my question to explain what I mean exactly. Thank you for your time!
updated the answer with a recursive option that filters out combos early on if above budget
1

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.

1 Comment

This is good and would normally work, however it forces me to run through all the combinations as opposed to ruling ones out early that do not meet the criteria. I updated my question to explain what I mean exactly. Thank you for your time!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.