60

I went looking through Raphael.js's source code to find out how he converted RGB values to HSB. I found out the function he did it in and I was in the process of converting it to Python when I bumped into this nice triple-nested ternary operator:

H = (C == 0 ? null :
    V == r ? (g - b) / C :
    V == g ? (b - r) / C + 2 :
             (r - g) / C + 4
    );

It threw me for a loop because Python doesn't have the same kind of ternary operator that Javascript does. I spent a while looking over it and eventually hashed this somewhat saner code (using only if/else) out of it:

if (C == 0) {
    H = null;
} else {
    if(V == r) {
        H = (g - b) / C;
    } else {
        if(V == g) {
            H = (b - r) / C + 2;
        } else {
            H = (r - g) / C + 4;
        }
    }
}

Was my interpretation correct? I'm only asking this because if it isn't correct, I'm faced with a lot of debugging. So. Did I "get it"?

Óscar López
  • 232,561
  • 37
  • 312
  • 386
Elliot Bonneville
  • 51,872
  • 23
  • 96
  • 123

10 Answers10

53

To my personal taste, a carefully aligned nested ternary beats the if-else mess:

const H =
  C == 0 ? null            :
  V == r ? (g - b) / C     :
  V == g ? (b - r) / C + 2 :
           (r - g) / C + 4 ;
uzay95
  • 16,052
  • 31
  • 116
  • 182
Andrey Mikhaylov - lolmaus
  • 23,107
  • 6
  • 84
  • 133
  • However in webkit nested unary expr is 60% slower and in geco a bit slower than else if or switch. – Zydnar May 23 '20 at 16:08
  • 3
    @Zydnar, I don't know, man. I just ran a simple benchmark in Chrome console and if-then-else is slower: https://i.imgur.com/Fqjyifl.png . Regardless of the variant used, a single operation takes around `0.000000001` seconds. That's a nanosecond, so you're fighting over a difference in picoseconds here! If you're procedurally generating data in real time, then it might matter. However, for the absolute majority of JS developers the difference is absolutely negligible. What does matter a lot though is code readability and speed of development. – Andrey Mikhaylov - lolmaus May 24 '20 at 07:52
  • Note that the nanosecond for a single iteration includes the time spent on the `for` logic and `=` assignment. – Andrey Mikhaylov - lolmaus May 24 '20 at 07:59
  • It depends on the project, if it's a simple website you don't care if it's high performance project with big data... Besides eslint also will mark nested tenary as bad practice, unless you'll turn it off. And simple benchmark doesn't count because of many reasons even some program in background or plugins can affect it. – Zydnar May 24 '20 at 17:23
  • https://jsben.ch/9zcXt ok, my bad if-else is slower, but switch is faster than unary and if-else – Zydnar May 24 '20 at 17:28
  • The difference is absolutely negligible. Those few developers who are working on projects where performance difference matters — are not looking for newbie answers on StackOverflow. – Andrey Mikhaylov - lolmaus May 25 '20 at 18:37
  • Also, `switch` does not handle varying conditions. – Andrey Mikhaylov - lolmaus May 25 '20 at 18:39
  • You're right I was thinking about different language probably python where performance is opposite when it comes to unary expression. Switch DOES HANDLE varying conditions - try: ```lol = true; n=2 switch (lol) { case 1 < n: console.log('yay'); }``` – Zydnar May 25 '20 at 18:59
42

I think you can have this to avoid the deep nesting:

var H

if(C == 0){
    H = null;
}
else if(V == r){
    H = (g - b) / C;
}
else if (V == g){
    H = (b - r) / C + 2;
}
else {
    H = (r - g) / C + 4;
}
Joseph
  • 117,725
  • 30
  • 181
  • 234
  • 9
    Why not use a nice [truthy switch statement](https://stackoverflow.com/questions/5464362/javascript-using-a-condition-in-switch-case/9055603#9055603) while you're at it ;) – Gust van de Wal Sep 20 '17 at 10:41
12

If your JavaScript codebase contains nested ternary statements like the one in question, consider converting the formatting to daisy chained ternary statements instead.

H = (C == 0)           // Is C zero?
    ? null             // Then return `null`, else ...
    : (V == r)         // Is V equal to r?
    ? (g - b) / C      // Then return this value, else ...
    : (V == g)         // Is V equal to g?
    ? (b - r) / C + 2  // Then return this value
    : (r - g) / C + 4; // Otherwise fall back to this default value

They simply read top to bottom in a straight line, returning a value as soon as they hit a truthy condition or the fallback.

Nested Ternaries are Great, Eric Elliot

Lars Gyrup Brink Nielsen
  • 3,939
  • 2
  • 34
  • 35
7

The same logic can be written in a simpler way:

var H

if (C == 0)
    H = null;
else if (V == r)
    H = (g - b) / C;
else if (V == g)
    H = (b - r) / C + 2;
else
    H = (r - g) / C + 4;

It's possible to omit the curly braces because there's a single statement in each condition. And given that the conditions are mutually exclusive, using else if is much clearer than nesting ifs.

Óscar López
  • 232,561
  • 37
  • 312
  • 386
  • 2
    Very cool, thanks. I was mostly looking for the logic validation, though, just fyi. :) – Elliot Bonneville May 10 '12 at 02:28
  • 6
    It's pretty widely accepted that the space/"cleanliness" savings of not using brackets is heavily outweighed by the dangerous implications for code maintainability. http://stackoverflow.com/a/2125078/205192 – DougW Jul 02 '14 at 18:12
6
H = C == 0 
    ? null 
    : V == r 
        ? (g - b) / C 
        : V == g 
            ? (b - r) / C + 2 
            : (r - g) / C + 4

I've seen Dan Abramov using this indentation placement pattern. While I don't like how the conditional operator ? no longer visually follows the condition, I prefer this to something like @lolmaus's example in that the indentation will always be consistent regardless the size of the conditional.

You actually start to look at it as ? true : false which is visually intuitive here. And this way, I find the ternary is much easier to spot and differentiate from the surrounding code.

ssomnoremac
  • 729
  • 1
  • 8
  • 16
3

Yes, it's right (apart from capitalisation differences). Yet, it may be cleaner written without any parentheses, readable as elseif:

if (C == 0)
    h = null;
else if (V == r)
    h = (g - b) / C;
else if (V == g)
    h = (b - r) / C + 2;
else
    h = (r - g) / C + 4;
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Since I'll be converting this to Python, the syntax doesn't really matter. Thanks anyways, though! – Elliot Bonneville May 10 '12 at 02:30
  • 3
    I think the python equivalent would be `elif`... the point that everyone seems to want to make is to avoid the unnecessary indentation. – Dagg Nabbit May 10 '12 at 02:34
  • 2
    @GGG: That is correct. And yeah. I guess if you're a programmer your natural instinct is to make anything readable... can't blame anybody for that! – Elliot Bonneville May 10 '12 at 02:35
  • It's pretty widely accepted that the space/"cleanliness" savings of not using brackets is heavily outweighed by the dangerous implications for code maintainability. http://stackoverflow.com/a/2125078/205192 – DougW Jul 02 '14 at 18:12
  • @DougW: There hardly will be second statement; the only purpose of this code is to assign to a single variable. Also, the OP is converting this to Python where no brackets are actually enforced :-) – Bergi Jul 02 '14 at 18:22
  • Ah, that's fair. I missed his mention of python. Someone should correct the question to remove the braces from the example then. That said, anticipating that there will never be a second statement is an assumption that proves wrong again and again and again... – DougW Jul 08 '14 at 18:27
3

As mentioned in MDN Docs:

function example(…) {
    return condition1 ? value1
         : condition2 ? value2
         : condition3 ? value3
         : value4;
}

// Equivalent to:

function example(…) {
    if (condition1) { return value1; }
    else if (condition2) { return value2; }
    else if (condition3) { return value3; }
    else { return value4; }
}
Shivam Jha
  • 3,160
  • 3
  • 22
  • 36
1

Here's another, more elegant idea...

if (C != 0) 
{
  if (V == r) return (g - b) / C;
  if (V == g) return (b - r) / C + 2;
  return (r - g) / C + 4;
}

return null;

Just wrap this in function and use instead of H...

onedevteam.com
  • 3,838
  • 10
  • 41
  • 74
0

Why not use ternary operators found in Python?

H = (
    None            if C == 0 else
    (g - b) / C     if V == r else
    (b - r) / C + 2 if V == g else
    (r - g) / C + 4
)
0

I tend to avoid nested if/else (even more nested ternary operators ? : ) statements as I find them hard to understand when I'm reading code.

One way to avoid nesting is to use early returns on functions. To do this we use a lambda.

const H = (() =>
  if(C == 0) {
    return null;
  }

  if(V == r) {
    return (g - b) / C;
  }

  if(V == g){
    return (b - r) / C + 2;
  } 

  return (r - g) / C + 4;
)();