I have a problem sorting data and getting an array with the right data. Here is the situation :
I get a table of data representing invoices (creation date and price) in this format :
invoices: [
0: {
amount: 200,
created_at: 1590572830425
},
1: {
amount: 799,
created_at: 1590572847553
}
...
]
I would like to loop on this table of invoices, and extract the total sum of the amount of invoices according to the month of the year (thanks to the fields created_at representing a timestamp.
At the end, I would like to obtain a table with 12 values maximum, representing the total amount of invoices classified by month
Expected result : [300, 450, 799, 650, 288, 400, 643, 809, 1073, 499, 640, 600]
Thank you in advance.
As you would not want amounts to get mingled up across different years, it would be better to have a result that lists the relevant years, and then for each year, have the 12 sums:
function monthTotals(invoices) {
let totals = {};
for (let {created_at, amount} of invoices) {
let date = new Date(created_at);
let year = date.getFullYear();
(totals[year] = (totals[year] || Array(12).fill(0)))[date.getMonth()] += amount;
}
return totals;
}
let invoices = [{
amount: 200,
created_at: 1590572830425
},{
amount: 799,
created_at: 1590572847553
}];
console.log(monthTotals(invoices));
If you can, try to always give a real input and output combination - i.e. ones that match (input maps to expected output), so that we can run it and test our output against your expected output. Also, often known as a minimal reproducible example (https://stackoverflow.com/help/minimal-reproducible-example):
const invoices = [
{
amount: 200,
created_at: 1590572830425
},
{
amount: 799,
created_at: 1590572847553
}
]
const outputArr = [0,0,0,0,0,0,0,0,0,0,0,0];
invoices.forEach(item => {
const myDate = new Date(item.created_at);
console.log(myDate.toDateString())
const month = parseInt(myDate.getMonth());
outputArr[month] += item.amount
});
console.log(outputArr);
Output:
[0,0,0,0,999,0,0,0,0,0,0,0]
Like this. I first did a reduce but that was overkill
This solution will not work across years
const invoices = [{
amount: 200,
created_at: 1590572830425
},
{
amount: 799,
created_at: 1590572847553
}
];
const monthVal = new Array(12).fill(0);
invoices.forEach(item => {
const month = new Date(item.created_at).getMonth();
monthVal[month] += item.amount;
});
console.log(monthVal)
Also you can sum up the entire process in reduce method.
var invoices = [ {amount: 200,created_at: 1590572830425},{amount: 799,created_at:1590572847553}];
var result = invoices.reduce((acc, elem)=>{
month = new Date(elem.created_at).getMonth();
acc[month] += elem.amount;
return acc;
},Array.from({length:12}, ()=>0));
console.log(result);
Related
I am working with Vue, but this is a general javascript question.
I make a call to an api to get cat messages for a chat UI. The initial call returns an array of objects where each object is a chat message object as below.
data: [
0: {id: 1, created_at: "2022-05-20T15:12:40.000000Z", updated_at: "2022-05-20T17:18:03.000000Z",…}
1: {id: 2, created_at: "2022-05-20T15:12:41.000000Z", updated_at: "2022-05-20T17:18:04.000000Z",…}
2: {id: 3, created_at: "2022-05-20T15:12:41.000000Z", updated_at: "2022-05-20T17:18:04.000000Z",…}
3: {id: 4, created_at: "2022-05-20T15:12:41.000000Z", updated_at: "2022-05-20T17:18:04.000000Z",…}
]
I initial wanted to format the message so they can be grouped by their dates in the chat window. This is the code I used to group them
This is a computed property in vue
const formattedChats = computed(() => {
let dateFormattedMessages = messages.value.map(message => {
return {...message, updated_at: new Date(message.updated_at).toDateString(), created_at: new Date(message.created_at).toDateString()}
})
return dateFormattedMessages.reduce((total, currentValue) => {
total[currentValue.updated_at] = total[currentValue.updated_at] || [];
total[currentValue.updated_at].push(currentValue);
return total;
}, Object.create(null));
})
The above will first take the each chat object an convert their updated_at and created_at to a date string and then group the array using the updated_at.
The result was as follows:
formattedChats = {
Fri Jun 24 2022: [
{...}, {...
]
Fri May 20 2022:[
{...}, {...
]
Mon Jun 27 2022:Array[
{...}, {...
]
Sat May 21 2022:Array[
{...}, {...
]
Tue Jun 28 2022:Array[
{...}, {...
]
}
If you notice, the problem I am facing is that the dates are not arranged in any order. it doesnt make sense to render it to the UI like this because the resulting chats with not be arranged by date.
This is how the UI should look
You need to use arrays (or a map) to keep the order sorted. I would use an array of arrays. First you need to sort the data on date (I used updated_at, which I made of type Date in the data array). Then iterate over the sorted array. See snippet below.
const data = [{
id: 1,
updated_at: new Date("2022-05-21T15:12:40.000000Z"),
created_at: "2022-05-20T17:18:03.000000Z"
},
{
id: 2,
updated_at: new Date("2022-05-20T15:12:41.000000Z"),
created_at: "2022-05-20T17:18:04.000000Z"
},
{
id: 3,
updated_at: new Date("2022-05-23T15:12:41.000000Z"),
created_at: "2022-05-20T17:18:04.000000Z"
},
{
id: 4,
updated_at: new Date("2022-05-21T15:12:41.000000Z"),
created_at: "2022-05-20T17:18:04.000000Z"
},
]
const sortedData = data.sort(
(a, b) => Number(a.updated_at) - Number(b.updated_at),
);
let currentDay = sortedData[0].updated_at;
const stillCurrentDay = (dayOfMessage) => {
return dayOfMessage.getFullYear() === currentDay.getFullYear() &&
dayOfMessage.getMonth() === currentDay.getMonth() &&
dayOfMessage.getDate() === currentDay.getDate()
}
let dayMessageArray = [];
const fullMessageArray = [];
const createMessagesArray = (messages) => {
const newDay = {};
newDay[currentDay.toISOString().split('T')[0]] = messages;
fullMessageArray.push(newDay);
}
sortedData.forEach(message => {
if (!stillCurrentDay(message.updated_at)) {
createMessagesArray(dayMessageArray);
currentDay = message.updated_at;
dayMessageArray = [];
}
dayMessageArray.push(message);
});
createMessagesArray(dayMessageArray);
console.log(fullMessageArray);
Hope this helps. Perhaps there are easier ways, please let me know.
I am working with the object like this:-
{
0:
buyAmount: 16.664328043964396
buyAmountInUsd: 16.685266204095775
date: {date: '2021-12-07'}
sellAmount: {USD: 500, INR: 35000}
1:
buyAmount: 1004.7015442959262
buyAmountInUsd: 1005.9639175379324
date: {date: '2021-12-07'}
sellAmount: {USD: 1000, INR: 79000}
......
and I am trying to make a new array using useState hook with this data but the problem I am facing is how to filter data and make an almost similar array with the same data.
e.g.:-
0: [amount: 500, date: '2021-12-07'],
1: [amount: 1000, date: '2021-12-07']
The problem I am facing is I don't know the approach how to get the data like amount = sellAmount.USD and date = date.date
I thought of trying the for...of But I don't think it will be a good hit.
You can do this with Array.map
const arr = [{
buyAmount: 16.664328043964396,
buyAmountInUsd: 16.685266204095775,
date: {date: '2021-12-07'},
sellAmount: {USD: 500, INR: 35000}
},{
buyAmount: 1004.7015442959262,
buyAmountInUsd: 1005.9639175379324,
date: {date: '2021-12-07'},
sellAmount: {USD: 1000, INR: 79000}
}]
console.log(
arr.map(initialValue => {
return {
amount: initialValue.sellAmount.USD,
date: initialValue.date.date
}
})
)
The better idea would be the having an array of objects rather than array of array elements
let result = yourArray.map(each => ({ amount: each.sellAmount.USD, date: each.date.date }))
This is not related to ReactJS - just native Javascript object handling.
If your original data has the shape of an Object you can convert it into an Array like so:
const objData = { 0: {...}, 1: {...} };
const arrData = Object.entries(objData); // [[0, {...}], [1, {...}]];
From there, you can filter/map/sort your array with native array methods:
const reshapedArray = arrData.map(([key, value]) => {
return {
amount: value.sellAmount.USD,
date: value.date.date,
};
});
Then sort:
const sortedArray = reshapedArray.sort((prev, next) => {
return prev.amount - next.amount; // sort by amount ascending
});
You can of could chain these array functions and shorten the syntax a bit:
Object.entries(objData)
.map(([_, { sellAmount, date: { date } }]) => ({ amount: sellAmount.USD, date }))
.sort((a, b) => a.amount - b.amount);
I have an array of objects that contain a date and an amount (among other things).
There is an object for each date with a specific amount, but there can also be multiple objects for the same date containing different amounts.
I'd like to consolidate the objects so I only have one object in the array for each date ... and have the amount corresponding to that date be the total sum of all previous amounts in those objects.
Examples will probably help here:
What my array looks like now:
[
{
date: "2019-1-1", // this is a dupe date
amount: 20,
...
},
{
date: "2019-1-1", // this is a dupe date
amount: 40,
...
},
{
date: "2019-1-2",
amount: 40,
...
},
{
date: "2019-1-3",
amount: 40,
...
}
]
What I would like my array to look like:
[
{
date: "2019-1-1", // this is now a unique date
amount: 60, // and the amount was totaled
...
},
{
date: "2019-1-2",
amount: 40,
...
},
{
date: "2019-1-3",
amount: 40,
...
}
]
Use .reduce to reduce an array into an object (or into anything else) by iterating over its properties. You just need to test to see if an object with a matching date already exists in the accumulator first:
const input = [
{
date: "2019-1-1", // this is a dupe date
amount: 20,
foo: 'bar',
},
{
date: "2019-1-1", // this is a dupe date
amount: 40,
foo: 'bar',
},
{
date: "2019-1-2",
amount: 40,
foo: 'bar',
},
{
date: "2019-1-3",
amount: 40,
foo: 'bar',
}
];
const output = input.reduce((accum, item) => {
const { date, amount } = item;
const foundObj = accum.find(({ date: findDate }) => findDate === date);
if (foundObj) {
foundObj.amount += amount;
return accum;
}
accum.push(item);
return accum;
}, []);
console.log(output);
You may do as follows;
var data = [ { date: "2019-1-1", // this is a dupe date
amount: 20},
{ date: "2019-1-1", // this is a dupe date
amount: 40},
{ date: "2019-1-2",
amount: 40},
{ date: "2019-1-3",
amount: 40}
],
result = Object.values(data.reduce((r,d) => r[d.date] ? (r[d.date].amount += d.amount, r)
: (r[d.date] = d, r), {}));
console.log(result);
Regarding a comment i guess i have to explain this a little for those who may not be familiar with some ES6 functionalities.
Object.values() is a Object method which returns all own property values in an array.
So we are reducing our objects into an hash object of which we collect the properties by Object.values() later. While reducing we check if the currently examined object's date value exists as key in our map. If not we create that key and insert the examined object at that key position, if yes then we increment the previously inserted objects amount property by the value of currently examined objects amount value.
If you don't want to mutate the original data then please change r[d.date] = d into r[d.date] = Object.assign({},d).
The way I would do it is to create an object with the dates as the key, then you can iterate over the array and create a new date property if it doesn't exist or increase the amount if it does, then convert it back into an array:
const items = data.reduce((acc, curr) => {
if (!acc[curr.date]) { // basically creating a property with the date as the key and the value is the current object
acc[curr.date] = { ...curr };
} else { // if it exists already, then just increment the amount
acc[curr.date].amount += curr.amount;
}
return acc;
}, {});
const newArray = Object.values(items); // grab all the values from the object above
I want a function that takes an array and filters out old duplicates.
Specifically, if duplicate ids exist in myList, keep only the object with the newest date. Given the following array
let myList = [{
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-21 21:04:13"
},
{
id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5",
date: "2018-02-22 21:04:13"
},
{
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-23 21:04:13"
}]
the function should return:
[{
id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5",
date: "2018-02-22 21:04:13"
},
{
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-23 21:04:13"
}]
You can use the function reduce to build the desired output.
let myList = [{ id: "e9519e95-5a10-4274-ac24-de72ad60ffd7", date: "2018-02-21 21:04:13"},{ id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5", date: "2018-02-22 21:04:13"},{ id: "e9519e95-5a10-4274-ac24-de72ad60ffd7", date: "2018-02-23 21:04:13"}];
let result = Object.values(myList.reduce((a, {id, date}) => {
if (a[id]) {
if (a[id].date < date) a[id] = {id, date};
} else a[id] = {id, date};
return a;
}, {}));
console.log(result);
Put the entries into a hash table keyed by id. Each time you add an entry, look up the id and either keep the existing entry or replace it with the new one, based on whichever has a more recent date.
Map and Array.prototype.map() can be combined to functionally filter key based duplicates from arrays.
Array.prototype.sort() can be leveraged to guarantee order.
See below for a practical example.
// Input.
const input = [
{id: "e9519e95-5a10-4274-ac24-de72ad60ffd7", date: "2018-02-21 21:04:13"},
{id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5", date: "2018-02-22 21:04:13"},
{id: "e9519e95-5a10-4274-ac24-de72ad60ffd7", date: "2018-02-23 21:04:13"}
]
// Sort By Date.
const sortDate = array => array.sort((A, B) => new Date(A.date)*1 - new Date(B.date)*1)
// Filter Duplicates.
const filter = array => [...new Map(array.map(x => [x.id, x])).values()]
// Output.
const outputRaw = filter(input) // No guaranteed order.
const outputSorted = sortDate(filter(sortDate(input))) // Guaranteed latest.
// Proof.
console.log('Raw', outputRaw)
console.log('Sorted', outputSorted)
This isn't the best answer, just another take on #Ele's solution offered for completeness. Instead of plucking the values after the unique set is found, it works on the returned array for each iteration. The find during each iteration should be less efficient than a key lookup, which is one of the reasons it's not the best answer.
let myList = [{
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-21 21:04:13"
}, {
id: "026e7ecf-d236-4aff-b26d-7546ac85b7d5",
date: "2018-02-22 21:04:13"
}, {
id: "e9519e95-5a10-4274-ac24-de72ad60ffd7",
date: "2018-02-23 21:04:13"
}]
let result = myList.reduce((arr, { id, date }) => {
let found = arr.find(v=>v.id==id)
if (found) {
if (found.date < date)
found.date = date
}
else
arr.push({ id, date });
return arr;
}, []);
console.log(result);
How do you get the data from distinct values also sum the total values from the same unique row
Here is the JSON Data result
As you can see they have the same full name. How do you get the distinct value while sum the total priceGross.
The result should be returning
eg. "Khalem Williams" and adding the total priceGross " 1200 + 1200 " = 2400
result return
FullName : "Khalem Williams"
TotalPriceGross: "2400"
I'm getting the distinct values with the code below but I'm wondering how to sum the total priceGross while also getting the distinct values.
var countTotal = [];
$.each(data.aaData.data,function(index, value){
if ($.inArray(value.invoiceToFullName, countTotal)==-1) {
countTotal.push(value.invoiceToFullName);
}
});
I would honestly suggest converting your data to an object with names as keys (unique) and total gross prices as values:
{
"Khalem Williams": 2400,
"John Doe": 2100
}
However, I have included in the snippet below example code for how to convert your data to an array of objects as well. I used a combination of Object.keys, Array#reduce and Array#map.
var data = {
aaData: {
data: [{
invoiceToFullName: "Khalem Williams",
priceGross: 1200
},
{
invoiceToFullName: "Khalem Williams",
priceGross: 1200
},
{
invoiceToFullName: "John Doe",
priceGross: 500
},
{
invoiceToFullName: "John Doe",
priceGross: 1600
},
]
}
}
var map = data.aaData.data.reduce(function(map, invoice) {
var name = invoice.invoiceToFullName
var price = +invoice.priceGross
map[name] = (map[name] || 0) + price
return map
}, {})
console.log(map)
var array = Object.keys(map).map(function(name) {
return {
fullName: name,
totalPriceGross: map[name]
}
})
console.log(array)
Just created my own dummy data resembling yours hope you get the idea, if not you can paste your json and i can adjust my solution, was just lazy to recreate your json.
Your problem seems to be a typical group by field from sql.
Sample data:
var resultsData = [{name: 'sipho', amount: 10}, {name: 'themba', amount: 30}, {name: 'sipho', amount: 60}, {name: 'naidoo', amount: 30}, {name: 'gupta', amount: 70}, {name: 'naidoo', amount: 10}];
Get value of customer(field) you going to group by:
var customers = $.unique(resultsData.map(function(value){return value.name}));
Go through each customer and add to their sum:
var desiredSummary = customers.map(function(customer){
var sum =0;
$.each(test,function(index, value){sum+=(value.name==customer?value.amount:0)});
return {customer: customer, sum: sum}
});
Hope it helps, Happy coding