2

Let's say I have 4 arrays:

var arrays =  [
    [1, 2, 1],
    [1, 3, 4],
    [1, 2, 3],
    [0, 2, 2]
];

And I want to return the child/sub arrays that start with both 1 and 2, what type of loop would I need?

Currently, this is what I have:

var arrays =  [
    [1, 2, 1],
    [1, 3, 4],
    [1, 2, 3],
    [0, 2, 2]
];
        
var selected = [1, 2]; // These are the values that need to match
var result = [];
for (var i = 0; i < selected.length; i++) {
    for (var j = 0; j < arrays.length; j++) {
        if (arrays[i][j] === selected[i]) {
            result.push(arrays[i]);
        }
    }
}

When there's more than 1 value in the selected array, it seems to return all the ones that match 2 on the second index, so the result would be:

[
    [1, 2, 1],
    [1, 2, 3],
    [0, 2, 2]
]

The loop needs to ensure that on the second iteration it's making sure the first value is still true, as my intended result would be:

[
    [1, 2, 1],
    [1, 2, 3]
]

Please someone help me, I've had my head trying hundreds of different loop and checks variations for 2-3 days.

Thanks so much!!

Jake

Jake
  • 37
  • 5

3 Answers3

2

Your current code pushes to the result array whenever any given index matches between arrays and selected. Instead you will need to reverse your loops and iterate over selected for every sub array and check if every element matches, if not break the inner loop and don't push.

const arrays = [
  [1, 2, 1],
  [1, 3, 4],
  [1, 2, 3],
  [0, 2, 2],
];

const selected = [1, 2]; // These are the values that need to match
const result = [];

for (let i = 0; i < arrays.length; i++) {
  let match = true;
  for (let j = 0; j < selected.length; j++) {
    if (arrays[i][j] !== selected[j]) {
      match = false;
      break;
    }
  }
  if (match) {
    result.push(arrays[i]);
  }
}

console.log(result);

A more modern solution would be to use filter() with a nested every() call on selected.

const arrays = [
  [1, 2, 1],
  [1, 3, 4],
  [1, 2, 3],
  [0, 2, 2],
];

var selected = [1, 2];
const result = arrays.filter(arr => selected.every((n, i) => n === arr[i]));

console.log(result);
pilchard
  • 12,414
  • 5
  • 11
  • 23
0

Here is another approach where you turn both arrays to string and check it those inner arrays start with selected array.

var arrays = [
  [1, 2, 1],
  [1, 3, 4],
  [1, 2, 3],
  [0, 2, 2]
];

var selected = [1, 2];
const result = arrays.filter(e => e.toString().startsWith(selected.toString()))
console.log(result)
Nenad Vracar
  • 118,580
  • 15
  • 151
  • 176
0

Let's try to put your condition into words. That way, an implementation may come to mind more easily.

A short wording may be: "Take all arrays that match (rather: start with) a certain sub-array." In code, it may look like this:

const arrays = [
  [1, 2, 1],
  [1, 3, 4],
  [1, 2, 3],
  [0, 2, 2]
];
const selection = [1, 2];

const result = filterArrays(arrays, selection);
console.log(result);

function filterArrays(arrays, selection) {
  const selectedArrays = [];
  for (let i = 0; i < arrays.length; ++i) {
    const array = arrays[i];
    const subarray = array.slice(0, selection.length); // Get starting sub-array
    if (compareArrays(subarray, selection)) {
      selectedArrays.push(array);
    }
  }
  
  return selectedArrays;
}

/*Ignore; helper function*/
function compareArrays(array1, array2) {
  if (array1.length !== array2.length) return false;
  
  const length = array1.length;
  for (let i = 0; i < length; ++i) {
    if (array1[i] !== array2[i]) return false;
  }
  
  return true;
}
.as-console-wrapper {max-height:100%!important}

Another, more specific wording may be: "Take all arrays that match a selection at an index." Note that we only reworded the "match a sub-array" part. I believe this is what you tried.

Refer to pilchard's answer for an implementation. Note that their implementation assumes the arrays in arrays to be at least the same length as selected.


I see you used var instead of the preferred modern let/const declarators. Here's a short outline of their differences:

  • let/const declarators:
    • Block-scoped.
      • Narrower scope means less name-space pollution.
    • More similar to declarators in other well-known languages:
      • Variables of these declarators cannot be used before their declaration (see TDZ).
  • var declarator:
    • Function-scoped.
    • Hoisted and with no TDZ, resulting in this (perhaps confusing) behaviour:
      • Variables declared with var can be used even before their declaration.
      • Duplicate declarations are allowed since they are effectively the same.

Also, JavaScript has different kinds of for-loops:

  • for-loop: The for-loops you used are this kind. It is the most versatile kind.
  • for...of-loop: A loop to iterate over an iterable object (see iterators). For example, arrays are iterable, so you can get its values with a for...of-loop:
    const values = [1, 2, 3];
    let sum = 0;
    for (const value of array) {
      sum += value;
    }
    console.log(sum); // -> 6
    
  • for...in-loop: A loop to iterate over enumerable properties of an object. It is easily confused with a for...of-loop, but MDN's example demonstrates the differences understandably.

In my code example above, the for-loop in filterArrays() can be replaced with a for...of-loop to better convey my intention: To iterate over all arrays in arrays, disregarding their index:

for (let i = 0; i < arrays.length; ++i) {
  const array = arrays[i];
  // ...
}
// Same as
for (const array of arrays) {
  // ...
}
Oskar Grosser
  • 2,804
  • 1
  • 7
  • 18