A somewhat painful triple-nested ternary operator
Asked Answered
E

10

62

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"?

Epicritic answered 10/5, 2012 at 2:22 Comment(6)
Yes, looks like you got the logic right, aside from a couple of capitalization discrepanciesHolo
the first c at if(c == 0) should be a capital c, no?Forspent
Huh, did I? I feel like a threw a dart at a dartboard with my hinds tied behind my back, blindfolded, and scored a bullseye. And yes, @NiftyDude, that's correct. Thanks for the catch!Epicritic
Looks correct. Since you're converting to python, I'm sure you won't care in this case, but note that == in JavaScript does type coercion, so there is potential for the two to be different.Flamen
You're doing refactoring. Have a few representative test cases and run these to verify the output is what you want after translation.Exterritorial
@TimothéeGroleau: Naturally, thanks for the tip. :)Epicritic
A
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;
}
Antilog answered 10/5, 2012 at 2:26 Comment(1)
Why not use a nice truthy switch statement while you're at it ;)Fluidize
S
55

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 ;
Saltsman answered 2/9, 2016 at 19:0 Comment(8)
However in webkit nested unary expr is 60% slower and in geco a bit slower than else if or switch.Satyr
@Zydnar, I don't know, man. I just ran a simple benchmark in Chrome console and if-then-else is slower: 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.Saltsman
Note that the nanosecond for a single iteration includes the time spent on the for logic and = assignment.Saltsman
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.Satyr
jsben.ch/9zcXt ok, my bad if-else is slower, but switch is faster than unary and if-elseSatyr
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.Saltsman
Also, switch does not handle varying conditions.Saltsman
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'); }Satyr
A
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;
}
Antilog answered 10/5, 2012 at 2:26 Comment(1)
Why not use a nice truthy switch statement while you're at it ;)Fluidize
F
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

Faculty answered 10/4, 2018 at 20:20 Comment(0)
D
7
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.

Diversity answered 21/2, 2017 at 15:46 Comment(0)
D
6

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.

Deonnadeonne answered 10/5, 2012 at 2:27 Comment(2)
Very cool, thanks. I was mostly looking for the logic validation, though, just fyi. :)Epicritic
It's pretty widely accepted that the space/"cleanliness" savings of not using brackets is heavily outweighed by the dangerous implications for code maintainability. https://mcmap.net/q/135933/-is-it-a-bad-practice-to-use-an-if-statement-without-curly-braces-closedAlphonso
S
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;
Suprasegmental answered 10/5, 2012 at 2:30 Comment(6)
Since I'll be converting this to Python, the syntax doesn't really matter. Thanks anyways, though!Epicritic
I think the python equivalent would be elif... the point that everyone seems to want to make is to avoid the unnecessary indentation.Popularity
@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!Epicritic
It's pretty widely accepted that the space/"cleanliness" savings of not using brackets is heavily outweighed by the dangerous implications for code maintainability. https://mcmap.net/q/135933/-is-it-a-bad-practice-to-use-an-if-statement-without-curly-braces-closedAlphonso
@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 :-)Suprasegmental
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...Alphonso
T
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; }
}
Toro answered 15/10, 2020 at 11:32 Comment(0)
P
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...

Penstemon answered 18/4, 2020 at 19:12 Comment(0)
A
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
)
Alpers answered 28/10, 2020 at 19:54 Comment(0)
A
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;
)();
Aplenty answered 26/8, 2022 at 17:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.