Sort nested array of object in javascript - javascript

let arr = [{
name: 'Apple',
trades: [{
date: '2017.01.01',
volume: 100
}, {
date: '1995.02.01',
volume: 150
}, {
date: '2008.01.01',
volume: 250
}]
}]
Hello, I googled many documents for sorting nested object in JavaScript, but I couldn't find the way of my case and I struggled so many hours so I want to ask to how can I sort above array of objects.
What I expected result is sort array of object by trades.date like this
sortedArray = [{
name: 'Apple',
trades: [{
date: '2017.01.01',
volume: 100
}, {
date: '2008.01.01',
volume: 250
}, {
date: '1995.02.01',
volume: 150
}]
}]
How can I do this?

arr[0].trades.sort(function(a, b) {
return (new Date(b.date) - new Date(a.date));
});
You can use the array's sort method for achieving this. If you want to sort in the reverse order then just swap a and b in the return code.

Read about array.sort() and datetime in Javascript.
let arr = [{
name: 'Apple',
trades: [{
date: '2017.01.01',
volume: 100
}, {
date: '1995.02.01',
volume: 150
}, {
date: '2008.01.01',
volume: 250
}]
}]
console.log(arr[0].trades.sort((tradeA, tradeB)=>{
return (new Date(tradeA.date) - new Date(tradeB.date)) * (-1)
// or return (new Date(tradeB.date) - new Date(tradeA.date))
}))

First in your array, date needs to be a string. You can than use arrays.sort with a function which returns the result
let arr = [
{
name : 'Apple',
trades : [
{date : "2017.01.01",
volume : 100
},
{date : "1995.02.01",
volume : 150
},
{date : "2008.01.01",
volume : 250
}
]
}
]
function compare(a,b) {
var dateA = new Date(a.date);
var dateB = new Date(b.date);
if (dateA > dateB)
return -1;
if (dateA < dateB)
return 1;
return 0;
}
arr[0].trades.sort(compare);
console.log(arr);

Ensure your date format, dot is not an iso delimiter.
let toArr = (aDate) => aDate.split('.')
let toDate = ([year, month, day]) => new Date(year, month - 1, day)
let compareTrades = (a, b) => toDate(toArr(a.date)) - toDate(toArr(b.date))
let arr = [{
name: 'Apple',
trades: [{
date: '2017.01.01',
volume: 100
}, {
date: '1995.02.01',
volume: 150
}, {
date: '2008.01.01',
volume: 250
}]
}]
arr[0].trades.sort(compareTrades)
console.log(arr[0])

Related

The sum of keys in an array of objects for each day

I am having a problem: I am trying to calculate the sum of each key for each day. However, I cannot get the desired result. Perhaps you can help fix the error?
This is my way of solving the problem:
const result = input.reduce((acc, curr) => {
const node = acc.find((i) => i.date === curr.date);
if (node) {
return [...acc.filter((i) => i.date !== curr.date), { ...node, date: curr.date, [curr.source]: [curr.source].length + 1 }];
}
return [...acc, { date: curr.date, [curr.source]: [curr.source].length }];
}, []);
const input = [
{
date: "01-01-2021",
source: "TT",
},
{
date: "01-01-2021",
source: "TT",
},
{
date: "02-01-2021",
source: "FB",
},
{
date: "02-01-2021",
source: "TT",
},
];
**Expected result: **
const output = [
{
date: "01-01-2021",
TT: 2,
},
{
date: "02-01-2021",
TT: 1,
FB: 1,
},
];
And this is what I get now:
const result = [
{
date: "01-01-2021",
TT: 2,
},
{
date: "02-01-2021",
TT: 2,
FB: 1,
},
];
The issue with your code is the following part in your if-statement:
[curr.source].length + 1
Here you're creating an array with curr.source (1 element), grabbing its length which will always be 1, and then adding 1 to it, giving you 2. Instead, you can change this line to be:
(node[curr.source] ?? 0) + 1
This will grab the source's value from your found node, and it will add one to the current value. If the value hasn't been set on the node, it will default to 0 by using ?? 0 (here ?? is the Nullish coalescing operator - it could be replaced with || if you need better browser support). I would also suggest defaulting your node value to an empty object if it isn't found when using .find() by using ?? {}, which will allow you to remove your inner if-statement and run that portion of code each time:
const input = [ { date: "01-01-2021", source: "TT", }, { date: "01-01-2021", source: "TT", }, { date: "02-01-2021", source: "FB", }, { date: "02-01-2021", source: "TT", }, ];
const result = input.reduce((acc, curr) => {
const node = acc.find((i) => i.date === curr.date) ?? {};
return [...acc.filter((i) => i.date !== curr.date), { ...node, date: curr.date, [curr.source]: (node[curr.source] ?? 0) + 1 }];
}, []);
console.log(result);
Another option that would be more efficient is to build a Map/object that is keyed by your date key from your objects. For each iteration of your reduce method you can get the current accumulated object at your current date from the Map, and update the source count based on the current source. You can then transform the Map into an array of your accumulated objects by using Array.from() on that Map's .values() iterator:
const input = [ { date: "01-01-2021", source: "TT", }, { date: "01-01-2021", source: "TT", }, { date: "02-01-2021", source: "FB", }, { date: "02-01-2021", source: "TT", }, ];
const res = Array.from(input.reduce((acc, {date, source}) => {
const seen = acc.get(date) ?? {};
return acc.set(date, {...seen, date, [source]: (seen[source] ?? 0) + 1});
}, new Map).values());
console.log(res);
You could group with an object and increment the given source property.
const input = [
{ date: "01-01-2021", source: "TT" },
{ date: "01-01-2021", source: "TT" },
{ date: "02-01-2021", source: "FB" },
{ date: "02-01-2021", source: "TT" }
];
const result = Object.values(input.reduce((r, { date, source }) => {
r[date] ??= { date };
r[date][source] = (r[date][source] || 0) + 1;
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Use reduce method to sum up values in array of objects and find duplicates - JS

I have an array of objects:
const fruits = [{
type: oranges,
amount: 10
},
{
type: apples,
amount: 0,
}, {
type: oranges,
amount: 5
}
]
I need to do the following:
sum up the fruits, if they're the same (f.ex. sum up oranges: {type: oranges, amount: 15})
add a new key-value pair to object, depending on how many times it is present in the array (f.ex. oranges are present two times: {types: oranges, amount: 15, count: 2} and apples are present one time {types: apples, amount: 0, count: 1} )
So my goal is to have something like this:
const fruits = [{
type: oranges,
amount: 15,
count: 2
},
{
type: apples,
amount: 0,
count: 1
}
]
I found out that the reduce method is a good way to achieve this. I'm working with the following code (this should sum up the amount), but no matter what I try I don't get the result. I just get a new key-value {..., marks: NaN} - so maybe there's a better approach:
const fruits = [{
type: "oranges",
amount: 10
},
{
type: "apples",
amount: 0,
}, {
type: "oranges",
amount: 5
}
]
const endresult = Object.values(fruits.reduce((value, object) => {
if (value[object.type]) {
['marks'].forEach(key => value[object.type][key] = value[object.type][key] + object[key]);
} else {
value[object.type] = { ...object
};
}
return value;
}, {}));
console.log(endresult)
I'm not sure what I'm doing wrong and how can I add the new key-value pair to count the times the object is present in the array? Could someone point me in the right direction? Thanks!
Your code looks more complex than what is required. Somewhere you must be adding an undefined to a number and hence getting NaN(not a number).
Also :
you are not initializing the count property.
you are not adding the amount and count, in the if condition.
Making a few changes to your code itself this can be fixed:
const fruits = [{
type: "oranges",
amount: 10
},
{
type: "apples",
amount: 0,
}, {
type: "oranges",
amount: 5
}
]
const endresult = Object.values(fruits.reduce((value, object) => {
if (value[object.type]) {
value[object.type].amount += object.amount;
value[object.type].count++;
} else {
value[object.type] = { ...object , count : 1
};
}
return value;
}, {}));
console.log(endresult)
You can easily achieve the result using Map, reduce, and Array.from
const fruits = [
{
type: "oranges",
amount: 10,
},
{
type: "apples",
amount: 0,
},
{
type: "oranges",
amount: 5,
},
];
const endresult = Array.from(
fruits
.reduce((map, curr) => {
if (!map.has(curr.type)) map.set(curr.type, { ...curr, count: 1 });
else {
map.get(curr.type).amount += curr.amount;
map.get(curr.type).count++;
}
return map;
}, new Map())
.values()
);
console.log(endresult);
/* This is not a part of answer. It is just to give the output fill height. So IGNORE IT */
.as-console-wrapper { max-height: 100% !important; top: 0; }
This reduce works better
const fruits = [{ type: "oranges", amount: 10 }, { type: "apples", amount: 0}, { type: "oranges", amount: 5 }]
const endresult = fruits.reduce((acc,fruit) => {
acc[fruit.type] = acc[fruit.type] || fruit;
const theFruit = acc[fruit.type]
theFruit.count = !!theFruit.count ? theFruit.count : 0;
theFruit.count++
theFruit.amount += fruit.amount
return acc;
}, {});
console.log(Object.values(endresult))

Get only the last Date from an Array

I have an array of objects having a DateTime, like this:
[{Date1, Count1}, {Date2, Count2}, ...]
The Dates in the array are given by Hour (Date2 = Date1 + 1H), so I am interested in taking only the Date's last hour count.
{Date: 2020-03-21T20:00:00Z, Count: 3}
{Date: 2020-03-21T22:00:00Z, Count: 4}
{Date: 2020-03-21T23:00:00Z, Count: 15}
{Date: 2020-03-22T00:00:00Z, Count: 66}
{Date: 2020-03-22T01:00:00Z, Count: 70}
How can I reduce this Array to take in consideration only the last item of each day?
{Date: 2020-03-21T23:00:00Z, Count: 15}
{Date: 2020-03-22T01:00:00Z, Count: 70}
Something like myArray.groupBy(Date).TakeLast()...
Here's some code that only works if the dates are sorted (if they're not you can just sort via dates.sort((a, b) => a.Date.getTime() - b.Date.getTime()):
var dates = [
{ Date: new Date("2020-03-21T20:00:00Z"), Count: 3 },
{ Date: new Date("2020-03-21T22:00:00Z"), Count: 4 },
{ Date: new Date("2020-03-21T23:00:00Z"), Count: 15 },
{ Date: new Date("2020-03-22T00:00:00Z"), Count: 66 },
{ Date: new Date("2020-03-22T01:00:00Z"), Count: 70 }
];
var lastPerDay = [];
// just need to set to a value that's impossible to get normally
var prevDate = null;
// go backwards through the array to find the last instance
for (var i = dates.length - 1; i >= 0; i--) {
// need some way of combining year, month, and date into a value
var curDate = [dates[i].Date.getUTCFullYear(), dates[i].Date.getUTCMonth(), dates[i].Date.getUTCDate()].join(",");
// we haven't seen the date before
if (curDate !== prevDate) {
// add the day to the front
lastPerDay.unshift(dates[i]);
// update the previous date
prevDate = curDate;
}
}
console.log(lastPerDay);
With this, there is no need for the dates to be sorted.
let lastsHour = {}, array = [
{ date: new Date("2020-03-21T20:00:00Z"), count: 3 },
{ date: new Date("2020-03-21T22:00:00Z"), count: 4 },
{ date: new Date("2020-03-21T23:00:00Z"), count: 15 },
{ date: new Date("2020-03-22T00:00:00Z"), count: 66 },
{ date: new Date("2020-03-22T01:00:00Z"), count: 70 }
];
array.map(function (e) {
let currentDate = ""+e.date.getUTCDate()+e.date.getUTCMonth()+e.date.getUTCFullYear();
if (! lastsHour[currentDate]) {
lastsHour[currentDate] = e;
} else if (lastsHour[currentDate].date < e.date) {
lastsHour[currentDate] = e;
}
});
let result = [];
for (let key in lastsHour ) {
if (lastsHour.hasOwnProperty(key)) {
result.push(lastsHour[key]);
}
}
console.log(result);
We can use reduce method and decide on each iteration whether it is a next hour of current day. Then we can delete an array element which contains previous hour. We have O(N) by using reduce method:
const oneHourInMilliseconds = 3600000;
const result = arr.reduce((a, {Date: date, Count}) => {
let [y, m, d] = date.split(/\D+/);
let key = new Date(date).getTime();
a[key] = a[key] || { Date: date, Count };
if (a[key - oneHourInMilliseconds]) {
let [yPrev, mPrev, dPrev] = a[key - oneHourInMilliseconds].Date.split(/\D+/);
if (d == dPrev)
delete a[key-oneHourInMilliseconds];
}
return a;
},{})
console.log(Object.values(result));
An example:
let arr = [
{Date : '2020-03-21T22:00:00Z', Count: 4},
{Date : '2020-03-21T23:00:00Z', Count: 15},
{Date : '2020-03-22T00:00:00Z', Count: 66},
{Date : '2020-03-22T01:00:00Z', Count: 70},
];
const oneHourInMilliseconds = 3600000;
const result = arr.reduce((a, {Date: date, Count}) => {
let [y, m, d] = date.split(/\D+/);
let key = new Date(date).getTime();
a[key] = a[key] || { Date: date, Count };
if (a[key - oneHourInMilliseconds]) {
let [yPrev, mPrev, dPrev] = a[key - oneHourInMilliseconds].Date.split(/\D+/);
if (d == dPrev)
delete a[key-oneHourInMilliseconds];
}
return a;
},{})
console.log(Object.values(result));
var items = [
{ Date: new Date("2020-03-21T20:00:00Z"), Count: 3 },
{ Date: new Date("2020-03-21T22:00:00Z"), Count: 4 },
{ Date: new Date("2020-03-21T23:00:00Z"), Count: 15 },
{ Date: new Date("2020-03-22T00:00:00Z"), Count: 66 },
{ Date: new Date("2020-03-22T01:00:00Z"), Count: 70 },
{ Date: new Date("2020-03-22T20:00:00Z"), Count: 170 }
];
var filtered = items.filter((e, i, arr) => {
return (i == arr.length - 1 ||
arr[i].Date.toDateString() != arr[i + 1].Date.toDateString());
});
console.log(filtered);

Group array of objects by multiple properties with Lodash

is possible with lodash library group elements by 2 properties?
I have array of objects like this:
[{
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"
}]
My goal is group objects in array by:
year
month
Purpose of that grouping is that I need in result order these objects' sum of amount by newest. So for that reason I need distinguish January 2017 from January 2018. It should be 2 different groups.
I am not sure if my approach is correct so I write here my required output:
[
3000, // sum of 2018-2
8000, // sum of 2018-1
3000 // sum of 2017-7
5000 // sum of 2017-3
11500 // sum of 2017-1
]
I tried following command but it doesn't work and give me error:
let xxx = _(data)
.groupBy(function(i) {
new Date(i.date).getFullYear()
})
.groupBy(function(i) {
new Date(i.date).getMonth()
})
.map(x => x.amount)
.sum()
.orderBy('date').value();
Can you help me to fix it ? Thanks.
You can just concat your year and month with groupBy and use it.
var grouped = _.groupBy(data, function(i) {
return new Date(i.date).getFullYear()+'-'+new Date(i.date).getMonth()
})
var resultUnsorted = _.map(t, (val, key) => ({key: key, val: val.reduce((p, c) => c.amount + p, 0) }));
then sort using _.orderBy
const output = _.orderBy(resultUnsorted, 'key');
you can write your custom sort function using the behaviour you want.

Creating arrays in JavaScript by comparing values in another array

In JavaScript, I'm trying to create arrays based on the values of another array and I'm having difficultly.
I've got an array of dates in string format (dates) e.g.
["30/09/2015", "31/10/2015", "30/11/2015", "31/12/2015"]
I've got an Object to represent multiple bank accounts (balancesSortByAccId) e.g.
Cash - (Array size: 3)
id: 1, date: "30/09/2015", balance: 30
id: 2, date: "31/10/2015", balance: 50
id: 3, date: "30/11/2015", balance: 100
Natwest - (Array size: 2)
id: 4, date: "30/11/2015", balance: 400
id: 5, date: "31/12/2015", balance: 200
Whilst looping through all the accounts in balancesSortByAccId, I want to be able to create an array for the balance at each date in the dates array i.e.
[30, 50, 100, null]
[null, null, 400, 200]
How could I achieve this?
UPDATE: jsfiddle code - https://jsfiddle.net/gx8bLehb/
The easiest way would be to transform your cash and natwest arrays into a hash sorted by date, something like balancesByDate:
var balancesByDate = _.groupBy(cash, function(entry) {return entry.date});
Then use an array map() function, e.g. from lodash to iterate the dates array and for each date look up the account line in the balancesByDate hash. From that line, return the balance property in the map function.
dates.forEach(function(date){
if (balancesByDate[date]) {
results.push(_.map(balancesByDate[date], function(line){
return line.balance;
}));
} else {
results.push(null);
}
});
However, you need to be aware that your dataset most likely could contain duplicate balances for a day, you should plan for that (my code returns an array for each day).
https://jsfiddle.net/hdpuuc5d/1/
A solution in plain javascript with a helper object for the dates:
var dates = ["30/09/2015", "31/10/2015", "30/11/2015", "31/12/2015"],
datesObj = dates.reduce(function (r, a, i) { r[a] = i; return r; }, {}),
balances = {
Cash: [
{ id: 1, date: "30/09/2015", balance: 30 },
{ id: 2, date: "31/10/2015", balance: 50 },
{ id: 3, date: "30/11/2015", balance: 100 }
],
Natwest: [
{ id: 4, date: "30/11/2015", balance: 400 },
{ id: 5, date: "31/12/2015", balance: 200 }
]
},
result = {};
Object.keys(balances).forEach(function (k) {
result[k] = Array.apply(null, { length: dates.length }).map(function () { return null; });
balances[k].forEach(function (a) {
result[k][datesObj[a.date]] = a.balance;
});
});
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');

Categories

Resources