Sum values depending on other value in array - javascript

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

Related

Sum values for multiple objects in the array

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)

How to go through a complex object formatting the given required fields using a function passed by parameter?

I'm trying to create a function that goes through a complex object formatting the given fields in an array.
The function must receive the object that must be formatted, then the next parameter is an array with the attributes that must be formatted and finally the last function receives the function that will format the value of the field.
The function must return the object in it's original structure.
my code until now:
const formatFields = (obj = {}) => (fieldsToFormat = []) => (formatFunction = () => {}) => {
let newObj = { ...obj };
for (let [k, v] of Object.entries(obj)) {
if (typeof v === 'object' && v !== null) formatFields(v)(fieldsToFormat)(formatFunction);
if (fieldsToFormat.includes(k)) newObj = { ...newObj, [k]: formatFunction(v) };
else newObj = { ...newObj, [k]: v };
}
return newObj;
}
const toMoney = (num) => '$' + num;
const obj = {
totalAmount: 83.24,
quoteItems:
[ { max: '1',
code: '1',
quantity: 1,
unitPrice: 23.21,
totalPrice: 23.21,
description: 'test'
},{
max: '3',
code: '2',
quantity: 3,
unitPrice: 20.01,
totalPrice: 60.03,
description: 'test2'
} ],
};
const priceFormatAttributes = [
'unitPrice',
'totalPrice',
'totalAmount'
];
console.log(formatFields(obj)(priceFormatAttributes)(toMoney));
The nested objects are not being formatted!
I know this is a logical problem... it's being challenging and I can't move on.
I think that the recursion is wrong but I can't see why!
if anyone has an idea how to solve this in another way it is also welcome.
While I'm not sure what the expected output is I would try this:
const formatFields = (obj = {}) => (fieldsToFormat = []) => (formatFunction = () => {}) => {
let newObj = { ...obj }; // clone object to prevent changing the original object
if (Array.isArray(obj)) { // gotta deal with arrays too unless you want to change them all into objects
newObj = [ ...obj ];
}
for (let [key, value] of Object.entries(obj)) {
if (Array.isArray(newObj)) {
newObj.splice(key - 1, 1); // remove the previous it
newObj.push(formatFields(value)(fieldsToFormat)(formatFunction));
}
else if (typeof value === 'object' && value !== null) {
newObj = { ...newObj, [key]: formatFields(value)(fieldsToFormat)(formatFunction) };
}
else if (fieldsToFormat.includes(key)) {
newObj[key] = formatFunction(value)
}
else newObj = { ...newObj, [key]: value };
}
return newObj;
}
const toMoney = (num) => {
return '$' + num;
}
const obj = {
totalAmount: 83.24,
quoteItems: [
{
max: '1',
code: '1',
quantity: 1,
unitPrice: 23.21,
totalPrice: 23.21,
description: 'test',
},
{
max: '3',
code: '2',
quantity: 3,
unitPrice: 20.01,
totalPrice: 60.03,
description: 'test2',
}
],
};
const priceFormatAttributes = [
'unitPrice',
'totalPrice',
'totalAmount',
];
console.log(formatFields(obj)(priceFormatAttributes)(toMoney));
This outputs:
{
totalAmount: '$83.24',
quoteItems: [
{
max: '1',
code: '1',
quantity: 1,
unitPrice: '$23.21',
totalPrice: '$23.21',
description: 'test'
},
{
max: '3',
code: '2',
quantity: 3,
unitPrice: '$20.01',
totalPrice: '$60.03',
description: 'test2'
}
]
}

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

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/

Updating selection of array's values

I have two arrays. Each array could have a different number of objects but they each have the same properties but could have different values. For example
var Array1 = [ { id: '1', value: a },
{ id: '2', value: b } ]
var Array2 = [ { id: '', value: c },
{ id: '', value: d },
{ id: '', value: a } ]
What I want
AfterArray = [ { id: '1', value: a },
{ id: '3', value: c },
{ id: '4', value: d } ]
What's happening is that array1's object will be removed if it doesn't have array2's value. If it does have array2's value, it will keep the original id. If an object is in array2 that isn't in array1, an id will be generated (UUID).
I'm assuming it might go something like this
afterArray = []
this.Array1.forEach((res, i) => {
this.Array2.forEach((res2, 2) => {
if(res.value == res2.value){
afterArray = afterArray.concat(this.Array1[i])
}
else {
// do something if values are not present then add to array.
// if added, add id to those empty properties.
}
})
})
Thanks!
You just need a simple mapping over Array2 with a find inside it, to find the matching value in Array1 if it exists:
const array1 = [
{
id: '1',
value: 'a'
},
{
id: '2',
value: 'b'
}
];
const array2 = [
{
id: '',
value: 'c'
},
{
id: '',
value: 'd'
},
{
id: '',
value: 'a'
}
];
const generateId = (() => {
// example generator function, use your own instead
let possibleIds = ['3', '4'];
let i = -1;
return () => {
i++;
return possibleIds[i];
};
})();
const result = array2.map(({ id, value }) => {
// find a matching value in array1 to merge the id:
const foundArr1Item = array1.find(({ value: ar1Val }) => ar1Val === value);
// otherwise, generate a new ID:
if (foundArr1Item) return { value, id: foundArr1Item.id };
return { value, id: generateId() };
});
console.log(result);
If I understood it right, this should do your job:
(find the comments in the code)
Array1 = [
{
id: '1',
value: "a"
},
{
id: '2',
value: "b"
}
]
Array2 = [
{
id: '',
value: "c"
},
{
id: '',
value: "d"
},
{
id: '',
value: "a"
}
]
// keep Array1's objects if it has a value matching a value from any Array2 object
// Also remove those objects from Array2
newArray1 = Array1.reduce((acc, elem) => {
let indexOfObInArray2 = Array2.findIndex(eachArray2Elem => {
return elem.value == eachArray2Elem.value
});
if (indexOfObInArray2 > -1) {
acc.push(elem);
Array2.splice(indexOfObInArray2, 1);
}
return acc;
}, [])
// Array of ids already taken by Objects from Array2, if they are non empty
idsTakenInArray2 = Array2.reduce((acc, x) => {
if (x.id != "") {
acc.push(x.id);
}
return acc;
}, []);
// random number to give ids
randomId = 1;
Array2 = Array2.map(eachElem => {
if (eachElem.id == '') {
while (Array1.find(eachArray1Elem => {
return eachArray1Elem.id == randomId
}) || idsTakenInArray2.indexOf(randomId) !== -1) {
randomId++;
}
eachElem.id = randomId;
idsTakenInArray2.push(randomId);;
}
return eachElem;
})
console.log(newArray1.concat(Array2));
check this, here is the code online https://stackblitz.com/edit/angular-zhzuqk , check your console you will see what you want to have as result
formtarrays(array1,array2) {
let ar = array1.concat(array2);
// delete items that exist in array1 but not in array2
ar = ar.filter((elem) => {
return !(array1.findIndex(item => item.value === elem.value) !== -1 && array2.findIndex(item => item.value === elem.value) === -1)
})
// get distinct values
const idList = [];
const distinct = [];
ar.forEach((item, index) => {
if (item !== undefined) {
idList['id'] = item.value;
if (idList.indexOf(item.value) < 0) {
if(item.id === '') {
item.id = (index + array1.length).toString();
}
distinct.push(item);
idList.push(item.value);
}
}
})
console.log(distinct);
return distinct;
}

Categories

Resources