Sort grouped objects by Lodash - javascript

I have following array of objects:
[{
id: 1,
amount: 2000,
date: "2018-01-31T00:00:00.000Z"
},{
id: 2,
amount: 3000,
date: "2017-07-31T00:00:00.000Z"
},{
id: 3,
amount: 6000,
date: "2018-01-31T00:00:00.000Z"
},{
id: 4,
amount: 7000,
date: "2017-01-31T00:00:00.000Z"
},{
id: 5,
amount: 5000,
date: "2017-03-31T00:00:00.000Z"
},{
id: 6,
amount: 3000,
date: "2018-02-22T00:00:00.000Z"
},{
id: 7,
amount: 4500,
date: "2017-01-31T00:00:00.000Z"
}]
I am calling following command to group objects by year and date:
_(data)
.groupBy(o => new Date(o.date).getFullYear() + '-' + new Date(o.date).getMonth())
.map(o1 => _(o1).map(o2 => o2.amount).sum())
Code above give me array of sums like [xx, yy, aaa, bbb, ...]
Now I need ensure that these values in array will be ordered (sum of 2018-2 will be first, and sum of 2017-1 will be on the end).
Also will be nice when result will be contains array of sorted objects as I describe above, where each object will be contains also period key "year-month" to detect what current value is. Expected output will be something like this:
[
{period: "2018-2", amount:3000}, // sum of 2018-2
{period: "2018-1", amount: 8000 }, // sum of 2018-1
{period: "2017-7", amount: 3000} // sum of 2017-7
{period: "2017-3", amount: 5000} // sum of 2017-3
{period: "2017-1" amount: 11500} // sum of 2017-1
]
Is possible edit this chain to get required result please? Thanks!

You can use the function reduce along with the function Object.values
Just vanilla javascript
var data = [{ id: 1, amount: 2000, date: "2018-01-31T00:00:00.000Z"},{ id: 2, amount: 3000, date: "2017-07-31T00:00:00.000Z"},{ id: 3, amount: 6000, date: "2018-01-31T00:00:00.000Z"},{ id: 4, amount: 7000, date: "2017-01-31T00:00:00.000Z"},{ id: 5, amount: 5000, date: "2017-03-31T00:00:00.000Z"},{ id: 6, amount: 3000, date: "2018-02-22T00:00:00.000Z"},{ id: 7, amount: 4500, date: "2017-01-31T00:00:00.000Z"}];
var result = Object.values(data.reduce((a, c) => {
var [year, month] = c.date.split('-');
var key = `${year}-${+month}`;
(a[key] || (a[key] = {period: key, amount: 0})).amount += c.amount;
return a;
}, {})).sort((a, b) => b.period.localeCompare(a.period));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You could get the grouped data and sort it ascending (the only possible way with lodash) and reverse the order.
var data = [{ id: 1, amount: 2000, date: "2018-01-31T00:00:00.000Z" }, { id: 2, amount: 3000, date: "2017-07-31T00:00:00.000Z" },{ id: 3, amount: 6000, date: "2018-01-31T00:00:00.000Z" }, { id: 4, amount: 7000, date: "2017-01-31T00:00:00.000Z" }, { id: 5, amount: 5000, date: "2017-03-31T00:00:00.000Z" }, { id: 6, amount: 3000, date: "2018-02-22T00:00:00.000Z" }, { id: 7, amount: 4500, date: "2017-01-31T00:00:00.000Z" }],
result = _(data)
.groupBy(o => o.date.slice(0, 7))
.map((array, sort) => ({ sort, date: sort.split('-').map(Number).join('-'), amount: _.sumBy(array, 'amount') }))
.sortBy('sort')
.reverse()
.map(({ date, amount }) => ({ date, amount }))
.value();
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.15.0/lodash.min.js"></script>

With lodash you could first groupBy, then sort entries and then map those entries to get objects with period and sum of amount for each period.
const data = [{"id":1,"amount":2000,"date":"2018-01-31T00:00:00.000Z"},{"id":2,"amount":3000,"date":"2017-07-31T00:00:00.000Z"},{"id":3,"amount":6000,"date":"2018-01-31T00:00:00.000Z"},{"id":4,"amount":7000,"date":"2017-01-31T00:00:00.000Z"},{"id":5,"amount":5000,"date":"2017-03-31T00:00:00.000Z"},{"id":6,"amount":3000,"date":"2018-02-22T00:00:00.000Z"},{"id":7,"amount":4500,"date":"2017-01-31T00:00:00.000Z"}]
const result = _.chain(data)
.groupBy(({date}) => {
const d = new Date(date)
return d.getFullYear() + '-' + (d.getMonth() + 1)
})
.entries()
.sort(([a], [b]) => b.localeCompare(a))
.map(([period, arr]) => ({period, amount: _.sumBy(arr, ({amount}) => amount)}))
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.js"></script>

Related

How can I see if Object Array has elements in Another Object Array?

Is there a way to tell if an object array has any common elements to another object array, and what that object intersect is? (like a Contains function). In the example below,ProductId3 in Object Array 1, is also contained in Object Array 2.
I'm thinking of using a double for loop . However is there a more efficient/optimal way, or shorthand ecma or lodash function?
We are checking all object members, not just ProductId.
array1.forEach(arr1 => {
array2.forEach(arr2 => {
if (arr1.productId === arr2.productId &&
arr1.productName === arr2.productName ...
Object Array 1:
[
{
ProductId: 50,
ProductName: 'Test1',
Location: 77,
Supplier: 11,
Quantity: 33
},
{
ProductId: 3,
ProductName: 'GHI',
Location: 1,
Supplier: 4,
Quantity: 25
}
]
Object Array 2:
[
{
ProductId: 1,
ProductName: 'ABC',
Location: 3,
Supplier: 4,
Quantity: 52
},
{
ProductId: 2,
ProductName: 'DEF',
Location: 1,
Supplier: 2,
Quantity: 87
},
{
ProductId: 3,
ProductName: 'GHI',
Location: 1,
Supplier: 4,
Quantity: 25
},
{
ProductId: 4,
ProductName: 'XYZ',
Location: 5,
Supplier: 6,
Quantity: 17
}
]
Resources:
How to determine if Javascript array contains an object with an attribute that equals a given value?
Javascript: Using `.includes` to find if an array of objects contains a specific object
Is there a way to tell if an object array has any common elements to another object array ? - Yes you can achieve this with the help of Array.some() method. It returns true if, in the array, it finds an element for which the provided function returns true; otherwise it returns false.
const array1 = [{
ProductId: 50,
ProductName: 'Test1',
Location: 77,
Supplier: 11,
Quantity: 33
}, {
ProductId: 3,
ProductName: 'GHI',
Location: 1,
Supplier: 4,
Quantity: 25
}];
const array2 = [{
ProductId: 1,
ProductName: 'ABC',
Location: 3,
Supplier: 4,
Quantity: 52
}, {
ProductId: 2,
ProductName: 'DEF',
Location: 1,
Supplier: 2,
Quantity: 87
}, {
ProductId: 3,
ProductName: 'GHI',
Location: 1,
Supplier: 4,
Quantity: 25
}, {
ProductId: 4,
ProductName: 'XYZ',
Location: 5,
Supplier: 6,
Quantity: 17
}];
const isCommonProducts = array2.some(({ ProductId }) => array1.map(obj => obj.ProductId).includes(ProductId));
console.log(isCommonProducts);
Update : As per the author comment, we have to check all the properties of an object. Hence, we can achieve that by comparing the JSON string by converting the object into a string.
Live Demo :
const array1 = [{
ProductId: 50,
ProductName: 'Test1',
Location: 77,
Supplier: 11,
Quantity: 33
}, {
ProductId: 3,
ProductName: 'GHI',
Location: 1,
Supplier: 4,
Quantity: 25
}];
const array2 = [{
ProductId: 1,
ProductName: 'ABC',
Location: 3,
Supplier: 4,
Quantity: 52
}, {
ProductId: 2,
ProductName: 'DEF',
Location: 1,
Supplier: 2,
Quantity: 87
}, {
ProductId: 3,
ProductName: 'GHI',
Location: 1,
Supplier: 4,
Quantity: 25
}, {
ProductId: 4,
ProductName: 'XYZ',
Location: 5,
Supplier: 6,
Quantity: 17
}];
const getFilteredProducts = array2.filter(productObj => JSON.stringify(array1).indexOf(JSON.stringify(productObj)) !== -1);
console.log(getFilteredProducts);
If we can assume that each array's elements (we will call them sub-dictionaries) contain exactly the same keys in the same order, then this is my idea:
Convert each array into a new array whose elements are the JSON representations of the original sub-dictionaries values. This is an o(N) operation performed twice.
Of the new, converted arrays find the shortest one. Convert the other into a set. This is also o(N).
For each element of the shorter converted array, check to see if the set contains this value. This is also o(N).
let arr1 = [
{
ProductId: 50,
ProductName: 'Test1',
Location: 77,
Supplier: 11,
Quantity: 33
},
{
ProductId: 3,
ProductName: 'GHI',
Location: 1,
Supplier: 4,
Quantity: 25
}
];
let arr2 = [
{
ProductId: 1,
ProductName: 'ABC',
Location: 3,
Supplier: 4,
Quantity: 52
},
{
ProductId: 2,
ProductName: 'DEF',
Location: 1,
Supplier: 2,
Quantity: 87
},
{
ProductId: 3,
ProductName: 'GHI',
Location: 1,
Supplier: 4,
Quantity: 25
},
{
ProductId: 4,
ProductName: 'XYZ',
Location: 5,
Supplier: 6,
Quantity: 17
}
];
// Convert each sub-array's values to JSON string:
let arr1New = arr1.map(function(arr) {return JSON.stringify(Object.values(arr));});
let arr2New = arr2.map(function(arr) {return JSON.stringify(Object.values(arr));});
// Find shortest array of JSON strings:
const l1 = arr1New.length;
const l2 = arr2New.length;
// enumerate shortest list
let list, set, l, arr;
if (l1 <= l2) {
list = arr1New;
set = new Set(arr2New);
l = l1;
arr = arr1;
}
else {
list = arr2New;
set = new Set(arr1New);
l = l2;
arr = arr2;
}
for(let i = 0; i < l; i++) {
if (set.has(list[i])) {
console.log(arr[i]);
}
}
Update
If the sub-dictionary keys are not necessarily in order, then we have to create new sub-dictionaries from these where the keys are in order:
// Create function to create new dictionaries sorted by keys
function sort_dict(d) {
items = Object.keys(d).map(function(key) {
return [key, d[key]];
});
items.sort(function(first, second) {
return first[0] < second[0] ? -1 : (first[0] > second[0] ? 1 : 0);
});
sorted_dict = {};
items.forEach(function(x) {
sorted_dict[x[0]] = x[1];
});
return(sorted_dict);
}
// And then we have these modified lines:
// Convert each sub-array's values to JSON string:
let arr1New = arr1.map(function(arr) {return JSON.stringify(Object.values(sort_dict(arr)));});
let arr2New = arr2.map(function(arr) {return JSON.stringify(Object.values(sort_dict(arr)));});
Modified Code
let arr1 = [
{
ProductId: 50,
ProductName: 'Test1',
Location: 77,
Supplier: 11,
Quantity: 33
},
{
ProductName: 'GHI',
Location: 1,
Supplier: 4,
Quantity: 25,
ProductId: 3 // Not in the same order as the others
}
];
let arr2 = [
{
ProductId: 1,
ProductName: 'ABC',
Location: 3,
Supplier: 4,
Quantity: 52
},
{
ProductId: 2,
ProductName: 'DEF',
Location: 1,
Supplier: 2,
Quantity: 87
},
{
ProductId: 3,
ProductName: 'GHI',
Location: 1,
Supplier: 4,
Quantity: 25
},
{
ProductId: 4,
ProductName: 'XYZ',
Location: 5,
Supplier: 6,
Quantity: 17
}
];
function sort_dict(d) {
items = Object.keys(d).map(function(key) {
return [key, d[key]];
});
items.sort(function(first, second) {
return first[0] < second[0] ? -1 : (first[0] > second[0] ? 1 : 0);
});
sorted_dict = {};
items.forEach(function(x) {
sorted_dict[x[0]] = x[1];
});
return(sorted_dict);
}
// Convert each sub-array's values to JSON string:
let arr1New = arr1.map(function(arr) {return JSON.stringify(Object.values(sort_dict(arr)));});
let arr2New = arr2.map(function(arr) {return JSON.stringify(Object.values(sort_dict(arr)));});
// Find shortest array of JSON strings:
const l1 = arr1New.length;
const l2 = arr2New.length;
// enumerate shortest list
let list, set, l, arr;
if (l1 <= l2) {
list = arr1New;
set = new Set(arr2New);
l = l1;
arr = arr1;
}
else {
list = arr2New;
set = new Set(arr1New);
l = l2;
arr = arr2;
}
for(let i = 0; i < l; i++) {
if (set.has(list[i])) {
console.log(arr[i]);
}
}
For a simple yet reasonably fast solution, you can (1) use a Set of productIds from the first array, then (2) filter the second array based on the ids from the first one, this you only have to go over each array once O(n).
let arr1 = [
{
ProductId: 50,
ProductName: "Test1",
Location: 77,
Supplier: 11,
Quantity: 33,
},
{
ProductId: 3,
ProductName: "GHI",
Location: 1,
Supplier: 4,
Quantity: 25,
},
];
let arr2 = [
{
ProductId: 1,
ProductName: "ABC",
Location: 3,
Supplier: 4,
Quantity: 52,
},
{
ProductId: 2,
ProductName: "DEF",
Location: 1,
Supplier: 2,
Quantity: 87,
},
{
ProductId: 3,
ProductName: "GHI",
Location: 1,
Supplier: 4,
Quantity: 25,
},
{
ProductId: 4,
ProductName: "XYZ",
Location: 5,
Supplier: 6,
Quantity: 17,
},
];
const getCommonItems = (arr1, arr2) => {
let firstIdSet = new Set(arr1.map((product) => product.ProductId)); //1
return arr2.filter((product) => firstIdSet.has(product.ProductId)); //2
};
console.log(getCommonItems(arr1, arr2));
If you want a deep equality comparison(for nested objects or for all (key, value) pairs), I would suggest a slightly better approach which is using the base64 encoding/decoding to improve on comparison performance.
So my approach is to:
merge the arrays and convert the object to base64 strings.
Group the recurrences together
Filter on duplicates
revert the base64 strings into their original object.
const convertObjToBase64 = o => btoa(JSON.stringify(o));
const convertBase64ToObj = str => JSON.parse(atob(str));
const arrayToObjCount = arr => arr.reduce((res, v) => {
res[v] = (res[v] ?? 0) + 1;
return res;
}, {});
const findDuplicateObjectsInMultipleArrays = (...arrays) => {
const base64Array = Array.from(arrays.flat(), convertObjToBase64);
const objCount = arrayToObjCount(base64Array);
const duplicates = Object.entries(objCount).reduce((prev, [k, v]) => {
if (v > 1) {
prev.push(convertBase64ToObj(k));
}
return prev;
}, []);
return duplicates;
}
let arr1 = [{
ProductId: 50,
ProductName: 'Test1',
Location: {
LocationId: 77,
LocationName: 'Location 77'
},
Supplier: 11,
Quantity: 33
},
{
ProductId: 3,
ProductName: 'GHI',
Location: {
LocationId: 1,
LocationName: 'Location 1'
},
Supplier: 4,
Quantity: 25
}
];
let arr2 = [{
ProductId: 1,
ProductName: 'ABC',
Location: {
LocationId: 3,
LocationName: 'Location 3'
},
Supplier: 4,
Quantity: 52
},
{
ProductId: 2,
ProductName: 'DEF',
Location: {
LocationId: 1,
LocationName: 'Location 1'
},
Supplier: 2,
Quantity: 87
},
{
ProductId: 3,
ProductName: 'GHI',
Location: {
LocationId: 1,
LocationName: 'Location 1'
},
Supplier: 4,
Quantity: 25
},
{
ProductId: 4,
ProductName: 'XYZ',
Location: {
LocationId: 5,
LocationName: 'Location 5'
},
Supplier: 6,
Quantity: 17
}
];
let arr3 =[
{
ProductId: 2,
ProductName: 'DEF',
Location: {
LocationId: 1,
LocationName: 'Location 1'
},
Supplier: 2,
Quantity: 87
},
{
ProductId: 3,
ProductName: 'GHI',
Location: {
LocationId: 2,
LocationName: 'Location 2'
},
Supplier: 4,
Quantity: 25
},
{
ProductId: 4,
ProductName: 'XYZ',
Location: {
LocationId: 6,
LocationName: 'Location 5'
},
Supplier: 6,
Quantity: 17
}
];
console.log(findDuplicateObjectsInMultipleArrays(arr1, arr2, arr3));
I will post two solutions:
First Solution is readable one
Code is not 100% performance optimized, but it is readable and elegant.
Playground link with working code
First, we need a method that compares two objects of any type. The method compares the first-level properties, so if we have nested object properties, it will compare them by reference.
const areTheSame = (a: any, b: any) => {
const objAProps = Object.keys(a).filter(key => typeof a[key] !== "function")
const objBProps = Object.keys(b).filter(key => typeof b[key] !== "function")
if (objAProps.length !== objBProps.length) {
return false;
}
return objAProps.every((propName) => a[propName] === b[propName]);
}
And then we can implement readable intersect method which will work for any array types:
const getIntersection = (array1: Array<any>, array2: Array<any>) => {
return array1.filter(a1Item => array2.some(a2Item => areTheSame(a1Item, a2Item)));
}
The Second solution is performance-oriented, its drawback is that it is not so readable
First, we calculate the has for all objects, then within a single forEach loop we can identify the intersection based on that Hash. I have used md5, but any hash algorithm or library can be used.
Hers is stack blitz link playground. It can be run, ignore the import error.
const getArrayIntersection = (
firstArray: Array<any>,
secondArray: Array<any>
) => {
const array1Hashed = firstArray.map((i) => md5(JSON.stringify(i)));
const array2Set = new Set(secondArray.map((i) => md5(JSON.stringify(i))));
const result: Array<any> = [];
array1Hashed.forEach((itemHash, index) => {
if (array2Set.has(itemHash)) {
result.push(firstArray[index]);
}
});
return result;
};
Just to piggyback #Rohìt Jíndal, you can check if an array has a specific object like so:
const resultObj = arr1.filter(obj => obj.id=== "whatever" && obj.productname == "whatever") // ETC ETC

How to aggregate nested values in array of objects

I have an existing function that creates a summary object from two arrays (one array is transaction data, the other is master data).
Currently, the returned object contains a sum of the transaction quantity by year and month - example below.
I'm looking for help to add a sum of sums at the root level for each item - example also below.
I've reviewed answers for similar questions but wasn't able to find a solution close enough to my use case.
Here's the existing code:
const
array1 = [{ material: "ABC123", cost: 100 },
{ material: "DEF456", cost: 150 }],
array2 = [{ material: "ABC123", date: "1/1/20", quantity: 4 },
{ material: "ABC123", date: "1/15/20", quantity: 1 },
{ material: "ABC123", date: "2/15/20", quantity: 3 },
{ material: "ABC123", date: "4/15/21", quantity: 1 },
{ material: "DEF456", date: "3/05/20", quantity: 6 },
{ material: "DEF456", date: "3/18/20", quantity: 1 },
{ material: "DEF456", date: "5/15/21", quantity: 2 }],
groups = [
({ material }) => material,
_ => 'byYear',
({ date }) => '20' + date.split('/')[2],
_ => 'byMonth'
],
sum = {
key: ({ date }) => date.split('/')[0],
value: ({ quantity }) => quantity
},
result = array2.reduce(
(r, o) => {
const temp = groups.reduce((t, fn) => t[fn(o)] ??= {}, r);
temp[sum.key(o)] ??= 0;
temp[sum.key(o)] += sum.value(o);
return r;
},
Object.fromEntries(array1.map(({ material, cost }) => [material, { cost }]))
);
console.log(result);
As mentioned above, I would like it to add a "totalSum" key/value at the root level. The result would look like:
{
"ABC123": {
"totalSum": 8,
"cost": 100,
"byYear": {
"2020": {
"byMonth": {
"1": {
"sum": 5,
"max": 4
},
"2": {
"sum": 3,
"max": 3
}
}
}
}
}
}
Any help is greatly appreciated!
You can actually achieve the entire refactoring with a single Array#reduce() call, combined with a Map of the master array to retrieve cost.
Adding a total sum is then trivial since you have the all the properties to hand and don't have to remap your aggregated data.
const
array1 = [{ material: "ABC123", cost: 100, critical: true }, { material: "DEF456", cost: 150, critical: false }],
array2 = [{ material: "ABC123", date: "1/1/20", quantity: 4 }, { material: "ABC123", date: "1/15/20", quantity: 1 }, { material: "ABC123", date: "2/15/20", quantity: 3 }, { material: "ABC123", date: "4/15/21", quantity: 1 }, { material: "DEF456", date: "3/05/20", quantity: 6 }, { material: "DEF456", date: "3/18/20", quantity: 1 }, { material: "DEF456", date: "5/15/21", quantity: 2 }],
master = new Map(array1.map(({ material, ...item }) => [material, item])),
result = array2.reduce((acc, { material, date, quantity }) => {
let [month, , year] = date.split('/');
year = '20' + year;
const
_material = (acc[material] ??= { ...master.get(material), totalSum: 0, byYear: {} }),
_byYear = (_material.byYear[year] ??= { byMonth: {} }),
_byMonth = (_byYear.byMonth[month] ??= { sum: 0, max: 0 });
_material.totalSum += quantity;
_byMonth.sum += quantity;
_byMonth.max = Math.max(_byMonth.max, quantity);
return acc;
}, {});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Edit
In a comment you mentioned that you might have more properties in array1 that should be included in the final result. I've edited to accommodate this by changing the Map to reference the entire object as found in array1. We can then include all the properties available using spread syntax when we first declare the relevant object in the accumulator.
// split out 'material' from the rest of the object in the Map
const master = new Map(array1.map(({ material, ...item }) => [material, item]));
// later access the object by material and spread its props into the accumulator object.
_material = (acc[material] ??= { ...master.get(material), totalSum: 0, byYear: {} }),

Taking an array of transactions and summing values to respective dates. Conditionally

So I have an array of transactions that looks like this:
[
{
date: '22-03-2021,
amount: 2,
value: 10,
fees: 0.1,
type: "BUY"
transactionId: '21412412',
}
]
And what I want to do with the array is two things...
One is get an array of objects per date. Summing the value property of objects that are of type 'BUY' and deducting the value property from objects of type 'SELL'. Eventually I will need to deal with other transaction types, but not now...
Example: (you can see the value rise and fall, due to SELL and BUY both taking place)
[
{
date: "04-06-2021",
value: 20.0,
},
{
date: "05-06-2021",
value: 28.99,
},
{
date: "06-06-2021",
value: 47.99,
},
{
date: "07-06-2021",
value: 37.99,
},
{
date: "08-06-2021",
value: 42.29,
},
]
Two is pretty similar. I want to generate an array of objects per date but associated to a particular ticker.
Example: (value rises due to only BUY taking place)
[
{
TSLA: [
{
date: "04-06-2021",
value: 20.0,
},
{
date: "05-06-2021",
value: 23.99,
},
{
date: "06-06-2021",
value: 42.99,
},
{
date: "07-06-2021",
value: 47.29,
},
]
}
]
Here is what ive come up with so far:
const valueByDate = useMemo(
() =>
holdings
.map((data: any) => data)
.reduce((r: any, a: any) => {
// Find objects where ticker matches.
r[a.date] = r[a.date] || [];
// Push found object into array.
r[a.date].push({ type: a.type, value: a.value });
return r;
}, Object.create(null)),
[holdings],
);
// valueByDate creates an object like so...
let arr = {
'22-05-2021': [{ type: 'SELL', value: '9' }],
'22-06-2021': [{ type: 'SELL', value: '9' }],
'22-07-2021': [{ type: 'SELL', value: '9' }],
};
// Sum or Deduct VALUE per date depending on TYPE
const reduce = Object.values(valueByDate).forEach((element: any) =>
Object.values(element)
.flat()
.reduce((accumulator: any, currentValue: any) => {
if (currentValue.type == 'BUY') {
return (
parseFloat(accumulator) + parseFloat(currentValue.value)
);
} else if (currentValue.type == 'SELL') {
return (
parseFloat(accumulator) - parseFloat(currentValue.value)
);
}
}),
);
But I just have not been able to wrap my head around getting into the nested arrays and conditionally computing those values. Any insight would be a help.
Thank you
Those are the functions that I created:
type Transaction = {
date: string,
amount: number,
value: number,
fees: number,
type: "BUY" | "SELL",
transactionId: string
}
const transactions: Transaction[] = [
{
date: '22-03-2021',
amount: 2,
value: 10,
fees: 0.1,
type: "BUY",
transactionId: '21412412',
},
{
date: '22-03-2021',
amount: 2,
value: 10,
fees: 0.1,
type: "SELL",
transactionId: '21412412',
},
{
date: '22-03-2021',
amount: 2,
value: 10,
fees: 0.1,
type: "BUY",
transactionId: '21412412',
},
{
date: '23-03-2021',
amount: 2,
value: 10,
fees: 0.1,
type: "BUY",
transactionId: '21412412',
},
{
date: '23-03-2021',
amount: 2,
value: 10,
fees: 0.1,
type: "BUY",
transactionId: '21412412',
},
{
date: '23-03-2021',
amount: 2,
value: 10,
fees: 0.1,
type: "SELL",
transactionId: '21412412',
},
{
date: '23-03-2021',
amount: 2,
value: 10,
fees: 0.1,
type: "BUY",
transactionId: '21412412',
},
{
date: '23-03-2021',
amount: 2,
value: 10,
fees: 0.1,
type: "BUY",
transactionId: '21412412',
},
]
type DatedTransactions = { [key: string]: Transaction[] }
function getValuesByDate(transactions: Transaction[]): DatedTransactions {
let result: { [key: string]: Transaction[] } = {}
transactions.forEach(transaction => {
result[transaction.date] ? result[transaction.date].push(transaction) : result[transaction.date] = [transaction]
})
return result
}
function getTotalValue(transactions: Transaction[]): number {
return transactions.reduce((prev, curr) => {
if (curr.type === "BUY") {
return prev + curr.value
}
return prev - curr.value
}, 0)
}
function getTotalValueForEveryDate(datedTransactions: DatedTransactions): { date: string, value: number }[] {
const result: { date: string, value: number }[] = []
Object.keys(datedTransactions).forEach(date => {
const datedValue = { date, value: getTotalValue(datedTransactions[date]) }
result.push(datedValue)
})
return result
}
console.log(getTotalValueForEveryDate(getValuesByDate(transactions)))
in order to accomplish also the last part just iterate for every ticker :)
let me know if there is something not clear

ES6 Nested Grouping with Sum

I have an array like this:
[{
id: 1,
amount: 10,
date: '21/01/2017'
},{
id: 1,
amount: 10,
date: '21/01/2017'
},{
id: 1,
amount: 30,
date: '22/01/2017'
},{
id: 2,
amount: 10,
date: '21/01/2017'
},]
And I would like this to output like:
{
'21/01/2017': {
1: {
amount: 20
},
2: {
amount: 10
}
},
'22/01/2017': {
1: {
amount: 30
}
}
}
Essentially grouping by data, nested grouping by id, whilst summing the relevant amounts.
So far I have tried using reduce, and have looked at lodash functions but have been unsuccessful. Reduce makes it easy to group by and sum by one prop, but I cannot find an elegant way to create the second level. I have created the a sandbox to demonstrate:
https://codesandbox.io/s/woj9xz6nq5
const dataToConsolidate = [{
id: 1,
amount: 10,
date: '21/01/2017'
}, {
id: 1,
amount: 10,
date: '21/01/2017'
}, {
id: 1,
amount: 30,
date: '22/01/2017'
}, {
id: 2,
amount: 10,
date: '21/01/2017'
},]
export const consolidate = (data) => {
const result = data.reduce(function (res, obj) {
(!(obj.date in res)) ?
res.__array.push(res[obj.date] = obj) :
res[obj.date].amount += obj.amount;
return res;
}, { __array: [] });
return result;
};
console.log(consolidate(dataToConsolidate))
You could take the object as hash table and add the properties with a default style.
var array = [{ id: 1, amount: 10, date: '21/01/2017' }, { id: 1, amount: 10, date: '21/01/2017' }, { id: 1, amount: 30, date: '22/01/2017' }, { id: 2, amount: 10, date: '21/01/2017' }],
result = {};
array.forEach(function (o) {
result[o.date] = result[o.date] || {};
result[o.date][o.id] = result[o.date][o.id] || { amount: 0 };
result[o.date][o.id].amount += o.amount;
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's a relatively ES6-y solution using reduce:
const initial = [{ id: 1, amount: 10, date: '21/01/2017' }, { id: 1, amount: 10, date: '21/01/2017' }, { id: 1, amount: 30, date: '22/01/2017' }, { id: 2, amount: 10, date: '21/01/2017' }];
const parse = obj => obj.reduce((final, { date, id, amount }) => {
let group = final[date] = final[date] || {};
group[id] = group[id] || { amount: 0 };
group[id].amount += amount;
return final;
}, {});
const final = parse(initial);
console.log(final);
You'll want to add appropriate logic to handle missing/erroneous date, id, or amount keys.
As a fan of the library Ramda (disclaimer: I'm one of the authors), I might well use it to make everything a bit more explicit:
const {pipe, groupBy, prop, map, pluck, sum} = R
const data = [
{"amount": 10, "date": "21/01/2017", "id": 1},
{"amount": 10, "date": "21/01/2017", "id": 1},
{"amount": 30, "date": "22/01/2017", "id": 1},
{"amount": 10, "date": "21/01/2017", "id": 2}
]
const consolidate = pipe(
groupBy(prop('date')),
map(groupBy(prop('id'))),
map(map(pluck('amount'))),
map(map(sum))
)
console.log(consolidate(data))
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
Of course using a library just to solve this problem is simply overkill, when something like the solution from #NinaSholz does just fine. But if you find several places where it could help, it might be worth looking into something like that.
you can do it thru _.groupBy:
const res = _.chain(data)
.groupBy('date')
.mapValues(dateObj => _.chain(dateObj)
.groupBy('id')
.mapValues(idObj => ({ amount: _.sumBy(idObj, 'amount') }))
.value()
)
.value();

How do i combine data values in array to create a new array

var players = [{id: 17, amount: 126},
{id: 17, amount: 45},
{id: 12, amount: 44},
{id: 12, amount: 23}];
How would i turn the above array into a new array adding the amount for each id.
var newArray=[{id:17.amount:171},{id:12,amount:67}];
You could group the same id with a hash table and add the amount, where necessary.
var players = [{ id: 17, amount: 126 }, { id: 17, amount: 45 }, { id: 12, amount: 44 }, { id: 12, amount: 23 }],
grouped = players.reduce(function (hash) {
return function (r, a) {
(hash[a.id] = hash[a.id] || r[r.push({ id: a.id, amount: 0 }) - 1]).amount += a.amount;
return r;
};
}(Object.create(null)), []);
console.log(grouped);
Basically this line
(hash[a.id] = hash[a.id] || r[r.push({ id: a.id, amount: 0 }) - 1]).amount += a.amount;
checks if hash[a.id] exists and if not, then this part
r[r.push({ id: a.id, amount: 0 }) - 1]
is perfomed, which contains of two parts, a part to access the result set and inside it pushes a new objetc set to the result set. While push returns the new length of the array, it need to decrease the index to get the last inserted element.
r[ - 1]
r.push({ id: a.id, amount: 0 })
After that, take the property amount and increase by the amount of the actual element.
You could also change the output format a little, to make it easier to search:
var players = [{ id: 17, amount: 126 }, { id: 17, amount: 45 }, { id: 12, amount: 44 }, { id: 12, amount: 23 }],
result = {};
for(var i = 0; i < players.length; i++){
result[players[i].id] =
(result[players[i].id] || 0) + // The existing amount, or 0 for a new `id`
players[i].amount; // Add the current amount
}
console.log(result);
console.log('Amount for 17: ' + result[17]);
This way, you don't have to filter the result on it's id value, to get the amount.
You could push element in a a new array if id is new or just add amount to the previous if it already exists :
var players = [{ id: 17, amount: 126 }, { id: 12, amount: 103 }, { id: 17, amount: 45 }, { id: 12, amount: 44 }, { id: 12, amount: 23 }];
var results = [];
results.push(players.sort((a,b) => a.id-b.id)[0]);
var shift = 0;
players.forEach((item, index, arr) => {
if (index < arr.length - 1){
if (item.id === arr[index+1].id) {
results[index + shift].amount += arr[index+1].amount;
shift--;
}
else results.push(players[index+1]);
}
});
console.log(results); // [ { id: 12, amount: 170 }, { id: 17, amount: 171 } ]

Categories