understanding Complex inArray ternary operator

1.9k views Asked by At

I was just going through the inArray method code and came across the following ::

inArray: function (elem, arr, i) {
    var len;

    if (arr) {
        if (indexOf) {
            return indexOf.call(arr, elem, i);
        }

        len = arr.length;
        i = i ? i < 0 ? Math.max(0, len + i) : i : 0;

        for (; i < len; i++) {
            // Skip accessing in sparse arrays
            if (i in arr && arr[i] === elem) {
                return i;
            }
        }
    }

    return -1;
},

now i understand how tenary operators work , but can somebody tell me , how the below line of code really works ? is it even a ternary operator ?

i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;

or is it some kind of a new construct in JS ?

Thank you.

Alex-z.

7

There are 7 answers

1
Tushar On BEST ANSWER

Original Statement:

i = i ? i < 0 ? Math.max(0, len + i) : i : 0;

To understand it better,

i = i ? (i < 0 ? Math.max(0, len + i) : i) : 0;
//      ^                                ^

Yes, this is nested ternary operator ? :.

Following is the if else representation of the above statement, represented in if..else step by step.

if (i) {
    i = i < 0 ? Math.max(0, len + i) : i;
} else {
    i = 0;
}

It works as follow:

if (i) {
    if (i < 0) {
        i = Math.max(0, len + i);
    } else {
        i = i;
    }
} else {
    i = 0;
}
4
Cerbrus On

It's 2 ternary operators, nested. You can read it like this:

i = i ? (i < 0 ? Math.max( 0, len + i ) : i) : 0;

Or, completely converted to if / else:

if(i)
    if (i < 0)
        i = Math.max(0, len + i);
    else
        i = i;
else
    i = 0;

You can shorten the if / else structure a bit:

if(i) {
    if (i < 0)
        i = Math.max(0, len + i);
} else
    i = 0;

Or:

if(i && i < 0)
    i = Math.max(0, len + i);
if(!i)
    i = 0;

This removes the redundant else i = i. In ternary statements, an else is required, but it can be omitted here.


Keep in mind that all the i = assignments you seen in these if / else statements are based on the single i = assignment in front of the ternary operator. Ternary operators on their own (a ? b : c) do not assign values to variables.

7
Tschallacka On

it breaks down to this in simple logic what kinda goes down in your mind when you code it. Note that this function does not catch undefineds, nan's nulls, strings, floats, booleans, bytes or whatever can be inputted that would be caught by the normal flow.

This is what I believe is the intention behind the line in simplified logic. This is kinda what goes on in my mind when I code such lines.

function calculatewhat(i) {
    if(i != 0) {/*i = i;*/ // i gets assigned and then evaluated. 
                           //If its anything but zero it's true, if its zero its false.
        if(i < 0) { // Test if its smaller than zero
          return Math.max( 0, len + i ) ; 
        }
        else { // its bigger than 0
           return i 
        }
    else { // if its 0... but you could just as wel return i 
           // instead of creating a new variable for the return since i is zero.
       return 0;
    }
}

I would have coded it instead of the nested as follows

i = i < 0 ? Math.max( 0, len + i ) : i

And to satisfy Cerbrus this is how it really works.

function calculatewhat(i) {
    if(i) { //check if its a true value. This will evaluate true also on "1", "blah",true, etc... 
             //To be typesafe you should make it an integer test. if(typeof i === 'number' && i !== 0);
        if(i < 0) { // Test if its smaller than zero This will also return true on "-20" and -20.555
          return Math.max( 0, len + i ) ; 
        }
        else { // its bigger than 0 return i. but its type can be anything but an integer, so beware.
           return i 
        }
    else { //it's not a number or its 0. 
           //We can't be sure about type so lets return 0 to i making it a valid integer.
       return 0;
    }
}
1
Norguard On

It's two ternaries nested.

Unrolling the outer layer would give us:

var x;
if (i) {
  x = i < 0 ? Math.max( 0, len + i ) : i;
} else {
  x = 0;
}
i = x;

Unwinding the inner branch, then gives us:

var x;
if (i) {
  if (i < 0) {
    x = Math.max( 0, len + i );
  } else {
    x = i;
  }
} else {
  x = 0;
}

i = x;

The x representing the temporary value which gets reassigned to i.

Parens might help (and parents or newlines should break these up, any time they are any harder than dirt-simple):

i = i ? ( i < 0 ? Math.max( 0, len + i ) : i ) : 0;

Now you can see the subexpression hiding in the middle of the true branch, of the outer ternary.

0
scign On

As mentioned in other answers, this is a nested ternary. What I hope to provide here is a natural-language translation of the job this line is performing for the parent function.

i = i ? i < 0 ? Math.max(0, len + i) : i : 0;
       [------|----------------------|--]         inner ternary
   [--|----------------------------------|---]    outer ternary

Translation:

i ? ... : 0

(Outer ternary)

If an index to start searching from i is provided as a parameter to the function (this uses the fact that if the parameter is not provided then i will be "falsy") then proceed to evaluate the inner ternary and update i to the result, otherwise set i to 0.

i < 0 ? Math.max(0, len + i) : i

(Inner ternary)

If i is less than zero, return the the array length + i (which, since i is less than zero, finds the index of the element i positions from the end of the array) with a lower bound of zero; otherwise return i.


We can now see that this line permits the function to interpret a positive integer as a position from the start of the array and a negative integer as a position from the end of the array, while including an array bounds limitation on the position index and also allowing the parameter to be omitted completely to default to the array start.

0
alexander perlov On

actually this is enough:

if (i < 0) {
  return Math.max(0, len + I);
}
0
Lekan1554 On

in the initial code, we have:

inArray: 
function (elem, arr, i) {

    /* [...] */
    i = i ? i < 0 ? Math.max(0, len + i) : i : 0;

    for (; i < len; i++) {
        /* [...] */
    }

    return -1;
}

so basically, this:

   i = i ? i < 0 ? Math.max(0, len + i) : i : 0;

can also mean: (provided that i = 5)

// i = 5 is same as 5 = i

if (5) {
   if (5 < 0) {
      i = Math.max(0, len + 5);
   } else {
      i = 5;
   }
} else {
   i = 0;
}

it's just an over simplified way of doing things.