views:

310

answers:

6

Is using 50 if-else statements too resource-intensive for one action?

I'm doing something like this:

if team.players.count > 1
   assign_team_type(..)
elsif team.players.count > 3
   assign_team_type(..)
...
etc.
...
end

Also, is it more efficient to place the 50 if-else statements in your create action inside your controller instead of the after_create method? Or would it be more efficient to use a case switch statement instead, or just avoid it altogether?

EDIT: Thanks for the very quick responses! The code is for a community sports tournament to assign teams based on the number of players on that team. I'm trying to write something that assigns a team type to each team according to how many players are added to that team. So there are teams for 1 player, 3 players, 5 players, 7 players, etc., up to 200 players, which requires 50 if-else statements in total.

The statements happen in the players_controller, after the user visits http://localhost/players/new, adds a player, and then the application decides what team to assign his or her team based on how many players are currently on that team. It's very straight-forward (a basic CRUD application that just needs these 50 if-else statements)

models:

Team (has_many :players)
Player (belongs_to :team)

scaffold team name:string team_type:string
scaffold player team_id:integer name:string

That's pretty much it :)

+1  A: 

How about assigning each function to hash / dict?

d={1:assign_team_type(1),2:assign_team_type(2)}
S.Mark
This is ruby 1.9 syntax...
hurikhan77
Personally I'd prefer the hash to just return the team type. Meta-abuse, imho.
klochner
This won't work by design: It would call assign_team_type for each element during assignment of d...
hurikhan77
+7  A: 

Ugh, this is a very bad code smell. You likely have some substantial duplication that can be pushed down into the Model or, in the least, rolled up into something else.

Why not put them into an array or hash? Something like:

TeamTypes = { 1 => something, 2 => something_else, .. }

assign_team_type( TeamTypes[team.players.count] )
Matt Rogish
Having all of the TeamTypes in one array sounds like the most organized. I'll test and see what works best. Thank you Matt!
sjsc
it's a hash, not an array
klochner
+8  A: 

You could try to rewrite it as


assign_team_type(case team.players.count
                 when 2    then ...
                 when 3..5 then ...
                 else raise "Assignment failed"
                 end
)

which should probably be faster as team.players.count is evaluated only once. Additionally it is cleaner and shorter. A benchmark will help.

hurikhan77
Thank you hurikhan! That seems much more efficient. By benchmarking, is that just running "rake test"? (Sorry, I know I'm a total noob)
sjsc
Partially: Newer Rails version have a performance test suite. Otherwise have a look at Benchmark class and create a rake task to load some fixtures and call your code.
hurikhan77
certainly cleaner. His code had no speed problem anyway.
klochner
@klochner yeah because the original code always evaluated no more than the first if case... ;-)
hurikhan77
`team.players.count` in both cases will be evaluated only once. @hurikhan77 nope, it will go depper if `team.players.count` returns <= 1 :D
klew
@klew the work which "count" does will be evaluated only once because it is memoized. What about the chain team - players - count? The parser has to go through two objects over and over again. This cannot be cached by the parser because of possible side effects.
hurikhan77
@hurikhan77 yes, you are right. So it will be better to add some local variable and compare this value in ifs.
klew
+3  A: 

Based on your new info I suggest to add a max_players column to your team_types model and ask the model for something like this:

find(:first, :conditions => ["max_players < ?", self.player_count], :order => "max_players ASC")

This makes it totally dynamic and you can manage the team type assignment through the web interface.

hurikhan77
+2  A: 

My first instinct is to pack your data into a 2D array like:

x=[[1, "arg 1"],
   [3, "arg 2"],
   [9, "arg 3"],
   ...
  ]

where the first column is the team count and the second is the parameter that you would pass to assign_team_type.

You can search through the table for the argument you want using something like:

func_arg = x.collect{|w| w[0] < team.players.count ? w[1] : nil}.compact.last
assign_team_type(func_arg)

I admit it's kind of a C-like approach, but it will let you change your grouping parameters on the fly by simply modifying the array.

bta
Why collect and not select? x.select{|w,| w < t.p.count }.last[1]BTW: This also takes a hash as input...
hurikhan77
@hurikhan77: That is true. Out of habit I default to using `collect`, but `select` or `find_all`can be used with the syntax you listed. It is shorter and easier to read as well.
bta
A: 

modern processors are capable of "branch prediction"

when there are if-else's to parse the processor will make an attempt to guess which one it'll need, if it guesses wrong that can slow down your app.

i would recommend refactoring any overly large branching sections into nice clean switch statements.

I think this doesn't apply here but only on assembler level. Ruby does however not compile code. But your recommendation is still true.
hurikhan77