108

For example, something like this:

var value = someArray.indexOf(3) !== -1 ? someArray.indexOf(3) : 0

Is there a better way to write that? Again, I am not seeking an answer to the exact question above, just an example of when you might have repeated operands in ternary operator expressions...

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user1354934
  • 8,139
  • 15
  • 50
  • 80

17 Answers17

176

Personally I find the best way to do this is still the good old if statement:

var value = someArray.indexOf(3);
if (value === -1) {
  value = 0;
}
slebetman
  • 109,858
  • 19
  • 140
  • 171
  • 32
    To be clear, this answer advocates the usage of a variable to store an intermediate result, not the usage of an `if` statement in place of a ternary. Ternary statements are still frequently useful to perform a choice as part of an expression. – Kenan Banks Apr 12 '17 at 14:44
  • 5
    @Triptych: Look at it again. It does not use an intermediate variable at all. Instead it assigns the result directly to the final variable and then overwrite it if the condition is met. – slebetman Apr 12 '17 at 15:12
  • 14
    Incorrect. The value stored in `value` after the first line is intermediate. It does not contain the correct value which is then fixed up on the next line and is thus an intermediate. It is only after the `if` statement concludes that `value` contains the correct value. The ternary in the OP is a better solution that this because it never enters an intermediate state. – Jack Aidley Apr 12 '17 at 15:16
  • 45
    @JackAidley: "The ternary in the OP is a better solution that this because it never enters an intermediate state." - I'm going to have to disagree. This is a lot more readable than OP's code, and entirely idiomatic. It also makes a bug in OP's logic a bit more obvious to me (Namely, what happens if indexOf() returns zero? How do you distinguish a "real" zero from a "not found" zero?). – Kevin Apr 12 '17 at 16:50
  • 2
    this is close to what I would do, except that `value` here is technically mutated, which I try to avoid. – Dave Cousineau Apr 12 '17 at 21:09
  • @Kevin I don't understand what logic error is implied in that code. Could you point me at something useful so that I and others can be informed? – Marc.2377 Apr 13 '17 at 17:30
  • 2
    @Marc.2377 it's less of a "real" logic error and more of a "possible" logic error. If `someArray.indexOf(3)` returns 0, `value` is set to 0, just like if it returns -1. Two different states with the same return value (especially when one is a special case) *could* be a bug, but isn't necessarily. – Delioth Apr 13 '17 at 18:19
107

Code should be readable, so being succinct should not mean being terse whatever the cost - for that you should repost to https://codegolf.stackexchange.com/ - so instead I would recommend using a second local variable named index to maximize reading comprehensibility (with minimal runtime cost too, I note):

var index = someArray.indexOf( 3 );
var value = index == -1 ? 0 : index;

But if you really want to cut this expression down, because you're a cruel sadist to your coworkers or project collaborators, then here are 4 approaches you could use:

1: Temporary variable in a var statement

You can use the var statement's ability to define (and assign) a second temporary variable index when separated with commas:

var index = someArray.indexOf(3), value = index !== -1 ? index: 0;

2: Immediately-Invoked Function Expression (IIFE)

Another option is an anonymous function which is invoked immediately after it’s defined:

// Traditional syntax:
var value = function( x ) { return x !== -1 ? x : 0 }( someArray.indexOf(3) );

// ES6 syntax:
var value = ( x => x !== -1 ? x : 0 )( someArray.indexOf(3) );

3: Comma operator

There is also the infamous "comma operator" which JavaScript supports, which is also present in C and C++.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator

You can use the comma operator when you want to include multiple expressions in a location that requires a single expression.

You can use it to introduce side-effects, in this case by reassigning to value:

var value = ( value = someArray.indexOf(3), value !== -1 ? value : 0 );

This works because var value is interpreted first (as it's a statement), and then the left-most, inner-most value assignment, and then the right-hand of the comma operator, and then the ternary operator - all legal JavaScript.

4: Re-assign in a subexpression

Commentator @IllusiveBrian pointed out that the use of the comma-operator (in the previous example) is unneeded if the assignment to value is used as a parenthesized subexpression:

var value = ( ( value = someArray.indexOf(3) ) !== -1 ? value : 0 );

Note that the use of negatives in logical expressions can be harder for humans to follow - so all of the above examples can be simplified for reading by changing idx !== -1 ? x : y to idx == -1 ? y : x - or idx < 0 ? y : x.

var value = ( ( value = someArray.indexOf(3) ) == -1 ? 0 : value );
Dai
  • 141,631
  • 28
  • 261
  • 374
  • 99
    All of these are the sort of "clever" coding that would make me stare at it for a few seconds before thinking "huh, I guess that works." They do not help with clarity, and since code is read more than it is written that is more important. – Gavin S. Yancey Apr 12 '17 at 09:39
  • 18
    @g.rocket approach 1 is 100% readable and clear, and the only way to go if you want to avoid repetition (that could be really harmful if you are calling some complex and problematic function instead of a simple `indefOf`) – edc65 Apr 12 '17 at 10:24
  • 5
    Agreed, #1 is very readable and maintainable, the other two not so much. – forgivenson Apr 12 '17 at 11:12
  • 6
    For #3, I believe you can simplify to `var value = ((value = someArray.indexOf(3)) === -1 ? 0 : value);` instead of using a comma. – IllusiveBrian Apr 12 '17 at 13:57
  • @IllusiveBrian Thank you - I've added your suggestion to my posting. – Dai Apr 12 '17 at 20:31
  • 2 and 4 are abhorrent, 3 is reasonable but I suspect many wouldn't understand it, option 1 is certainly the best solution. – Pharap Apr 13 '17 at 06:29
  • make 2 have a named function `indexOr0`and I find it not so abhorrent – Robin Gertenbach Apr 13 '17 at 07:30
  • 2
    In the second example _Self-executing anonymous function_ you can omitted parentheses from arrow function. `var value = ( x => x !== -1 ? x : 0 ) ( arr.indexOf(3) );` because is only one parameter. – Alex Char Apr 13 '17 at 09:50
  • 1
    I consider 1 to be ok, 2 also ok, at least from a programmer used to FP. 3 and 4 are harder for me since I have to think about the order of side effects. They are still quite clear, but they require more thought. – chi Apr 13 '17 at 11:30
  • The imperfection with 1: it leaks a variable into the code below. It's better practice to limit the scope of variables to where they are really needed. – reinierpost Apr 14 '17 at 11:01
  • @edc65 You wouldn't want to repeat an `indexOf` since it's a linear-time operation. – David Conrad Apr 14 '17 at 17:47
  • @DavidConrad running a O(n) operation twice is still O(n), so it isn't that big a problem. – Dai Apr 14 '17 at 18:02
  • This answer is a good example why the general rule is to supply only one solution to this type of question per answer. With multiple, simple, approaches in a single answer, the votes on the answer can not be correlated to people's opinions as to the best solution to use. As it is, we have to read through the comments to get a feel for what people think of the various solutions. – Makyen Apr 14 '17 at 19:23
  • @Dai Sure, and running it a million times or a billion times is still O(n), too. – David Conrad Apr 14 '17 at 20:15
  • @DavidConrad Indeed it is. Just like an `indexOf` method that systematically iterates over the first million characters still has a O(1) complexity. – ccjmne Apr 15 '17 at 11:13
54

For numbers

You can use the Math.max() function.

var value = Math.max( someArray.indexOf('y'), 0 );

It will keep the boundaries of the result from 0 until the first result greater than 0 if that's the case. And if the result from indexOf is -1 it will return 0 as is greater than -1.

For booleans and boolean-y values

For JS there is no general rule AFAIK specially because how falsy values are evaluated.

But if something can help you most of the time is the or operator (||):

// Instead of
var variable = this_one === true ? this_one : or_this_one;
// you can use
var variable = this_one || or_this_one;

You have to be very careful with this, because in your first example, indexOf can return 0 and if you evaluate 0 || -1 it will return -1 because 0 is a falsy value.

Kev
  • 15,899
  • 15
  • 79
  • 112
Crisoforo Gaspar
  • 3,740
  • 2
  • 21
  • 27
  • 2
    Thanks. I guess my example is bad I'm just giving a general example haha not seeking a solution to the exact question. I've faced some scnarios like the example where i would like to use a ternary, but end up repeating :( – user1354934 Apr 12 '17 at 03:53
  • 1
    At first example, how do we determine that `3` or `"y"` is not at index `0` of `someArray`? – guest271314 Apr 12 '17 at 04:26
  • On the `Math.max` example? `indexOf` returns the index of the element and if the element is not found returns -1 so you have a a chances to get a number from -1 to the length of the string then with `Math.max` you just set the boundaries from 0 to the length to remove the chance to return a -1, – Crisoforo Gaspar Apr 12 '17 at 04:42
  • @mitogh The logic at code at OP creates an issue, generic example though it is; where 0 could both indicate index `0` of matched element within array, or `0` set at `Math.max()`; or at conditional operator at OP. Consider `var value = Math.max( ["y"].indexOf("y"), 0 )`. How do you determine which `0` is being returned? `0` passed to `Math.max()` call, or `0` reflecting index of `"y"` within array? – guest271314 Apr 12 '17 at 05:08
  • @guest271314 Good thought, but I think it depends on context as to whether that's an issue or not. Perhaps it doesn't matter where the 0 came from, and the only important thing is that it's not -1. An example: maybe you need to pick an item from an array. You want a particular item (in the OP, the number 3), but if that's not in the array, you still need an item, and you're fine with defaulting to whatever the first item is, assuming you know the array isn't empty. – Kev Apr 12 '17 at 08:30
  • @isanae It's turning the sentinel value back into a domain value. Seems reasonable to me. – ErikE Apr 13 '17 at 04:44
27

Not really, just use another variable.

Your example generalizes to something like this.

var x = predicate(f()) ? f() : default;

You're testing a computed value, then assigning that value to a variable if it passes some predicate. The way to avoid re-calculating the computed value is obvious: use a variable to store the result.

var computed = f();
var x = predicate(computed) ? computed : default;

I get what you mean - it seems like there ought to be some way to do this that looks a little cleaner. But I think that's the best way (idiomatically) to do this. If you were repeating this pattern a lot in your code for some reason, you might write a little helper function:

var setif = (value, predicate, default) => predicate(value) ? value : default;
var x = setif(someArray.indexOf(3), x => x !== -1, 0)
Kenan Banks
  • 207,056
  • 34
  • 155
  • 173
20

EDIT: Here it is, the proposal for Nullary-coalescing now in JavaScript!


Use ||

const result = a ? a : 'fallback value';

is equivalent to

const result = a || 'fallback value';

If casting a to Boolean returns false, result will be assigned 'fallback value', otherwise the value of a.


Be aware of the edge case a === 0, which casts to false and result will (incorrectly) take 'fallback value' . Use tricks like this at your own risk.


PS. Languages such as Swift have nil-coalescing operator (??), which serves similar purpose. For instance, in Swift you would write result = a ?? "fallback value" which is pretty close to JavaScript's const result = a || 'fallback value';

Community
  • 1
  • 1
Lyubomir
  • 19,615
  • 6
  • 55
  • 69
  • 3
    PHP (>7.0) and C# also support the null coalescing operator. Syntactic sugar but surely lovely. – Hissvard Apr 13 '17 at 13:10
  • 5
    This only works when the function returns a falsey value when it fails, but `indexOf()` can't be used in this pattern. – Barmar Apr 13 '17 at 14:53
  • 1
    Correct, but he's not asking for the specific example > "Again, not seeking an answer to the exact question above, just an example of when you might have repeated things in the ternary" <, mind you he's rather asking for a generic way to replace repeating ternary (result = a ? a : b). And repeating ternary is equivalent to || (result = a || b) – Lyubomir Apr 13 '17 at 14:55
  • 2
    Is that *really* how `||` works in JavaScript? If I am understanding you correctly, then it works different than many other primary languages (I'm thinking primarily C and its descendants [C++, Java, etc.]) Even if that really is how `||` works in JavaScript, I would advise against using tricks like this which require maintainers to know special quirks about the language. While that trick is cool, I would consider it bad practice. – Loduwijk Apr 13 '17 at 18:51
  • 1
    Also notice that the question is comparing the value to `-1`. Again, I cannot speak for JavaScript and its quirks, but generally `-1` would be a true value, not a false one, and therefore your answer would not work in the question's case and certainly not the general case, rather would work only in a specific (but common enough) sub-case. – Loduwijk Apr 13 '17 at 18:53
  • If I could use a time machine to tweak some early design features of C, I'd make || return the first operand if truthy or else the second, and make && return the first operand if falsy or else the second. Coercing the values to the integers 0 and 1 requires a compiler to generate extra code, or else go through extra effort to avoid doing so, than would simply having the operators return one of the already-computed values. JS did the right thing by changing the behavior vs C. – supercat Apr 16 '17 at 19:34
8

Use an extract variable refactoring:

var index = someArray.indexOf(3);
var value = index !== -1 ? index : 0

It is even better with const instead of var. You could also do an additional extraction:

const index = someArray.indexOf(3);
const condition = index !== -1;
const value = condition ? index : 0;

In practice, use more meaningful names than index, condition, and value.

const threesIndex = someArray.indexOf(3);
const threeFound = threesIndex !== -1;
const threesIndexOrZero = threeFound ? threesIndex : 0;
Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80
6

I personally prefer two variants:

  1. Pure if, like @slebetman suggested

  2. Separate function, which replaces invalid value with default one, like in this example:

function maskNegative(v, def) {
  return v >= 0 ? v : def;
}

Array.prototype.indexOfOrDefault = function(v, def) {
  return maskNegative(this.indexOf(v), def);
}

var someArray = [1, 2];
console.log(someArray.indexOfOrDefault(2, 0)); // index is 1
console.log(someArray.indexOfOrDefault(3, 0)); // default 0 returned
console.log(someArray.indexOfOrDefault(3, 123)); // default 123 returned
Iłya Bursov
  • 23,342
  • 4
  • 33
  • 57
  • 1
    +1, option 2 respects the inline intent of the question, can apply efficiently to other languages than javascript, and promotes modularity. – Devsman Apr 14 '17 at 12:33
6

You're probably looking for a coalescing operator. Luckily, we can leverage the Array prototype to create one:

Array.prototype.coalesce = function() {
    for (var i = 0; i < this.length; i++) {
        if (this[i] != false && this[i] != null) return this[i];
    }
}

[null, false, 0, 5, 'test'].coalesce(); // returns 5

This could be further generalized to your case, by adding a parameter to the function:

Array.prototype.coalesce = function(valid) {
    if (typeof valid !== 'function') {
        valid = function(a) {
            return a != false && a != null;
        }
    }

    for (var i = 0; i < this.length; i++) {
        if (valid(this[i])) return this[i];
    }
}

[null, false, 0, 5, 'test'].coalesce(); // still returns 5
[null, false, 0, 5, 'test'].coalesce(function(a){return a !== -1}); // returns null
[null, false, 0, 5, 'test'].coalesce(function(a){return a != null}); //returns false
Tyzoid
  • 1,072
  • 13
  • 31
  • Adding to the prototype of arrays is risky, because the new element becomes an index inside every array. This means that iterating through the indices also includes the new method: `for (let x in ['b','c']) console.log(x);` prints `0`,`1`,`"coalesce"`. – Charlie Harding Apr 18 '17 at 21:08
  • @CharlieHarding True, but It's generally not recommended to use the for-in operator when looping through arrays. See http://stackoverflow.com/a/4374244/1486100 – Tyzoid Apr 18 '17 at 21:42
5

I like @slebetman's answer. The comment under it express concern about the variable being in an "intermediate state". if this is a big concern for you then I suggest encapsulating it in a function:

function get_value(arr) {
   var value = arr.indexOf(3);
   if (value === -1) {
     value = 0;
   }
   return value;
}

Then just call

var value = get_value( someArray );

You could do more generic functions if you have uses for them in other places, but don't over-engineer if it's a very specific case.

But to be honest I would just do as @slebetman unless I needed to re-use from several places.

Adam
  • 6,539
  • 3
  • 39
  • 65
4

There are two ways I can see of looking at your question: you either want to reduce line length, or you specifically want to avoid repeating a variable in a ternary. The first is trivial (and many other users have posted examples):

var value = someArray.indexOf(3) !== -1 ? someArray.indexOf(3) : 0;

can be (and should be, given the function calls) shortened like so:

var value = someArray.indexOf(3);
value = value !== -1 ? value : 0;

If you are looking for a more generic solution that prevents the repetition of a variable in a ternary, like so:

var value = conditionalTest(foo) ? foo : bar;

where foo only appears once. Discarding solutions of the form:

var cad = foo;
var value = conditionalTest(foo) ? cad : bar;

as technically correct but missing the point, then you are out of luck. There are operators, functions, and methods that possesses the terse syntax you seek, but such constructs, by definition, aren't ternary operators.

Examples:

javascript, using || to return the RHS when the LHS is falsey:

var value = foo || bar; // equivalent to !foo ? bar : foo
asgallant
  • 26,060
  • 6
  • 72
  • 87
3

Use a helper function:

function translateValue(value, match, translated) {
   return value === match ? translated : value;
}

Now your code is very readable, and there's no repetition.

var value = translateValue(someArray.indexOf(3), -1, 0);

The hierarchy of coding concerns is:

  1. Correct (including true performance or SLA concerns)
  2. Clear
  3. Concise
  4. Fast

All the answers on the page so far appear to be correct, but I think my version has the highest clarity, which is more important than conciseness. If you don't count the helper function—as it can be reused—it is the most concise as well. The somewhat similar suggestion to use a helper function unfortunately uses a lambda that, to me, just obscures what it's doing. A simpler function with one purpose that doesn't take a lambda, just values, is to me much better.

P.S. If you like ES6 syntax:

const translateValue = (value, match, translated) => value === match ? translated : value;
let value = translateValue(someArray.indexOf(3), -1, 0); // or const
ErikE
  • 48,881
  • 23
  • 151
  • 196
  • 2
    "my version has the highest clarity" - I disagree. The function name is far too long and naming parameters **input** and **output** is not helpful at all. – adelphus Apr 13 '17 at 14:59
  • So can you suggest better names? I'd be happy to entertain them. Your complaint is solely about cosmetics, so let's fix the cosmetics. Right? Otherwise you're just bike shedding. – ErikE Apr 13 '17 at 15:02
  • In some cases, such a helper function can be useful. In this case, where it is replacing only a specific case of ternary operator, your function will be less clear. I am not going to remember what your function does, and I will have to go look it up again every time I come across it, and I would never remember to use it. – Loduwijk Apr 13 '17 at 18:45
  • 1
    @Aaron That's a reasonable assessment for a particular use case. For what it's worth, my original function name was `translateValueIfEqual` which I thought was more descriptive, but I changed it after someone thought it was too long. Much like the `Nz` function in Access, if you know it, you know it, and if you don't, you don't. In modern IDEs you can just press a key to jump to the definition. And the fallback would be the intermediate variable. I don't really see a big down-side here. – ErikE Apr 13 '17 at 18:50
  • Extra-long symbol names are annoying, yes, but sometimes are necessary for clarity. In your case, I would have gone with the longer name if I used a function at all, but I would have avoided the function altogether. Using a function can be part of a technically-correct answer to the question, sure; I just don't see it as clear as merely using the ternary operator itself in-line. So I'm not up-voting but not down-voting either; I'm just agreeing with @adelphus that the function does not provide more clarity here. I would, however, have left the longer name, even though long names are annoying. – Loduwijk Apr 13 '17 at 19:50
  • 1
    This just goes to show that if you ask the same question to 5 different engineers you will get 10 different answers. – Loduwijk Apr 13 '17 at 19:51
2

I think the || operator can be tailored to indexOf:

var value = ((someArray.indexOf(3) + 1) || 1) - 1;

The returned value is shifted up by 1, making 0 from -1, which is falsey and therefore gets replaced by the second 1. Then it is shifted back.

However, please keep in mind that readability is superior to avoiding repetition.

IS4
  • 11,945
  • 2
  • 47
  • 86
2

This is a simple solution with bitwise NOT and a default value of -1 which results later to zero.

index = ~(~array.indexOf(3) || -1);

It works basically with a double bitwise NOT, which returns the original value or a default value, which after applying bitwise NOT returns zero.

Let's have a look to the table of truth:

 indexOf    ~indexOf   boolean    default     value      result         comment
---------  ---------  ---------  ---------  ---------  ---------  ------------------
      -1          0     falsy          -1         -1          0   take default value
       0         -1    truthy                     -1          0
       1         -2    truthy                     -2          1
       2         -3    truthy                     -3          2
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

You could use re-assignment:

  • initialize variable to one value
  • use the serialization of the && operator for reassignment, because if the first condition is false, the second expression won't be evaluated

Ex.

var value = someArray.indexOf(3);
value == -1 && (value=0);

var someArray = [4,3,2,1];

var value = someArray.indexOf(1);
value == -1 && (value=0);
console.log('Found:',value);

var value = someArray.indexOf(5);
value == -1 && (value=0);
console.log('Not Found:',value);
vol7ron
  • 40,809
  • 21
  • 119
  • 172
  • 3
    @MinusFour To a degree. The variable is repeated, not the expression `someArray.indexOf` is only performed once – vol7ron Apr 12 '17 at 03:51
  • You repeat `value`. – MinusFour Apr 12 '17 at 03:53
  • 3
    @MinusFour Correct, but this is more useful for larger expressions, repeating a variable is insignificant compared to saving the ops. My guess is the OP is not working with `-1` and `0`; otherwise the `max()` would be the best option – vol7ron Apr 12 '17 at 03:55
  • 2
    Right... but the question is... "How to write ternaries **without repeating** yourself" – MinusFour Apr 12 '17 at 03:56
  • 4
    It also says `Again, not seeking an answer to the exact question above`, which leaves the the question to interpretation ;) – vol7ron Apr 12 '17 at 03:58
  • I like this except that it doesn't read well. If you added the right prototype methods, you could get something like `value.OrNullIf(-1) || 0;` or `value.OrNullIf(-1).OrIfNull(0);`. Those could be combined into `value.OrIf(-1, 0);`. – Dave Cousineau Apr 13 '17 at 17:47
  • @Sahuagin I think what people are missing is that this is *an* answer, not necessarily the best, but not incorrect -- the vote is misleading because someone grudge voted as retaliation to another comment. Regarding your *prototyping* solution, you're correct; though, something to keep in mind is that it would take more code, but yes, that would be a way to go too. This solution above is flexible in that it doesn't have to be an array that is being looked up. The pattern is *`compute value`*, *`when bad value, assign default value`* that computation can be a function or operations... anything – vol7ron Apr 14 '17 at 15:34
0

Given the example code at Question it is not clear how it would be determined that 3 is or is not set at index 0 of someArray. -1 returned from .indexOf() would be valuable in this instance, for the purpose of excluding a presumed non-match which could be a match.

If 3 is not included in array, -1 will be returned. We can add 1 to result of .indexOf() to evaluate as false for result being -1, where followed by || OR operator and 0. When value is referenced, subtract 1 to get index of element of array or -1.

Which leads back to simply using .indexOf() and checking for -1 at an if condition. Or, defining value as undefined to avoid possible confusion as to actual result of evaluated condition relating to original reference.

var someArray = [1,2,3];
var value = someArray.indexOf(3) + 1 || 1;
console.log(value -= 1);

var someArray = [1,2,3];
var value = someArray.indexOf(4) + 1 || 1;
// how do we know that `4` is not at index `0`?
console.log(value -= 1);

var someArray = [1,2,3];
var value = someArray.indexOf(4) + 1 || void 0;
// we know for certain that `4` is not found in `someArray`
console.log(value, value = value || 0);
guest271314
  • 1
  • 15
  • 104
  • 177
0

A ternary is like an if-else, if you don't need the else part, why not just a single if instead..

if ((value = someArray.indexOf(3)) < 0) value = 0;
Khaled.K
  • 5,828
  • 1
  • 33
  • 51
0

For this particular case, you could use short-circuiting with the logical || operator. As 0 is considered falsy, you can add 1 to your index, thus, if index+1 is 0 then you'll get the right-hand side of the logical-or as your result, otherwise, you'll get your index+1. As your wanted result is offset by 1, you can then subtract 1 from it to get your index:

const someArray = [1, 2, 3, 4];
const v = ((someArray.indexOf(3)+1) || 1)-1;
console.log(v);
Nick Parsons
  • 45,728
  • 6
  • 46
  • 64