4

I'm afraid this is a terribly stupid question. However, I can't find an answer to it and therefore require some help :)

Let's start with a simplification of my real problem:

Assume I have a couple of boxes each filled with a mix of different gems.

I'm now creating an object gem which has the attribute colour and a method getColour to get the colour of the gem. Further I'm creating an object box which has a list of gems as attribute and a method getGem to get a gem from that list.

What I want to do now is to count all gems in all boxes by colour. Now I could either do something like

int sapphire = 0;
int ruby = 0;
int emerald = 0;

for(each box = i)
    for(each gem = j)
        if(i.getGem(j).getColour().equals(“blue”)) sapphire++;
        else if(i.getGem(j).getColour().equals(“red”)) ruby++;
        else if(i.getGem(j).getColour().equals(“green”)) emerald++;

or I could do

int sapphire = 0;
int ruby = 0;
int emerald = 0;
String colour;

for(each box = i)
    for(each gem = j)
        colour = i.getGem(j).getColour();
        if(colour.equals(“blue”)) sapphire++;
        else if(colour.equals(“red”)) ruby++;
        else if(colour.equals(“green”)) emerald++;

My question is now if both is essentially the same or should one be preferred over the other? I understand that a lot of unnecessary new string objects are produced in the second case, but do I get a speed advantage in return as colour is more “directly” available?

WhatsOEver
  • 41
  • 2
  • The second one makes clearer source code, so you should favor that. This has nothing to do with performance by the way, you won't see the effect even if one of them were faster. – Kayaman Aug 06 '14 at 09:55
  • 1
    Do you have any evidence that this is a bottleneck in your performance? – Jon Skeet Aug 06 '14 at 09:56
  • 2
    I think this question is rather interesting, but it would be better fit for [CodeReview.SE]. There might be a different approach altogether that is better than the 2 you suggest. – MarioDS Aug 06 '14 at 09:58
  • Second format is nicer. I would choose this from a clean code perspective, not performance which will be negligible. – JamesB Aug 06 '14 at 09:58
  • Maintainability is another factor here that needs improvement, and I think it should even be addressed first. To begin with, you will need to modify this code every time a new gem with a different colour comes into existence. And, what will you do if there are suddenly two gems with the same colour but you need to distinguish on other properties? – MarioDS Aug 06 '14 at 10:02
  • If your `getColour()` method runs a web crawler before it returns your attribute, you might notice a performance increase in the second code snippet. Also, it's clearer, and it doesn't create new unnecessary Strings, as it uses those in your objects, which are already there. I'd favor the second. – webuster Aug 06 '14 at 10:03
  • Kayaman, JamesB: Clear and simple, thanks; Jon Skeet: No, I was wondering and couldn't find an easy answer on my own; MDe...: (1)I'm new, didn't know that it exists, thanks for pointing, (2) Sry, but its just an example. I just wanted to reduce my question to "new string" vs "2 method calls" - other things will come later ;); webuster: but isn't the second one creating the "unnecessary" strings? – WhatsOEver Aug 07 '14 at 11:37

3 Answers3

2

I would dare to make a third improvement:

int sapphire = 0;
int ruby = 0;
int emerald = 0;

for(each box = i) {
    for(each gem = j) {
        String colour = i.getGem(j).getColour();
        if(“blue”.equals(colour)) sapphire++;
        else if(“red”.equals(colour)) ruby++;
        else if(“green”.equals(colour)) emerald++;
    }
}
  1. I use a local variable inside the for-loop. Why? Because you probably need it only there.
  2. It is generally better to put STATIC_STRING.equals(POSSIBLE_NULL_VALUE).

This has the advantage: easier to read and should have no performance problem. If you have a performance problem, then you should consider looking somewhere else in your code. Related to this: this answer.

Community
  • 1
  • 1
V G
  • 18,822
  • 6
  • 51
  • 89
  • (1) True, thanks for the correction. (2) Could you be so kind as to elaborate why this is **generally** better – WhatsOEver Aug 07 '14 at 11:42
  • (Point 2) Because if you do the opposite `POSSIBLE_NULL_VALUE.equals(STATIC_STRING)` you will get an `NullPointerException` when `POSSIBLE_NULL_VALUE` is null. – V G Aug 07 '14 at 13:36
2

conceptually both codes have equal complexity i.e.: O(i*j). But if calling a method and get a returned value are considered to be two processes then the complexity of your first code will be 4*O(i*j).(consider O(i*j) as a function) and of your second code will be O(i*(j+2)). although this complexity difference is not considerable enough but if you are comparing then yes your first code is more complex and not a good programming style.

Vivek Mishra
  • 1,772
  • 1
  • 17
  • 37
  • Unfortunately, I'm terrible in calculating time and space complexities. Could you please elaborate how you got to 4*O(i*j) and O(i*(j+2))? – WhatsOEver Aug 07 '14 at 11:26
  • this is relative evaluation of complexities between two codes. Complexities of both codes is not exact but relative to each other. such as equal no. of if else statements have been used in both codes so I did not count it. To get better details on computing complexities, have a look on following Url- http://stackoverflow.com/questions/11032015/how-to-find-time-complexity-of-an-algorithm – Vivek Mishra Aug 08 '14 at 05:30
1

The cost of your string comparisons is going to wipe out all other considerations in this sort of approach.

You would be better off using something else (for example an enum). That would also expand automatically.

(Although your for each loop isn't proper Java syntax anyway so that's a bit odd).

 enum GemColour {
     blue,
     red,
     green
 }

Then in your count function:

 Map<GemColour, Integer> counts = new EnumMap<GemColour, Integer>(GemColour.class);

 for (Box b: box) {
    for (Gem g: box.getGems() {
       Integer count = counts.get(g.getColour());
       if (count == null) { 
          count=1;
       } else {
          count+=1;
       }

       counts.put(g.getColour(), count);
    }
 }

Now it will automatically extend to any new colors you add without you needing to make any code changes. It will also be much faster as it does a single integer comparison rather than a string comparison and uses that to put the correct value into the correct place in the map (which behind the scenes is just an array).

To get the counts just do, for example:

counts.get(GemColour.blue);

As has been pointed out in the comments the java Stream API would allow you to do all of this in one line:

boxes.stream().map(Box::getGems).flatMap(Collection::stream).collect(groupingBy‌​‌​(Gem::getColour, counting()))

It's less easy to understand what it is doing that way though.

Tim B
  • 40,716
  • 16
  • 83
  • 128
  • Now if you rewrite this with Streams, it's a one-liner. – William F. Jameson Aug 06 '14 at 10:13
  • 1
    `boxes.stream().map(Box::getGems).flatMap(Collection::stream).collect(groupingBy(identity(), counting()))` – William F. Jameson Aug 06 '14 at 10:19
  • @WilliamF.Jameson great answer! And it also works with the original code. But `Gem::getColour` should be used as classifier instead of `identity()` I think. – isnot2bad Aug 06 '14 at 10:26
  • 1
    @isnot2bad You're right, that slipped my attention. So, `boxes.stream().map(Box::getGems).flatMap(Collection::stream).collect(groupingBy‌​(Gem::getColour, counting()))` – William F. Jameson Aug 06 '14 at 10:30
  • That's all very interesting, thanks, but not really answering my question. And as stated, it's just an example. I also don't now all values to compare against at compile time. – WhatsOEver Aug 07 '14 at 11:23