4

What modification would bring to this piece of code? In the last lines, should I use more if-else structures, instead of "if-if-if"

if (action.equals("opt1"))
{
    //something
}
else
{
    if (action.equals("opt2"))
    {
        //something
    }
    else
    {
        if ((action.equals("opt3")) || (action.equals("opt4")))
        {
            //something
        }
        if (action.equals("opt5"))
        {
            //something
        }
        if (action.equals("opt6"))
        {
            //something
        }
    }
}

Later Edit: This is Java. I don't think that switch-case structure will work with Strings.

Later Edit 2:

A switch works with the byte, short, char, and int primitive data types. It also works with enumerated types (discussed in Classes and Inheritance) and a few special classes that "wrap" certain primitive types: Character, Byte, Short, and Integer (discussed in Simple Data Objects ).

Alex Baranosky
  • 48,865
  • 44
  • 102
  • 150
cc.
  • 5,533
  • 14
  • 37
  • 46
  • Do the options have to be strings? If you could make them an enum (or resolve the string to an enum when you obtain it) then you could use a switch. – Dale Oct 08 '09 at 06:45
  • 1
    Please specify what you want "optimized" when you ask this kind of question: e.g. readability, performance, etc. Simply saying "How do you optimize this code?" has no meaning. I only mention this because I see some responses assume you mean performance, while others assume you mean readability - which could actually be mutually exclusive optimizations. – SingleShot Oct 08 '09 at 07:25
  • Please change the title of your post to something like: "Optimizing if-else /switch-case with string options". The current title is meaningless. I will vote your question up after that – Thomas Maierhofer Oct 08 '09 at 07:27

13 Answers13

10

Even if you don't use a switch statement, yes, use else if to avoid useless comparison: if the first if is taken, you don't want all others ifs to be evaluated here since they'll always be false. Also you don't need indenting each if making the last block being so indented that you can't see it without scrolling, the following code is perfectly readable:

if (action.equals("opt1")) {
}
else if (action.equals("opt2")) {
}
else if (action.equals("opt3")) {
}
else {
}
Julien Lebosquain
  • 40,639
  • 8
  • 105
  • 117
  • 1
    Since Java apparently can't `switch` on string values, I'd go with this guy's approach. (Notably the lack of overindentation) – Twisol Oct 08 '09 at 06:43
  • ...although I'd still argue that your braces are on the wrong lines, but that's a whole nother debate :) – mpen Oct 08 '09 at 06:47
  • Better than before, but still O(n) for worst case. – Danny Varod Oct 08 '09 at 06:50
  • 1
    You could further order them by probability. – middus Oct 08 '09 at 07:05
  • Even better - if there're lots of strings you could compute hashes for all variants, hash the string in question and first compare hashes then, if hashes match, compare strings - that's much faster. – sharptooth Oct 08 '09 at 07:13
  • @Gordon: for example, see my answer or @Danny's answer. Basically, any solution using a properly configured HashMap will be O(1) on average, with a small constant of proportionality. – Stephen C Oct 08 '09 at 13:51
  • @Stephen: for a small number of small strings, the constant cost of an hash isn't small, but typically much higher than that of multiple equality comparisons. – Eric Grange Feb 29 '12 at 09:55
  • @EricGrange - there are too many variables to say "typically". Though I would agree with a statement like this: *"For a small enough number of small enough strings, the cost of hashing **will** be higher than multiple string equality comparisons"* – Stephen C Feb 29 '12 at 23:00
  • In addition, I personally wouldn't seriously consider micro-optimizations such as hashing unless this switch statement was a *proven* bottleneck. – Stephen C Feb 29 '12 at 23:18
  • In all the cases I've seen where there was a "switch" on strings, the number of alternatives was quite low, and they were very short (command names, instructions, that kind of things). I actually don't remember seeing any case at all where there was more than one word in the strings. – Eric Grange Mar 01 '12 at 12:16
5

Use a dictionary with string as key type and delegates* as value type. - Retrieving the method from using the string will take O(1+load).

Fill the dictionary within the class's constructor.

  • Java does not support delegate, so as a work around you may need to define a few inner classes - one for each case and pass the instance of the inner classes instead of the methods as values.
Danny Varod
  • 17,324
  • 5
  • 69
  • 111
  • 1
    A Map will perform better if there are a lot of cases. This is O(1) for map against O(N) for the if/else. Everything depends on the constant lookup cost which is bigger for maps. – Thomas Jung Oct 08 '09 at 07:07
  • @Thomas: your comment is misleading. Firstly, not all Maps have O(1) lookup cost; e.g. TreeMaps don't, and even HashMaps don't if use a daft load factor. Second, for a HashMap with a default (good) load factor, the lookup cost depends on the length of the String, whether you've previously hashed it (String hashcodes are cached), and whether are hashcode collisions for the String. With best case assumptions, the amortized cost of a HashMap lookup **can be** less than the cost of executing `if (key.equals("value")) {`. – Stephen C Oct 08 '09 at 07:43
  • @Stephen: you're right. I had HashMap in mind. And yes, only a lookup without hash collisions will be 0(1). But I don't think that the lookup speed depends on the string length once the hash value is computed *and* there are no collisions. Only for collisions the String.equals has to be called. And this will only after it tried instance equality. This will only happen if you're using different string instance. But all in all you're right, although it *can* be used with practically O(1) performance. – Thomas Jung Oct 08 '09 at 08:21
  • @Thomas. I was unclear. Assuming there are no collisions, the amortized cost of `HashMap.get(str)` does not depend on `str.length()`. But the cost of an `if (str.equals("opt1") ... else if ` sequence does, assuming that `str` matches one of the String literals. – Stephen C Oct 08 '09 at 14:10
4

Use a switch statement assuming your language supports switching on a string.

switch(action)
{
   case "opt6":
      //
      break;
   case "opt7":
      //
   ...
   ...
   ...
}
DevDevDev
  • 5,107
  • 7
  • 55
  • 87
  • 1
    Switching on Strings will be introduced in JDK 7: http://blogs.sun.com/darcy/entry/project_coin_final_five – Joachim Sauer Oct 08 '09 at 08:18
  • Does someone know if this will be compiled to the tableswitch byte code instruction in Java 7? So it will make a performance difference to if/else. – Thomas Jung Oct 08 '09 at 08:24
  • @ThomasJung - I imagine that it will be compiled to close to optimal code, but that code will depend on the number and size of the constant strings. Java 7 is now released, so you can check for yourself. But unless the switch is (still ... in Java 7) a performance bottleneck, you are wasting your time looking into it. Just let the JIT compiler do it its stuff. – Stephen C Feb 29 '12 at 23:51
4

There are a number of ways to do this in Java, but here's a neat one.

enum Option {
    opt1, opt2, opt3, opt4, opt5, opt6
}

...

switch (Option.valueOf(s)) {
case opt1:
    // do opt1
    break;
case opt2:
    // do opt2
    break;
case opt3: case opt4:
    // do opt3 or opt4
    break;
...
}

Note that valueOf(String) will throw an IllegalArgumentException if the argument is not the name of one of the members of the enumeration. Under the hood, the implementation of valueOf uses a static hashmap to map its String argument to an enumeration value.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
2

You can use a switch.

switch (action)
{
 case "opt3":
 case "opt4":
 doSomething;
 break;
 case "opt5":
 doSomething;
 break;
 default:
 doSomeWork;
 break;
}
Adriaan Stander
  • 162,879
  • 31
  • 289
  • 284
1

It depends on your language, but it looks C-like, so you could try a switch statement:

switch(action)
{
case "opt1":
    // something
    break;

case "opt2":
    // something
    break;

case "opt3":
case "opt4":
    // something
    break;

case "opt5":
    // something
    break;

case "opt6":
    // something
    break;
}

However, sometimes switch statements don't provide enough clarity or flexibility (and as Victor noted below, will not work for strings in some languages). Most programming languages will have a way of saying "else if", so rather than writing

if (condition1)
{
    ...
}
else
{
    if (condition2)
    {
        ...
    }
    else
    {
        if (condition3)
        {
            ...
        }
        else
        {
            // This can get very indented very fast
        }
    }
}

...which has a heap of indents, you can write something like this:

if (condition1)
{
    ...
}
else if (condition2)
{
    ...
}
else if (condition3)
{
    ...
}
else
{
    ...
}

In C/C++ and I believe C#, it's else if. In Python, it's elif.

Smashery
  • 57,848
  • 30
  • 97
  • 128
1

It could help if you specified the language... As it looks like C++, you could use switch.

switch (action) {
   case "opt1":
      // something
      break;
   case "opt2":
      // something
      break;
   ...
}

And in case you want to use if statements, I think you could improve readability and performance a bit if you used "else if" without the curly braces, as in:

if (action.equals("opt1")) {
    //something
} else if (action.equals("opt2")) {
    //something
} else if ((action.equals("opt3")) || (action.equals("opt4"))) {
    //something
} else if (action.equals("opt5")) {
    //something
} else if (action.equals("opt6")) {
    //something
}

I think some compilers can optimize else if better than a else { if. Anyways, I hope I could help!

jpmelos
  • 3,283
  • 3
  • 23
  • 30
1

I would just clean it up as a series of if/else statements:

if(action.equals("opt1"))
{
    // something
}
else if (action.equals("opt2"))
{
    // something
}
else if (action.equals("opt3"))
{
    // something
}
etc...
Barry Brown
  • 20,233
  • 15
  • 69
  • 105
0

The answers advising the use of a switch statement are the way to go. A switch statement is much easier to read than the mess of if and if...else statements you have now.

Simple comparisons are fast, and the //something code won't executed for all but one case, so you can skip "optimizing" and go for "maintainability."

Of course, that's assuming that the action.equals() method does something trivial and inexpensive like a ==. If action.equals() is expensive, you've got other problems.

Matt Miller
  • 3,501
  • 5
  • 27
  • 26
0

In fact this depends on branch analysis. If 99% of your decisions are "opt1" this code is already pretty good. If 99% of your decisions are "opt6" this code is ugly bad.

If you got often "opt6" and seldom "opt1" put "opt6" in the first comparison and order the following comparisons according to the frequency of the strings in your execution data stream.

If you have a lot of options and all have equal frequency you can sort the options and split them into a form of a binary tree like this:

if (action < "opt20")
{
   if( action < "opt10" )
   {
     if( action  ==  "opt4" ) {...}
     else if( action  ==  "opt2" ) {...}
     else if( action  ==  "opt1" ) {...}
     else if( action  ==  "opt8" ) {...}
   }
}
else
{
    if( action < "opt30 )
    {

    }
    else
    {
      if( action  ==  "opt38" ) {...}
      else if( action  ==  "opt32" ) {...}
    }
}

In this sample the the range splits reduces the needed comparisons for "opt38" and "opt4" to 3. Doing this consequent you get log2(n) +1 comparisons in every branch. this is best for equal frequencies of the options.

Don't do the binary spit to the end, at the end use 4-10 "normal" else if constructs that are ordered by the frequency of the options. The last two or three levels in a binary tree don't take much advance.

Summary

At least there are two optimizations for this kind of comparisons.

  1. Binary Decision Trees
  2. Ordering due to the frequency of the options

The binary decision tree is used for large switch-case constructs by the compiler. But the compiler don't know anything about frequencies of an option. So the ordering according to the frequencies can be a performance benefit to the use of switch-case if one or two options are much more frequent than others. In this case this is a workaround:

if (action == "opt5") // Processing a frequent (99%) option first
{
}
else // Processing less frequent options (>1%) second
{
    switch( action )
    {
    case "opt1": ...
    case "opt2": ...
    }
}

Warning

Don't optimize your code until you have done profiling and it is really necessary. It is best to use switch-case or else-if straight forward and your code keeps clean and readable. If you have optimized your code, place some good comments in the code so everybody can understand this ugly peace of code. One year later you won't know the profiling data and some comments will be really helpful.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
Thomas Maierhofer
  • 2,665
  • 18
  • 33
  • @zproxy - Wrong - The compiler has no clue about the frequency at which a particular branch will be executed. The programmer usually had a good idea of this. – Derrick Apr 07 '11 at 13:08
0

Procedural switching like this very often is better handled by polymorphism - rather than having an action represented by a string, represent an action by an object who has a 'something' method you can specialise. If you find you do need to map a string to the option, use a Map<String,Option>.

If you want to stick to procedural code, and the options in your real code really are all "optX":

if ( action.startsWith("opt") && action.length() == 4 ) {
    switch ( action.charAt(3) ) {
        case '1':  something; break;
        case '2':  something; break;
        case '3':  something; break;
        ...
    }
}

which would be OK in something like a parser ( where breaking strings up is part of the problem domain ), and should be fast, but isn't cohesive ( the connection between the object action and the behaviour is based on the parts of its representation, rather than anything intrinsic in of the object ).

Pete Kirkham
  • 48,893
  • 5
  • 92
  • 171
0

Note that using strings in the cases of a switch statement is one of the new features that will be added in the next version of Java.

See Project Coin: Proposal for Strings in switch

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
Jesper
  • 202,709
  • 46
  • 318
  • 350
0

If you find the native java switch construct is too much limiting give a glance to the lambdaj Switcher that allows to declaratively switch on any object by matching them with some hamcrest matchers.

Mario Fusco
  • 13,548
  • 3
  • 28
  • 37