Merge two array of timelines into one - javascript

Hey guys I am currently stuck on a question.
I have to merge to timelines. Each timeline is given as an array of objects.
Currently my thought process was to concat then sort both inputs. After that, compare start and end times. Any help ?
The original question:
Write a function to merge two array timelines into one. If an objects value is different, your new value should be false.
I CREATED THIS TO VISUALLY UNDERSTAND THE QUESTION BETTER:
Timeline1:
// null 45 89 null
// <-----------||---------------------||----------------->
// true false true
Timeline2:
// null 67 null
// <-----------------------||---------------------------->
// true false
MergedTimeline:
// null 45 67 89 null
// <-----------||----------||---------||----------------->
// true false false false
Example inputs:
let timeline1 = [
{ start: null, end: 45, value: true },
{ start: 45, end: 89, value: false },
{ start: 89, end: null, value: false }
]
let timeline2 = [
{ start: null, end: 67, value: true },
{ start: 67, end: null, value: false }
]
//expected output
return [
{ start: null, end: 45, value: true },
{ start: 45, end: 67, value: false },
{ start: 67, end: 89, value: false },
{ start: 89, end: null, value: false }
]
Here is my current attempt:
const mergeTimeline = (arr1,arr2) =>{
let combine = arr1.concat(arr2)
let sortedTimeline= combine.sort((a,b)=>{
return a.start - b.start
})
const mergedTimeline = [sortedTimeline[0]];
for (let i = 1; i < sortedTimeline.length; i++) {
const currentTimeLine = sortedTimeline[I];
const lastMergedTime = mergedTimeline[mergedTimeline.length - 1]
if (lastMergedTime.value !== currentTimeLine.value) {
currentTimeLine.value = false
lastMergedTime.end = Math.max(lastMergedTime.end,currentTimeLine.start)
mergedTimeline.push(currentTimeLine)
} else{
mergedTimeline.push(currentTimeLine)
}
}
return mergedTimeline
}
mergeTimeline(timeline1,timeline2)

Related

Validating free time slot in schedule

I am trying to come up with an efficient algorithm to do the following, ideally in javascript or golang:
Given a set of busy time intervals (start timestamp + end timestamp in ms), validate an incoming timeslot to be scheduled. Assume there aren't any existing overlaps.
Example:
const timeslots = [
{ start: 10, end: 14 },
{ start: 17, end: 21 },
{ start: 30, end: 37 },
// ...
];
const ts = timeslots.sort((a, b) => {
return a.start - b.start;
});
const checkValid = (inc) => {
// validation here
};
console.log(checkValid({ start: 15, end: 16 })); // should be true
console.log(checkValid({ start: 15, end: 18 })); // should be false
console.log(checkValid({ start: 16, end: 27 })); // should be false
console.log(checkValid({ start: 8, end: 39 })); // should be false
And any other edge cases not described should work as well.
If the intervals are (1) non-overlapping and (2) sorted then you can perform a binary search to find the first one that lies just after the desired interval, and then simply check if the previous one overlaps with it. This would be O(logN) instead of O(N).
Example in Go:
package main
import (
"fmt"
"sort"
)
type Slot struct {
start int
end int
}
var timeslots = []Slot{
{start: 10, end: 14},
{start: 17, end: 21},
{start: 30, end: 37},
}
func checkValid(slot Slot) bool {
i := sort.Search(len(timeslots), func(i int) bool {
return timeslots[i].start > slot.end
})
return i == 0 || timeslots[i-1].end < slot.start
}
func main() {
sort.Slice(timeslots, func(a, b int) bool {
return timeslots[a].start < timeslots[b].start
})
fmt.Println(checkValid(Slot{start: 15, end: 16})) // should be true
fmt.Println(checkValid(Slot{start: 15, end: 18})) // should be false
fmt.Println(checkValid(Slot{start: 16, end: 27})) // should be false
fmt.Println(checkValid(Slot{start: 8, end: 39})) // should be false
}
validate(start, end)
for timeslot in timeslots
if start >= timeslot["start"] and start <= timeslot["end"]
return false
if start < timeslot["start"] and end >= timeslot["start"]
return false
if end >= timeslot["start"] and end <= timeslot["end"]
return false
return true
I think something like this should work.

How can I access a specific attribute of an object that is within an array of objects? [duplicate]

This question already has answers here:
How to iterate (keys, values) in JavaScript? [duplicate]
(11 answers)
Closed last year.
I have a function called "howmanyOnline" that receives an object users.
Each user has a property online which is a boolean. It should return the number of users with the property online equal to true.
For example
How many Online (users) returns 2
this the object of object:
let users = {
Victor: {
age: 33,
online: true
},
joan: {
age: 25,
online: true
},
frank: {
age: 25,
online: false
},
Emy: {
age: 24,
online: false
}
};
function howmanyOnline(users) {
}
How can I do this?
There are lots of ways you can do this, but the most common are one of either:
Use array.filter after grabbing the values from the object to filter down to only the users who are online, and then take the count of that array. This method takes a callback function, and is the more 'javascripty' way of doing this.
const values = {one: {a: 1}, two: {a: 2}, three: {a: 3}};
const arr = Object.values(values);
// filter down to only the elements which a value >= 2 under key 'a'
const filtered = arr.filter(obj => obj.a >= 2);
filtered.length; // 2
Start with a variable initialized to 0, then map or otherwise iterate across the object and increment the variable by 1 for every user who is online.
let count = 0;
const values = {one: {a: 1}, two: {a: 2}, three: {a: 3}};
// check if the value under key 'a' is >= 2, if so add 1 to count,
// otherwise add 0
for(value in values){count += values[value]['a'] >= 2 ? 1 : 0};
count; // 2
let users = {
Victor: {
age: 33,
online: true
},
joan: {
age: 25,
online: true
},
frank: {
age: 25,
online: false
},
Emy: {
age: 24,
online: false
}
};
function howmanyOnline(users) {
let count = 0;
for (const { online } of Object.values(users)) {
if (online) count += 1;
}
return count;
}
console.log(howmanyOnline(users));
This should work by using a for loop to get the online value and a if statement to check if it is true.
let users = {
Victor: {
age: 33,
online: true
},
joan: {
age: 25,
online: true
},
frank: {
age: 25,
online: false
},
Emy: {
age: 24,
online: false
}
};
let number =0
function howmanyOnline(users) {
for(let value of Object.values(users)){
if(value.online)
number++
}
return number
}
console.log(howmanyOnline(users))

how to assign object in object

how to assign the object in object and filter the value which pass and fail;
the input is:
[
{
name: 'John',
score: 90,
time: 'evening'
},
{
name: 'Doni',
score: 68,
time: 'morning'
},
{
name: 'Jiu',
score: 50,
time: 'evening'
},
{
name: 'Shin',
score: 92,
time: 'morning'
},
];
and i want the output like this :
{
"evening": {
"pass": [
{
"name": "John",
"score": 90
}
],
"fail": [
{
"name": "jiu",
"score": 50
}
]
},
"morning": {
"pass": [
{
"name": "Shin",
"score": 92
}
],
"fail": [
{
"name": "Doni",
"score": 68
}
]
}
}
do we need to use Object.assign for this ? and how many loop we use for this ??
i do love to know how to add another string in the object beside that ouput,
thanks
There's a lot of ways to do this. The simplest is probably to make a base object that represent your empty results. Then loop over the students and fill the arrays:
let students = [{name: 'John',score: 90,time: 'evening'},{name: 'Doni',score: 68,time: 'morning'},{name: 'Jiu',score: 50,time: 'evening'},{name: 'Shin',score: 92,time: 'morning'},];
// Empty case
let base = {
"evening": {"pass": [], "fail": []},
"morning": {"pass": [], "fail": []}
}
const PASSING = 70
students.forEach(({name, score, time}) => {
let key = score >= PASSING ? 'pass' : 'fail'
base[time][key].push({name, score})
})
console.log(base)
This makes is easy to have empty arrays, which is probably what you want if there are no students in a particular category.
EDIT based on comment:
To support arbitrary times, you can just create the times on the object as you find them. reduce() is good for this, but you could also use a regular loop. For example with an added afternoon time:
let students = [{name: 'Mark',score: 95,time: 'afternoon'}, {name: 'John',score: 90,time: 'evening'},{name: 'Doni',score: 68,time: 'morning'},{name: 'Jiu',score: 50,time: 'evening'},{name: 'Shin',score: 92,time: 'morning'},];
const PASSING = 70
let result = students.reduce((obj, {name, score, time}) => {
if (!obj[time]) obj[time] = {'pass': [], 'fail': [] }
let key = score >= PASSING ? 'pass' : 'fail'
obj[time][key].push({name, score})
return obj
}, {})
console.log(result)
You can do something like this:
const data = [{ name: 'John', score: 90, time: 'evening' }, { name: 'Doni', score: 68, time: 'morning' }, { name: 'Jiu', score: 50, time: 'evening' }, { name: 'Shin', score: 92, time: 'morning' }, ];
const grp = (d, p) => d.reduce((r,c) => (r[c[p]] = [...r[c[p]] || [], c], r), {})
const grpV = (d, rng) => d.reduce((r,{name, score}) => {
let key = score > rng ? 'pass' : 'fail'
r[key] = [...r[key] || [], {name, score}]
return r
}, {})
const r = Object.entries(grp(data, 'time')).map(([k,v]) => ({[k]: grpV(v, 75)}))
console.log(r)
The idea is the group 2 times one on the time and 2nd on the score.
grp: function to group by a property (in this case 'time') which returns an object with 2 properties: evening and morning each of which is an array containing the classes.
grpV: function to group by value (in this case 75) which returns an object with 2 properties: pass and fail each of which is an array containing the classes.
On the end once we have those tools we are saying ... give me the entries of the grouped by time object and for each of the groups ... group by score.
Here how something like this could look like if we ware using lodash:
const data = [{ name: 'John', score: 90, time: 'evening' }, { name: 'Doni', score: 68, time: 'morning' }, { name: 'Jiu', score: 50, time: 'evening' }, { name: 'Shin', score: 92, time: 'morning' }, ];
const partition = (x, p) => _(x)
.partition(y => y.score > p)
.map((x,i) => ({ [i==0 ? 'pass': 'fail']: _.omit(x[0], 'time')}))
.value()
const r = _(data)
.groupBy('time')
.mapValues(x => partition(x, 75))
.value()
console.log(r)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
Adding it as an example since it does help with readability of what the ES6 example is doing to some extend.
I'm sure there are more elegant ways to do this. But this one is probably one of the simplest beginner-friendly ways you can go about this.
I loop through the input array, check the existence of the .time values as keys on the output object and create the pass and fail keys. Then evaluate the .score against the passingScore and push the necessary data to it.
Should be pretty easy to understand once you see and try the code below:
const data = [
{name: 'John',score: 90, time: 'evening'},
{name: 'Doni',score: 68, time: 'morning'},
{name: 'Jiu',score: 50, time: 'evening'},
{name: 'Shin',score: 92, time: 'morning'},
{name: 'Fubar',score: 75, time: 'noon'},
];
function formatData(data){
const passingScore = 75;
const output = {};
data.forEach(function(item){
if(!output[item.time]) output[item.time] = {pass: [], fail: []};
const stud = { name: item.name, score: item.score };
if(item.score >= passingScore) output[item.time]['pass'].push(stud)
else output[item.time]['fail'].push(stud)
});
return output;
}
console.log(formatData(data));

Change index in array.map

I have a variable of the form:
var data=[
{
start:22,
end: 8
},
{
start:60,
end: 43
},
{
start: 35,
end: 55
},
{
start:25,
end:40
}
];
I want to map it to look like this
var newData = { 22:8, 60:43, 35:55, 25:40};
Is this possible? I mainly just want to use the start numbers as a key to access the end numbers without using search. I have tried to do this:
var mapData = data.map(function(data){
var x = {};
x[data.start]=data.end;
return x;
});
but it gives:
0
:
{22: 8}
1
:
{60: 43}
2
:
{35: 55}
3
:
{25: 40}
which means i have to use 0, 1,2, 3 as indices.
Only Array#map does not work in this case, because without post processing, you get a single array with objects. You need to combine all objects into a single object.
With Object.assign and spread syntax ..., you get a single array with all properties from the objects in the array.
var data = [{ start: 22, end: 8 }, { start: 60, end: 43 }, { start: 35, end: 55 }, { start: 25, end: 40 }],
result = Object.assign(...data.map(({ start, end }) => ({ [start]: end })));
console.log(result);
You can use array.reduce:
var data=[
{
start:22,
end: 8
},
{
start:60,
end: 43
},
{
start: 35,
end: 55
},
{
start:25,
end:40
}
];
var res = data.reduce((m, o) => {
m[o.start] = o.end;
return m;
}, {});
console.log(res);

Merging two multidimensional arrays based on matching keys?

So currently I am generating records that should be grouped together to form the following JSON:
{
"Monday": [{
"Index": "1",
"To": "200",
"From": "1200"
},
{
"Index": "2",
"To": "1300",
"From": "1400"
}
],
"Tuesday": [{
"Index": "1",
"To": "100",
"From": "200"
},
{
"Index": "2",
"To": "1000",
"From": "1200"
},
{
"Index": "3",
"To": "1300",
"From": "1500"
}
]
}
But currently, the output looks more like this:
As you can see, two records that share the same "Index" value are being created- this is mainly because I have yet to figure out how to do a where index = <index> insert to handle finding records with matching indexes and adding the properties to it.
Currently, the code looks like this-
var controlsJson;
//Assume two loops are running through different sets of values for "type". setting them to "To" and "From" pragmatically.
//Array building
if (controlsJson == null) {
controlsJson = [];
}
if (controlsJson[date] == null) {
controlsJson[date] = [];
}
var pusher = [];
pusher["Index"] = index;
pusher[type] = time;
controlsJson[date].push(pusher);
Is there any other ways I can improve this aside from solving the issue with matching index keys?
This would be a lot easier to traverse if you instead represent your data in the following format:
let state = {
Monday: {
1: {
To: 200,
From: 1200
},
2: {
To: 1300,
From: 1400
}
},
Tuesday: {
1: {
To: 100,
From: 200
},
2: {
To: 1000,
From: 1200
},
3: {
To: 1300,
From: 1500
}
}
}
state = { ...state, Tuesday: { ...state.Tuesday, 2: { To: 1500, From: 1400 } } }
console.log(state)
If you do not care about immutability, the following would also work:
let state = {
Monday: {
1: {
To: 200,
From: 1200
},
2: {
To: 1300,
From: 1400
}
},
Tuesday: {
1: {
To: 100,
From: 200
},
2: {
To: 1000,
From: 1200
},
3: {
To: 1300,
From: 1500
}
}
}
state["Tuesday"][2] = { To: 1500, From: 1400 };
console.log(state)
As cchamberlain pointed out, I was approaching indexes in a much more complicated manner than was really necessary.
I modified my code to the following and using an immutable approach (I think that's what it is called), records can be added pragmatically just by using an index!
//Array building
if (controlsJson == null) {
controlsJson = [];
}
if (controlsJson[date] == null) {
controlsJson[date] = [];
}
if (controlsJson[date][index] == null) {
controlsJson[date][index] = [];
}
controlsJson[date][index][type] = time;
}
Also note that if you wish to use JSON.Stringify, you will need to change the = [] into = {}
Again- if you fell I can further improve this code, let me know.
If for each day the index will start with 0, this little snippet should do the trick:
// your object that you start with
var days = {
'Monday': [
{Index: 0, Start: 330},
{Index: 0, End: 1330},
{Index: 1, Start: 330},
{Index: 1, End: 1330},
{Index: 2, Start: 330},
{Index: 2, End: 1330},
],
'Tuesday': [
{Index: 0, Start: 330},
{Index: 0, End: 1330},
{Index: 1, Start: 330},
{Index: 1, End: 1330},
{Index: 2, Start: 330},
{Index: 2, End: 1330},
{Index: 3, Start: 330},
{Index: 3, End: 1330},
],
};
// create an array of indexes. For each index, filter days by that Index,
// then pass those to Object.assign to merge the 2 items together.
var i, dayMaxIndex;
for (i in days) {
if (days.hasOwnProperty(i)) {
dayMaxIndex = Math.max.apply(null, days[i].map(function(d) { return d.Index; }));
days[i] = Array.from({length: dayMaxIndex+1}, (v, i) => i).map(function(d) {
return Object.assign.apply(null, days[i].filter(function(day) {
return day.Index == d;
}));
});
}
}
console.log(days);
You can ready more about Object.assign here.
I found this piece was a little confusing to work with:
Array.from({length: dayMaxIndex+1}, (v, i) => i)
it generates an array of sequential numbers, starting with 0 and ends at dayMaxIndex. Here's where I found this example.

Categories