Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to filter an array of numbers using different intervals?

In the example code below I want to filter numbersArray based on different intervals. An interval is defined by a combination of 2 arrays with the lower and upper bound of said interval. How do I identify matches or non-matches like below?

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

If the code works the following test should return true:

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

Assume the arrays contains more than 1000 items and that the numbers doesn't follow any pattern.

Below is a less readable but more realistic scenario.

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);
}
like image 439
Simon Avatar asked Oct 24 '20 22:10

Simon


People also ask

How do I filter multiple values in an array?

To filter JavaScript array elements with multiple criteria or conditions, you need to call the Array object's filter() method and write multiple validations in its callback function. The filter criteria are added using the logical AND && or OR || operator.

How do you filter numbers in an array?

Use the filter() method to filter an array to only numbers, e.g. arr. filter(value => typeof value === 'number') . The filter method returns an array with all the elements that satisfy the condition. In our case, all array elements with a type of number .

How do you filter out all the even numbers in an array?

To filter even numbers of an integer Array in JavaScript, call Array. filter() method on this integer array, and pass a function as argument that returns true for an even number or false otherwise. filter() method returns an array with elements from the original array that returns true for the given callback function.


2 Answers

I'm going to assume that we can change the data structure a little bit because this is quite awkward to work with:

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

If the elements at the same indexes are meant to be together then let's just do that:

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

Will come to bound later.

Now let's build a curried function that validates n if a < n && n < b:

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

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

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

Now let's build another curried function that validates n if at least one function returns true:

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

const check = or(x, y);

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

We will now transform bound into an or function after we transformed each pair into a between function:

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

check is now a function that takes an n and returns true if n is between 1 and 3 or between 4 and 6, ...

const between = (a, b) => n => a < n && 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}]`);
like image 145
customcommander Avatar answered Oct 23 '22 09:10

customcommander


I think I would extract the lowerbound and upperbound values first into a list of predicates, and then just iterate those per number in the array. Depending if one matches, it either goes into the match result or the non-match result.

function filtered( array, lower, upper) {
  const predicates = lower.map( (v, i) => (value) => value > v && 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();

Personally, I would also check if the lowerbound & upperbound arrays have the same length, but the question doesn't seem to define a behavior for this scenario. I'm also not sure what should happen in case of overlapping ranges, but these are all not specified

like image 30
Icepickle Avatar answered Oct 23 '22 10:10

Icepickle