I am trying to read through an array of objects (that are start/end times) and combine two (or more) of those times if they are back to back.
ie. the first objects end time is the same as the next objects start time. If they are, combine them. Then check the newly combined objects end time with the next object in the array
Here is a simplified array of times:
var times = [
{
start: 1,
end: 2
},{
start: 2,
end: 3
},{
start: 4,
end: 5
},{
start: 6,
end: 7
},
]
I would like that (or have a diff Array) to output like the below:
var newTimes = [
{
start: 1,
end: 3
},{
start: 4,
end: 5
},{
start: 6,
end: 7
},
]
It gets trickier if there are 3 times in a row.
var threeTime = [
{
start: 1,
end: 2
},{
start: 2,
end: 3
},{
start: 3,
end: 5
},{
start: 6,
end: 7
},
]
The above should turn into:
var newThreeTimes = [
{
start: 1,
end: 5
},{
start: 6,
end: 7
},
]
The original array of times will always be sorted from oldest (smallest start time) to newest (largest start time). The output does not need to be in any specific order. All time objects will be moment times.
Can someone help me solve this?
This is the code i have come up with
function mergeArr(arr) {
// Sort the array in descending order
arr.sort(function(a, b) {
return b.start - a.start;
});
// Traverse from the top as you will need to remove the elements
// Merge the elements based on start of one and end of the previous
for (var i = arr.length - 1; i > 0; i--) {
if (arr[i].end == arr[i - 1].start) {
arr[i].end = arr[i - 1].end;
arr.splice(i - 1, 1);
}
}
// Sort it again in reverse order.
return arr.sort(function(a, b) {
return a.start - b.start;
});
}
Comments are making the code self explanatory.
Working Fiddle
Related
Consider I am having below array:
[{Id:'1' , Title: 'Group A' , Start: '100' , End:'200'} ,
{Id:'2' , Title: 'Group B' , Start: '350' , End:'500'} ,
{Id:'3' , Title: 'Group C' , Start: '600' , End:'800'} ]
I want to get unoccupied ranges between 100 and 999.
my required final result would be:
[{Start: '201' , End:'349'} ,
{Start: '501' , End:'599'} ,
{Start: '801' , End:'999'} ]
You need to iterate through each array item and find the prev unoccupied range and insert it in a new array. Then in the last item, you have to check if the last item occupies all space at the end up to maximum value. If not we need to add that range as well.
You can do the following using Array.reduce function,
let sortedArr = [{Id:'1' , Title: 'Group A' , Start: '100' , End:'200'} ,
{Id:'2' , Title: 'Group B' , Start: '350' , End:'500'} ,
{Id:'3' , Title: 'Group C' , Start: '600' , End:'800'} ];
const MIN = 100;
const MAX = 999;
let res = sortedArr.reduce((prev, curr, index, arr) => {
obj = {Start: 0, End: 0};
// checking if the first item has any unoccupied range before it's Start value.
if(index === 0 && Number(curr.Start) > MIN) {
obj = {Start: MIN, End: Number(curr.Start) - 1};
prev.push(obj);
} else if(index !== 0 && Number(arr[index - 1].End) + 1 < Number(curr.Start)) {
// If there is a range between the previous item's End and curr item's Start.
obj = {Start: Number(arr[index - 1].End) + 1, End: Number(curr.Start) -1};
prev.push(obj);
}
// checking if the last item has any range after it's End value.
if(index === arr.length -1 && Number(curr.End) < MAX) {
obj = {Start: Number(curr.End) + 1, End: MAX};
prev.push(obj);
} else {
return prev;
}
return prev;
}, [])
console.log(res);
Taking a back-to-basics approach: start with the full range as unoccupied, loop through each range in the data, if it starts inside an existing unoccupied range in the output (conversely, the first always will), cut that unoccupied range at the start of the data range and add a new unoccupied entry from the end of the data range to the end of the existing range that's being cut (actually clearer in code):
var range = [{ min: 100, max: 999 }];
var data = [
{Id:'1' , Title: 'Group A' , Start: 100 , End:200 },
{Id:'2' , Title: 'Group B' , Start: 350 , End:500 },
{Id:'3' , Title: 'Group C' , Start: 600 , End:800 }
];
for (let i=0; i<data.length; ++i) {
for (let r=0;r<range.length; ++r) {
// check if this data matches the existing data
// update existing data rather than create a new entry
if (range[r].min == data[i].Start) {
range[r].min = data[i].End+1;
break;
}
// check if this data sits inside the range
if (range[r].max > data[i].Start) {
// split the range, end one where data starts and add another where data ends
// (add the end part first before changing existing range[r].max)
range.push({ min: data[i].End+1, max: range[r].max})
range[r].max = data[i].Start-1;
break;
}
}
}
console.log(range);
If your data starts at the out-range start, then you'll need remove where max<min or put an explicit check in for this condition.
This uses the pre-requisites that the output data doesn't have any overlapping data (sort order doesn't matter, but output order will also not be sorted).
You can change min/max to Start/End in the output, kept them with different names for clarity.
I've also converted the data.Start/data.End to numeric, but you could add that inside the script if preferred (.Start*1 everywhere or your other preferred string-to-int conversion).
Here goes an example of how you can do it in javascript. You can use a for loop to iterate through the object and can create an row for each row in your input object by selecting End+1 from current row and Start-1 from next row. For the last row End will be 999.
output is your desired result.
var p = [
{Id:'1' , Title: 'Group A' , Start: '100' , End:'200'} ,
{Id:'2' , Title: 'Group B' , Start: '350' , End:'500'} ,
{Id:'3' , Title: 'Group C' , Start: '600' , End:'800'}
];
var i;
var output = [];
for (i = 0; i < p.length; i++) {
if (i + 1 < p.length)
output.push({ Start: Number(p[i].End) + 1, End: Number(p[i + 1].Start) - 1 });
else
output.push({ Start: Number(p[i].End) + 1, End: 999 });
}
console.log(output);
The console.log(output); you will get below output:
Array [Object { Start: 201, End: 349 }, Object { Start: 501, End: 599 }, Object { Start: 801, End: 999 }]
Here is another slightly more tolerant and generic approach that will allow any unsorted exclusion array (the exclusion ranges may even be overlapping):
const excl=[[400,450],[100,200],[350,500],[600,800],[150,250]],
sortedArr = [{Id:'1' , Title: 'Group A' , Start: '100' , End:'200'} ,
{Id:'2' , Title: 'Group B' , Start: '350' , End:'500'} ,
{Id:'3' , Title: 'Group C' , Start: '600' , End:'800'} ];
function allowedRanges(inc,excl){
var incl=[inc];
excl.forEach(x=>{
incl.forEach((r,i,a)=>{
if (x[0]<=r[1] && x[1]>=r[0])
a.splice(i,1,[r[0],x[0]-1],[x[1]+1,r[1]])
});
});
// now, remove the nonsensical ones
// and return the result:
return incl.filter(r=>r[1]>r[0]);
}
// test with an unsorted array excl:
console.log(allowedRanges([1,999],excl));
// test with sortedArr:
console.log(allowedRanges([101,999],sortedArr.map(e=>[+e.Start,+e.End])));
This question already has answers here:
Sorting an array of objects by property values
(35 answers)
Closed 2 years ago.
I have an array of object and want to reorder objects inside array Is there any way to do that?
var obj = [ {start: 12, end: 14}, {start: 2, end:8}]
I should check if start date of first object is greater than second object's start date and change objects order, The result in this case should be =>
var obj = [ {start: 2, end:8}, {start: 12, end: 14}]
I know about sorting inside object but cant find way to reorder the whole object.
Use your expeceted property to conduct compared function
var obj = [
{ start: 12, end: 14 },
{ start: 2, end: 8 },
]
const res = obj.sort((a, b) => a.start - b.start)
console.log(res)
I've got an example array that I'm trying to reduce by the counts of the occurrence of a key (sentiment in this example):
const sentimentLog = [
{
id: 1,
createdOn: new Date('2020-02-13'),
sentiment: 1
},
{
id: 2,
createdOn: new Date('2020-02-12'),
sentiment: 1
},
{
id: 3,
createdOn: new Date('2020-02-12'),
sentiment: 2
},
{
id: 4,
createdOn: new Date('2020-02-11'),
sentiment: 3
},
{
id: 5,
createdOn: new Date('2020-02-11'),
sentiment: 2
},
{
id: 6,
createdOn: new Date('2020-02-10'),
sentiment: 1
},
{
id: 7,
createdOn: new Date('2020-02-10'),
sentiment: 2
},
{
id: 8,
createdOn: new Date('2020-02-09'),
sentiment: 1
}
]
I'm using:
const sentimentGrouped = (sentiments) => {
return sentiments.reduce((hash, { sentiment }) => {
hash[sentiment] = (hash[sentiment] || 0) + 1
return hash
}, [])
}
And it's nearly there. What I can't figure out is how to replace undefined when there's no sentiment scores of 0 (which is a possibility).
console.log('sentimentGrouped', sentimentGrouped(sentimentLog))
The above produces:
"sentimentGrouped" [undefined, 4, 3, 1]
Whereas I'd like:
"sentimentGrouped" [0, 4, 3, 1]
What am I missing?
Thanks in advance.
Edit: I'll elaborate a bit further, there's 4 scores that will be returned (0 to 3). The data returned will be based on a date range. So there may be instances where there'll be no 1s returned, similarly no 3s returned by a different date range.
The issue is that if you never touch an element of the array, then it stays as a hole in the array, which means it's treated as undefined. Since you know the length of the array i would just prefill the array with zeros. Any sentiment score that does occur will be incremented. Any one that doesn't will stay with its initial value.
return sentiments.reduce((hash, { sentiment }) => {
hash[sentiment] = hash[sentiment] + 1
return hash
}, [0, 0, 0, 0])
I wrote a script to render a timeline with a dataset in javascript.
But i have to mark the area's where no info is found (yellow in the image).
I absolutely have no idea how to get the ranges without info in the dataset.
(i use momentjs for date calculations)
Example dataset:
data: [
{id: 1, lane: 1, start: 05-02-2006, end: 09-09-2008},
{id: 2, lane: 2, start: 01-01-2008, end: 31-07-2010},
{id: 3, lane: 3, start: 15-12-2013, end: 12-02-2016}
]
Example image:
I would create an new array and fill in the voids, something like this
var dates = [];
for (var i=0; i<data.length; i++) {
var now = data[i],
thisStart = parseDate(now.start),
thisEnd = parseDate(now.end),
prevEnd = data[i-1] ? parseDate(data[i-1].end) : null;
if ( prevEnd && prevEnd <= thisStart ) {
dates.push({id: i + 0.5, lane: 0, start: prevEnd, end: thisStart});
}
dates.push({id : now.id, lane: now.lane, start: thisStart, end: thisEnd});
}
and you'll end up with an array like this
dates : [
{id: 1, lane: 1, start: "2006-02-04T23:00:00.000Z", end: "2008-09-08T22:00:00.000Z"},
{id: 2, lane: 2, start: "2007-12-31T23:00:00.000Z", end: "2010-07-30T22:00:00.000Z"},
{id: 2.5, lane: 0, start: "2010-07-30T22:00:00.000Z", end: "2013-12-14T23:00:00.000Z"},
{id: 3, lane: 3, start: "2013-12-14T23:00:00.000Z", end: "2016-02-11T23:00:00.000Z"}
]
Note that I marked the object that fills the voids with an id if .5 and lane 0, you could do whatever you wanted there, as long as it's recognizable to you when you create the layout.
I also created a parseDate function, all it does is parse the dates for you to valid date objects, it's included in the fiddle below
FIDDLE
I'm using the Gridster plugin, and I need to capture the newly dragged order in an 1-5 manner. I know that we choose row first, then order (column). So, row:1, order:1 would be id: grid_74. The next closest order number greater than 1 in row 1 is 5, so id: grid_78. How can I accomplish this?
1 - grid_74
2 - grid_78
(etc...)
var gridinfo = gridster.serialize()
I think you might just need a sort.
// Just setting up object array to match example:
var gridinfo = [
{ id: "grid_75", order: 5, row: 4 },
{ id: "grid_74", order: 1, row: 1 },
{ id: "grid_91", order: 9, row: 1 },
{ id: "grid_85", order: 5, row: 7 },
{ id: "grid_78", order: 5, row: 1 }
]
// This sort is what does all the magic.. it first sorts by row, then order.
// I lifted this bit of javascript ninjutsu from:
// http://www.sitepoint.com/sophisticated-sorting-in-javascript/
gridinfo.sort(function(a, b)
{
if(a.row === b.row)
{
return a.order < b.order ? -1 : a.order > b.order ? 1 : 0;
}
return a.row - b.row;
});
// Display sorted array:
for(var k=0; k<gridinfo.length; k++)
{
document.write('id: '+gridinfo[k].id+'<br>');
document.write('row: '+gridinfo[k].row+'<br>');
document.write('order: '+gridinfo[k].order+'<br>');
document.write('-------<br>');
}