Why is this recursive function overwriting the values of the second call? - javascript

Sorry for the title, I'm not even sure how to phrase what is happening here.
I'm working on an expense tracking program in React that supports multiple currencies. The expenses being tracked can be nested arbitrarily deep in a JSON object.
entertainment: {
_values: {
USD: 23,
AUD: 5,
},
'food & drink': {
_values: {
AUD: 83,
},
'local bar': {
_values: {
AUD: 28,
USD: 2,
},
},
},
minigolf: {
_values: {
USD: 112,
},
}
}
An expense can have an amount stored directly in it, but it can also act as a 'parent' category to further detailed sub-expenses.
To display the total value of an expense I've written a pair of functions:
sumValues(values)
Sums up an array of _values objects (a value object is a key-value store of currency codes and integers)
totalExpense(expense)
Returns the total value of an expense. ie any _values it has, + the totalExpense of any children expenses.
I thought I'd written these as pure functions, but when calling totalExpense() recursively the first child of an expense returns the wrong total.
totalExpense(entertainment);
//-> Object { USD: 137, AUD: 116 }
OK
totalExpense(entertainment['food & drink']);
//-> Object { AUD: 111, USD: 2 }
OK
totalExpense(entertainment);
totalExpense(entertainment['food & drink']);
//-> Object { AUD: 139, USD: 4 }
NOT OK
I've been poking at this code for hours now, but for the life of me can't see what is happening:
sumValues = values => {
return values.reduce((acc, cur) => {
for (const currency in cur) {
acc[currency]
? (acc[currency] = acc[currency] + cur[currency])
: (acc[currency] = cur[currency]);
}
return acc;
});
};
totalExpense = expense => {
const values = [];
if (expense['_values']) {
values.push(expense['_values']);
}
const subExpenses = Object.keys(expense).filter(child => {
return child[0] !== '_';
});
if (subExpenses.length > 0) {
for (const subExpense of subExpenses) {
let subtotal = this.totalExpense(expense[subExpense]);
values.push(subtotal);
}
}
if (values.length) {
return this.sumValues(values);
} else {
throw Error('No values in this expense');
}
};
render() {
const entertainment = {
_values: {
USD: 23,
AUD: 5,
},
'food & drink': {
_values: {
AUD: 83,
},
'local bar': {
_values: {
AUD: 28,
USD: 2,
},
},
},
minigolf: {
_values: {
USD: 112,
},
},
};
console.log(this.totalExpense(entertainment));
console.log(this.totalExpense(entertainment['food & drink']));
console.log(this.totalExpense(entertainment['minigolf']));
return;
}

The problem is that your reduce callback's initial value is the first item in the values array, and then you proceed to assign to that item:
acc[currency]
? (acc[currency] = acc[currency] + cur[currency])
: (acc[currency] = cur[currency]);
So, the first item gets mutated every time sumValues is called. Instead, provide an empty object as the initial value for the reduce:
sumValues = values => {
return values.reduce((acc, cur) => {
for (const currency in cur) {
acc[currency]
? (acc[currency] = acc[currency] + cur[currency])
: (acc[currency] = cur[currency]);
}
return acc;
}, {});
};
sumValues = values => {
return values.reduce((acc, cur) => {
for (const currency in cur) {
acc[currency] ?
(acc[currency] = acc[currency] + cur[currency]) :
(acc[currency] = cur[currency]);
}
return acc;
}, {});
};
totalExpense = expense => {
const values = [];
if (expense['_values']) {
values.push(expense['_values']);
}
const subExpenses = Object.keys(expense).filter(child => {
return child[0] !== '_';
});
if (subExpenses.length > 0) {
for (const subExpense of subExpenses) {
let subtotal = this.totalExpense(expense[subExpense]);
values.push(subtotal);
}
}
if (values.length) {
return this.sumValues(values);
} else {
throw Error('No values in this expense');
}
};
const entertainment = {
_values: {
USD: 23,
AUD: 5,
},
'food & drink': {
_values: {
AUD: 83,
},
'local bar': {
_values: {
AUD: 28,
USD: 2,
},
},
},
minigolf: {
_values: {
USD: 112,
},
},
};
console.log(totalExpense(entertainment));
console.log(totalExpense(entertainment['food & drink']));

Related

How to merge a nested array of objects in javascript with a specific shape

I am trying to merge an array of objects by summing up the totals of each key-value pair under the totals object. For example, the below array would yield one object with a totals object of 3 apples and 5 oranges. This should be dynamic. If pears were to be a key in another object, the resulting object would include three keys under the totals object: apples, oranges, and pears.
Sample Input:
[
{
summary: {
totals: {
apples: 2,
oranges: 3
}
}
},
{
summary: {
totals: {
apples: 1,
oranges: 2
}
}
}
]
Expected Output:
{
summary:{
totals:{
apples:3,
oranges:5
}
}
}
What I've tried:
function mergeObjects(arr) {
let shape = {
summary:{
totals:{}
}
}
return arr.reduce((prev, cur) => {
if(cur.summary.totals.apples){
shape.summary.totals.apples.push(cur.summary.totals.apples)
}
}, shape);
}
Using Array#reduce, iterate over the array while updating an object
In every iteration, using Object#entries and
, iterate over the current totals pairs and update the accumulator.
const arr = [
{ summary: { totals: { apples: 2, oranges: 3 } } },
{ summary: { totals: { apples: 1, oranges: 2 } } },
];
const res = arr.reduce((map, current) => {
const { totals: currentTotals = {} } = current.summary ?? {};
const { totals } = map.summary;
Object.entries(currentTotals).forEach(([ key, value ]) => {
totals[key] = (totals[key] ?? 0) + value;
});
return map;
}, { summary: { totals: {} } });
console.log(res);
You can try something like. Just loop through the array and sum up apples and oranges.
const arr = [
{
summary: {
totals: {
apples: 2,
oranges: 3,
},
},
},
{
summary: {
totals: {
apples: 1,
oranges: 2,
},
},
},
];
function mergeObjects(arr) {
let shape = {
summary:{
totals:{
apples:0,
oranges:0
}
}
}
arr.forEach(x => {
if(x.summary.totals.apples){
shape.summary.totals.apples += x.summary.totals.apples;
shape.summary.totals.oranges += x.summary.totals.oranges;
}
});
return shape;
}
let result = mergeObjects(arr);
console.log(result);
The second option of the reduce function initializes the value.
And the initialized value can be used in prev!
[1] store the value in prev.
[2] prev can accumulate values. You have to return to use the accumulated value. If not returned, the value will be undefined.
[3] apples is not an array type, so you cannot use the push method. it is a number type, you must use a numeric operator.
function mergeObjects(arr) {
const shape = {
summary: {
totals: {},
},
};
return arr.reduce((prev, cur) => {
const { apples, oranges } = cur.summary.totals;
// [1]
prev.summary.totals.apples
// [3]
? (prev.summary.totals.apples += apples)
: (prev.summary.totals.apples = apples);
prev.summary.totals.oranges
? (prev.summary.totals.oranges += oranges)
: (prev.summary.totals.oranges = oranges);
// [2]
return prev;
}, shape);
}
tips!
Use Destructuring Assignment
const { apples, oranges } = cur.summary.totals;
Use Ternary Operator
prev.summary.totals.apples
? (prev.summary.totals.apples += apples)
: (prev.summary.totals.apples = apples);
Make code look nice!
You can combine Object.entries() with Array#reduce() and Array.forEach()
Code:
const data = [{summary: {totals: {apples: 2,oranges: 3}}},{summary: {totals: {apples: 1,oranges: 2}}}]
const result = data.reduce((a, c) => {
Object
.entries(c.summary.totals)
.forEach(([k, v]) => a.summary.totals[k] += v)
return a
},
{ summary: { totals: { apples: 0, oranges: 0 } } })
console.log(result)

Calculate average of an array with objects that has a nested Object

I am trying to loop through array of Objects and calculate the average of a nested Object containing several different keys.
This is the start array:
[{
course: "math",
id: 4,
values: {
2017: 8,
2018: 9
}
}, {
course: "math",
id: 4,
values: {
2017: 5,
2019: 7
}
}]
This is my goal:
{2017:6.5,2018:9,2019:7}
Now it returns correct for 2017 but NaN for 2018 and 2019. If anyone have better way of solving this that doesn't require so much please provide to.
This is what I have tried so far. I have been searching a lot but not really found anything I can use.
const testObject = [{
id: 4,
course: "math",
values: {
2017: 8,
2018: 9
}
},
{
id: 5,
course: "English",
values: {
2017: 8,
2018: 9
}
},
{
id: 4,
course: "math",
values: {
2017: 5,
2019: 7
}
},
{
id: 4,
course: "english",
values: {
2017: 5,
2019: 7
}
},
]
//First I filter out the id 4 and course Math
const mathid1 = testObject.filter((e) => e.id === 4 && e.course === "math");
//I than find all the different years
const ArrayOfAllYears = []
mathid1.map((element) => {
ArrayOfAllYears.push(Object.keys(element.values));
})
//I here find all the different years
const withDuplicates = ArrayOfAllYears.reduce(function(arrayOne, arrayTwo) {
return arrayOne.concat(arrayTwo);
}, []);
const withoutDuplicates = Array.from(new Set(withDuplicates));
//Here I just create the calculate average function
const Result = {}
const calculateAverage = (array) => {
const sum = array.reduce((a, b) => a + b);
return sum / array.length;
};
const newObj = {}
withoutDuplicates.map((year) => {
let reformattedArray = mathid1.map(obj => {
if (obj["values"][year]) {
return obj["values"][year]
}
})
newObj[year] = calculateAverage(reformattedArray)
})
console.log(newObj)
// I want to calculate the average of the mathid1 values and return it on a Object like {2017:..,2018..}
There are two simple steps to the problem.
First, you need to reduce the array to an object with years and values:
// this outputs
// { 2017: [8, 5], 2018: [9], 2019: [7] }
function byYear(array) {
// take each item of an array
return array.reduce((acc, data) => {
// take the values of that item
Object.entries(data.values).forEach(([year, value]) => {
// and map all the values to years
acc[year] = acc[year] || []
acc[year].push(value)
})
return acc
}, {})
}
The second step is just taking averages:
function average(object) {
const averages = {}
for (let key in object) {
averages[key] = object[key].reduce((sum, value) => sum + value) / object[key].length
}
return averages
}
And now you put them together:
average(byYear(input))
In here, the input is the filtered array. As a whole snippet:
function byYear(array) {
return array.reduce((acc, data) => {
Object.entries(data.values).forEach(([year, value]) => {
acc[year] = acc[year] || []
acc[year].push(value)
})
return acc
}, {})
}
function average(object) {
const averages = {}
for (let key in object) {
averages[key] = object[key].reduce((sum, value) => sum + value) / object[key].length
}
return averages
}
const output = average(byYear([{
course: "math",
id: 4,
values: {
2017: 8,
2018: 9
}
}, {
course: "math",
id: 4,
values: {
2017: 5,
2019: 7
}
}]))
console.log(output)
The problem with your current code lies in how you build the reformattedArray variable. First, notice that your map function implicitly returns undefined whenever that year is missing from the current object:
let reformattedArray = mathid1.map(obj => {
if (obj["values"][year]) {
return obj["values"][year]
}
// There is an implicit return undefined, right here...
})
When you use the array .map method, every item of the array will be replaced by the return value of the map function. In the case that the year is not present, it will not go into the if block, and so it implicitly returns undefined upon reaching the end of the function.
So, ultimately all you have to do is remove the undefined entries from this array, and your code will work as-is.
One way to do that is to just use .filter(Boolean) on the array, which removes any falsey entries (which undefined is). Eg:
let reformattedArray = mathid1.map(obj => {
/* code here */
}).filter(Boolean); // Note the filter here...
Here is your snippet with that modification:
const testObject = [{
id: 4,
course: "math",
values: {
2017: 8,
2018: 9
}
},
{
id: 5,
course: "English",
values: {
2017: 8,
2018: 9
}
},
{
id: 4,
course: "math",
values: {
2017: 5,
2019: 7
}
},
{
id: 4,
course: "english",
values: {
2017: 5,
2019: 7
}
},
]
//First I filter out the id 4 and course Math
const mathid1 = testObject.filter((e) => e.id === 4 && e.course === "math");
//I than find all the different years
const ArrayOfAllYears = []
mathid1.map((element) => {
ArrayOfAllYears.push(Object.keys(element.values));
})
//I here find all the different years
const withDuplicates = ArrayOfAllYears.reduce(function(arrayOne, arrayTwo) {
return arrayOne.concat(arrayTwo);
}, []);
const withoutDuplicates = Array.from(new Set(withDuplicates));
//Here I just create the calculate average function
const Result = {}
const calculateAverage = (array) => {
const sum = array.reduce((a, b) => a + b);
return sum / array.length;
};
const newObj = {}
withoutDuplicates.map((year) => {
let reformattedArray = mathid1.map(obj => {
if (obj["values"][year]) {
return obj["values"][year]
}
}).filter(Boolean)
newObj[year] = calculateAverage(reformattedArray)
})
console.log(newObj)
// I want to calculate the average of the mathid1 values and return it on a Object like {2017:..,2018..}
Group items by year.
Calculate average.
const items=[{
course: "math",
id: 4,
values: {
2017: 8,
2018: 9
}
}, {
course: "math",
id: 4,
values: {
2017: 5,
2019: 7
}
}]
const groupedValues=items.reduce((groupedValues,item)=>{
Object.entries(item.values).forEach(([year,value])=>{
if(groupedValues[year]){
groupedValues[year]={value:groupedValues[year].value+value,items:groupedValues[year].items+1};
} else {
groupedValues[year]={value,items:1};
}
});
return groupedValues;
},{})
console.log(groupedValues);
const result = Object.entries(groupedValues).reduce((result,item)=>{
result[item[0]]=item[1].value/item[1].items;
return result;
},{})
console.log(result);
I would recommend extracting the years information into a map:
/** #type {Map<string, number[]} */
const years = new Map();
testObject.forEach((obj) => {
Object.keys(obj.values).forEach((key) => {
if (!years.has(key)) years.set(key, []);
years.set(key, [...years.get(key), obj.values[key]]);
});
});
Then you can simply loop over the map and create the resulting object:
const result = {};
years.forEach((values, key) => {
Object.defineProperty(result, key, {
value: values.reduce((acc, val) => acc + val) / values.length,
enumerable: true,
});
});
console.log(result);
It should output:
{ '2017': 6.5, '2018': 9, '2019': 7 }

How to get sum of tickets as per priority from an array using javascript

I have the following array
{
agent_id:001,
priority:"High",
task_id:T1
},
{
agent_id:001,
priority:"High",
task_id:T1
},
{
agent_id:001,
priority:"Medium",
task_id:T1
}
{
agent_id:002,
priority:"High",
task_id:T1
}
I need to get an output of the above array as follows:
{agent_id:001,High_Count:2,Medium_Count:1},
{agent_id:002,High_Count:1,Medium_Count:0}
Here is my code:
for (let i = 0; i < dataModel.ticketList.length; i++) {
var priority = JSON.stringify(dataModel.ticketList[i].Priority);
if (!map.has(priority))
{
map.set(priority, {
Agent: dataModel.ticketList[i].Agent,
Low: 1,
});
}
else
{
map.get(priority).Low++;
}
}
const res = Array.from(map.values())
return res;
You could use Array.prototype.reduce like this:
const data = [
{ agent_id: 001, priority: "High", task_id: "T1" },
{ agent_id: 001, priority: "High", task_id: "T1" },
{ agent_id: 001, priority: "Medium", task_id: "T1" },
{ agent_id: 002, priority: "High", task_id: "T1" },
];
const result = data.reduce((acc, cur) => {
const index = acc.findIndex((x) => x.agent_id === cur.agent_id);
if (~index) {
const value = acc[index][`${cur.priority}_Count`] || 0;
acc.splice(index, 1, {
...acc[index],
[`${cur.priority}_Count`]: value + 1,
});
} else {
acc.push({ agent_id: cur.agent_id, [`${cur.priority}_Count`]: 1 });
}
return acc;
}, []);
console.log(result);
Please use Array.reduce() function.
const data = [{ agent_id:001, priority:"High", task_id:"T1" }, { agent_id:001, priority:"High", task_id:"T1" }, { agent_id:001, priority:"Medium", task_id:"T1" }, { agent_id:002, priority:"High", task_id:"T1" }];
const urgent = "Urgent";
let result = data.reduce((total, item) => {
if(total.map(val => val.agent_id).includes(item.agent_id)) {
const index = total.map(val => val.agent_id).indexOf(item.agent_id);
if(total[index].hasOwnProperty(item.priority + "_Count"))
total[index][item.priority + "_Count"] ++;
else
total[index][item.priority + "_Count"] = 1;
} else {
let obj = {agent_id: item.agent_id};
let priorities = data.map(val => val.priority).filter((val, i, self) => self.indexOf(val) === i);
if(!!urgent) priorities.push(urgent);
priorities.forEach(val => {obj[val + "_Count"] = 0});
obj[item.priority + "_Count"] = 1;
total.push(obj);
}
return total;
},[]);
result = result.map(val => {
const total = Object.keys(val).reduce((total, key) => key === 'agent_id' ? total : total + val[key], 0);
return {...val, total: total};
});
console.log(result);

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.
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

Combine objects in an array and add values. JavaScript / node.js

is there a way to turn
[ { JavaScript: 41837, Batchfile: 47 },
{ 'C#': 7484 },
{ Batchfile: 110 },
{ Scala: 50597 },
{ Java: 18180 },
{ Java: 55689 } ]
into:
{
JavaScript: 41837,
Batchfile: 157,
'C#': 7484
Scala: 50597,
Java: 73869
}
The size of the array is different every time we run the application.
reduce is your friend
const list = [ { JavaScript: 41837, Batchfile: 47 },
{ 'C#': 7484 },
{ Batchfile: 110 },
{ Scala: 50597 },
{ Java: 18180 },
{ Java: 55689 } ];
const summed = list.reduce((totals, obj) => {
Object.keys(obj).forEach(k => {
const cur = totals[k] || 0;
totals[k] = cur + obj[k];
});
return totals;
}, {});
console.log(summed);
Make a new object. Iterate through each object in the input array. For each object, iterate through the keys of that object. If they key exists in your new object, add the value, otherwise add the key and value to the new object;
var arr = [ { JavaScript: 41837, Batchfile: 47 },
{ 'C#': 7484 },
{ Batchfile: 110 },
{ Scala: 50597 },
{ Java: 18180 },
{ Java: 55689 } ]
var result = {}
arr.forEach(function(group){
Object.keys(group).forEach(function(key){
if(result[key]) {
result[key] += group[key];
} else {
result[key] = group[key];
}
})
});
console.log(result);
Array reduce in combination with For in to loop through the object keys in them would get what you want.
a = [ { JavaScript: 41837, Batchfile: 47 },
{ 'C#': 7484 },
{ Batchfile: 110 },
{ Scala: 50597 },
{ Java: 18180 },
{ Java: 55689 } ]
result = {}
b = a.reduce((result, item) => {
for (let eachKey in item) {
const curSum = result[eachKey] || 0;;
result[eachKey] = curSum + item[eachKey];
}
return result;
}, result)

Categories

Resources