Summarize array of objects by common property (date) [closed] - javascript

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
Hi i am having an angular project .
Currently I am having an array named historyArr . it has some statistics data for 2 days as below.
[
{
"dateRange": "2020-07-01T16:00:00.000+0000",
"total": 20,
"delivered": 5,
"undeliverable": 5,
"expired": 5,
"enroute": 5
},
{
"dateRange": "2020-07-01T17:00:00.000+0000",
"total": 50,
"delivered": 10,
"undeliverable": 15,
"expired": 10,
"enroute": 15
},
{
"dateRange": "2020-07-01T18:00:00.000+0000",
"total": 8,
"delivered": 2,
"undeliverable": 2,
"expired": 2,
"enroute": 2
},
{
"dateRange": "2020-07-02T00:00:00.000+0000",
"total": 160,
"delivered": 40,
"undeliverable": 40,
"expired": 40,
"enroute": 40
},
{
"dateRange": "2020-07-02T01:00:00.000+0000",
"total": 200,
"delivered": 50,
"undeliverable": 50,
"expired": 50,
"enroute": 50
}
]
I want to reduce the above array , so that i would like to perform the summation of statistics per day . So I want to transform the above array to the one below. I am a free to use moment.js libaries for parsing dates.
[
{
"dateRange": "2020-07-01",
"total": 78,
"delivered": 17,
"undeliverable": 22,
"expired": 17,
"enroute": 22
},
{
"dateRange": "2020-07-02",
"total": 360,
"delivered": 90,
"undeliverable": 90,
"expired": 90,
"enroute": 900
}
]
i know its a difficult question. the project is uploaded in stackblitz
https://stackblitz.com/edit/angular-zqmdpy
https://angular-zqmdpy.stackblitz.io
https://stackblitz.com/edit/angular-zqmdpy?embed=1&file=src/app/app.component.html
really appreciate any help
thank you

You may
do String.prototype.slice() to obtain meaningful portion of dateRange and use that as a key
to traverse your source array (e.g. with Array.prototype.reduce())
and build up the Map having grouped/summarized objects as respective value,
then extract those values into array with Map.prototype.values()
assuming all properties, other than dateRange should get summed up, you may use slight shortcut not to hardcode all of them explicitly:
const src = [{"dateRange":"2020-07-01T16:00:00.000+0000","total":20,"delivered":5,"undeliverable":5,"expired":5,"enroute":5},{"dateRange":"2020-07-01T17:00:00.000+0000","total":50,"delivered":10,"undeliverable":15,"expired":10,"enroute":15},{"dateRange":"2020-07-01T18:00:00.000+0000","total":8,"delivered":2,"undeliverable":2,"expired":2,"enroute":2},{"dateRange":"2020-07-02T00:00:00.000+0000","total":160,"delivered":40,"undeliverable":40,"expired":40,"enroute":40},{"dateRange":"2020-07-02T01:00:00.000+0000","total":200,"delivered":50,"undeliverable":50,"expired":50,"enroute":50}],
result = [...src
.reduce((acc, o) => {
const key = o.dateRange.slice(0,10),
group = acc.get(key)
if(group){
const { dateRange, ...rest } = o
Object
.keys(rest)
.forEach(key =>
group[key] = (group[key] || 0) + (o[key] || 0))
} else {
acc.set(key, {...o, dateRange: key})
}
return acc
}, new Map)
.values()
]
console.log(result)
.as-console-wrapper{min-height:100%;}

This is an idea how you should do it, I'm not taking care at all of the Correct format for the date, you should improve it, I created an auxiliar arr that doesnt mutate in order to not change the original array.
ngOnInit() {
var temp = {};
var obj = null;
this.arr = data;
this.historyArr = this.arr['histogramDistributionbyCdrStatuses'];
this.historyArr.forEach(el=>{
el.dateRange=moment(el.dateRange).format('YYYY-MM-DD')
})
let auxArr = JSON.parse(JSON.stringify(this.historyArr));
for(var i=0; i < auxArr.length; i++) {
obj=auxArr[i];
if(!temp[obj.dateRange]) {
temp[obj.dateRange] = obj;
} else {
temp[obj.dateRange].total += obj.total;
}
}
var result = [];
for (var prop in temp)
result.push(temp[prop]);
console.log(result)
}

I iterate over the array and get the date from an entry. I look if this date is in the help-array index. If no than I create a new entry with the properties from the entry and append it to the result. Otherwise I look with this index in my result-array and sum upevery prperty of my element to this entry.
function transformArray( array ) {
let result = [];
let index = [];
array.forEach(obj => {
const PROPERTIES = Object.keys(obj);
PROPERTIES.splice(PROPERTIES.indexOf('dateRange'),1);
let dat = obj.dateRange.substr(0,10);
let resIndex = index.indexOf(dat);
if ( resIndex == -1) {
index.push(dat);
let entry = { dateRange: dat}
PROPERTIES.forEach (prop => entry[prop] = obj[prop] || 0);
result.push(entry);
} else {
let entry = result[resIndex];
PROPERTIES.forEach (prop => entry[prop] = (entry[prop] || 0) + (obj[prop] || 0));
}
});
return result;
}
let historyArr = [
{
"dateRange": "2020-07-01T16:00:00.000+0000",
"total": 20,
"delivered": 5,
"undeliverable": 5,
"expired": 5,
"enroute": 5
},
{
"dateRange": "2020-07-01T17:00:00.000+0000",
"total": 50,
"delivered": 10,
"undeliverable": 15,
"expired": 10,
"enroute": 15
},
{
"dateRange": "2020-07-01T18:00:00.000+0000",
"total": 8,
"delivered": 2,
"undeliverable": 2,
"expired": 2,
"enroute": 2
},
{
"dateRange": "2020-07-02T00:00:00.000+0000",
"total": 160,
"delivered": 40,
"undeliverable": 40,
"expired": 40,
"enroute": 40
},
{
"dateRange": "2020-07-02T01:00:00.000+0000",
"total": 200,
"delivered": 50,
"undeliverable": 50,
"expired": 50,
"enroute": 50
}
];
console.log(transformArray(historyArr));

Related

Javascript, get position of ALL repeating values in an object

Have an array of notes and corresponding time, Im separating ALL the repeating Time entries with code below to a new object called duplicatesTimeValues.
const allNotes = [
{
"note": 69,
"time": 0
},
{
"note": 57,
"time": 0
},
{
"note": 60,
"time": 1.5
},
{
"note": 64,
"time": 2
},
{
"note": 69,
"time": 2.5
},
{
"note": 71,
"time": 3
},
{
"note": 52,
"time": 3
},
{
"note": 64,
"time": 4.5
},
{
"note": 68,
"time": 5
},
{
"note": 71,
"time": 5.5
}
];
const getDuplicates = () => {
const values = allNotes;
const lookup = values.reduce((a, e) => {
a[e.time] = ++a[e.time] || 0;
console.log(e.keys);
return a;
}, {});
console.log('repeating');
const duplicatesTimeValues = values.filter(e => lookup[e.time]);
console.log(duplicates);
const uniqueTimeValues = values.filter(e => !lookup[e.time]);
console.log('unique');
console.log(uniqueValues);
Now I need to compare this output with another array, that might look something like this
[57, 69, 60, 64, 69, 52, 71, 64, 68, 71]
but I need to be able to split that array based on how this object was split.
In order to do that, I would like to get position in original object of the repeating elements.
In this case this entries are
[
{
"note": 69,
"time": 0
},
{
"note": 57,
"time": 0
},
{
"note": 71,
"time": 3
},
{
"note": 52,
"time": 3
}
]
and result I need is
[0, 1, 5, 6]
as repeating elements were on these positions in original allNotes object. And then I will use these positions array to split array I want to compare with, so it looks like this
[60, 64, 69, 64, 68, 71]
How could I do that?
What you want to do is not very clear according to me, but here is a code that gets the indexes of the duplicate elements in an array. Note that it gets the index of a duplicate only if it has been seen before (therefore you'll never get 0 in the output).
const allNotes = [
{
note: 69,
time: 0,
},
{
note: 57,
time: 0,
},
{
note: 60,
time: 1.5,
},
{
note: 64,
time: 2,
},
{
note: 69,
time: 2.5,
},
{
note: 71,
time: 3,
},
{
note: 52,
time: 3,
},
{
note: 64,
time: 4.5,
},
{
note: 68,
time: 5,
},
{
note: 71,
time: 5.5,
},
];
function getIndexesOfDuplicates() {
const indexes = [];
const previousNotes = [];
for (let i = 0; i < allNotes.length; i++) {
let currentNote = allNotes[i].note;
let found = false;
for (let previousNote of previousNotes) {
if (previousNote === currentNote) {
indexes.push(i);
found = true;
break;
}
}
if (!found) previousNotes.push(currentNote);
}
return indexes;
}
console.log(getIndexesOfDuplicates());
Here's a one-liner that makes use of Lodash, specifically the lodash/fp module:
let result =
_.map(
_.last,
_.flatten(
_.filter(
x => x.length > 1,
_.groupBy(
_.compose(_.iteratee('time'), _.first),
_.zip(allNotes, [...Array(allNotes.length).keys()])))))
// result now contains the array [0, 1, 5, 6]
I zip allNotes together with their indices
then I group them if the resulting arrays have the 'time' of their first entry equal
then I retain only the groups longer than 1 element, filtering out the others
then I flatten the resulting array of groups in a single array
and finally get the last element (which is the index appended via _.zip) from each entry
Found solution where I get keys of duplicate items based on values, it isn't cleanest solution, though it will work in my case, as in my use case, values are always unique...
const array = [];
for (var o = 0; o < duplicates.length; o++) {
const value = duplicates[o];
array.push(Object.keys(window.allNotes)[Object.values(window.allNotes).indexOf(value)]);
}
console.log(array);

How to create dynamic array filter expression?

I need a function to filter array of objects based on given structure of object. So I have this object:
{
"2": [
{
"fd_id": 16,
...others
}
],
"3": [
{
"fd_id": 2,
...others
},
{
"fd_id": 3,
...others
}
]
}
I would like to filter another array based on this object. Like this;
const result = products.filter(item => {
// returns array of numbers [1, 2, 3]
const filters = item.filters;
if(filters){
// Here must be refactored
return ((filters.includes(givenObj[2][0].fd_id))
&& (filters.includes(givenObj[3][0].fd_id) || filters.includes(givenObj[3][1].fd_id)));
}
});
But this function must be dynamic. Because the input object may change. So for between each parent "&&", and between each children "||" condition must be applied. Thanks for any help. This is the link to example https://jsfiddle.net/cadkt86n/
A function to loop the data will help.
My Logic
Generate the list of fd_ids from the groups using Array.map
Filter products array. Check for the matching combination in filters node of products array. Condition is there should be a matching combination in each nodes of fdIdList array.
Working Fiddle
var groups = {
"2": [
{ "fd_id": 16, "fd_fRef": 2, "fd_ad": "35 - 50", "fd_siraNo": 255, "checked": true }
],
"3": [
{ "fd_id": 2, "fd_fRef": 3, "fd_ad": "KURU", "fd_siraNo": 255, "checked": true },
{ "fd_id": 3, "fd_fRef": 3, "fd_ad": "KARMA", "fd_siraNo": 255, "checked": true }
]
}
// Aggregates the list of fd_id s - This wil be an array of arrays
// [[16],[2,3]] => This will be the value
const fdIdList = Object.values(groups).map(a => a.map(b => b.fd_id));
var products = [
{
"id": 1,
"filters": [2, 3, 4, 13, 16, 17, 18, 19, 31, 48, 309, 318],
},
{
"id": 2,
"filters": [2, 3, 4, 13, 15, 17, 18, 19, 31, 48, 309, 318],
}
];
// Check if there is a common element in each node of fdIdList
var result = products.filter(item => {
const filters = item.filters;
if (filters) {
let isFound = true;
fdIdList.forEach(idListNode => {
isFound = isFound && idListNode.filter(value => filters.includes(value)).length > 0;
})
return isFound
}
});
console.log(result)

Sum every last index value with previous values in JS

I have an array:
array = {
"data": [
{ "value": [ 100, 13, 16 ] },
{ "value": [ 101, 14, 17 ] },
{ "value": [ 12, 15, 18 ] }
]
}
Which I am reformatting into a new array of just the columns:
const columnArray = jsonData.map( (current, index, arr) => {
let out = [];
for( let i = 0; i < current.value.length; i++ ) {
out.push( arr[ i ].value[ index ] );
}
return out;
});
// output
[
[ 100, 101, 12 ],
[ 13, 14, 15 ],
[ 16, 17, 18 ]
]
How would I re-write the columnArray mapping to do the column array and be able to sum from the previous value?
So the intended output from the original array would be:
[
[ 100, 201, 213 ],
[ 13, 27, 42 ],
[ 16, 33, 51 ]
]
I would also like the summing to be scalable (though it will always be in a 1:1 ratio). So if the data has 20 items, then each value will have 20 integers in that array too.
I have tried looping through but that didn't work as I only sum from the previous, not all the previous. And this wouldn't scale either:
const columnArray = jsonData.map( (current, index, arr) => {
let out = [];
for( let i = 0; i < current.value.length; i++ ) {
// dont touch first
if( i < 1 ) {
out.push( arr[ i ].value[ index ] );
} else {
out.push( arr[ i ].value[ index ] + arr[ i - 1 ].value[ index ] )
}
}
return out;
});
Instead of pushing the array element, add it to a variable accumulating the running totals, and push that.
const jsonData = [{
"value": [100, 13, 16]
},
{
"value": [101, 14, 17]
},
{
"value": [12, 15, 18]
}
];
const columnArray = jsonData.map((current, index, arr) => {
let out = [];
let total = 0;
for (let i = 0; i < current.value.length; i++) {
total += arr[i].value[index]
out.push(total);
}
return out;
});
console.log(columnArray);
or with a nested map():
const jsonData = [{
"value": [100, 13, 16]
},
{
"value": [101, 14, 17]
},
{
"value": [12, 15, 18]
}
];
const columnArray = jsonData.map((current, index, arr) => {
let total = 0;
return arr.map(el => total += el.value[index])
});
console.log(columnArray);
You're thinking this in the wrong way. You're storing the sum in the list, not anywhere else. So even tho your index is increasing, the resulting sum resides in the list, so to achieve your goal you have to save it in some variable then push the variable into the final list. Follow this code below:
const columnArray = array.data.map((current, index, arr) => {
let out = [];
let temp;
for (let i = 0; i < current.value.length; i++) {
// dont touch first
if (i < 1) {
temp = arr[i].value[index];
out.push(arr[i].value[index]);
} else {
temp = arr[i].value[index] + temp;
out.push(temp);
}
}
return out;
});
something like that...
const array0 = {
"data": [
{ "value": [ 100, 13, 16 ] },
{ "value": [ 101, 14, 17 ] },
{ "value": [ 12, 15, 18 ] }
]
}
const
rowCount = array0.data.reduce((c,{value})=>Math.max(c,value.length) ,0)
, arrResult = Array(rowCount).fill(0).map(x=>Array(array0.data.length).fill(0))
;
arrResult.forEach((_,i,arr)=>
{
array0.data[i].value.forEach((v,j)=>
{
arr[j][i] = v + (i? arr[j][i-1] : 0 )
})
})
console.log( arrResult)
.as-console-wrapper {max-height: 100%!important;top:0}

How to get the min, max, and sum of JSON data

I had JSON data that came back with single int values. With some changes, the values are now coming back as arrays of ints (as well as the original format).
{
"value": 10,
"value": 70,
"value": 30,
"value": 200
}
- and -
{
"value": [64, 13, 55, 34, 52, 43, 59, 20, 20],
"value": [10, 90, 20, 80, 30, 70, 60, 40, 50]
}
I had a formula that would return the min, max, and sum of the old version of JSON data. Now it doesn't work, and I can't figure out what would be the best way to re-write the function to handle the arrays. Or if its better to make a second function to handle just arrays and do a check if it is an int or array?
Is there a way that would return (from the numbers above):
// no value array, apply to all
[ 10, 200, 310 ] // min, max, sum
- and -
// for each of the value arrays
[ 23, 64, 360 ] // val 1 - min, max, sum
[ 10, 90, 450 ] // val 2 - min, max, sum
// input data
const value = document.querySelectorAll( "div" ).forEach( el => {
const contents = el.textContent, // get the text in the <div>
json = JSON.parse( contents ), // parse the data
jsonData = json.data; // get the data only
// normalise the data
// #from: https://stackoverflow.com/a/67294607/1086990
const normaliseData = arr => {
const data = arr.map(({ value }) => value);
return typeof arr[0].value === 'number' ? [data] : data;
};
// add into const
const valueArray = normaliseData( jsonData );
// get the min / max / sum
const minMaxSum = valueArray.forEach( e => {
return [
Math.min(...e),
Math.max(...e),
[...e].reduce((v, w) => v + w)
];
});
// output
console.log( minMaxSum );
});
<div>
{ "data": [ { "value": [64, 23, 45, 34, 52, 43, 59, 40] }, { "value": [10, 90, 20, 80, 30, 70, 60, 40, 50] } ] }
</div>
<div>
{ "data": [ { "value": 600 }, { "value": 70 }, { "value": 30 } ] }
</div>
Normalize the data by testing the type of the value of the first object in each array:
const valueInArray = [{ value: [64, 23] }, { value: [45, 34] }];
const valueAsSingle = [{ value: 600 }, { value: 70 }];
const normalizeData = arr => {
const data = arr.map(({ value }) => value);
return typeof arr[0].value === 'number'
? [data]
: data;
};
console.log(normalizeData(valueInArray));
//=> [ [ 64, 23 ], [ 45, 34 ] ]
console.log(normalizeData(valueAsSingle));
//=> [ [ 600, 70 ] ]
Now they are the same shape, and so you can treat them equally.
You can use Math.max and Math.min to find the maximum and minimum of the array then assign the values in the specific variables.
Currently, when you are using val.value it is consisting of the whole array and hence you also need to iterate over the array to find the max, min, or sum.
To find the sum use reduce on the val.value array and then add it in the acc[2].
// input data
const valueInArray = document.getElementById("valueInArray").innerHTML,
valueAsSingle = document.getElementById("valueAsSingle").innerHTML;
// parse
const jsonArray = JSON.parse( valueInArray ),
jsonNumber = JSON.parse( valueAsSingle ),
jsonArrayData = jsonArray.data,
jsonNumberData = jsonNumber.data;
// get numbers
const minMaxSumArray = jsonArrayData.reduce( ( acc, val ) => {
// smallest number
acc[0] = (
( acc[0] === undefined || Math.min(...val.value) < acc[0] ) ?
Math.min(...val.value) : acc[0]
)
// largest number
acc[1] = (
( acc[1] === undefined || Math.max(...val.value) > acc[1] ) ?
Math.max(...val.value) : acc[1]
)
// sum of numbers
acc[2] = (
acc[2] === undefined ?
val.value.reduce((v, w) => v + w) : val.value.reduce((v, w) => v + w) + acc[2]
)
console.log('answer', acc)
// return the array
return acc;
}, [] );
<div id="valueInArray">
{ "data": [ { "value": [64, 23, 45, 34, 52, 43, 59, 40] }, { "value": [10, 90, 20, 80, 30, 70, 60, 40, 50] } ] }
</div>
<div id="valueAsSingle">
{ "data": [ { "value": 10 }, { "value": 70 }, { "value": 30 } ] }
</div>
My take on it: first, create a single Array of all values (either arrays or single) by concatening them and using Array.flat() to flatten it. Then use a reducer to determine the sum and use Math.min/max for the min and max values.
// input data
const valuesInArray = JSON.parse(
document.querySelector("#valueInArray").textContent).data;
const singleValues = JSON.parse(
document.querySelector("#valueAsSingle").textContent).data;
// get all values from the objects to a single Array of values
// (so: convert all to single values)
const allValues = valuesInArray.map( v => v.value )
.concat(singleValues.reduce( (acc, val) => [...acc, +val.value], [] ) )
.flat();
// let's see what we have
console.log(`All values from both objects: ${JSON.stringify(allValues)}`);
// create sum, min and max
const [ sum, min, max, ] = [
allValues.reduce( (a, v) => a + +v, 0),
Math.min(...allValues),
Math.max(...allValues) ];
console.log(`From all values sum is ${sum}, min ${min} and max ${max}`);
div {
display: none;
}
<div id="valueInArray">
{ "data": [
{ "value": [64, 23, 45, 34, 52, 43, 59, 40] },
{ "value": [10, 90, 20, 80, 30, 70, 60, 40, 50] } ]
}
</div>
<div id="valueAsSingle">
{ "data":
[ { "value": 10 }, { "value": 70 }, { "value": 30 } ]
}
</div>
The second snippet aggregates data per value, where the single values are added as a values array to valuesInArray.
// input data
const valuesInArray = JSON.parse(
document.querySelector("#valueInArray").textContent).data;
const singleValues = JSON.parse(
document.querySelector("#valueAsSingle").textContent).data;
// create sum, min and max *per value*, in one go
const aggregatesAdded = valuesInArray
.concat({ value: singleValues.reduce( (acc, val) => [...acc, +val.value], [] ) } )
.reduce( (acc, val) => [...acc, {...val, aggregatedValues: {
sum: val.value.reduce( (a, v) => a + +v, 0 ),
min: Math.min(...val.value),
max: Math.max(...val.value) } }
], [])
document.querySelector("pre").textContent =
JSON.stringify({data: aggregatesAdded}, null, 2);
div {
display: none;
}
<div id="valueInArray">
{ "data": [
{ "value": [64, 23, 45, 34, 52, 43, 59, 40] },
{ "value": [10, 90, 20, 80, 30, 70, 60, 40, 50] } ]
}
</div>
<div id="valueAsSingle">
{ "data":
[ { "value": 10 }, { "value": 70 }, { "value": 30 } ]
}
</div>
<pre id="result"></pre>

how to sort a JSON list with two variables and switch the variable to sort

I'm setting up multiple lists with 2 values. Each value is important for sorting. I have added a Switch Button to change the sort logic for every list element.
How to sort each list separately when the ngchange function is triggered. I have no problems to sort arrays. But I didn't understand the sort logic of 2 variables in a string list.
HTML
<md-switch ng-model="data" ng-change="onChange(data)">
Sortierung: {{ message }}
</md-switch>
JS
var list = {
"languages": {
"deutsch": 542,
"französisch": 233,
"englisch": 64,
"italienisch": 43,
},
"keywordFacets": {
"Kunstschutz Frankreich": 52,
"Archivschutz": 20,
"Dépôts in Frankreich": 17,
"Bibliotheksschutz": 17,
},
"typFacets": {
"Akte": 619,
"Unterbestand / Aktengruppe": 97,
"Bestand": 22
},
"locationTypes": {
"Versailles": 5,
"Montecassino": 3,
"Marburg": 3,
"Aachen": 3,
},
"doctypFacets": {
"archivdokument": 738,
"person": 12,
"archiv": 10
}
}
if (cbState == true) {
var unordered = list.doctypFacets
const ordered = {};
Object.keys(unordered).sort().forEach(function (key) {
ordered[key] = unordered[key];
});
list.doctypFacets = ordered;
$scope.message = 'A-Z';
} else {
var unordered = list
const ordered = {};
Object.keys(unordered).sort().forEach(function (key, count) {
ordered[key, count] = unordered[key, count];
});
list.doctypFacets = ordered;
$scope.message = 'Count';
}
This is what I have so far. The Count sort doesn't work.
The result should be:
COUNT
var list = {
"languages": {
"deutsch": 542,
"französisch": 233,
"englisch": 64,
"italienisch": 43,
},
"keywordFacets": {
"Kunstschutz Frankreich": 52,
"Archivschutz": 20,
"Dépôts in Frankreich": 17,
"Bibliotheksschutz": 17,
....
}
A-Z
var list = {
"languages": {
"deutsch": 542,
"englisch": 64,
"französisch": 233,
"italienisch": 43,
},
"keywordFacets": {
"Archivschutz": 20,
"Bibliotheksschutz": 17,
"Dépôts in Frankreich": 17,
"Kunstschutz Frankreich": 52,
....
}

Categories