Sum values for multiple objects in the array - javascript

This is what I have:
const arrayA = [{name:'a', amount: 10, serviceId: '23a', test:'SUCCESS'},
{name:'a', amount: 9, test:'FAIL'},
{name:'b', amount: 15, serviceId: '23b', test:'SUCCESS'}]
(note that there's object not having 'serviceId')
I would like to get:
[{name:'a', amount: 19, test:'FAIL'},
{name:'b', amount: 15, test:'SUCCESS'}]
I want to sum amount field grouped by name.
'test' field should be FAIL if there's any object with the value FAIL (grouped by name)
I don't care about the value of serviceId and don't want to include it in the new object.
I have searched, and tried something like this:
(reference: https://stackoverflow.com/a/50338360/13840216)
const result = Object.values(arrayA.reduce((r, o) => (r[o.name]
? (r[o.name].amount += o.amount)
: (r[o.name] = {...o}), r), {}));
but still not sure how to assign test field.
Any help would be appreciated!

You need to check test and update the value, if necessary.
const
array = [{ name: 'a', amount: 10, serviceId: '23a', test: 'SUCCESS' }, { name: 'a', amount: 9, test: 'FAIL' }, { name: 'b', amount: 15, serviceId: '23b', test: 'SUCCESS' }],
result = Object.values(array.reduce((r, { name, amount, test }) => {
if (!r[name]) r[name] = { name, amount: 0, test };
r[name].amount += amount;
if (test === 'FAIL') r[name].test = 'FAIL';
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

This? Just test if the .test is already fail
const arrayA = [{ name: 'a', amount: 10, serviceId: '23a', test: 'SUCCESS' }, { name: 'a', amount: 9, test: 'FAIL' }, { name: 'b', amount: 15, serviceId: '23b', test: 'SUCCESS' }],
result = Object.values(
arrayA.reduce((r, o) => {
r[o.name] ? (r[o.name].amount += o.amount) : (r[o.name] = { ...o })
r[o.name].test = r[o.name].test === "FAIL" ? "FAIL" : o.test;
return r;
}, {})
);
console.log(result);

Store the sums and final values in an object and then convert them into an array.
const arrayA = [
{ name: "a", amount: 10, serviceId: "23a", test: "SUCCESS" },
{ name: "a", amount: 9, test: "FAIL" },
{ name: "b", amount: 15, serviceId: "23b", test: "SUCCESS" },
];
const map = {};
arrayA.forEach((row) => {
if (!map[row.name]) {
map[row.name] = { name: row.name, amount: 0, test: "SUCCESS" };
}
map[row.name].amount += row.amount;
if (row.test === "FAIL") {
map[row.name].test = "FAIL";
}
});
const result = Object.values(map);
console.log(result);

That is a nice use case of how to process data in js, particularly how functions like map and reduce can help you do that processing. Which are a nice alternative to loops and iterations.
Moreover, I'd advise you to do the processing in steps, as you have defined in the description. For example:
const arrayA = [
{name:'a', amount: 10, serviceId: '23a', test:'SUCCESS'},
{name:'a', amount: 9, test:'FAIL'},
{name:'b', amount: 15, serviceId: '23b', test:'SUCCESS'}
]
// First group the items by name
const byName = arrayA.reduce((ob, item) => {
if(!(item.name in ob))
ob[item.name] = []
ob[item.name].push({amount: item.amount, test: item.test})
return ob
}, {})
// Then compute the total amount in each group and find if there is any FAIL in a group
const sumByName = Object.keys(byName).map(name => { // This is a way to iterate through the groups
// Sum the amount in all elements of a group
const amount = byName[name].reduce((sum, item) => sum + item.amount , 0)
// Find if there is any FAIL in a group
const test = byName[name].map(item => item.test) // Get an array with only the test string
.includes('FAIL') ? 'FAIL': 'SUCCESS' // Evaluate if the array includes FAIL
return ({name, amount, test})
})
console.log(sumByName)
Finally, I'd advise you to watch these videos on map and reduce (and all the content of that channel for this matter)
Reduce
Map

you can use Array.reduce method:
const arrayA=[{name:"a",amount:10,serviceId:"23a",test:"SUCCESS"},{name:"a",amount:9,test:"FAIL"},{name:"b",amount:15,serviceId:"23b",test:"SUCCESS"}];
let result = arrayA.reduce((aac,{name,amount,test}) => {
let idx = aac.findIndex(n => n.name === name)
if( idx != -1){
aac[idx].amount += amount;
if(test === "FAIL")
aac[idx].test = "FAIL"
return aac
}
aac.push({name,amount,test})
return aac
},[])
console.log(result);
console.log(arrayA)

Related

Sum values depending on other value in array

I have an array of objects that represent transactions of shares:
[{
date : ...,
symbol: 'TSLA',
amount: 3,
price: 1000.00
},
{
date : ...,
symbol: 'AAPL',
amount: 1,
price: 1200.00
},
{
date : ...,
symbol: 'AAPL',
amount: 7,
price: 1300.00
}]
I need to get sum of amounts based of symbol of that array, so output would be:
[{
symbol: 'TSLA',
amount: 3,
},
{
symbol: 'AAPL',
amount: 8,
}]
Is there an efficient way to do this with build in operations in javascript, or is the only way to do it with 2 array and double loop?
I was thinking of saving symbols in separate Set, and then suming all amounts, but is there a better way?
I've tried this, but this seems to only copy the original array.
const checkIfExists = (array, value) => {
array.forEach((el, i) => {
if (el.symbol === value) {
return i;
}
});
return -1;
};
const calculateSameValues = (data) => {
let result = [];
data.forEach((el) => {
const index = checkIfExists(result, el.symbol);
if (index === -1) {
result.push({symbol: el.symbol, amount: el.amount});
} else result[index].amount += el.amount;
});
console.log(result);
};
Seems like my checkIfExists function was returning always -1.
I fixed it by saving index in seperate variable and than returning it.
Here's code:
const checkIfExists = (array, value) => {
let index = -1;
array.forEach((el, i) => {
if (el.symbol === value) {
console.log(i);
index = i;
}
});
return index;
};
Note that this still uses 2 loops, I was looking for something more efficient, but this works.
you can use array.reduce() something like this:
const arr = [{
symbol: 'TSLA',
amount: 3,
price: 1000.00
},
{
symbol: 'AAPL',
amount: 1,
price: 1200.00
},
{
symbol: 'AAPL',
amount: 7,
price: 1300.00
}]
const x = arr.reduce(function(acc, cur) {
const idx = acc.findIndex(el => el.symbol === cur.symbol);
const obj = {
symbol: cur.symbol,
amount: cur.amount,
}
if(idx < 0) {
acc.push(obj)
} else {
acc[idx].amount = acc[idx].amount + cur.amount;
}
return acc;
}, []);
console.log(x);

Reduce and sort object array in one step

I'm trying to get two values (label and data) from my source data array, rename the label value and sort the label and data array in the result object.
If this is my source data...
const sourceData = [
{ _id: 'any', count: 12 },
{ _id: 'thing', count: 34 },
{ _id: 'value', count: 56 }
];
...the result should be:
{ label: ['car', 'plane', 'ship'], data: [12, 34, 56] }
So any should become car, thing should become plane and value should become ship.
But I also want to change the order of the elements in the result arrays using the label values, which should also order the data values.
Let's assume this result is expected:
{ label: ['ship', 'car', 'plane'], data: [56, 12, 34] }
With the following solution there is the need of two variables (maps and order). I thing it would be better to use only one kind of map, which should set the new label values and also the order. Maybe with an array?!
Right now only the label values get ordered, but data values should be ordered in the same way...
const maps = { any: 'car', thing: 'plane', value: 'ship' }; // 1. Rename label values
const result = sourceData.reduce((a, c) => {
a.label = a.label || [];
a.data = a.data || [];
a.label.push(maps[c._id]);
a.data.push(c.count);
return a;
}, {});
result.label.sort((a, b) => {
const order = {'ship': 1, 'car': 2, plane: 3}; // 2. Set new order
return order[a] - order[b];
})
You could move the information into a single object.
const
data = [{ _id: 'any', count: 12 }, { _id: 'thing', count: 34 }, { _id: 'value', count: 56 }],
target = { any: { label: 'car', index: 1 }, thing: { label: 'plane', index: 2 }, value: { label: 'ship', index: 0 } },
result = data.reduce((r, { _id, count }) => {
r.label[target[_id].index] = target[_id].label;
r.data[target[_id].index] = count;
return r;
}, { label: [], data: [] })
console.log(result);
Instead of separating the data into label and data and then sorting them together, you can first sort the data and then transform.
const sourceData = [
{ _id: 'any', count: 12 },
{ _id: 'thing', count: 34 },
{ _id: 'value', count: 56 }
];
const maps = { any: 'car', thing: 'plane', value: 'ship' };
// Rename label values.
let result = sourceData.map(item => ({
...item,
_id: maps[item._id]
}));
// Sort the data.
result.sort((a, b) => {
const order = {'ship': 1, 'car': 2, plane: 3};
return order[a._id] - order[b._id];
})
// Transform the result.
result = result.reduce((a, c) => {
a.label = a.label || [];
a.data = a.data || [];
a.label.push(c._id);
a.data.push(c.count);
return a;
}, {});
console.log(result);

JS: Filter array of objects by max value per category

What is most efficient / elegant way to achieve sql-like filtering effect. I want to filter them and get only that objects which are max value in some group.
This is my code, it works but probably it's not best way:
uniqueValues = (arr) => [...new Set(arr)];
getMaxTimeOf = (arr) => Math.max(...arr.map(o => o.timeStamp), 0);
selectorName = (name) => (obj) => obj.name === name;
selectorTime = (time) => (obj) => obj.timeStamp === time;
getGroup = (obj, selector) => obj.filter(selector)
onlyLastChangedFrom = (history) => {
const uniqueNames = uniqueValues(history.map(o => o.name))
let filtered = []
uniqueNames.forEach(name => {
const group = getGroup(history, selectorName(name))
const groupLastTime = getMaxTimeOf(group)
const lastChange = getGroup(group, selectorTime(groupLastTime))
filtered.push(lastChange[0])
});
return filtered
}
onlyLastChangedFrom(history)
// Input:
[ { name: 'bathroom',
value: 54,
timeStamp: 1562318089713 },
{ name: 'bathroom',
value: 55,
timeStamp: 1562318090807 },
{ name: 'bedroom',
value: 48,
timeStamp: 1562318092084 },
{ name: 'bedroom',
value: 49,
timeStamp: 1562318092223 },
{ name: 'room',
value: 41,
timeStamp: 1562318093467 } ]
// Output:
[ { name: 'bathroom',
value: 55,
timeStamp: 1562318090807 },
{ name: 'bedroom',
value: 49,
timeStamp: 1562318092223 },
{ name: 'room',
value: 41,
timeStamp: 1562318093467 } ]
Reduce the array to an object, using the name property as the key. For each item, check if the item that exists in the accumulator has a higher value than the current item, and if not replace it with the current item. Convert back to an array with Object.values():
const arr = [{"name":"bathroom","value":54,"timeStamp":1562318089713},{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":48,"timeStamp":1562318092084},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}]
const result = Object.values(arr.reduce((r, o) => {
r[o.name] = (r[o.name] && r[o.name].value > o.value) ? r[o.name] : o
return r
}, {}))
console.log(result)
I love to use lodash for stuff like this. It's very functional and therefore very clear and straightforward.
Take a look at the following code:
const DATA = [
{
name: "bathroom",
value: 54,
timeStamp: 1562318089713
},
{
name: "bathroom",
value: 55,
timeStamp: 1562318090807
},
{
name: "bedroom",
value: 48,
timeStamp: 1562318092084
},
{
name: "bedroom",
value: 49,
timeStamp: 1562318092223
},
{
name: "room",
value: 41,
timeStamp: 1562318093467
}
];
let max = _
.chain(DATA)
.groupBy('name')
.sortBy('value')
.map(o => _(o).reverse().first())
.flatten()
.value();
console.log(max); // returns [{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}]
Here is another reduce alternative :
var arr = [{"name":"bathroom","value":54,"timeStamp":1562318089713},{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":48,"timeStamp":1562318092084},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}];
var obj = arr.reduce((r, o) => (o.value < (r[o.name] || {}).value || (r[o.name] = o), r), {});
console.log( Object.values(obj) );
What is most efficient / elegant way to achieve sql-like filtering effect.
You could take functions for every step and pipe all functions for a single result.
For example in SQL, you would have the following query:
SELECT name, value, MAX(timeStamp)
FROM data
GROUP BY name;
With an SQL like approach, you could group first and take the max object out of the result sets.
result = pipe(
groupBy('name'),
select(max('timeStamp'))
)(data);
const
pipe = (...functions) => input => functions.reduce((acc, fn) => fn(acc), input),
groupBy = key => array => array.reduce((r, o) => {
var temp = r.find(([p]) => o[key] === p[key])
if (temp) temp.push(o);
else r.push([o]);
return r;
}, []),
max = key => array => array.reduce((a, b) => a[key] > b[key] ? a : b),
select = fn => array => array.map(fn);
var data = [{ name: 'bathroom', value: 54, timeStamp: 1562318089713 }, { name: 'bathroom', value: 55, timeStamp: 1562318090807 }, { name: 'bedroom', value: 48, timeStamp: 1562318092084 }, { name: 'bedroom', value: 49, timeStamp: 1562318092223 }, { name: 'room', value: 41, timeStamp: 1562318093467 }],
result = pipe(
groupBy('name'),
select(max('timeStamp'))
)(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use .reduce() by keeping an accumulated object which keeps the max group currently found and then use Object.values() to get an array of those objects (instead of an key-value pair object relationship).
See example below:
const arr=[{name:"bathroom",value:54,timeStamp:1562318089713},{name:"bathroom",value:55,timeStamp:1562318090807},{name:"bedroom",value:48,timeStamp:1562318092084},{name:"bedroom",value:49,timeStamp:1562318092223},{name:"room",value:41,timeStamp:1562318093467}];
const res = Object.values(arr.reduce((acc, o) => {
acc[o.name] = acc[o.name] || o;
if (o.value > acc[o.name].value)
acc[o.name] = o;
return acc;
}, {}));
console.log(res);
Doing this in stages.
get a set of names
create a sorted array in descending order
then just map using find to get the first one
Below is an example.
const input = [{"name":"bathroom","value":54,"timeStamp":1562318089713},{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":48,"timeStamp":1562318092084},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}];
const output = [...new Set(input.map(m => m.name))].
map(m => [...input].sort(
(a,b) => b.value - a.value).
find(x => m === x.name));
console.log(output);
Use map() and foreach() to get desired output
const arr=[{name:"bathroom",value:54,timeStamp:1562318089713},
{name:"bathroom",value:55,timeStamp:1562318090807},
{name:"bedroom",value:48,timeStamp:1562318092084},
{name:"bedroom",value:49,timeStamp:1562318092223},
{name:"room",value:41,timeStamp:1562318093467}];
let res = new Map();
arr.forEach((obj) => {
let values = res.get(obj.name);
if(!(values && values.value > obj.value)){
res.set(obj.name, obj)
}
})
console.log(res);
console.log([...res])
You can use sort and filter methods
const arr = [{"name":"bathroom","value":54,"timeStamp":1562318089713},{"name":"bathroom","value":55,"timeStamp":1562318090807},{"name":"bedroom","value":48,"timeStamp":1562318092084},{"name":"bedroom","value":49,"timeStamp":1562318092223},{"name":"room","value":41,"timeStamp":1562318093467}]
arr.sort(function (a, b) {
return b.value - a.value;
}).filter((v, i, a) => a.findIndex((v2) => v2.name === v.name) === i);
console.log(arr);

Get duplicates except first occurrence in array of objects

I want to be able to return duplicates except first occurance in an array of objects based of the place and keyword. Both should match and return the documents in an new array. Here is my trial run:
var things = [
{place: 'hello', keyword: 'hey', id: 0},
{place: 'hi', id: 1},
{place: 'hello', keyword: 'hey', id: 2},
{place: 'hello', keyword: 'man', id: 3}
]
var duplicates = [];
things.forEach((item, index) => {
if(things.indexOf(item.place) != index && things.indexOf(item.keyword) != index) {
duplicates.push(item);
}
});
Expected output:
[{place: 'hello', keyword: 'hey', id: 2}]
Any help would be great (without any frameworks, just ES6 or older). Thanks
EDIT: It should match multiple specified values such as keyword and place.
You could count the same keys and filter if the count is greater than one with an object for counting
const
getKey = o => keys.map(k => o[k]).join('|'),
keys = ['place', 'keyword'],
things = [{ place: 'hello', keyword: 'hey', id: 0 }, { place: 'hi', id: 1 }, { place: 'hello', keyword: 'hey', id: 2 }, { place: 'hello', keyword: 'man', id: 3 }],
hash = Object.create(null),
duplicates = things.filter(o =>
(k => (hash[k] = (hash[k] || 0) + 1) > 1)
(getKey(o))
);
console.log(duplicates);
The obvious solution is that you'll have to track the objects you have seen in order to do it how you want.
const seen = [];
const duplicates = [];
things.forEach(item => {
const sawItem = seen.find(seenItem => item.place === seenItem.place && item.keyword === seenItem.keyword)
if (sawItem) {
duplicates.push(sawItem);
} else {
seen.push(sawItem);
}
});
This isn't a very efficient algorithm however, so I'm curious to see a better way to do it.
You could group the items based on place and then get the first item from those groups with length > 1
const things = [{ place: 'hello', keyword: 'hey', id: 0 }, { place: 'hi', id: 1 }, { place: 'hello', keyword: 'hey', id: 2 }, { place: 'hello', keyword: 'man', id: 3 }];
const merged = things.reduce((r, a) => {
(r[a.place] = r[a.place] || []).push(a)
return r
}, {})
const final = Object.values(merged)
.filter(a => a.length > 1)
.map(a => a[1])
console.log(final)

Get list of duplicate objects in an array of objects

I am trying to get duplicate objects within an array of objects. Let's say the object is like below.
values = [
{ id: 10, name: 'someName1' },
{ id: 10, name: 'someName2' },
{ id: 11, name: 'someName3' },
{ id: 12, name: 'someName4' }
];
Duplicate objects should return like below:
duplicate = [
{ id: 10, name: 'someName1' },
{ id: 10, name: 'someName2' }
];
You can use Array#reduce to make a counter lookup table based on the id key, then use Array#filter to remove any items that appeared only once in the lookup table. Time complexity is O(n).
const values = [{id: 10, name: 'someName1'}, {id: 10, name: 'someName2'}, {id: 11, name:'someName3'}, {id: 12, name: 'someName4'}];
const lookup = values.reduce((a, e) => {
a[e.id] = ++a[e.id] || 0;
return a;
}, {});
console.log(values.filter(e => lookup[e.id]));
Let's say you have:
arr = [
{ id:10, name: 'someName1' },
{ id:10, name: 'someName2' },
{ id:11, name: 'someName3' },
{ id:12, name: 'someName4' }
]
So, to get unique items:
unique = arr
.map(e => e['id'])
.map((e, i, final) => final.indexOf(e) === i && i)
.filter(obj=> arr[obj])
.map(e => arr[e]);
Then, result will be
unique = [
{ id:10, name: 'someName1' },
{ id:11, name: 'someName3' },
{ id:12, name: 'someName4' }
]
And, to get duplicate ids:
duplicateIds = arr
.map(e => e['id'])
.map((e, i, final) => final.indexOf(e) !== i && i)
.filter(obj=> arr[obj])
.map(e => arr[e]["id"])
List of IDs will be
duplicateIds = [10]
Thus, to get duplicates objects:
duplicate = arr.filter(obj=> dublicateIds.includes(obj.id));
Now you have it:
duplicate = [
{ id:10, name: 'someName1' },
{ id:10, name: 'someName2' }
]
Thanks https://reactgo.com/removeduplicateobjects/
You haven't clarified whether two objects with different ids, but the same "name" count as a duplicate. I will assume those do not count as a duplicate; in other words, only objects with the same id will count as duplicate.
let ids = {};
let dups = [];
values.forEach((val)=> {
if (ids[val.id]) {
// we have already found this same id
dups.push(val)
} else {
ids[val.id] = true;
}
})
return dups;
With lodash you can solve this with filter and countBy for complexity of O(n):
const data = [{ id: 10,name: 'someName1' }, { id: 10,name: 'someName2' }, { id: 11,name: 'someName3' }, { id: 12,name: 'someName4' } ]
const counts = _.countBy(data, 'id')
console.log(_.filter(data, x => counts[x.id] > 1))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
You could do the same with ES6 like so:
const data = [{ id: 10,name: 'someName1' }, { id: 10,name: 'someName2' }, { id: 11,name: 'someName3' }, { id: 12,name: 'someName4' } ]
const countBy = (d, id) => d.reduce((r,{id},i,a) => (r[id] = a.filter(x => x.id == id).length, r),{})
const counts = countBy(data, 'id')
console.log(data.filter(x => [x.id] > 1))
You can use an array to store unique elements and use filter on values to only return duplicates.
const unique = []
const duplicates = values.filter(o => {
if(unique.find(i => i.id === o.id && i.name === o.name)) {
return true
}
unique.push(o)
return false;
})
With lodash you can use _.groupBy() to group elements by their id. Than _.filter() out groups that have less than two members, and _.flatten() the results:
const values = [{id: 10, name: 'someName1'}, {id: 10, name: 'someName2'}, {id: 11, name:'someName3'}, {id: 12, name: 'someName4'}];
const result = _.flow([
arr => _.groupBy(arr, 'id'), // group elements by id
g => _.filter(g, o => o.length > 1), // remove groups that have less than two members
_.flatten // flatten the results to a single array
])(values);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
An alternative based in #ggorlen solution with new Map() as accumulator (for better performance) and without unary operator ++ (not advised by default in projects with ESLint).
const values = [{ id: 10, name: "someName1" }, { id: 10, name: "someName2" }, { id: 11, name: "someName3" }, { id: 12, name: "someName4" },];
const lookup = values.reduce((a, e) => {
a.set(e.id, (a.get(e.id) ?? 0) + 1);
return a;
}, new Map());
console.log(values.filter(e => lookup.get(e.id) > 1));
Try this
function checkDuplicateInObject(propertyName, inputArray) {
var seenDuplicate = false,
testObject = {};
inputArray.map(function(item) {
var itemPropertyName = item[propertyName];
if (itemPropertyName in testObject) {
testObject[itemPropertyName].duplicate = true;
item.duplicate = true;
seenDuplicate = true;
}
else {
testObject[itemPropertyName] = item;
delete item.duplicate;
}
});
return seenDuplicate;
}
referred from : http://www.competa.com/blog/lets-find-duplicate-property-values-in-an-array-of-objects-in-javascript/

Categories

Resources