2

I want to find out whether an element in some multidimensional array x is undefined, for example:

typeof x[i][j][k] === 'undefined'

The problem is that if for example x[i][j] is undefined I get an error. To be on the safe side I would have to instead check:

typeof x === 'undefined' 
|| typeof x[i] === 'undefined'
|| typeof x[i][j] === 'undefined'
|| typeof x[i][j][k] === 'undefined'

But is there an easier way in JavaScript to check whether x[i][j][k] is undefined?

Sergey
  • 1,608
  • 1
  • 27
  • 40
Daniel
  • 3,383
  • 4
  • 30
  • 61
  • Are i, j and k integers? Or Strings? Aren't you using a for loop which provides valid indexes/property names? Also why does the data have parts that are undefined? Are not all arrays the same length or are there gaps? – GantTheWanderer Jan 13 '17 at 19:07
  • The dimensions have different length. I am not looping through the whole array. I just need to check some element. – Daniel Jan 13 '17 at 19:09
  • Could you post a example of your array – Nenad Vracar Jan 13 '17 at 19:11
  • Is your data structure mapped positionally, meaning a certain position in the matrix has a particular meaning ie (x[5][6][3] is x velocity) so you have to refer to specific indexes? What requires you to have to refer to specific indexes? – GantTheWanderer Jan 13 '17 at 19:12
  • Have you tried !!x[i][j][k] – Nitin Jan 13 '17 at 19:13
  • @Nitin Just did. But throws an error. – Daniel Jan 13 '17 at 19:19

4 Answers4

2

if(x && x[i] && x[i][j] && x[i][j][k]) { } is one way to do it, based on the premise that undefined is a falsy value, and will short-circuit the if-statement if it's encountered.

As an alternative, "It's easier to ask forgiveness than it is to get permission". Sometimes you can just try to use it and handle it when it doesn't work using a try-catch block. Here is another question/answer that explains the philosophy.

Community
  • 1
  • 1
Harris
  • 1,775
  • 16
  • 19
  • Thanks. But I will still have to walk through all the dimensions. I see however that my formulation of "easier way" is quite vague. – Daniel Jan 13 '17 at 19:07
  • @Daniel I edited to include an alternate approach. If you're comfortable with kinda smashing through it, you can just use a try-catch block. It's not always recommended, but sometimes it's fine. – Harris Jan 13 '17 at 19:10
2

You can extract this into a tail-call recursive function that will check arbitrary levels of nesting:

const arr = [1, 2, ['a', 'b', [3, undefined]]];
const OUT_OF_BOUNDS = null;

function getNestedElement(arr, levels, levelIndex = 0) {
  const level = levels[levelIndex];
  if (level < 0 || level >= arr.length) {
    return OUT_OF_BOUNDS;
  }
  return (levelIndex === levels.length - 1)
    ? arr[level]
    : getNestedElement(arr[level], levels, levelIndex + 1);
}

function isNestedElementUndefined(arr, levels) {
  return typeof getNestedElement(arr, levels) === 'undefined';
}

// arr[2][2][0] === 3 which is not undefined
console.log(`arr[2][2][0] = ${getNestedElement(arr, [2,2,0])}`);
console.log(`is it undefined? ${isNestedElementUndefined(arr, [2,2,0])}`);

// arr[2][2][1] is undefined
console.log(`arr[2][2][1] = ${getNestedElement(arr, [2,2,1])}`);
console.log(`is it undefined? ${isNestedElementUndefined(arr, [2,2,1])}`);

// arr[2][2][2] is out of bounds so it will be null
console.log(`arr[2][2][2] = ${getNestedElement(arr, [2,2,2])}`);
console.log(`is it undefined? ${isNestedElementUndefined(arr, [2,2,2])}`);

Here's an iterative version as well:

const arr = [1, 2, ['a', 'b', [3, undefined]]];
const OUT_OF_BOUNDS = null;

function getNestedElement(arr, levels) {
  for (const level of levels) {
    if (level < 0 || level >= arr.length) {
      return OUT_OF_BOUNDS;
    }
    arr = arr[level];
  }
  
  return arr;
}

function isNestedElementUndefined(arr, levels) {
  return typeof getNestedElement(arr, levels) === 'undefined';
}



// arr[2][2][0] === 3 which is not undefined
console.log(`arr[2][2][0] = ${getNestedElement(arr, [2,2,0])}`);
console.log(`is it undefined? ${isNestedElementUndefined(arr, [2,2,0])}`);

// arr[2][2][1] is undefined
console.log(`arr[2][2][1] = ${getNestedElement(arr, [2,2,1])}`);
console.log(`is it undefined? ${isNestedElementUndefined(arr, [2,2,1])}`);

// arr[2][2][2] is out of bounds so it will be null
console.log(`arr[2][2][2] = ${getNestedElement(arr, [2,2,2])}`);
console.log(`is it undefined? ${isNestedElementUndefined(arr, [2,2,2])}`);

Note: the solutions above assume that null is a special value that won't be present in your arrays so it's used to convey a result is not found. If you cannot guarantee null won't be in your arrays, you can throw an error or maybe return an object holding the value and a boolean indicating if the value is found.

Something like:

const arr = [1, 2, ['a', 'b', [3, undefined]]];

function getNestedElement(arr, levels, levelIndex = 0) {
  const level = levels[levelIndex];
  if (level < 0 || level >= arr.length) {
    return { found: false };
  }
  return (levelIndex === levels.length - 1)
    ? { value: arr[level], found: true }
    : getNestedElement(arr[level], levels, levelIndex + 1);
}

function isNestedElementUndefined(arr, levels) {
  const { value, found } = getNestedElement(arr, levels);
  return found ? typeof value === 'undefined' : false;
}

// arr[2][2][0] === 3 which is not undefined
console.log(`arr[2][2][0] = ${getNestedElement(arr, [2,2,0]).value}`);
console.log(`is it undefined? ${isNestedElementUndefined(arr, [2,2,0])}`);

// arr[2][2][1] is undefined
console.log(`arr[2][2][1] = ${getNestedElement(arr, [2,2,1]).value}`);
console.log(`is it undefined? ${isNestedElementUndefined(arr, [2,2,1])}`);

// arr[2][2][2] is out of bounds
const { value, found } = getNestedElement(arr, [2,2,2]);
console.log(`arr[2][2][2] = ${found ? value : 'Not Found'}`);
console.log(`is it undefined? ${isNestedElementUndefined(arr, [2,2,2])}`);

And it's iterative cousin:

const arr = [1, 2, ['a', 'b', [3, undefined]]];
const OUT_OF_BOUNDS = null;

function getNestedElement(arr, levels) {
  for (const level of levels) {
    if (level < 0 || level >= arr.length) {
      return { found: false };
    }
    arr = arr[level];
  }
  
  return { value: arr, found: true };
}

function isNestedElementUndefined(arr, levels) {
  const { value, found } = getNestedElement(arr, levels);
  return found ? typeof value === 'undefined' : false;
}

// arr[2][2][0] === 3 which is not undefined
console.log(`arr[2][2][0] = ${getNestedElement(arr, [2,2,0]).value}`);
console.log(`is it undefined? ${isNestedElementUndefined(arr, [2,2,0])}`);

// arr[2][2][1] is undefined
console.log(`arr[2][2][1] = ${getNestedElement(arr, [2,2,1]).value}`);
console.log(`is it undefined? ${isNestedElementUndefined(arr, [2,2,1])}`);

// arr[2][2][2] is out of bounds
const { value, found } = getNestedElement(arr, [2,2,2]);
console.log(`arr[2][2][2] = ${found ? value : 'Not Found'}`);
console.log(`is it undefined? ${isNestedElementUndefined(arr, [2,2,2])}`);
Community
  • 1
  • 1
nem035
  • 34,790
  • 6
  • 87
  • 99
  • This is the awesome way to solve this problem, might be overkill however for this instance. :) – GantTheWanderer Jan 13 '17 at 19:21
  • @GantTheWanderer sure but that depends if the OP needs a general solution or more of a one-time-specific check :) – nem035 Jan 13 '17 at 19:22
  • I would add `if (arr===null)) {return null;}` inside the for loop to handle the case if `arr[level]` is `null` – John C. Feb 12 '22 at 23:45
1

There is no other way to put the check except that you can shorten your code by writing it like :

if(x && x[i] && x[i][j] && x[i][j][k]) {
 }

The above statement goes on checking as long as the term before && keeps returning true.As soon as it finds a false value or undefined it shall stop checking the reamining terms.

Ayan
  • 8,192
  • 4
  • 46
  • 51
1

A nested ternary would be easier, but it's also much less readable.

let x = [[[[]]]], // will return true
    i = 0,
    j = 0,
    k = 0;

    let isDefined = x ? x[i] ? x[i][j] ? x[i][j][k] ? true : false : false : false : false;

Once any part of the array is removed isDefined will return false. It will also evaluate in order, so you could return something else for the false value to notify at what depth it fails.

hdennen
  • 57
  • 8