luke
luke

Reputation: 23

include() method for nested arrays

I am struggling with checking an array for an array within. It doesn't seem that the include() method is designed for it because it always returns false in this case. Below is example of an identical scenario with indexOf. Maybe all I need is syntax help, any ideas are most welcome.

arr = [1,[9,9],3,4,5];

if (arr.indexOf(el) !== -1) {
   console.log(`the array contains ${el}`);
} else {
   console.log(`doesn't contain ${el}`);
}

Of course the above returns true for 1, 3, 4, 5 and false for 2. And now the problem. I am trying to feed this method with an array like [9,9] to see if there's one already.

let el = [9,9];
// console: "doesn't contain 9,9"

On the other hand the below is okay which makes me think it's just a syntax issue(?)

let el = arr[1];
// console: "the array contains 9,9"

I found a way around it by writing a checker function with for loop but this quickly becomes bulky as you add requirements. I would love to know a smarter way.

Thank you.

Upvotes: 2

Views: 762

Answers (3)

jo_va
jo_va

Reputation: 13964

The problem you have is that arrays are reference types. Because of this, comparing two arrays will return true only if the two arrays refer to the same underlying array, and will return false for different arrays, even if they hold the same values.

const arr1 = [1, 2];
const arr2 = [1, 2];
const arr3 = arr1;

console.log(arr1 === arr3);
console.log(arr1 !== arr2);

To fix your problem, your include function needs to compare by value (deep comparison), you can do this simply using JSON.stringify().

This method is fast but limited, it works when you have simple JSON-style objects without methods and DOM nodes inside

See this SO post about object comparison in JavaScript.

Here is a working example:

function include(arr, value) {
  const stringifiedValue = JSON.stringify(value);
  for (const val of arr) {
    if (JSON.stringify(val) === stringifiedValue) {
      return true;
    }
  }
  return false;
}

console.log(include([1, 2, 3, 4], 3));
console.log(include([1, 2, 3, [1, 2]], [1, 2]));
console.log(include([1, 2, 3, [1, 2]], [1, 2, 3]));

Here is another way to do this deep comparison without JSON.stringify(), it works for inner arrays, and scalar values:

function include(arr, value) {
  const valArity = Array.isArray(value) ? value.length : 1;
  for (let item of arr) {
    if (valArity > 1 && Array.isArray(item) && item.length === valArity) {
      if (item.every((val, i) => val === value[i])) {
        return true;
      }
    } else if (item === value) {
      return true;
    }
  }
  return false;
}

console.log(include([1, 2, 3, 4], 3));
console.log(include([1, 2, 3, [1, 2]], [1, 2]));
console.log(include([1, 2, 3, [1, 2]], [1, 2, 3]));
console.log(include([1, 2, 'abc', 4], 'abc'));

And here is a modified version that works for simple objects with scalar properties, arrays and scalars:

function include(arr, value) {
  const valArity = Array.isArray(value) ? value.length : 1;
  const isObject = value instanceof Object;
  for (let item of arr) {
    if (valArity > 1 && Array.isArray(item) && item.length === valArity) {
      if (item.every((val, i) => val === value[i])) {
        return true;
      }
    } else if (isObject && item instanceof Object && item) {
      const numEntries = Object.keys(value).length;
      const entries = Object.entries(item);
      if (numEntries === entries.length) {
        if (entries.every(([k, v]) => value[k] === v)) {
          return true;
        }
      }
    } else if (item === value) {
      return true;
    }
  }
  return false;
}

console.log(include([1, 2, 3, 4], 3));
console.log(include([1, 2, 3, [1, 2]], [1, 2]));
console.log(include([1, 2, 3, [1, 2]], [1, 2, 3]));
console.log(include([1, 2, { a: 1 }, 4], { a: 1 }));
console.log(include([1, 2, { a: 1, b: 2 }, 4], { a: 1 }));
console.log(include([1, 2, 'abc', 4], 'abc'));

Upvotes: 4

Bibberty
Bibberty

Reputation: 4768

Here we can do this using Array.prototype.some()

Notice we added a helper method to check each element, I did not bother writing object checking, but you get the idea.

Tweaked to support sub-arrays

let arr = [1, [9, 9], 3, 4, 5];
let el = [9, 9];

let arr2 = [1, [1,[9, 9]], 3, 4, 5];
let el2 = [1,[9, 9]];

const someCheck = (item, compare) => {
  if(typeof item !== typeof compare) return false;
  if(typeof item === 'string') return item === compare;
  if(typeof item === 'number') return item === compare;
  if(Array.isArray(item)) {
    console.log('checking array');
    return item.reduce((accum, o, i) => accum && (someCheck(o,compare[i])));
  }
  // no plain object support yet.
  return false;
}


if (arr.some(e => someCheck(e, el))) {
  console.log(`the array contains ${el}`);
} else {
  console.log(`doesn't contain ${el}`);
}

if (arr2.some(e => someCheck(e, el2))) {
  console.log(`the array contains ${el2}`);
} else {
  console.log(`doesn't contain ${el2}`);
}

Upvotes: 1

alanfcm
alanfcm

Reputation: 663

You need to loop through the array and check if each element is an array, then you check each index. Something like this:

let arr = [1,[9,9],3,4,5];
let el = [9,9];
let contains = true;

for (var i = 0; i < arr.length; i++) {
  if (Array.isArray(arr[i])) {
    var equals = true
    if(arr[i].length !== el.length)
        equals = false;
    for(var j = arr[i].length; j--;) {
        if(arr[i][j] !== el[j])
           equals = false;
    }  
    if (equals) {    
      contains = true;
      break;
    }
  } else {
    if (arr.indexOf(el) !== -1){
        contains = true;        
        break;
      }
    else{contains = false;}
  }
}

if (contains) {
  console.log(`the array contains ${el}`);
} else {
  console.log(`doesn't contain ${el}`)
}




  

Upvotes: -1

Related Questions