user3142695
user3142695

Reputation: 17362

Find object in array with closest value

I need to get an object in an array by the closest value. Let me explain it by an example:

const data = [
  { age: 52 },
  { age: 53 },
  { age: 54 },
  { age: 60, some: 'data' },
  { age: 66, something: 'else' },
  { age: 72 },
  { age: 78 },
  { age: 84 }
]

I do get the object by using data.find((d)=> d.age === 60). But I do not get an result if the age is 61. In this case I would like to get the same object.

For 64 the next object ({ age: 66, something: 'else' }) should be returned.

As you can see the age value is not linear.

Upvotes: 7

Views: 5091

Answers (12)

Nenad Jeremic
Nenad Jeremic

Reputation: 74

Suppose array isn't sorted. Following function returns result. If it find value that is equal to search value, it stops searching, so it is a small gain in performance.

function minDiff(data, val) {
    let res = null;
    let n = data.length;
    let diffGet = (val1, val2) => Math.abs(val1 - val2);

    if (n>0) {
        res = data[0];
        let diff = diffGet(res.age, val);
        let i = 1;
        while ( diff>0 && i<n ) {
            if (diffGet(data[i].age, val) < diff) {
                res = data[i];
                diff = diffGet(res.age, val);                
            }
            i++;            
        }        
    }

    return res;     
}

 

  

Upvotes: 0

chichilatte
chichilatte

Reputation: 1828

A general purpose version of @nick-parsons excellent answer...

/**
 * Find the closest number in an array.
 *
 * @param Number                needle    The number we're looking for.
 * @param Array<Number|Object>  haystack  An array to search.
 * @param String                [key]     We're searching an array of objects. 
 *                                        Use this key to find the number in each object.
 * @return Number|Object
 */
function closest (needle, haystack, key=null) {
    if (key==null) {
        return haystack.reduce((a, b) => Math.abs(needle - b) < Math.abs(needle - a) ? b : a);
    }
    return haystack.reduce((a, b) => {
        if (b[key] == null) return a;
        if (a[key] == null) return b;
        return Math.abs(needle - b[key]) < Math.abs(needle - a[key]) ? b : a;
    });
}

let arr = [ {speed: 0.1}, {speed: 0.4}, {speed: 1} ]
console.log( closest(0.5, arr, "speed").speed )
// output: 0.4

arr = [ 0.1, 0.4, 1 ]
console.log( closest(0.9, arr) )
// output: 1

Upvotes: 0

Nick Parsons
Nick Parsons

Reputation: 50954

You can find the difference between all the numbers and whichever one is closest to zero will be your result, to achieve this I have used .reduce() with Math.abs()

const data = [ { age: 52 }, { age: 53 }, { age: 54 }, { age: 60 }, { age: 66 }, { age: 72 }, { age: 78 }, { age: 84 } ];

const getAge = (data, target) => 
  data.reduce((acc, obj) =>
     Math.abs(target - obj.age) < Math.abs(target - acc.age) ? obj : acc
  );
  
console.log(getAge(data, 61)); // {age: 60}
console.log(getAge(data, 50)); // {age: 52}
console.log(getAge(data, -1)); // {age: 52}
console.log(getAge(data, 90)); // {age: 84}

This will also work for more generalized objects that have additional properties other than just age.

Upvotes: 15

k0pernikus
k0pernikus

Reputation: 66817

This is a functional approach to your problem with currying:

const data = [
    { age: 52 },
    { age: 53 },
    { age: 54 },
    {
        age: 60,
        some: "data"
    },
    {
        age: 66,
        something: "else"
    },
    { age: 72 },
    { age: 78 },
    { age: 84 }
];

const indexOfSmallest = (array) => {
    if (array.length === 0) {
        throw new Error("Empty array, expects at least one element");
    }
    return array.reduce((lowest, next, index) => {
        if (next < array[lowest]) {
            return index;
        }
        return lowest;
    }, 0);
};
const getClosestIndex = (numbers, referenceNumber) => {
    const diff = numbers.map(n => Math.abs(referenceNumber - n));
    return indexOfSmallest(diff);
};

const createGetClosestIndex = (numbers) => (number) => getClosestIndex(numbers, number);
const createGetClosestPerson = (people) => {
    return (targetAge) => {
        const numbers = people.map(d => d.age);
        const index = createGetClosestIndex(numbers)(targetAge);
        return people[index];
    };
};

const getClosest = createGetClosestPerson(data);
console.log(getClosest(1), getClosest(64));

Upvotes: 0

sumit
sumit

Reputation: 15464

You can find the minimum difference by subtracting the given number from every element and take the absolute value and then do both higher lookup and lower lookup

it will also consider when there are 2 different closest values

const data = [
  { age: 52 },
  { age: 53 },
  { age: 55 },
  { age: 60 },
  { age: 66 },
  { age: 72 },
  { age: 78 },
  { age: 84 }
]
function minimum(given){
  //let given=54
  //find the mimimun different
  let closest_diff=Math.min(...data.map(a=>Math.abs(a.age-given)))
  //for lower closest number
  let x1=data.find(a=>a.age===given-closest_diff);
  //for highter closest number
  let x2=data.find(a=>a.age===given+closest_diff);
  //filter the number which are in array above 
  console.log(...new Set([x1,x2].filter(x=>x)));
}

minimum(52); //52
minimum(54); //53 and 55
minimum(63); //60 and 66
minimum(75); //72 and 78
minimum(77); //78

Upvotes: 0

Berk Akkerman
Berk Akkerman

Reputation: 483

You can find closest item of array with minimum value of differences like below;

function getClosest(data, x) {
    if (data.length == 0) {
        return null;
    }
    var index = 0;
    var difference = Number.MAX_SAFE_INTEGER;
    for(var i = 0; i<data.length;i++) {    
        if (i < data.length) {
            var differ =  Math.abs(data[i].age - x);           
            if(differ < difference) {
                difference = differ;
                index = i;    
            }        
        }   
    }
    return data[index];
}

Usage:

getClosest(data, 64)

Upvotes: 0

Tok the digger
Tok the digger

Reputation: 141

I made a lil' snippet code to show you the way I would do this. This creates to use a findClosest method on any array of object, that expects an attribute name and a value. The function will then return the element of the array that has the closest value to the given attribute. It could be improved but this works pretty well.

document.addEventListener("DOMContentLoaded", function() {
  const listElem = document.getElementById('list');
  const closestElem = document.getElementById('closest');
  
  data.forEach(elem => {
    const listElemEntry = document.createElement('li');
    listElemEntry.innerHTML = elem.age;
    listElem.appendChild(listElemEntry);
  });
  
  const closest = data.findClosest('age', 80);
  closestElem.innerHTML = closest;
});

const data = [
  { age: 52 },
  { age: 53 },
  { age: 54 },
  { age: 60 },
  { age: 66 },
  { age: 72 },
  { age: 78 },
  { age: 84 }
];

Array.prototype.findClosest = function(attr, value) {
  const closestElem = { diff: Infinity, index: -1 };
  this.forEach((elem, index) => {
    const diff = Math.abs(elem[attr] - value);
    if (diff < closestElem.diff) {
      closestElem.diff = diff;
      closestElem.index = index;
    }
  });
  return this[closestElem.index][attr];
}
<h2>Elements list</h2>
<ul id="list"></ul>
<h2>Closest element</h2>
<pre id="closest"></pre>

Upvotes: 0

Nina Scholz
Nina Scholz

Reputation: 386858

With sorted data, you could take the one with the greatest value as start value an iterate from the beginning and stop the iteration if the delta grows.

var data = [{ age: 52 }, { age: 53 }, { age: 54 }, { age: 60 }, { age: 66 }, { age: 72 }, { age: 78 }, { age: 84 }],
    result = data[data.length - 1],
    age = 61;
    
data.some((o) => {
    if (Math.abs(age - o.age) >= Math.abs(age - result.age)) return true;
    result = o;
});

console.log(result);

Upvotes: 0

nick zoum
nick zoum

Reputation: 7325

Here is a fully abstract approach to your problem:

// Saves up vertical space
const data = JSON.parse(`[{"age":52},{"age":53},{"age":54},{"age":60},{"age":66},{"age":72},{"age":78},{"age":84}]`);

function getClosestValue(list, getDifference) {
  var smallestDiff = Infinity;
  return list.reduce(function(closestValue, currentValue, index) {
    var newDifference = Math.abs(getDifference(currentValue));
    if (!index) return smallestDiff = newDifference, currentValue;
    return smallestDiff = Math.min(smallestDiff, newDifference), newDifference === smallestDiff ? currentValue : closestValue;
  });
}

function getClosestAge(list, age) {
  return getClosestValue(list, function(listValue) {
    return listValue.age - age;
  });
}


console.log(getClosestAge(data, 65));

If it's always sorted you can instead use some:

// Saves up vertical space
const data = JSON.parse(`[{"age":52},{"age":53},{"age":54},{"age":60},{"age":66},{"age":72},{"age":78},{"age":84}]`);

function getClosestValue(list, getDifference) {
  var smallestDiff = Infinity;
  var closestValue;
  list.some(function(currentValue, index) {
    var newDifference = Math.abs(getDifference(currentValue));
    if (!index) return smallestDiff = newDifference, closestValue = currentValue, false;
    if (smallestDiff > newDifference) return smallestDiff = newDifference, closestValue = currentValue, false;
    else if (smallestDiff !== newDifference) return true;
  });
  return closestValue;
}

function getClosestAge(list, age) {
  return getClosestValue(list, function(listValue) {
    return listValue.age - age;
  });
}


console.log(getClosestAge(data, 65));

Upvotes: 2

Vadim Hulevich
Vadim Hulevich

Reputation: 1833

const data = [
    { age: 52 },
    { age: 53 },
    { age: 54 },
    { age: 60 },
    { age: 66 },
    { age: 72 },
    { age: 78 },
    { age: 84 }
];

const find = 64;

const result = data.map(({ age }) => age).reduce((best, el, index) => {

    if (Math.abs(find - el) < Math.abs(find - best)) {
        return el;
    }

    return best;

}, data[0].age)

console.log(result)

Upvotes: 0

Doğancan Arabacı
Doğancan Arabacı

Reputation: 3992

You can just sort the array by difference to lookup age:

const lookupAge = 61
const data = [
  { age: 52 },
  { age: 53 },
  { age: 54 },
  { age: 60 },
  { age: 66 },
  { age: 72 },
  { age: 78 },
  { age: 84 }
]

const result = data
  .map(d => d.age)
  .sort((a, b) => Math.abs(a - lookupAge) - Math.abs(b - lookupAge))

console.log('result', result)

Upvotes: 0

Matthias Meiling
Matthias Meiling

Reputation: 46

Assume, that your list ist not Sorted, and you do not want to sort your list. So you can pick the first object, iterate through your list and check if you get an item, which fits your requiremnt more than your currently picked item. If so, you just replace your item with the better one.

e.g.

var data = [/*...*/];
var find_age = 64;           // input
var best_item = data[0];     // pick any item as best item
for (var i = 1; i < data.length; i++) {

  // does date[i] match the requirement better than best_item?
  if (Math.abs (best_item.age - find_age) > Math.abs (data[i].age - find_age)) {

    // it does ... so update best_item
    best_item = data[i];
  }
}

// best_item stores the item which matches your requirement most.

If your dataset is sorted, you can optimize your runtime.

Upvotes: 0

Related Questions