How to filter an array of numbers using different intervals?
Tweaked my approach slightly to avoid the second filter loop. It's just a case of testing each number in the array against the implicit pair of lowerBound
and higherBound
. You can utilise the iteration variable in some to achieve this quite concisely, assuming the arrays have been properly formatted beforehand and there are no loose ends.
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 && num < higherBound[i];
});
matched ? matches.push(num) : nonMatches.push(num);
});
console.log(matches, nonMatches);
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