Merging multiple duplicate objects into one from JavaScript array - javascript

My JSON file looks like the following, somewhere around 1000-2000 objects.
[{
"date": "2015-01-25T22:13:18Z",
"some_object": {
"first_group": 20,
"second_group": 90,
"third_group": 39,
"fourth_group": 40
}
}, {
"date": "2015-01-25T12:20:32Z",
"some_object": {
"first_group": 10,
"second_group": 80,
"third_group": 21,
"fourth_group": 60
}
}, {
"date": "2015-02-26T10:53:03Z",
"some_object": {
"first_group": 12,
"second_group": 23,
"third_group": 13,
"fourth_group": 30
}
}]
After copying it in an array I need to perform the following manipulation on it:
First. Remove duplicate objects. 2 objects are considered the same if they have the same date (without taking the time into consideration). So in my JSON, the first two objects are considered the same. Now the tricky part is that when a duplicate is found, we shouldn't just randomly remove one of them, but merge (not sure if merge is the right word) the fields from some_object, so it becomes one object in the array. Therefore, with the JSON above, the first two objects would become one:
{
"date": "2015-02-26T00:00:00Z",
"some_object": {
"first_group": 30, //20+10
"second_group": 170, //90+80
"third_group": 60, //39+21
"fourth_group": 100 //40+60
}
}
Even trickier is that there could be some 3-10 objects with the same date, but different time in the array. Therefore those should be merged into 1 object according to the rule above.
Second. Sort this array of objects ascending (from oldest to newest of the date field).
So what's so hard? Where did you get stuck?
I found out how to sort the array ascending (based on date) by using this and some of this.
But I have no idea how to do the first point of removing the duplicates and merging, in a time-efficient manner. Maybe something inside:
var array = [];//reading it from the JSON file
var object_date_sort_asc = function (obj1, obj2) {
if (obj1.date > obj2.date) return 1;
if (obj1.date < obj2.date) return -1;
//some magic here
return 0;
};
array.sort(object_date_sort_asc);
Any ideas?

Use an object whose properties are the dates, to keep track of dates that have already been seen, and the values are the objects. When you encounter a date that's been seen, just merge the elements.
var seen = {};
for (var i = 0; i < objects.length; i++) {
var cur = objects[i];
if (cur.date in seen) {
var seen_cur = seen[cur.date];
seen_cur.some_object.first_group += cur.some_object..first_group;
seen_cur.some_object..second_group += cur.some_object..second_group;
...
} else {
seen[cur.date] = cur;
}
}
Once this is done, you can convert the seen object to an array and sort it.
var arr = [];
for (var k in seen) {
arr.push(seen[k]);
}

To remove duplicate objects, you can loop through your array using .map(). In each iteration, you push the dates, parsed using some simple regex (which removes the time), into an array—if and only if it is not present in the array to begin with:
If it is not in the array, push into array (of unique dates) and return the object
If it is in the array, do nothing
The logic above can be described as the following, assuming your array is assigned to the data variable:
// Remove duplicates
var dates = [];
var data_noDupes = $.map(data, function(item){
var item_date = item.date.replace(/([\d\-]+)T.*/gi, '$1');
if (dates.indexOf(item_date) === -1) {
dates.push(item_date);
return item;
}
});
This should remove all recurring instances of the same date.
With regards to the second part: to sort, you simply sort the returned array by the date, again parsed using some simply regex that removes the time:
// Sort data_noDupes
function sortByDate(a, b){
var a_item_date = a.date.replace(/([\d\-]+)T.*/gi, '$1'),
b_item_date = b.date.replace(/([\d\-]+)T.*/gi, '$1');
return ((a_item_date < b_item_date) ? -1 : ((a_item_date > b_item_date) ? 1 : 0));
}
If you want to be extra safe, you should use momentjs to parse your date objects instead. I have simply modified how the dates are parsed in the functional example below, but with exactly the same logic as described above:
$(function() {
var data = [{
"date": "2015-02-26T10:53:03Z",
"some_object": {
"first_group": 12,
"second_group": 23,
"third_group": 13,
"fourth_group": 30
}
}, {
"date": "2015-01-25T12:20:32Z",
"some_object": {
"first_group": 10,
"second_group": 80,
"third_group": 21,
"fourth_group": 60
}
}, {
"date": "2015-01-25T22:13:18Z",
"some_object": {
"first_group": 20,
"second_group": 90,
"third_group": 39,
"fourth_group": 40
}
}];
// Remove duplicates
var dates = [];
var data_noDupes = $.map(data, function(item) {
// Get date and format date
var item_date = moment(new Date(item.date)).format('YYYY-MM-DD');
// If it is not present in array of unique dates:
// 1. Push into array
// 2. Return object to new array
if (dates.indexOf(item_date) === -1) {
dates.push(item_date);
return item;
}
});
// Sort data_noDupes
function sortByDate(a, b) {
var a_item_date = moment(new Date(a.date));
return ((a_item_date.isBefore(b.date)) ? -1 : ((a_item_date.isAfter(b.date)) ? 1 : 0));
}
data_noDupes.sort(sortByDate);
console.log(data_noDupes);
$('#input').val(JSON.stringify(data));
$('#output').val(JSON.stringify(data_noDupes));
});
body {
padding: 0;
margin: 0;
}
textarea {
padding: 0;
margin: 0;
height: 100vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea id="input"></textarea>
<textarea id="output"></textarea>

Related

How to compare if two dates are closer to each other

I want to check if two dates are within a range of each other, and reorder my array. I need to compare the dates from an array with the current date.
I have this:
var currentDate = new Date(); /* current date = 2021/08/18 */
listOfObjects = [ { "user": "John", "date": "2021-08-20" }, { "user": "Bob", "date": "2021-08-17" }, { "user": "Joe", "date": "2021-08-09" } ]
The return shoud be like this:
[ { "user": "Bob", "date": "2021-08-17" }, { "user": "John", "date": "2021-08-20" }, { "user": "Joe", "date": "2021-08-09" } ]
In JavaScript, an array is sorted by sorting the textual representation of its items.
The default sort order is ascending, built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values. (Source: Array.prototype.sort() - JavaScript | MDN)
See following example:
const numbers = [1, 30, 4, 21, 100000];
numbers.sort();
console.log(numbers);
We see that the output is the array with alphabetically sorted numbers:
1, 100000, 21, 30, 4
In most cases, this is not what we want (or what most people expect). To sort numbers numerically, we pass a custom compare function to sort:
function i_cmp(a, b) {
let d = a-b;
if (d < 0)
return -1;
if (d > 0)
return 1;
return 0;
}
numbers.sort(i_cmp);
console.log(numbers);
output:
1,4,21,30,100000
To sort an array by a criterion that depends on further conditions, it's handy to pass a function bound to runtime values that is created by another function. Here we sort items by their absolute distance of a fixed value x.
function d_cmp(x) {
return function(a, b) {
let d = Math.abs(a-x)-Math.abs(b-x);
if (d < 0)
return -1;
if (d > 0)
return 1;
return 0;
}
}
numbers.sort(d_cmp(50));
console.log(numbers);
output:
30,21,4,1,100000
Hemera already answered how to get date distances. The rest, accessing date attributes, should be easy to implement.
For a live demo of above code (combined) see: https://ideone.com/e7DaOx
You can subtract the dates and compare the results. Like new Date("2021-08-18") - new Date("2021-08-17") = 86400000 cause dates are saved as milliseconds from a random but standardize reference date.
Then you can use this difference by using Math.abs(number) as a condition for finding the nearest dates to the given one and put it in a simple sorting function like below:
function orderByDateDistance(nDate, nList){
// easy sorting by finding the current min (also working with max)
for(let tA=0;tA<nList.length-1;tA++){ // iterating over all except the last will be sorted
let tIndex = tA; // current index
let tDifference = Math.abs(nDate-new Date(nList[tA]["date"])); // current difference
for(let tB=tA+1;tB<nList.length;tB++){ // iterating over unsorted list part
if(Math.abs(nDate-new Date(nList[tB]["date"])) < tDifference){ // compare current difference with stored
tIndex = tB; // save index
tDifference = Math.abs(nDate-new Date(nList[tB]["date"])); // save value optional
}
}
// change items
let tBuffer = nList[tA]; // save current object
nList[tA] = nList[tIndex]; // copy next lowest object
nList[tIndex] = tBuffer; // copy buffered object
}
return nList; // optional returning
}
// Your example
console.log(
orderByDateDistance(
new Date("2021/08/18"),
[
{"user": "John", "date": "2021-08-20"},
{"user": "Bob", "date": "2021-08-17"},
{"user": "Joe", "date": "2021-08-09"}
]
)
);

I want to add a new variable called Averagetimespent in the details obj inside the following javascript array. Could anyone help me out with this?

Following is the javascript array that I am using to populate an extra field called AverageTimeSpent. But somehow I am not able to convert it into the right format and add it accordingly.
-I tried different options. These are seen in the comments that I have added among the lines of code for entering the data.
Tried converting the array into a string and parsing it and the console loggin it and also vice versa.
None of them seem to be working
var Truck_data = [
{
date: "15/12/19",
details: [
{
driver_name: "ram",
truck_number: "KA 03 2132",
distance_travelled: 50,
orders_delivered: 45,
orders_missed: 10
},
{
driver_name: "raju",
truck_number: "KA 03 2354",
distance_travelled: 30,
orders_delivered: 15,
orders_missed: 1
}
{
date: "12/12/19",
details: [
{
driver_name: "ram",
truck_number: "KA 03 2132",
distance_travelled: 50,
orders_delivered: 65,
orders_missed: 20
},
{
driver_name: "raju",
truck_number: "KA 03 2354",
distance_travelled: 30,
orders_delivered: 65,
orders_missed: 34
}
]
}
]
for (var j in Truck_data) {
console.log(i);
for (var i in Truck_data.details) {
var temp = Math.random() * 100;
// Truck_data.details[i].Avg_time_spent = 0;
// Truck_data.details[i].Avg_time_spent = temp;
Truck_data.details[i].push(Avg_time_spent, temp);
console.log(Truck_data);
//TruckOrder.push(array);
}
}
Your for loop should be something like this:
for (var j of Truck_data) {
var innerDetails = j.details;
for (var i of innerDetails) {
var temp = Math.random() * 100;
i['Avg_time_spent'] = temp;
}
}
Previously you're using 'for in' and you should use 'for of' for what you're trying to do.
See this

Data structure/format of C3 data using JavaScript

I am trying to get my array of objects in the format that is needed to create a C3 bar chart but I am having trouble categorizing my data with JavaScript. Below is my JavaScript code.
data = [
{Service:"Army",Permanent:20,Itinerant:754,Region:"Western"},
{Service:"Air Force",Permanent:100,Itinerant:2,Region:"Eastern"},
{Service:"Army",Permanent:10,Itinerant:7,Region:"Western"},
{Service:"Air Force",Permanent:30,Itinerant:2,Region:"Eastern"}
]
var sumAry=[];
for (var x=0; x<data.length; x++){
var val =sumAry.indexOf(data[x].Service);
if(val === -1){
var permanent += data[x].Permanent;
sumAry.push(data[x].Service, permanent);
}else{
console.log("IN");
}
}
https://codepen.io/isogunro/pen/GYRKqE?editors=0012
The C3 chart looks for a data in the structure/format shown below:
['Army', 30],
['Airorce', 130],
['Navy', 190],
['Army1', 70],
['Airorce2', 130],
['Navy3', 100]
For each of the values, the 'Permanent' property will be added up to get the number part of the array. It becomes an aggregate of all the information.
Assuming the number in the preferred format comes from the Permanent property, you could use Array.map to transform your dataset.
var data = [{
Service: "Army",
Permanent: 654,
Itinerant: 754,
Region: "Western"
},
{
Service: "Air Force",
Permanent: 9,
Itinerant: 2,
Region: "Eastern"
},
{
Service: "Army",
Permanent: 6,
Itinerant: 7,
Region: "Western"
},
{
Service: "Air Force",
Permanent: 9,
Itinerant: 2,
Region: "Eastern"
}
];
var aggregates = data.map(function (o) {
// map original objects to new ones with zeroed-out data
return {
Service: o.Service,
Permanent: 0,
}
}).filter(function (o, index, a) {
// filter the list to have only unique `Service` properties
var service = o.Service;
var i;
for (i = 0; i < a.length; i += 1) {
if (a[i].Service === service) {
// break out of the loop when the first matching `Service` is found.
break;
}
}
// `i` is now the index of the first matching `Service`.
// if it's the first occurrence of that `Service`, keep it, otherwise ditch it.
return i === index;
});
data.forEach(function (o) {
// loop through the aggregate list and get the matching `Service` property.
var agg = aggregates.filter(function (p) {
return o.Service === p.Service;
})[0]; // first element is the match.
// sum the `Permanent` properties.
agg.Permanent += o.Permanent;
});
// now that the data has been aggregated, transform it into the needed structure.
var c3data = aggregates.map(function (d) {
return [d.Service, d.Permanent];
});
console.log(JSON.stringify(c3data));

How do i get rid of extra space in the front and back of a array when select certain number to print

I want to return an array containing all the elements of the array located at the given key that are equal to ten. It should print out [10, 10] but the result of my code is [ 10, 10 ] with extra space in the front and back or my code is just wrong.
var obj = {
key: [1000, 10, 50, 10],
key2: [],
key3: "abc"
};
function isValid(obj, key) {
var result = [];
if (!obj.hasOwnProperty("key") ||
!Array.isArray(obj[key]) ||
obj[key].length === 0) {
return []; //return empty array if those condition meet.
}
else {
for (var i = 0; i < obj[key].length; i++) {
if (obj[key][i] === 10) {
result.push(obj[key][i]); //push the 10 to result empty array.
}
}
return result;
}
}
var output = isValid(obj, 'key');
console.log(output); // --> should print out [10, 10]
It may be a problem with your browser. Space seperating contents in an array automatically removed when you iterate through it. Some browsers add a space between each entry for visual improvement. As you are using a number array, the space will not affect your normal code execution.

Iterating nested object in JavaScript withoud jQuery

I'm trying to iterate through a nested object.
array=[
{
id: 2,
items: [
{
id: 12
},
{
id: 13
},
{
id: 14
}
]
},
{
id: 3,
items: [
{
id: 15
},
{
id: 16
},
{
id: 17
},
{
id: 18
},
{
id: 19
}
]
},
{
id: 4,
items: [
{
id: 20
},
{
id: 21
}
]
},
{
id: 5,
items: [
{
id: 22
}
]
}
];
I need to push all the ID into one array in order that we can see in the code above. Something like this:
arrayOfId = [2, 12, 13, 14, 3, 15, 16, 17, 18, 19, 4, 20, 21, 5, 22];
I tried to do it by myself and found some examples here, but they are based on jQuery. I use Angular in my project.
Maybe someone knows solution of this issue with plain JS or Angular?
Thanks a lot.
It's actually very simple.
get the length of the array.
loop through the array and push a new array (ids) with the current id value.
get the length of the nested array.
loop through that and push the nested ids also in that array.
var l=array.length,i=0,ids=[];
for(;i<l;i++){
ids.push(array[i].id);
var nested=array[i].items,nl=nested.length,j=0;
for(;j<nl;ids.push(nested[j++].id));
};
console.log(ids);
alert(ids);
//ids=[2,12,13,14,3,15,16,17,18,19,4,20,21,5,22];
In this example i show various ways to write the for loop.
Also by caching the length and other variables like the nested array, the performance is increased. I also like to mention, that for simple functions like this, where you have to LOOP through multidimensional arrays/objects,
the use of forEach,map,filter or other "new" native javascript functions is discouraged, as there is a heavy performance loss over the standard while & for loop (1).They are about 3-4 times slower. At the other side, if you know that your multidimensional array is not that big and the clients use modern browsers the map solution is the "short code" alternative (2).
DEMO
http://jsfiddle.net/axaog3n2/1/
1 https://jsperf.com/for-vs-foreach/2
2 https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/map
if you have any questions just ask.
You can use a for loop inside of another for loop.
for (var i = 0; i < array.length; i++) {
arrayOfId.push(array[i].id);
for (var j = 0; j < array[i].length; j++) {
arrayOfId.push(array[i][j]);
}
}
Use Array.map
//This will give a 2d array of ids [[2,3],[2,3]];
var ids = array.map(function(item) {
return item.items.map(function(obj) {
return obj.id
})
});
//Now flatten and merge
var idList = [];
idList = idList.concat.apply(idList , ids);
//Output:
//[12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]
Demo: http://jsfiddle.net/2kmozrr7/
simple for loop
var arrayOfId = [];
for (var i = 0; i < array.length; i++) {
arrayOfId.push(array[i].id);
if (array[i]['items'] != undefined && array[i].items.length > 0) {
for(var j = 0; j < array[i].items.length; j++) {
arrayOfId.push(array[i].items[j].id);
}
}
}
var ids = [];
for (var x = 0; x < array.length; x++) {
ids.push(array[x].id);
if (array[x].items) {
var items = array[x].items;
for (var y = 0; y < items.length; y++) {
if (items[y].id) {
ids.push(items[y].id);
}
}
}
}
Any more nested than that, you'll need to use a recursive function to do it sanely.
var results = [];
function build(array, results) {
for (var x = 0; x < array.length; x++) {
results.push(results[x].id);
if (results[x].items) {
build(results[x].items, results);
}
}
}
build(thatObject, results);
// create array for result
var b = [];
// iterate over the source array
array.forEach(function (outer) {
// get the id property and push the value to the result array
b.push(outer.id);
// iterate over the items
outer.items.forEach(function (inner) {
// get the id property of the inner array object and push the value to the result array
b.push(inner.id);
});
});

Categories

Resources