Find upper and lower boundaries in array

Viewed 319

I am trying to get the upper and lower boundaries of a numeric value in an array.

const boundaries = [15, 30, 45, 60, 75, 90];
const age = 22;

For the above example, the outcome should be:

[15, 30]

If for example the value is a boundary, it would become the lower value in the outcome array. If it is the max boundary or above, it should become the max value.

Example outcomes:

15 => [15, 30]
22 => [15, 30]
30 => [30, 45]
90 => [90]

I tried mapping through the array and if the age is higher => return boundary. Then filter out the boundaries and calculate the indexes, but this doesn't feel like the correct way to accomplish this.

const boundaries = [15, 30, 45, 60, 75, 90];
const age = 22;

// get all lower values
const allLower = boundaries.map((b) => age > b ? b : null).filter(x => x);
const lower = allLower[allLower.length - 1]; // get lowest
const upper = boundaries[boundaries.indexOf(lower) + 1]; // get next

const result = [lower, upper]; // form result

console.log(result);

Is there a shorter / better / more reliable way to do this?

5 Answers

Why do you use the indices for this? What if the boundaries array is not sorted? Wouldn't it be easier to filter the lists to allLower and allUpper (containing the values below and above the threshold), and then use min and max on the resulting arrays?


Sample code:

const boundaries = [15, 30, 45, 60, 75, 90];
const age = 22;

const allLower = boundaries.filter(x => x < age);
const allUpper = boundaries.filter(x => x > age);

const lowerBound = Math.max(...allLower);
const upperBound = Math.min(...allUpper);

Looks like a good use case for reduce:

const boundaries = [15, 30, 45, 60, 75, 90];


for (let search of [1, 22, 30, 90, 100]) {

    let [low, upr] = boundaries.reduce(([low, upr], x) =>
        [
            x <= search ? Math.max(low, x) : low,
            x >  search ? Math.min(upr, x) : upr,

        ],
        [-Infinity, +Infinity]
    )

    console.log(low, '<=', search, '<', upr)

}

This doesn't require boundaries to be sorted. If they always are, you might consider binary search to locate the lower bound.

Looks like a plain for-loop might help you out ;)

function getBounds(age) {
  for (let i = 0; i < boundaries.length; i++) {
    if (boundaries[i] <= age && (boundaries[i + 1] ?? Infinity) > age) {
      return boundaries.slice(i, i + 2);
    }
  }
}

You could check the previous value and next value and filter the array.

const
    getLowerUpper = (array, pivot) => array
        .filter((v, i, { [i - 1]: prev, [i + 1]: next }) =>
            v <= pivot && next > pivot ||
            prev <= pivot && v >= pivot ||
            prev === undefined && next > pivot ||
            prev < pivot && next === undefined
        ),
    boundaries = [15, 30, 45, 60, 75, 90];

console.log(...getLowerUpper(boundaries, 22));  // between
console.log(...getLowerUpper(boundaries, 30));  // direct and next
console.log(...getLowerUpper(boundaries, 10));  // lowest
console.log(...getLowerUpper(boundaries, 15));  // direct and next
console.log(...getLowerUpper(boundaries, 90));  // highest
console.log(...getLowerUpper(boundaries, 100)); // highest

You can reduce the array, something like this:

const boundaries = [15, 30, 45, 60, 75, 90];

const getResult = (array, target) => {
  if (target < array[0] || target > array[array.length - 1]) {
    return [];
  }
  return array.reduce((a, c) => {
    if (c <= target) {
      a[0] = c;
    } else if (c > target && (!a[1] || c < a[a.length - 1])) {
      a[a.length] = c;
    }
    return a;
  }, []);
}

console.log(getResult(boundaries, 22));
console.log(getResult(boundaries, 15));
console.log(getResult(boundaries, 30));
console.log(getResult(boundaries, 90));
console.log(getResult(boundaries, 14));
console.log(getResult(boundaries, 91));

Related