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);
}
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.
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 .
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.
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}]`);
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With