views:

5193

answers:

27

I have this ugly code:

if ( v > 10 ) size = 6;
if ( v > 22 ) size = 5;
if ( v > 51 ) size = 4;
if ( v > 68 ) size = 3;
if ( v > 117 ) size = 2;
if ( v > 145 ) size = 1;
return size;

How can I get rid of the multiple if statements?

A: 

You want a case statement here. This also means theres no chance of more than one ifs being true.

m.edmondson
A case statement would not work, as this piece of code is testing ranges.
fredley
Can't use `switch` statement here. Conditions are `>`, not `==`.
Vivien Barousse
Using the case statement would make it longer and not very expressive really.
mfloryan
If you had switch (true) then the conditions > would work.
m.edmondson
@eddy556 ya-wah? Can you provide some code?
fredley
@eddy556 switch(true) doesn't work. java switch only works with constants (primitives or enums)
seanizer
@eddy556, does that mean a switch for every if?
Reddy
Every time I use a `switch`, I end up taking it out.
Tony Ennis
in java, switch statements are great for enums, but suck for everything else.
seanizer
@eddy556: In haskell you can use switch with arbitrary predicates AND pattern matching.
FUZxxl
It's a shame that Java stuck with the antique C switch statement when it could have used something like ... oh the Cobol 1985 EVALUATE statement, which is far more powerful. Geesh!
Jim Ferrans
@Fuzxxl yep, and in erlang, groovy, scala and many other languages too. in java you need to go the oop-way if you want that kind of flexibility
seanizer
@Tony Ennis - Did you graduate from UK in early to mid eighties? I graduated from there in 1985.
wageoghe
+53  A: 
if ( v > 145 ) size = 1;
else if ( v > 117 ) size = 2;
else if ( v > 68 ) size = 3;
else if ( v > 51 ) size = 4;
else if ( v > 22 ) size = 5;
else if ( v > 10 ) size = 6;

return size;     

This is better for your case.

Optionally you should choose Switch Case where ever possible

Update: If you have analyzed the value of 'v' generally resides in lower range(<10) in most of the cases than you can add this.

if(v < 10)           size = SOME_DEFAULT_VALUE;
else if ( v > 145 )  size = 1;
else if ( v > 117 )  size = 2;
else if ( v > 68 )   size = 3;
else if ( v > 51 )   size = 4;
else if ( v > 22 )   size = 5;
else if ( v > 10 )   size = 6;   

further : You can also alter the condition sequence, according to your analysis. If you know that most of the values are less than 10 and then in the second place most of values lie between 68-117, you can alter the condition sequence accordingly.

org.life.java
"Optionaly you should choose Switch Case where ever possible" why ?
NimChimpsky
@NimChimpsky It would be more `readable`. I don't say that it is better considering performance, It depends on many factors.
org.life.java
Even better: just return the value right away without using intermediate `size` variable.
Rene Saarsoo
@NimChimpsky - In this case, the switch statement would have 145 cases or more, and that would make it pretty unreadable. (Especially if the reader needs to consider the case where the programmer has mistyped one of this numbers. I guess that is kind of implied by "optionally", though I wouldn't have phrased it that way.
Stephen C
I don't think the switch is significantly more readable, you have to look out for break statement and fall through. Very useful, but not as simple as "if".
NimChimpsky
@NimChimpsky : It will be if you format it properly i think
org.life.java
@Rene Saarsoo It is best practice to have only one return statement
org.life.java
@org I hear that is best practice all the time, but when it makes the code awkward it seems silly to me. Do you have any substantiation of this claim that it's actually better? Note this is not the same as "it's consensus that this is best practice" but something proving that it IS best practice.
Emtucifor
I like the Javascript implementation of `switch` better: you can do `switch(true) {case size > 146: ... case size > 117: ...`
RedFilter
@org.life.java This "best practice" is entirely subjective and most probably outdated (since it is a throwback to the days of languages like C where you have to explicitly manage resources). There are many times when you get far more readable and maintainable code by multiple return statements. Smart compilers take care of the rest. See http://consultingblogs.emc.com/anthonysteele/archive/2008/07/14/a-method-should-have-only-one-return-statement-discuss.aspx
Dan Diplo
What value is returned when `v == 9` ?
Stephen P
@RedFilter that's irrelevant. java's switch doesn't work that way, whether you like it or not...
seanizer
@Stephen Obviously `size` is returned :D
trinithis
@Dan it's not an issue with the compiler not understanding it, multiple return statements make code less understandable.
seanizer
@seanizer: less understandable than using tons of useless intermediate steps for the sake of code "purity"?
ninjalj
@seanizer - It's *programmers* that make code readable or not readable, not rigid Stalanist orthodoxies that must be adhered to.
Dan Diplo
@dan *Stalanist*? Would you perhaps mean *Stalinist*? I can't remember murdering millions of innocent people any time recently, but then my memory isn't as good as it once was... @ninjalj if it takes many steps, that's an indication of poor code design.
seanizer
@seanizer - Yeah, it was a typo, and yet it was a metaphor for sticking to some set-in-stone ideology rather than being pragmatic and using whichever approach is best suited to the situation.
Dan Diplo
@seanizer: your recommended way takes one step too many, saving the return result to a temporary variable. It gets even uglier when used inside loops.
ninjalj
@Emtucifor I am not saying that it is good or bad, but its better to keep a standard, I personally follow that, It will make it code more readable i feel.
org.life.java
@org I think that returning right away when that's what you intend makes the code more "readable." If we define readability as the speed with which the structure and intentions of the code are made apparent to the reader, then strategic returns make sense to me. The **expectation** that code will only return at the end of a procedure is the problem, and an artificial one that makes programming more laborious. Calling it a standard doesn't make it sensible. We could aLtErNaTe CaPs and call it a standard, but that wouldn't make it reasonable.
Emtucifor
If there are multiple natural exit points in some function (which should already not be many pages long or it's violating other useful principles), the work necessary to carry "desired exit state" to the end of the function makes everything more complex. It also makes it harder to ensure the function is performing correctly because all branches flow through the entire thing and some of the conditionals could be complex. In the code in this question, returns make perfect sense because nothing is done afterward! No one could POSSIBLY get confused about what's going on here.
Emtucifor
@Emtucifor, Here it will be readable but I am talking about complex flows, there I feel it should be one return statement. It will also help you while debugning.
org.life.java
@org Your statement "It is best practice to have only one return statement" was global. If you no longer think this is a global rule all should follow, well, more power to you. :)
Emtucifor
@Emtucifor : I Have looked @ http://stackoverflow.com/questions/36707/should-a-function-have-only-one-return-statement ,Found out more no of programmers prefer it, I would still say in complex flow I will stick with it/
org.life.java
@org Best practice isn't the same as most common practice. I guess we just have to disagree. :)
Emtucifor
@Emtucifor :-) ...
org.life.java
in favor of org.life.java statement, I'd say that even though his suggested "best practice" is subjective (note for bias: I adhere to it as well), it makes testing and debugging is easier by allowing you to monitor only one exit point (you'll say with some language/IDE combination, they manage function exit points just fine, but that's not always the same. So it's one of those "smallest common denominator" practice.)
haylem
@haylem I found it more readable anyhow:-)
org.life.java
@org.life.java: maybe, but that's the subjective part :) My main reason is "less code, less bug", and I prefer single exit points for traceability when adding logging statement (need only one instead of N, except if using AOP) or debugging. Plus in the end, most compilers would produce the same machine code, so optimization is not really a valid reason here.
haylem
@haylem agree .
org.life.java
+31  A: 
return v > 145 ? 1 
     : v > 117 ? 2 
     : v > 68 ? 3 
     : v > 51 ? 4 
     : v > 22 ? 5 
     : v > 10 ? 6 
     : "put inital size value here";
gasan
Ahhhh! My eyes! My eyes!
Greg Beech
Are you for real? This is significantly more unreadable, and not trivial to change if the rules for 'size' change.
Noon Silk
could you please read initial question? "How can i get rid of multiple if statements?" There are no "if" statements in my answer as you can see.
gasan
@gasan, sure, you removed the `if's` but the op claimed his code was `ugly`, and intended to make it more readable...do you actually believe this is...prettier?
st0le
The original question probably was meant to be "How can I get rid of multiple if statements without resorting to extremely ugly code" :-)
Grodriguez
It does technically answer the question on how to get rid of "if" statements, but it increases the "ugly" factor by a few orders of magnitude.
Greg Beech
Seen as just a row of characters without meaning, it is actually rather regular and predictable. Some would consider that to be pretty!
Thorbjørn Ravn Andersen
I added some newlines. Readability among starters is still questionable.
BalusC
@st0le, I think the code in the question is ugly and makes no sense thus the better solution is to make it as much small and compact as possible to not annoy one's eyes (Hopefully, some sane developer will remove it in the nearest future). Besides all, this function (probably, it is a function) should be inlined. And it's better to do it in a concise way rather than by humpty-dumpty many-lines-many-brackets code.
gasan
@BalusC, Jesus Christ, what've you done with my code?! It was intended to be in a single line! (see why in my previous comment)
gasan
You're confusing me with someone else. My name is Bauke Scholtz, not Jesus Christ. As said in a comment, I added some newlines. It is less ugly and improves readability.
BalusC
@BalusC Surely if there was ever code that justified taking the Lord's name in vain, this is it!
bzlm
@gasan: feel free though to rollback the edit so that you can eat downvotes :) Was just being helpful.
BalusC
+1 I just love it.
mbq
haha yes! I love it, and it shouldn't be hard to read for anyone who knows the syntax :D
KennyCason
I like this form and find it readable, though I recognize that most people have a problem with it. I don't see why anyone who understands the chained `if/else-if/else` construct has a problem with the chained ternary `?:` operator. They are perfectly parallel to one another.
Bert F
+1 because awesome
Falmarri
@Bert F I agree 100%
KennyCason
If nothing else, this form forces you to write what the "default" case is. +1
badp
I find this much easier to read than the accepted if/else-if answer, thanks to Balus' reformatting. I agree with Bert, yes, people seem to have a problem with this; I just don't understand *why* people find the ternary `?:` so cryptic. Agree with badp too, neither the OP's code or the accepted answer deal with a default "otherwise" case.
Stephen P
@Stephen P — I think ternary itself ends up seeming cryptic simply because it's farther from English than we usually like. But even I, being familiar with and probably overusing ternary syntax, get confused by figuring out how to process chained ternary. I might memorize that this is its behavior, though, for future reference.
Matchu
I too agree with @Stephen P
Reddy
In the reformatted form this is nicer and more readable than a chain of if/elses. As for confusing people who don't understand the ternary operator -- well, anyone who doesn't understand ?: yet should jolly well learn it, and anyone who *can't* learn it clearly has deeper problems as a programmer.
Porculus
@porculus who put you in charge of deciding wbat a programmer should learn? The ternary operator is a nice gadget for an inline switch but it makes code less readable and imho this solution is a horrible abuse of it
seanizer
@seanizer: Well, obviously a programmer should learn the whole language (at least the basics), there can be no debate about this. Further, this is a common idiom to deal with chained-ifs that merely select a value and far from being an abuse it’s a perfect use-case. I actually prefer *your* (apparently very controversial, though why eludes me) solution, but only barely.
Konrad Rudolph
I remember seeing a style rule somewhere that recommended not nesting ternaries. Can't remember where it was, though.
R. Bemrose
That's hilarious.
Tony Ennis
+10  A: 

Here's my shot at it...

Update: Fixed. Previous Solution gave incorrect answers for exact values (10,22,51...). This one defaults to 6 for the if val < 10

   static int Foo(int val)
    {
                          //6, 5, 4, 3, 2 ,1
        int[] v = new int[]{10,22,51,68,117,145};
        int pos = Arrays.binarySearch(v, val-1);
        if ( pos < 0) pos = ~pos;
        if ( pos > 0) pos --;
        return 6-pos;
    }
st0le
I was just about posting a solution like yours, using `binarySearch`. +1 for using the `~` operator on `binarySearch` result, I never thought about that. Note : you can get rid of the `size` array, it's not 5,6 but 6,5.
barjak
It's clever, but IMHO less clear than the original.
LnxPrgr3
@LnxPrgr3, i agree. It's not, was just offering an alternative.
st0le
+1 for a solution that works quickly for large datasets, and doesn't have more overhead than the original for small datasets.
Thomas Ahle
+90  A: 

How about such approach:

int getSize(int v) {
    int[] thresholds = new int[]{145, 117, 68, 51, 22, 10};

    for (int i = 0; i < thresholds.length; i++) {
        if (v > thresholds[i]) return i+1;
    }
    return 1;
}
mfloryan
+1 Like Like ..
Bragboy
Nice thing about this approach is that it can be easily extended to more steps or wider range without much effort.
sbass
+1 this is the most elegant non-object-oriented solution here :-)
seanizer
Missing a last return statement, this won't compile (in java)
Ishtar
+1 good enough. @seanizer - once you get to the hardware, it's all non-OO anyway ;-)
Tony Ennis
Good call... definitely not something I've seen before.
WernerCD
+1, Very clever. But less readable at first glance IMHO.
MAK
I like this, too. Just to be safe I would put a comment in case someone adds/changes `steps` without realizing order is important. Alternately, a reverse sort on `steps` before the loop would work, too.
RedFilter
I would have returned `6` and not `1` at the end if there is no match (while there is bug in the original implementation `if v <= 10`)
ring0
This is less readable. With the if/elseif solution the code is instantly understandable. This code, while easy to understand, still requires a little thought to grok. Exact same number of lines of code, but requires a tiny bit more effort to read == less optimal solution IMO.
Bryan Oakley
I dislike the idea, what if you have to change and for 22 return 17 and 51 return 50? code does change and this is a maintenance nightmare.
01
I have to say, I had to think longer to understand the original code than I had with this loop.
Johannes Schaub - litb
+3  A: 
int[] arr = new int[] {145, 117, 68, 51, 22, 10};
for(int index = 0; index < arr.length; index++)
{
  if(v > arr[index]) return 1 + index; 
}

return defaultValue;
Ani
A: 

What about

int result = 6;
List<Integer> caps = Arrays.asList(22, 51, 68, 117, 145);
for (Integer cap : caps) {
    result = (v > cap) ? result - 1: result;
}
return result;

Greetz, GHad

GHad
+54  A: 

Using the NavigableMap API :

NavigableMap<Integer, Integer> s = new TreeMap<Integer, Integer>();
s.put(10, 6);
s.put(22, 5);
s.put(51, 4);
s.put(68, 3);
s.put(117, 2);
s.put(145, 1);

return s.lowerEntry(v).getValue();
barjak
If you leave the keys as the OP did, you can use lowerEntry instead of floorEntry. That way the data is consistent w/ OP.
basszero
Thanks, I edited the answer for using lowerEntry
barjak
very nice solution (+1), in fact I'd say it's the only non-ugly solution here
seanizer
Well, very non-ugly until you look at the overhead.
JUST MY correct OPINION
I think `HashMap` would be better solution if you are fix with map only.
org.life.java
A `HashMap` doesn't work here. `NavigableMap` allows to find the nearest key (either lower or greater) for a given value, which is important in our case.
barjak
@JUST : I confess I wouldn't use my solution in such a simple case, but rather a bunch of if statements. However, if some flexibility is needed (for example, if those numbers are not hard-coded but come from a configuration file), then this solution makes sense. Plus, this solution can be useful if the "f" key of my keyboard is broken :)
barjak
@JUST well there's always a tradeoff between code usability and performance. This is highly usable object oriented code, so naturally there is overhead. If you don't want overhead, stick with if/else horror.
seanizer
That's actually a very ugly solution. Simpler is always better. Why would you abuse TreeMap to simulate ifs? A general solution is to put the values in an array and do binary searching.
Alexandru
Rules of Abstraction:Rule 1: Don't do it.Rule 2 (for experts only): Don't do it yet.
LnxPrgr3
@seanizer: There is nothing particularly object-oriented about using a hash map or equivalent. I was using those kinds of data structures before I'd even caught the faintest whiff of OOP back in the '80s.
JUST MY correct OPINION
@JUST It's more object-oriented to use a custom object (see my solution) but less object-oriented to use if/else blocks. And I doubt that you used anything similar to the java map interface 20-30 years ago. Yes, you probably used hash tables, but you didn't have a clean interface that abstracts from the workings underneath (which are by no means equivalent, there are big differences between hash and tree based lookup).
seanizer
It screams OVERKILL...
badp
@badp if you think so, then OOP is clearly not the right concept for you (and hence java is not the right language)
seanizer
Interesting piece of code, I had never seen NavigableMap before. Good thing to know, thanks.
mattjames
@seanizer, I like OOP, but I think you're going a bit _too_ far with it there. I mean, setting up a _data structure_ then _navigating_ it just to make some range checks?
badp
@seanizer: please learn some history, and preferably some theory too. Abstract data types do not require OOP, and the concept of presenting a clean interface that abstracts from the workings underneath has been around for at least 50 years.
Porculus
@badp I think that's what stinks about java: it gives you the choice to do things the OOP way or not. If java were a real OOP language (and didn't try to be a C dialect and an OOP language at the same time) everything would be an object, there would be no primitives and hence there would be no *should I use OOP here* question.
seanizer
@Porculus *Don't know much about history, Don't know much biology, Don't know much about a science book, Don't know much about the French I took ...* Abstract Data Types do not require OOP, and I never said so, either. It would be a lot easier to do this kind of code in a functional language like Haskell or Erlang and even in a preudo-functional one like Scala or Groovy. But the topic is Java and in Java, OOP is the most elegant way to go.
seanizer
@JUST overhead? maybe for 6 values... but in the big picture, the navigable map will scale up nicely and the chained if-else won't. If you want something more efficient hard-coded, the if-else's will need to have a tree structure to perform an equivalent binary search.
fortran
@fortran: If you are in a position where you're making even chains of **six** if/else statements (or their equivalents) you need to rework your design quickly in 99.44% of cases.
JUST MY correct OPINION
@JUST I don't get your point. There are many things out there that do not follow simple and regular rules and need to be mapped or looked up with tables/switch's/if-else's or whatever.
fortran
+15  A: 

There are a ton of answers and suggestions here but I honestly don't see any of them "prettier" or "more elegant" than the original method.

If you had dozens or HUNDREDS of iterations to check then I could easily see going to some for loop but honestly, for the handful of comparisons you had, stick with the if's and move on. It's not that ugly.

cbmeeks
Agree. But these questions let people show alternate solutions. For example, I had never heard of NavigableMap.
Tony Ennis
Well you probably heard of a TreeMap, but you didn't know which interfaces it implements :-)
seanizer
Agreed… Take a good, hard look at your first revision and just say to yourself, [gloves].[gloves]: http://thedailywtf.com/Articles/The_Complicator_0x27_s_Gloves.aspx *Edit:* Why does this choke the Markdown parser?!
LnxPrgr3
A: 

Here is another way to solve the problem:

/**
 * Last item of haystack can be minimum integer (Integer.MIN_VALUE) 
 * to check later whether a match was found or not
 */
public static int siz(int needle,int haystack[],int pos){
  return (needle>haystack[pos])? ++pos: siz(needle,haystack,++pos);
}

Example usage:

int arr[] = {145,117,68,51,22,10,Integer.MIN_VALUE};
int size = siz(118,arr,0);

//Check if the item was found
if(arr.length!=size){
  //success
}else{
  //handle error
}
sym
+2  A: 

This is apparently a highly controversial post, it has even been on the DailyWTF site. The current score of this post (see for yourself by clicking between the up and down arrow if you've got 1000 or more rep) is

Upvotes / Downvotes: +13 / -12


Here is an object-oriented solution, a class called Mapper<S,T> that maps values from any type that implements comparable to any target type.

Syntax:

Mapper<String, Integer> mapper = Mapper.from("a","b","c").to(1,2,3);

// Map a single value
System.out.println(mapper.map("beef")); // 2

// Map a Collection of values
System.out.println(mapper.mapAll(
    Arrays.asList("apples","beef","lobster"))); // [1, 2, 3]

Code:

public class Mapper<S extends Comparable<S>, T> {

    private final S[] source;
    private final T[] target;

    // Builder to enable from... to... syntax and
    // to make Mapper immutable
    public static class Builder<S2 extends Comparable<S2>> {
        private final S2[] data;
        private Builder(final S2[] data){
            this.data = data;
        }
        public <T2> Mapper<S2, T2> to(final T2... target){
            return new Mapper<S2, T2>(this.data, target);
        }
    }


    private Mapper(final S[] source, final T[] target){
        final S[] copy = Arrays.copyOf(source, source.length);
        Arrays.sort(copy);
        this.source = copy;
        this.target = Arrays.copyOf(target, target.length);
    }

    // Factory method to get builder
    public static <U extends Comparable<U>, V> Builder<U> from(final U... items){
        return new Builder<U>(items);
    }

    // Map a collection of items
    public Collection<T> mapAll(final Collection<? extends S> input){
        final Collection<T> output = new ArrayList<T>(input.size());
        for(final S s : input){
            output.add(this.map(s));
        }
        return output;
    }

    // map a single item
    public T map(final S input){
        final int sourceOffset = Arrays.binarySearch(this.source, input);
        return this.target[
            Math.min(
                this.target.length-1,
                sourceOffset < 0 ? Math.abs(sourceOffset)-2:sourceOffset
            )
        ];
    }
}

Edit: finally replaced the map() method with a more efficient (and shorter) version. I know: a version that searches partitions would still be faster for large arrays, but sorry: I'm too lazy.

If you think this is too bloated, consider this:

  1. It contains a builder that lets you create the Mapper using varargs syntax. I'd say that's a must-have for usability
  2. It contains both a single item and a collection mapping method
  3. It's immutable and hence thread safe

Sure, all of these features could be easily removed, but the code would be less complete, less usable or less stable.

seanizer
Too complicated.... why should someone involve objects in something as simple as take decisions over primitive types?
JPCF
This is meant as a general-purpose solution. In this simple case it may be overhead, but in more advanced use cases (staring with the last example I provided) the benefits of utilizing the comparable interface are huge.
seanizer
Of course it would be even better if you could pass in a custom comparator, maybe I'll rework it to that.
seanizer
How is this an improvement over the [NavigableMap-based answer](http://stackoverflow.com/questions/3786358/get-rid-of-ugly-if-statements/3786541#3786541)? At the very least, if I wanted this interface, I would have wrapped NavigableMap before writing anything like this. I'd be interested to see a benchmark of your solution vs. NavigableMap too.
LnxPrgr3
Holy Grace Hopper, what monstrosity is this? They just wanted to tighten up a few IFs.
@LnxPrgr3 losing a benchmark against a standard library used by millions of users ain't such a shame, is it? Let's see your wrapper around NavigableMap... @user257493 imho 6 is not a few. If there are more than 2 if clauses, refactoring is a must. and if you don't like object-oriented solutions, maybe you should stick with a strictly procedural language like c. btw, who are you to take grace hopper's name in vain?
seanizer
@seanizer Premature abstraction is the root of all evil. No… losing a benchmark to the standard library isn't a shame. Rolling your own solution *anyway* without gaining *something* over the standard library is an odd choice though, especially when it means replacing 6 `if` statements with 54 lines of Java (not counting the code to actually *use* this thing). Besides, when was being object oriented inherently an advantage?
LnxPrgr3
@Lnx if you don't think being object oriented is inherently an advantage, maybe you should stick with a non-oo language. Java is not c sans pointers, it's an object oriented language (even if it's not a clean one). object orientation gains nothing at the machine level, but it makes code readable and reusable and reduces bugs because it's easily testable. Also: If 10 similar situations occur in a given code base, then my solution can be reused as a oneliner, while if/else based solutions would have to be repeated entirely.
seanizer
BTW I strongly disagree with your point about abstraction. Abstraction is what makes code manageable.
seanizer
@seanizer Do you seriously believe this is more maintainable than the original? I'm all for OOP when it makes sense, but the chain of if statements was already easy to read and modify.
LnxPrgr3
@Lnx Yes, just as I would prefer an ArrayList over an array. This is a class you can define once and use it for many different purposes. It's immutable, thread safe and reasonably fast, while being extensible and reusable.
seanizer
Intuition reading your code without reading the Mapper class would make me think 10 maps to 6, etc., which it does not. If I ran into this in someone's codebase, I'd have to read and comprehend your class (or its documentation, had you bothered to write any) to know what this really does. This takes longer than understanding a chain of if statements. Sure, your solution is more general and even clever, but who said either was needed in this context?
LnxPrgr3
Even if you were to include a custom comparator, it still wouldn't be clear whether your mapper will "round" up or down to the thresholds. You have to go and look inside the class. Given that the > operator has been thoroughly tested, I'm betting your coworkers would rather use that. Also, there's no indication that your abstraction is even right. You've hard-coded the default case, and the > relationship, while at the same time assuming that the OP "might" need to put in strings at a later date. Given the variable name "size", that just seems like an odd assumption to make.
Chris Burt-Brown
Although I will agree with you that other answers here are also worth the downvote.
Chris Burt-Brown
@lnx a solution like this relies on documentation, that's clear. 2 lines of javadoc and every fellow worker would immediately see what this is about, without even leaving the IDE. @chris I am not saying others deserve a downvote, quite the contrary. I am saying when someone takes the time to elaborate a working solution they deserve some respect cos they are the people who keep this site alive. My own policy is this: upvote what I like, downvote crap only. And whether you like my solution or not, it definitely is not crap. It may be overkill, but it works
seanizer
Does it? Even after importing `java.util.*` your code fails to compile on this line in the constructor for Mapper: `final U[] copy = Arrays.copyOf(source, source.length);`
LnxPrgr3
Sorry I refactored the code in the browser earlier and apparently forgot to change the name of a type variable. It should qork now, just as the first version worked (but I have no way of testing it right now as there's no jdk on my iphone)
seanizer
Work, not qork. Although it might also qork. I'll test that when I get home. Btw including import statements in SO listings is considered unnecessary noise in obvious cases like this one.
seanizer
This compiles, but has unexpected results: `Mapper<Integer, Integer> map = Mapper.from(145, 117, 68, 51, 22, 10).to(1, 2, 3, 4, 5, 6); System.out.println(map.mapAll(Arrays.asList(11, 23, 52, 69, 118, 146)));` returns `[1, 2, 3, 4, 5, 6]` where I'd expect quite the opposite. This would be much easier to spot and fix in a chain of if statements.
LnxPrgr3
Unexpected? Since Comparable is used internally, every other behavior would be nonsense and potentially undecidable. E.g. If you asked the program to map 10,30,50,20,40 to a, b, c, d and e what are going to do with input value 25? You have to sort the input first to successfully use comparable. And of course one could extend it easily to reverse sort order something like `Mapper.descending().from(..).to(..)`, but I`d prefer to sort my input data, as it's the more intuitive way to do things.
seanizer
What's obvious is that this solution relies on documentation, but that's common to all solutions here. (I assume the requested mapping will be used in more than one place and hence will be stored in some static method in the if/else case just as it will be stored in an instance variable or a singleton in the case of my solution.)
seanizer
What are you talking about? Notice all I did was flip both arrays… so it should provide the same output as your example. I notice you sort the input array, but not the output array… it looks like you intended to allow the conditions to be specified out of order, but your class does not support this. Because of this, I can't write the conditions in the natural order they would be written in a chain of if statements. If this is a feature and not a bug, then this class *really* needs documentation, which I notice has not been written.
LnxPrgr3
A better approach would be to use a sorted map of some variety in the first place. This would allow you to sort the values to match the keys, and then users of your class can provide input in arbitrary order. At the very least, this should support writing the conditions in their natural order (`if (v > 145) /* ... */; else if (v > 117) /* ... */`), since the if statement is at least this flexible.
LnxPrgr3
@Lnx Of course no doc has been written, this is no software project of mine, but a quick solution I hacked up in 20 minutes as a tech demo. Sorting the input array is necessary, as I said (and consistent with the NavigableMap version), but sorting the output array would limit the possibilities of this version. "Natural Order" in Java means the order created by the Comparable interface (read http://download.oracle.com/javase/6/docs/api/java/lang/Comparable.html) and hence exactly what I implemented. And it's more intuitive, too. I don't know about you, but I count 1, 2, 3 not 3, 2, 1.
seanizer
I am not saying this is the solution everybody should use, but it's a valid approach and certainly more original than the if / else boredom we all learned 30 or 40 years ago. All I'm saying is: there's more than one way to do it, and coming from an OOP and somewhat functional background this is the kind of solution I favor.
seanizer
+1 Nice syntax, I like it!
Viktor Sehr
@Viktor Thanks. It's +6 vs -6 now. What a strange score...
seanizer
Your answer has earned kind of award: http://forums.thedailywtf.com/forums/p/20192/234306.aspx
BalusC
kewl. fortune and fame, finally. :-)
seanizer
Strangely, reputation has gone *up* since the WTF article. Probably not what the author had in mind. Thanks very much, "Enterprise Architect".
seanizer
We cannot easily say whether this answer is an overkill or not because the OP does not mention where he needs this type of code. All these magic numbers (in the OP's question) make me wonder where is this used.
cherouvim
Exactly. I *assumed* this was a mapping of importance that would be used in several places and have therefor designed a simple reusable solution that can be dependency injected all over the place. If it's a one time thing then yes, of course it's overkill.
seanizer
+1 Good one seanizer
Emil
if you wanted to beat the TreeMap, you should have sorted the arrays and use binary search...
fortran
I did not want to *beat the TreeMap*. The TreeMap is a standard library element that's been under development for years. My code was written in 20 minutes. I'd have to be Jon Skeet to win that battle. That said, yes of course binary search would be a good idea.
seanizer
@Epaga there have been enough comments of people who actually *liked* my solution, both on this page and on the WTF thread that I think I can live both with that possibility and with your own sarcastic downvote.
seanizer
@seanizer sure...and it's all meant in good spirit. for me it's just a great example of enterprise programming where we take something that could be done far faster, far more readably, and far simpler without OOP and morph it into an abstract OOP monster. sometimes that might be needed, but my experience is those times are few and far between.
Epaga
@Epaga *good spirit*? Like this maybe? http://www.flickr.com/photos/gillyberlin/4931194595/ Whatever. For the x + nth time: I was just trying to make the picture complete by adding an object oriented solution. At that time, several good solutions were already present, some of which I had praised and upvoted, so I never said mine is the one that *should* be used. The question was to get rid of ugly if statements and that is exactly what I did, at the cost of efficiency. It was an experiment, nothing more. BTW: I'd say my calling syntax is way more readable than a multi if/else solution.
seanizer
If you have a hammer, everything looks like a nail. I feel sorry for whoever would have to read through that code just to replicate the functionality of an if-statement chain.
Lotus Notes
@Lotus when I look at your user page I see PHP, PHP and even more PHP. Perhaps you should stick with topics that better fit your "Skilset". Sorry, I'm starting to get really p***ed off by people like yourself who dump their bad karma on me. You don't like my code? Fine. You downvote it? Fine. But saying you feel sorry for people who have to read someone's code is unnecessary and f***ing offensive.
seanizer
@seanizer If PHP was really my best skillset, why would I be asking so many questions about it? My main skillset (and career) is Java. For the record, PHP has all the object-oriented features that you need. It's quite ignorant to make a blanket statement about an entire language simply because it doesn't **enforce** OOP. My intent wasn't to be offensive, but rather to be practical. I never said that your code was bad, I simply said that it's an unnecessary burden for any co-worker who would have to read that. In my opinion, managers wouldnt be happy with someone going overboard on abstraction.
Lotus Notes
a) For the record: I used PHP for about 10 years, I'm still recovering. Why would anyone intentionally use PHP if they know Java? Why on earth not Ruby or Python which are now almost equally available everywhere? b) I said if anything they should use it, not read it (when was the last time you read the PHP or Java compiler's code for parsing if statements?) c) Last sentence: agreed, and rightly so. There are a few situations where this solution of mine could solve problems, but in most situations it would be cludgy (and I never said otherwise). I still think my code is prettier than long if...
seanizer
.../else chains but we all know they work so I also use them more often then I like to. I'm not the OOP-maniac everybody seems to think I am, I was just interested in making a technology demonstration.
seanizer
@seanizer sorry if my downvote or comment offended you. i wouldn't take downvotes so personal if i were you. this is your style of code. it seems that about 50% of SO users disagree with you. that could be a lot worse. it's kind of a glass-half-full thing I suppose. ;-)
Epaga
and about this statement: *it seems that about 50% of SO users disagree with you*. Most of the flaming commentators above have little to no activity in the java tags, so I am guessing it will be the same for the downvoters. I have not received criticism (much less flaming) from *anyone* with at least a bronze java badge while those who liked my solution are mostly very active in the Java tag. So while it's probably more than 95% of SO users that find my solution awful, the experienced java guys apparently don't and that's what counts for me.
seanizer
@seanizer dude now you're the one getting personal. chill. I was not asking people to ridicule you - but since that's the way you took it, I just deleted the comment and I apologize it came across that way.
Epaga
OK, mine is gone too. Maybe it's about time someone closed this thread.
seanizer
amen to that, man. :)
Epaga
+6  A: 

My commenting ability isn't turned on yet, hopefully no one will say "rightfully" based on my answer...

Pretty-ing up the ugly code could/should be defined as trying to achieve:

  1. Readability (OK, stating the obvious -- redundant to the question perhaps)
  2. Performance -- at best seeking optimal, at worst it's not a big drain
  3. Pragmatism -- it's not far off the way most people do things, given an ordinary problem that's not in need of an elegant or unique solution, changing it later on should be a natural effort, not in need of much recollection.

IMO the answer given by org.life.java was the prettiest and extremely easy to read. I also liked the order in which the conditions were written, for reasons of reading and performance.

Looking over all the comments on this subject, at the time of my writing, it appears that only org.life.java raised the issue of performance (and maybe mfloryan, too, stating something would be "longer"). Certainly in most situations, and given this example it shouldn't bear a noticeable slowdown however you write it.

However, by nesting your conditions and optimally ordering the conditions can improve performance [worthwhile, particularly if this were looped].

All that being said, nesting and ordering conditions (that are more complex than your example) brought on by determination to achieve as fast as possible execution will often produce less readable code, and code that's harder to change. I refer again to #3, pragmatism... balancing the needs.

Chris Adragna
+1  A: 

Is there an underlying mathematical rule to this? If so you should use that: but only if it comes from the problem domain, not just some formula that happens to fit the cases.

EJP
+18  A: 

The original code seems fine to me, but if you don't mind multiple returns you might prefer a more tabular approach:

if ( v > 145 ) return 1;
if ( v > 117 ) return 2;
if ( v >  68 ) return 3;
if ( v >  51 ) return 4;
if ( v >  22 ) return 5;
if ( v >  10 ) return 6;
return ...;     // The <= 10 case isn't handled in the original code snippet. 

See the multiple return or not discussion in org.life.java's answer.

Jim Ferrans
A: 

I realise this is ultimately the same as mfloryan's approach, except for the sorting improvement which improves maintainability and extensibility. And comments.

Sorry for the ridiculously mixed syntax -- this is supposed to be C but I cannot remember nor can be bothered to look it up.

This returns 7 for the <= 10 case, which is the logical extension although undefined. And I would rather much like it to return null in mfloryan's example if the code actually runs into the impossibility regime. It had better fail as early as possible.

int thresholds[] = sort({10,22,51,68,117,145});

// compares v -- retval is _supposed_ to be in reverse order!

for( int i = thresholds.length - 1, i >= 0, --i ) {

  if( v > thresholds[i] ) return thresholds.length - i;

}

Note: the index counts down because I want to highlight the fact that the optimising compiler will automatically reverse the for statement so that the comparison to zero is done last. This means that even if you count up from zero, this algorithm will hold true. If the problem space tends to be small and you want to count the other way round (for optimising the loop), you will need to reverse the output of the sort function. This you will probably want to do in the case of large thresholds arrays.

Or maybe I should just simply put my money where my mouth is:

int thresholds[] = reverse(sort({10,22,51,68,117,145}));

// compares v -- retval is _supposed_ to be in reverse order!

for( int i = 0, i < thresholds.length,
++i ) {

  if( v < thresholds[i] ) return 1+i;

}

There! Much nicer! I do realise that sort in C is really qsort and that means the reverse and sort functions are combined. (Inner Lisper in me is screaming that this is definitely not the most optimal in functional programming, but this is really so simple it does not warrant any more time than I have already given it.)

anon
+6  A: 
return (v-173) / -27;
mhaller
btw, it's not correct, only an approximation
mhaller
+1 because it was an interesting approach. `(173-v) / 27` to remove the `-`. It doesn't work for big numbers, and at near the thresholds. But interesting...
ring0
"btw, it's not correct" LOL
Chris Burt-Brown
What is the formula behind this?
kofucii
+1  A: 

This is my code sample, using SortedSet. You initialise boundaries once.

SortedSet<Integer> boundaries = new SortedSet<Integer>;

boundaries.add(10);

boundaries.add(22);

boundaries.add(51);

boundaries.add(68);

boundaries.add(117);

boundaries.add(145);

Then use it subsequently this way for multiple values of v (and initialised size)

SortedSet<Integer> subset =  boundaries.tailSet(v);
if( subset.size() != boundaries.size() )
  size = subset.size() + 1;
CashCow
This is intended to show a generic way to make a "histogram" type lookup, and uses an O(log N) lookup for the number of boundaries. Where N is small this may prove to be less efficient than nested if statements.
CashCow
First of all: this won't compile. It must be `SortedSet<Integer>`, not `SortedSet<int>`. Also this approach has already been taken more effectively using `NavigableMap.lowerEntry()` here: http://stackoverflow.com/questions/3786358/get-rid-of-ugly-if-statements/3786541#3786541
seanizer
No, NavigableMap is different because you had to associate the value with the entry. Now what do you think would happen if they decided to insert 89 as an extra boundary and we now return 1..7. I would require one extra add() whilst NavigableMap would require changing the values on all the lower existing ones.
CashCow
Yup, I know, my own solution is the most flexible one :-)
seanizer
@seanizer: Your solution is indeed the most flexible one, but if the intention of the OP is that of what Neil thinks it is (count the # of boundaries above value v) his solution would be more correct.
disown
@disown True. There is never a perfect solution for all cases. The question is whether you want to make a perfect solution for a tiny number of cases or good solution that matches many cases. In a performance-critical part of your application you'll take the first approach, but if you're going to have to meet changing requirements (and don't they always change?) then a flexible solution is more appropriate.
seanizer
@seanizer: I think that the real problem is that you cannot read out the original problem statement from a general solution. This leads to a lot of solutions to non-existent problems, trying to keep the flexibility of the first solution. We simply don't know if the sequence 6..1 is arbitrary or not, so why not just ask the OP? If you have changing requirements, chances are they change something which isn't reflected in your 'semi-general' solution, so you are better of making a very specific solution.
disown
a) At the time I wrote my solution, there were about 8 answers, one of which was already accepted. I didn't think the OP would still be participating in the discussion. b) all the other good versions were already there, my approach was to point out a different and unusual approach to make the picture complete. c) yes, I was making some assumptions and I think I stated that I did. Remember, the OP is not my client, he's a guy who asked a question on SO. He got good answers and bad answers but first of all he got free answers from people who regularly charge loads of money for their services.
seanizer
I don't think that implies the kind of relationship a developer has with a client, do you?
seanizer
@seanizer: I don't mind people posting solutions, and I didn't think that your solution was bad, I'm just saying that the guess of Neal (1..6 won't change and the problem is 'count boundaries above') is just as good as yours (1..6 will change and the problem is 'map number to integer interval'). You both posted working solutions, and both are good, but none is better than the other, since we simply don't know what the original problem was.
disown
@disown sure, but you forget the context: my solution was WTFed, flamed and downvoted, others weren't, while I respected other solutions and upvoted several of them. So when I get some yahoos calling my code a monstrosity, I at least reserve to point out the weaknesses of other solutions, even if it's not Neil's fault. I think this solution is short-sighted and I am allergic to that kind because short-sighted solutions from other developers have caused me hundreds of work hours in the past. That's why I don't subscribe to the YAGNI philosophy, I've seen it fail too often.
seanizer
@seanizer: I don't subscribe to YAGNI in that sense either, I do believe that it is warranted to prepare for 'any change' if it is possible to do so (like using data models, strategy patterns etc). But I'm against preparing for non-general change like 'maybe someone will want to change the layout of this button so I'll stick the dimensions in a property file'. Of course it's a delicate balance trying to decide when to introduce general flexibility, which is why I think YAGNI is phrased as it is now.
disown
Yes, I can agree to that.
seanizer
+1  A: 

Just for completeness, let me suggest that you could set up an array SIZES with 145 elements so the answer could be returned directly as SIZES[v]. Pardon me for not writing the whole thing out. You would have to make sure v was in range, of course.

The only reason I can think of for doing it that way would be if you were going to create the array once and use it thousands of time in an application that had to be really fast. I mention it as an example of a trade-off between memory and speed (not the problem it once was), and also between setup time and speed.

phv3773
This is the same as my solution below that got -1 for whatever reason even though it is a very reasonable situation for some situations.
CashCow
+8  A: 

I have one more version for you. I don't really think it's the best one because it adds unnecessary complexity in the name of "performance" when I'm 100% sure this function will never be a performance hog (unless someone is calculating size in a tight loop a million times ...).

But I present it just because I thought performing a hard-coded binary search to be sort of interesting. It doesn't look very binary-y because there aren't enough elements to go very deep, but it does have the virtue that it returns a result in no more than 3 tests rather than 6 as in the original post. The return statements are also in order by size which would help with understanding and/or modification.

if (v > 68) {
   if (v > 145) {
      return 1
   } else if (v > 117) {
      return 2;
   } else {
      return 3;
   }
} else {
   if (v > 51) {
      return 4;
   } else if (v > 22) {
      return 5;
   } else {
      return 6;
   }
}
Emtucifor
Efficient and original (+1), but a maintenance nightmare
seanizer
Clearly a maintenance nightmare. But it was fun to think up! :)
Emtucifor
If you use a code generation mechanism to build this automatically this could actually be pretty good.
seanizer
I welcome downvotes, but WISH you would comment why, please?
Emtucifor
upvoted because I also said that in another comment, but I was too lazy to post the answer xD
fortran
+33  A: 

The most obvious problem with the OPs solution is branching, so I would suggest a polynomial regression. This will result in a nice branchless expression on the form

size = round(k_0 + k_1 * v + k_2 * v^2 + ...)

You will of course not get an exact result, but if you can tolerate some deviance it's a very performant alternative. Since the 'leave unmodified' behavior of to original function for values where v<10 is impossible to model with a polynomial, I took the liberty of assuming a zero-order hold interpolation for this region.

For a 45-degree polynomial with the following coefficients,

-9.1504e-91 1.1986e-87 -5.8366e-85 1.1130e-82 -2.8724e-81 3.3401e-78 -3.3185e-75  9.4624e-73 -1.1591e-70 4.1474e-69 3.7433e-67 2.2460e-65 -6.2386e-62 2.9843e-59 -7.7533e-57 7.7714e-55 1.1791e-52 -2.2370e-50 -4.7642e-48 3.3892e-46 3.8656e-43 -6.0030e-41 9.4243e-41 -1.9050e-36 8.3042e-34 -6.2687e-32 -1.6659e-29 3.0013e-27 1.5633e-25 -8.7156e-23  6.3913e-21 1.0435e-18 -3.0354e-16 3.8195e-14 -3.1282e-12 1.8382e-10 -8.0482e-09 2.6660e-07 -6.6944e-06 1.2605e-04 -1.7321e-03 1.6538e-02 -1.0173e-01 8.3042e-34 -6.2687e-32 -1.6659e-29 3.0013e-27 1.5633e-25 -8.7156e-23 6.3913e-21 1.0435e-18 -3.0354e-16 3.8195e-14 -3.1282e-12 1.8382e-10 -8.0482e-09 2.6660e-07 -6.6944e-06 1.2605e-04 -1.7321e-03 1.6538e-02 -1.0173e-01 3.6100e-01 -6.2117e-01 6.3657e+00

, you get a beautifully fitted curve:

alt text

And as you can see, you get an 1-norm error of just 1.73 across the whole range from 0 to 200*!

*Results for v∉[0,200] may vary.

disown
+1 real funny :-)
fortran
I love this because I've always enjoyed coming up with expressions that "pack" the data for a lookup into mathematical expressions. Take some constants, throw in some multiplication, addition, modulus, abs(), floor(), and so on, and you can do fun stuff to calculate things like the number of days in each month. But I never considered a polynomial for this sort of thing!
Emtucifor
My head hurts...
Blixt
Wow! Could You elaborate more on the tools You used to produce the output?
Rekin
That's almost disgusting...
musicfreak
How did you find out the coefficients? Is it some advanced math?
Johannes Schaub - litb
It's just a polynomial regression, see http://en.wikipedia.org/wiki/Polynomial_regression. I used Octave to write the output function and computed a best fit using the polyfit function.
disown
If you round to nearest int after calculating, you should get near perfect results.
Thomas Ahle
A: 

If you really want the fastest O(N) time solution for this particular answer this one is constant lookup.

final int minBoundary = 10;
final int maxBoundary = 145;
final int maxSize = 6;
Vector<Integer> index = new Vector<Integer>(maxBoundary);
    // run through once and set the values in your index

subsequently

if( v > minBoundary )
{
   size = (v > maxBoundary ) ? maxSize : index[v];
}

The issue with this is that it uses more memory and of course if maxBoundary is a lot bigger it will be very space inefficient (as well as take a longer time to initialise).

This may sometimes be the best solution for the situation.

CashCow
A: 

Actually, if the sizes are likely to change, doing it in the database could be a good alternate strategy:

CREATE TABLE VSize (
   LowerBound int NOT NULL CONSTRAINT PK_VSize PRIMARY KEY CLUSTERED,
   Size int NOT NULL
)
INSERT VSize VALUES (10, 6)
INSERT VSize VALUES (22, 5)
INSERT VSize VALUES (51, 4)
INSERT VSize VALUES (68, 3)
INSERT VSize VALUES (117, 2)
INSERT VSize VALUES (145, 1)

And a stored procedure or function:

CREATE PROCEDURE VSizeLookup
   @V int,
   @Size int OUT
AS
SELECT TOP 1 @Size = Size
FROM VSize
WHERE @V > LowerBound
ORDER BY LowerBound
Emtucifor
:-) agreed, since oracle is the master you have to use a database for this
bobndrew
Why the down votes? You guys like releasing a new version just to accommodate data changes?
Emtucifor
A: 

A clean up of mfloryan's answer:

int getSize( int v ) {
    int r = 0;
    for( int t : new int [] { 145,117,68,51,22,10}){
        r++;
        if( v > t ) { return r; }
     }
     return 1;
} 
OscarRyz
+1  A: 

It is interesting that there are plenty of beautiful answers for a simple "ugly" question. I like mfloryan's answer best, however I would push it further by removing the hard-coded array inside the method. Something like,

int getIndex(int v, int[] descArray) {
    for(int i = 0; i < descArray.length; i++)
        if(v > descArray[i]) return i + 1;
    return 0;
}

It now becomes more flexible and can process any given array in descending order and the method will find the index where the value 'v' belongs.

PS. I cannot comment yet on the answers.

Adrian M
A: 

So many answers and no one has suggested using Drools. if you think if's are ugly you should find Drools amazing.

01
A: 

The obvious answer is to use Groovy:

def size = { v -> [145,117,68,51,22,10].inject(1) { s, t -> v > t ? s : s + 1 } }

One liners are always better. Returns 7 for the undefined case where v <= 10.

Zutty
A: 
7 - x>10 + x>22 + x>51 + x>68 + x>117 + x>145

where 7 is the default value (x <= 10).

George B.