Как отфильтровать массив чисел с использованием разных интервалов?

#javascript #conditional-statements

#javascript #условные операторы

Вопрос:

В приведенном ниже примере кода я хочу отфильтровать numbersArray на основе разных интервалов. Интервал определяется комбинацией 2 массивов с нижней и верхней границей указанного интервала. Как мне определить совпадения или несоответствия, как показано ниже?

 const numbersArray = [1,2,3,4,5,6,7,8,9,10];
const lowerBound = [1, 4, 8];
const higherBound = [3, 6, 10];
  

Если код работает, следующий тест должен возвращать true:

 matches == [2, 5, 9];
nonmatches == [1, 3, 4, 6, 7, 8, 10];
  
  

Предположим, что массивы содержат более 1000 элементов и что числа не соответствуют никакому шаблону.

Ниже приведен менее читаемый, но более реалистичный сценарий.

 let numbersArray = [];
let lowerBound = [];
let higherBound = [];

for (let i = 0; i< 1000; i  ){
  numbersArray.push(i);
}

for(let i = 0; i < 100; i  ) {
  lowerBound.push(i * 10);
  higherBound.push((i * 10)   Math.random() * 10);
}
  

Комментарии:

1. . Вы хотите найти способ сравнить различия или совпадения между младшими и старшими значениями?

2. Будут ли связанные массивы всегда иметь одинаковую длину? Имеют ли они дело с -Infinity Infiity? Насколько вы заботитесь о производительности?

3. Это всегда будет интервал, который мы ищем. Поэтому, если массивы имеют разную длину, это будет ошибкой. Интервал — это комбинация определенного индекса, например lowerBound[index] , to higherBound[index] . Что касается производительности: она должна быть достаточно быстрой, чтобы не было видимой задержки в пользовательском интерфейсе. Таким образом, максимум 10 мс и чем меньше, тем лучше.

4. Я попытался понять вашу постановку задачи о том, что такое интервал, как границы относятся к интервалу и каким должно быть предполагаемое поведение между интервалом и numbersArray . Но я, честно говоря, не могу этого понять, я думаю, что проблема недостаточно определена, и читатели догадываются, каково предполагаемое поведение.

Ответ №1:

Я собираюсь предположить, что мы можем немного изменить структуру данных, потому что с этим довольно неудобно работать:

 const lowerBound = [1, 4, 8];
const higherBound = [3, 6, 10];
  

Если элементы с одинаковыми индексами должны быть вместе, тогда давайте просто сделаем это:

 const bound = [[1, 3], [4, 6], [8, 10]];
  

Будет bound позже.

Теперь давайте создадим функцию curried, которая проверяет n , если a < n amp;amp; n < b :

 const between = (a, b) => n => a < n amp;amp; n < b;

const x = between(1, 3);
const y = between(4, 6);

x(1); // false
x(2); // true
y(1); // false
y(5); // true
  

Теперь давайте создадим еще одну функцию curried, которая проверяет n , возвращает ли хотя бы одна функция true :

 const or = (...fns) => n => fns.some(fn => fn(n));

const check = or(x, y);

check(1); // false
check(2); // true
check(5); // true
  

Теперь мы преобразуем bound в функцию или после того, как мы преобразовали каждую пару в функцию between:

 const bound = [[1, 3], [4, 6], [8, 10]];
const check = or(...bound.map(([a, b]) => between(a, b)));
  

check теперь функция, которая принимает n и возвращает true , если n находится между 1 и 3 или между 4 и 6, …

 const between = (a, b) => n => a < n amp;amp; n < b;
const or = (...fns) => n => fns.some(fn => fn(n));

const bound = [[1, 3], [4, 6], [8, 10]];
const check = or(...bound.map(([a, b]) => between(a, b)));

const [nomatch, match] =
  [1,2,3,4,5,6,7,8,9,10].reduce(
    (acc, n) =>
      (acc[ check(n)].push(n), acc),
        [[], []]);

console.log(`match: [${match}]`);
console.log(`no match: [${nomatch}]`);  

Комментарии:

1. Я согласен с предикатами и, безусловно, с неуклюжестью структуры данных, которая просто вызывает много вопросов. Хорошая работа по структурированию различных методов 🙂

2. .reduce((results, n) => (results[ check(n)].push(n), results), [[],[]]) reduce в последнем фрагменте будет компактная версия. (Он меняет местами результирующее match и nomatch .)

3. Это очень удобочитаемое решение и очень удобно в сочетании с zip-решением @3limin4t0r для неудобной структуры данных. Это, безусловно, самое быстрое решение для предоставленных тестовых данных, а также приятное разделение проблем.

Ответ №2:

Я думаю, что сначала извлек бы значения нижнего и верхнего пределов в список предикатов, а затем просто повторил бы их для каждого числа в массиве. В зависимости от того, совпадает ли одно из них, оно либо попадает в результат совпадения, либо в результат несоответствия.

 function filtered( array, lower, upper) {
  const predicates = lower.map( (v, i) => (value) => value > v amp;amp; value < upper[i] );
  return array.reduce( (agg, cur) => {
    if (predicates.some( predicate => predicate(cur) )) {
      agg[0].push(cur);
    } else {
      agg[1].push(cur);
    }
    return agg;
  }, [[],[]]);
}

function simpleTest() {
  const numbersArray = [1,2,3,4,5,6,7,8,9,10];
  const lowerBound = [1, 4, 8];
  const higherBound = [3, 6, 10];

  const [matches, nonmatches] = filtered( numbersArray, lowerBound, higherBound );

  console.log( 'matches' );
  console.log( matches );
  console.log( 'matches' );
  console.log( nonmatches );
}

function suggestedTest() {
  // with suggested test
  let numbersArray = [];
  let lowerBound = [];
  let higherBound = []
  for (let i = 0; i< 1000; i  ){
    numbersArray.push(i);
  }
  for(let i=0;i<100;i  ) {
    lowerBound.push(i*10);
    higherBound.push((i*10) Math.random()*10);
  }

  const [matches, nonmatches] = filtered( numbersArray, lowerBound, higherBound );
  console.log( 'matches' );
  console.log( matches );
  console.log( 'matches' );
  console.log( nonmatches );
}

console.log('basic');
simpleTest();

console.log('suggested');
suggestedTest();  

Лично я бы также проверил, имеют ли нижние и верхние массивы одинаковую длину, но вопрос, похоже, не определяет поведение для этого сценария. Я также не уверен, что должно произойти в случае перекрытия диапазонов, но все они не указаны

Ответ №3:

Немного изменил свой подход, чтобы избежать второго цикла фильтрации. Это просто случай проверки каждого числа в массиве на соответствие неявной паре lowerBound и higherBound . Вы можете использовать переменную итерации в некоторых, чтобы добиться этого достаточно лаконично, предполагая, что массивы были правильно отформатированы заранее и нет свободных концов.

 const numbersArray = [1,2,3,4,5,6,7,8,9,10];
const lowerBound = [1, 4, 8];
const higherBound = [3, 6, 10];

let matches = [];
let nonMatches = [];
  
numbersArray.forEach(num => {
  const matched = lowerBound.some((bound, i) => {
    return num > bound amp;amp; num < higherBound[i];
  });

  matched ? matches.push(num) : nonMatches.push(num);
});

console.log(matches, nonMatches);  

Комментарии:

1. По какой причине вы не пошли reduce ? Кажется более естественным использовать reduce over a forEach , когда это приводит к данным после forEach цикла

2. В наши дни я стараюсь избегать сокращения , если я не делаю именно то, что указано в tin (т.Е. Уменьшаю итерацию до одного значения / массива / объекта). Ни одна из приведенных здесь данных фактически не изменилась, поэтому я не думаю, что это хороший вариант использования.

Ответ №4:

Этот подход довольно прост. Сначала zip lowerBound и higherBound вместе для удобства (это необязательно). Затем для каждого number in numbersArray проверьте, есть ли хотя бы одно совпадение (с использованием some ) между нижней и верхней границей. Добавьте число в массив «совпадение» или «несоответствие».

 const numbersArray = [1,2,3,4,5,6,7,8,9,10];
const lowerBound = [1, 4, 8];
const higherBound = [3, 6, 10];

// zip the lower and higher bounds together
const bounds = lowerBound.map((_, i) => [lowerBound[i], higherBound[i]]);

const result = {true: [], false: []};
for (const number of numbersArray) {
  const match = bounds.some(([low, high]) => low < number amp;amp; number < high);
  result[match].push(number);
}

console.log("matches:", ...result[true]);
console.log("non-matches:", ...result[false]);  

Ответ №5:

Это то, что вы хотите?

Я использую reduce для создания массива совпадений, в который я помещаю новый элемент, когда он совпадает (между нижним и верхним), а для несовпадений — просто фильтр чисел в numbersArray, которые не представлены в совпадениях.

 const numbersArray = [1,2,3,4,5,6,7,8,9,10];
const lowerBound = [1, 4, 8];
const higherBound = [3, 6, 10];

const matches = lowerBound.reduce((acc, val, i) => {
  return [
    ...acc,
    ...numbersArray.filter(o => !acc.includes(o) amp;amp; o > lowerBound[i] amp;amp; o < higherBound[i])
    ]
}, []);

const nonmatches = numbersArray.filter(o => !matches.includes(o));
console.log(matches);
console.log(nonmatches);  

Комментарии:

1. Имея в виду, что это приводит к дублированию в matched массиве, когда границы перекрываются.

2. Обратите внимание, что для этого примера этот ответ должен повторяться 3 раза numbersArray . Или обобщенный: его нужно повторить N раз numbersArray , где N — длина lowerBound (или higherBound ) массива.