Find object in array with closest value - javascript

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.

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.

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));

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.

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)

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)

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);

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>

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)

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

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;
}

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));

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

Related

How to write this function with forEach?

I am trying to group the people by their age and as you can see, my code works with the reduce method. I managed to get this working but now I want to do the same with forEach method. Here's my code:
{name:'Kyle', age:42},
{name:'Suk', age:34},
{name:'Lol', age:35},
{name:'Pol', age:23},
{name:'Kol', age:23}
]
people.reduce((groupedPeople, person)=>{
const age = person.age
if(groupedPeople[age] == null) {groupedPeople[age]=[]
}
groupedPeople[age].push(person)
return groupedPeople
})
Reduce accepts initial value that you update each iteration and return. So if you wish to use forEach, just move initial value before the forEach:
const people = [{
name: 'Kyle',
age: 42
},
{
name: 'Suk',
age: 34
},
{
name: 'Lol',
age: 35
},
{
name: 'Pol',
age: 23
},
{
name: 'Kol',
age: 23
}
]
const groupedPeople = {}
people.forEach((person) => {
const age = person.age
if (groupedPeople[age] == null) {
groupedPeople[age] = []
}
groupedPeople[age].push(person)
})
console.log(groupedPeople)
However, I am not sure why you wish to do that. Code with reduce is much cleaner.
Why forEach is just reduce with an accumulative value. Might as well be "global".
var people = [
{name:'Kyle', age:42},
{name:'Suk', age:34},
{name:'Lol', age:35},
{name:'Pol', age:23},
{name:'Kol', age:23}
];
var result = people.reduce((groupedPeople, person) => {
const age = person.age
if (groupedPeople[age] == null) {
groupedPeople[age] = []
}
groupedPeople[age].push(person)
return groupedPeople
}, {}) // <---- you forgot this {}
console.log(result)
var result = {};
people.forEach(function(person) {
if (result[person.age] == null) {
result[person.age] = []
}
result[person.age].push(person)
})
console.log(result)
.as-console-wrapper {
max-height: 100%!important;
top: 0;
}

Divide object array elements into groups of n each javascript

I have an Object as below:
const boxOfFruits = {
apples: [
{
name: "Kashmiri",
},
{
name: "Washington",
},
{
name: "Himalayan",
},
{
name: "Fuji",
}
],
oranges: [
{
name: "Nagpur",
},
{
name: "Clementine",
},
],
mangoes: [
{
name: "Totapuri",
},
{
name: "Alphonso",
},
{
name: "Langda",
},
],
}
I want to divide these fruits into boxes; maximum of n each, let's say where n is 3 and apples, oranges and mangoes are equally distributed.
So the output in this case would be:
box_1 = [{name: "Kashmiri"}, {name: "Nagpur"},{name: "Totapuri"}];
box_2 = [{name: "Washington"}, {name: "Clementine"},{name: "Alphonso"}];
box_3 = [{name: "Himalayan"},{name: "Langda"}, {name: "Fuji"}];
The type of fruits(apple,oranges,etc)/keys in object can increase/decrease and n is also variable. In case total fruits are less than n, then it would be just 1 box of fruits.
What I have tried so far:
Using Lodash, I am calculating the minimum and the maximum fruits in a single type:
const minFruitType = _.min(Object.values(basket).map((eachBasket: any) => eachBasket.length));
Total teams will the sum of the fruits / n
Will distribute the minimum fruits (l) in the first l boxes and fill the rest with the remaining fruits at every iteration while at the start of every iteration will calculate the minimum type of fruits again.
You can use Object.values(), array#reduce and array#forEach to transform your object.
const boxOfFruits = { apples: [ { name: "Kashmiri", }, { name: "Washington", }, { name: "Himalayan", }, ], oranges: [ { name: "Nagpur", }, { name: "Clementine", }, ], mangoes: [ { name: "Totapuri", }, { name: "Alphonso", }, { name: "Langda", }, ], },
result = Object.values(boxOfFruits).reduce((r, arr) => {
arr.forEach((o,i) => {
const key = `box_${i+1}`;
r[key] ??= r[key] || [];
r[key].push(o)
});
return r;
},{});
console.log(result);
The easiest way would be to use lodash.js's zip() function:
const boxes = _.zip( Object.values(boxOfFruits) );
Note that _.zip() will give you undefined values when the source arrays are different lengths, so you'll need/want to filter those out:
const boxes == _.zip( Object.values(boxOfFruits) )
.map(
box => box.filter(
x => x !== undefined
)
);
But that will not distribute the fruits evenly. For that, it shouldn't get much for difficult than this:
function distribute(boxOfFruits, n) {
const boxes = [];
const fruits = Object.keys(boxOfFruits);
for ( const fruit of fruits ) {
let i = 0;
const items = boxOfFruits[fruit];
for (const item of items) {
boxes[i] = !boxes[i] ?? [];
boxes[i] = boxes[i].push(item);
++i;
i = i < n ? i : 0 ;
}
}
return boxes;
}
A modified version of #Nicholas Carey's answer worked for me:
function distribute(boxOfFruits, n) {
let boxes = [];
let totalFruits = Object.values(boxOfFruits)
.reduce((content, current) => content + current.length, 0);
let maxBoxes = Math.ceil(totalFruits / 4);
Object.values(boxOfFruits).forEach((fruits) => {
let i = 0;
fruits.forEach((fruit) => {
boxes[i] ??= boxes[i] || [];
boxes[i].push(fruit);
++i;
i = i < (n+1) ? i : 0;
});
});
// Extra boxes created, redistribute them to
// starting boxes
let newBoxes = teams.slice(0, maxBoxes);
let pendingBoxes = teams.slice(maxBoxes);
let pendingFruits = pendingBoxes.flat();
let distributedBoxes = newBoxes.map((eachBox) => {
let required = n - eachBox.length;
if (required > 0) {
eachBox.push(...pendingFruits.splice(0, required));
}
return eachBox;
});
return distributedBoxes;
}
Code is pretty much the same as Nicholas's accept the below changes:
Directly fetched the values and iterated over those
empty array creation was failing, this way works
and checking on the max box size with n+1 instead of n

How to write a javascript function that takes an array with names and scores, and returns array with letter grades and students?

I've been stuck for days. Please help! new to javascript
first I mapped the students scores, and got an array of just the number scores. Then I wrote a if/else function to take the student score and convert it to a letter grade. But how can I take this array of letter grade and list out all the students that got each grade? and then write this in es6 into the getStudentsByGrade const??
var studentScores = students.map(function (student) {
return student.score;
})
console.log(studentScores);
function toLetterGrade(studentScores) {
var textG = '';
var result = [];
for (i = 0; i < studentScores.length; i++) {
textG = '';
if (studentScores[i] >= 90) {
textG = "A";
} else if (studentScores[i] >= 80) {
textG = "B";
} else if (studentScores[i] >= 60) {
textG = "C";
} else if (studentScores[i] >= 50) {
textG = "D";
} else if (studentScores[i] >= 32) {
textG = "E";
} else {
textG = "F";
}
result.push(textG);
}
return result;
}
console.log(toLetterGrade(studentScores));
Given a list of students with a name and a score, write a function getStudentsByGrade that takes in an array of students and a set of grade boundaries and gives you an gives you an object with a list of grades and the names of students for each grade.
the output should be:
{
A: ['Dan'],
C: ['Emily', 'Daisy'],
E: ['Brendan']
}
And must be written inside the following
const getStudentsByGrade = (students, gradeBoundaries) => {
// solution in here
}
Given:
const students = [{name: 'Daisy', score: 65},
{name: 'Dan', score: 99},
{name: 'Emily', score: 77},
{name: 'Brendan', score: 49}];
const gradeBoundaries = {A: 90, B: 80, C: 60, D: 50, E: 32, F: 0};
const getStudentsByGrade = (students, gradeBoundaries) => {
// How do I do this?
}
You want to transform an input array into an object indexed by grade - the appropriate tool to transform an array into an object (or any sort of single value) is reduce. You might separate out the code that turns the score into a letter grade into its own function, for better readability. For each student, get their appropriate letter grade, and then add that student's name to that grade array in the accumulator. If that grade doesn't exist yet in the accumulator, create it as a new array first:
const makeGetGrade = gradeBoundaries => {
const gradeBoundaryArr = Object.entries(gradeBoundaries)
.map(([grade, cutoff]) => ({ grade, cutoff }))
.sort((a, b) => b.cutoff - a.cutoff);
return findGrade => gradeBoundaryArr
.find(({ grade, cutoff }) => findGrade > cutoff)
.grade;
};
const getStudentsByGrade = (students, gradeBoundaries) => {
const getGrade = makeGetGrade(gradeBoundaries);
return students.reduce((a, { name, score }) => {
const grade = getGrade(score);
if (!a[grade]) a[grade] = [];
a[grade].push(name);
return a;
}, {});
};
const students=[{name:'Daisy',score:65,},{name:'Dan',score:99,},{name:'Emily',score:77},{name:'Brendan',score:49}];
const possibleGradeBoundaries = {
A: 90,
B: 80,
C: 60,
D: 50,
E: 32,
F: 0,
};
console.log(getStudentsByGrade(students, possibleGradeBoundaries));
Try this:
const students = [{
name: 'Daisy',
score: 65,
}, {
name: 'Dan',
score: 99,
}, {
name: 'Emily',
score: 77
}, {
name: 'Brendan',
score: 49
}];
console.log('-----------------------OLD-------------------', students);
setScore(students);
var result = [];
function setScore (students) {
students.forEach(function (student, i) {
if (student.score >= 90) {
student["grade"] = "A";
} else if (student.score >= 80) {
student["grade"] = "B";
} else if (student.score >= 60) {
student["grade"] = "C";
} else if (student.score >= 50) {
student["grade"] = "D";
} else if (student.score >= 32) {
student["grade"] = "E";
} else {
student["grade"] = "F";
}
});
console.log('-----------------------NEW-------------------', students);
}
You would want something like this. With the help of Array.filter and Object.keys, you can easily get your result in the specified format.
const getStudentsByGrade = (students, gradeBoundaries) => {
let result = {};
let letterGrades = Object.keys(gradeBoundaries);
letterGrades.forEach((letterGrade, index) => {
result[letterGrade] = students.filter(student => {
if (student.score >= gradeBoundaries[letterGrade]) {
if (index > 0) {
let higherLetterGrade = letterGrades[index - 1];
if (student.score < gradeBoundaries[higherLetterGrade]) {
return student.name;
}
} else {
return student.name;
}
}
});
});
return result;
}
Then simply invoke the function with data to see the result:
let result = getStudentsByGrade(students, gradeBoundaries);
console.log(result);
This should be the result output:
{
"A":[
{
"name":"Dan",
"score":99
}
],
"B":[
],
"C":[
{
"name":"Daisy",
"score":65
},
{
"name":"Emily",
"score":77
}
],
"D":[
],
"E":[
{
"name":"Brendan",
"score":49
}
],
"F":[
]
}

Get particular values inbetween an array javascript?

I have an array like,
0: "City1"
1: {name="sds", age="asd",....}
2: {name="sweds", age="accxsd",....}
3: {name="sdqws", age="asssd",....}
4: "City2"
... and many more
So I need to get the elements between index[0] and index[4],
Am able to check the string and object using typeof
for(i=0; i<=arr.length; i++){
if(typeof arr[i] == 'string'){
... // need to find next element eith type string
}
}
Is there a way to find the next element in an array whose value is string, so I can get elements between them.
You can use this alternative using the function reduce.
This approach builds an object grouping the objects into an array with the found string value.
var array = [ "City1", {name:"sds", age:"asd"}, {name:"sweds", age:"accxsd"}, {name:"sdqws", age:"asssd"}, "City2", {name:"sds2", age:"asd2"}, {name:"sweds2", age:"accxsd2"}, {name:"sdqws2", age:"asssd2"}];
var result = array.reduce((a, c) => {
if (typeof c === 'string') {
a[c] = [];
a.current = c;
} else if (a.current !== "") {
a[a.current].push(c);
}
return a;
}, {current: ""});
delete result.current;
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
How can I adjust to a particular String value, like if my input id 'city3', I need to get all elements between 'city3' and its next string value
The above approach groups the elements by the previously found string element, so you can directly access the desired target City3
var array = [ "City1", {name:"sds", age:"asd"}, {name:"sweds", age:"accxsd"}, {name:"sdqws", age:"asssd"}, "City3", {name:"sds3", age:"asd3"}, {name:"sweds3", age:"accxsd3"}, {name:"sdqws3", age:"asssd3"}, "City4", {name:"sds4", age:"asd4"}, {name:"sweds4", age:"accxsd4"}, {name:"sdqws4", age:"asssd"}];
var result = array.reduce((a, c) => {
if (typeof c === 'string') {
a[c] = [];
a.current = c;
} else if (a.current !== "") {
a[a.current].push(c);
}
return a;
}, {
current: ""
});
delete result.current;
var target = "City3";
// Now you have a direct access to find the desired target.
console.log(result[target]);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can just filter your array:
var arr = [
"City1",
{name:"sds", age:"asd"},
{name:"sweds", age:"accxsd"},
{name:"sdqws", age:"asssd"},
"City2"
];
var res = arr.filter(e => typeof e !== 'string');
console.log(res);
EDIT: if you want result from a specified start string, it should be:
var arr = [
"City1",
{name:"sds1", age:"asd"},
{name:"sweds1", age:"accxsd"},
{name:"sdqws1", age:"asssd"},
"City2",
{name:"sds2", age:"asd"},
{name:"sweds2", age:"accxsd"},
{name:"sdqws2", age:"asssd"},
"City3"
];
var str = 'City2';
var start = arr.indexOf(str);
var end = arr.findIndex((s, i) => typeof s === 'string' && i > start);
var res = arr.filter((e, i) => i > start && i < end);
console.log(res);
You could take a flag for filtering.
If a string is found switch the filter flag by checking the value with the wanted group 'City3'.
var array = ["City1", { name: "city1", age: 22 }, { name: "city1", age: 23 }, "City2", { name: "city2", age: 22 }, { name: "city2", age: 23 }, "City3", { name: "city3", age: 21 }, { name: "city3", age: 22 }, { name: "city3", age: 23 }, "City4", { name: "city4", age: 23 }, "City5"],
group = 'City3';
result = array.filter(
(f => v => typeof v === 'string' ? (f = v === group, false) : f)(false)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Using a traditional for...loop you can use continue in the loop to progress to the next index if your condition is a match:
const data = [
"City1",
{ name:"sds", age: "asd" },
{ name: "sweds", age: "accxsd" },
{ name: "sdqws", age: "asssd" },
"City2"
]
for (let i = 0; i < data.length; i++) {
if (typeof data[i] === 'string') continue;
console.log(data[i].name)
}

populate array based on value banding

I need to create an array of data based on the "age" of a person. This banding is not linear, but grouped something like
1,2,3,4,5,6,7,8,9,10,11-15,16-20,21-30,31-50,51+
so for any set of data, I need to run through this and put the person into the appropriate age "bucket"
At the end of the loop, I want to see how many people are in each banding.
Now, I have done this by using very crude techniques (aka if this .. then .. else if .. ) ;), but was wondering if I could learn new techniques and if there was any library or function that could take a range and fill the bucket accordingly
That's what I would do:
let buckets = '1,2,3,4,5,6,7,8,9,10,11-15,16-20,21-30,31-50,51+'.split(','),
bucketByAge = {}; // our bucket-age connector where we search later on
// prepare the index
buckets.forEach(function(bucket, index) {
// get the range (+ will be replaced with 1000; for the age it is infinity)
// and transform them to numbers
let range = bucket.replace('+', '-1000').split('-').map(Number);
// if it was not a range, simulate one
range[1] = range[1] || range[0];
// go through the range and fill our connector
while (range[0] <= range[1]--) {
bucketByAge[range[1]] = index;
}
});
// search
function getBucket(age) {
return { age: age, bucketRange: buckets[bucketByAge[age]], bucketIndex: bucketByAge[age] };
}
console.log(getBucket(1));
console.log(getBucket(12));
console.log(getBucket(61));
What's good here: you build the index once and later just take the result. Very performant.
A VERY rough o(n^2) implementation
const buckets = [
{
lowerLim: 1,
upperLim: 1,
persons: 0
},
{
lowerLim: 2,
upperLim: 2,
persons: 0
},
{
lowerLim: 3,
upperLim: 5,
persons: 0
},
{
lowerLim: 6,
upperLim: 10,
persons: 0
},
{
lowerLim: 11,
persons: 0
}
];
const persons = [{name: 'john', age: 1},{name: 'john', age: 2}, {name: 'john', age: 6}, {name: 'john', age: 20}, {name: 'john', age: 40}, {name: 'john', age: 7}, {name: 'john', age: 1}];
persons.forEach(person => {
buckets.forEach(bucket => {
const age = person.age;
const bucketLowerLimit = bucket.lowerLim;
const bucketUpperLimit = bucket.upperLim || Number.MAX_SAFE_INTEGER;
if(age >= bucketLowerLimit && age <= bucketUpperLimit) {
bucket.persons++;
}
})
});
buckets.forEach(bucket => {
console.log(bucket.persons);
});
You could use a reference for all values inbetween and a separate last value.
var slots = '1,2,3,4,5,6,7,8,9,10,11-15,16-20,21-30,31-50,51+'.split(','),
last = slots.pop(),
reference = Object.create(null),
histogram = Object.create(null),
i, value;
slots.forEach(function (a) {
var temp = a.split('-'),
left = temp[0],
right = temp[1] || temp[0];
histogram[a] = 0;
reference[right] = a;
while (left < right) {
reference[left] = a;
left++;
}
});
histogram[last] = 0;
for (i = 0; i < 1000000; i++) {
value = Math.floor(Math.random() * 100);
histogram[reference[value] || last]++;
}
console.log(histogram);
console.log(reference);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources