Removing items from an array in both directions - javascript

I have an array of times (these are not constant) [1:00, 2:00, 3:00, 4:00, 5:00, 6:00, 7:00]. The goal is to calculate times that can be bookable. If there is an existing booking from 4:00 - 5:00 then 3:00 should be unavailable as it would overlap the existing booking. To calculate this, I have a function that tells us the start and end indexes of the booking, I need to find a way to remove x times from behind the start index.
To make it more clear, I drew a diagram.
Doing this calculation will allow to calculate available times no matter how long the existing booking is. I'm trying to figure out how to create a function that does what I described. Below is the code I have to return the available times based on the start/end index provided however I'm stuck on how to approach the calculation I described above.
// This filters times that are less than the start index
const filteredTimes1 = availableHours.filter((item, index) => index < (
(startTimeIndex || availableHours.length - 0)
))
// This filters times that greater than the end index
const filteredTimes2 = availableHours.filter((item, index) =>
index > (endTimeIndex || availableHours.length)
)
// Then combine the results into an array
const validAvailableHours = [...filteredTimes1 , ...filteredTimes2]
Any help would be appreciated, thanks.

You're way overcomplicating this.
Just keep a map of the times already booked, in this case 4pm.
Later add 3pm since you're adding that too.
Or if you want it simpler just use a Set.
const bookedHours = new Set()
// too add booked times
bookedHours.add(4)
// to get an array with available times
const available = [1,2,3,4,5,23,24]
const availableUpdate = available.filter(n => !bookedHours.has(n))

I mean you could simply use array.slice() to do the trick.
let allHours = [1, 2, 3, 4, 5, 6, 7]
let [startHours, endHours] = [4, 5];
let duration = 1;
let availableHours = [
...allHours.slice(
0,
allHours.indexOf(startHours - duration)
),
...allHours.slice(
allHours.indexOf(endHours + 1)
)]
console.log(availableHours)

You alreay have a couple good answers. For fun, an alternate use I like to take for more advanced situations like a schedule etc.. make a schedule object and give it all the logic it needs to control the entire schedule.I added a few more features than you requested for fun, such as add time, remove time and set all available times.
const schedule = {
availHours: [],
responses: ['Appointment Confirmed', 'Time Not available, please choose
another time'],
set addHour(hour) {
this.availHours = [...schedule.availHours, hour];
},
set removeHour(hour) {
this.availHours = this.availHours.filter((availHours) => availHours !=
hour);
},
set setHours(hoursArray) {this.availHours = hoursArray},
set request(reqHour) {
this.availHours.includes(reqHour) ? console.log(this.responses[0]) :
console.log(this.responses[1]);
this.availHours = this.availHours.filter(
(hour) => hour != reqHour && hour != Number(reqHour) + 1 && hour !=
Number(reqHour) - 1
);
},
};
I dont know the exact logic you need for your situation but this is just a general idea of how it can work. I chose to work in numbers only and add the :00 when I call anything that out puts a time.
heres a general example below of how it would function and log out
schedule.setHours = [1, 2, 3, 4, 5, 6, 7]
schedule.request = 5;
console.log(schedule.availHours.map((hour) => (hour += ':00'))); //[ '1:00', '2:00', '3:00', '7:00' ] 'Appointment Confirmed'
schedule.addHour = 8;
console.log(schedule.availHours.map((hour) => (hour += ':00')));//[ '1:00', '2:00', '3:00', '7:00', '8:00' ]
schedule.removeHour = 7;
console.log(schedule.availHours.map((hour) => (hour += ':00')));//[ '1:00', '2:00', '3:00', '8:00' ]
schedule.request = 12;
console.log(schedule.availHours.map((hour) => (hour += ':00')));// ['1:00', '2:00', '3:00', '8:00'] 'Time Not available, please choose another time'
schedule.setHours = [1,2,3,4,5,6,7,8,9,10]
console.log(schedule.availHours.map( hour=> hour += ':00') ) // [ '1:00', '2:00', '3:00', '4:00','5:00', '6:00','7:00', '8:00','9:00', '10:00']
and finally, completely aside from above.. if you just want a function option different from the examples above, you can do something like this.
function openTimes(availTimeArray, reqTime = '') {
availTimeArray = availTimeArray.map((time) => time.replace(/:00/, ''));
return availTimeArray.includes(reqTime)
? (availTimeArray = availTimeArray
.filter((time) => time != reqTime && time != Number(reqTime) + 1 && time != reqTime - 1)
.map((times) => (times += ':00')))
: (availTimeArray = availTimeArray.map((time) => (time += ':00')));
}
let availHours = openTimes(['1:00', '2:00', '3:00', '4:00', '5:00', '6:00', '7:00'], '3');
console.log(availHours); // [ '1:00', '5:00', '6:00', '7:00' ]
availHours = openTimes(availHours, '6')
console.log(availHours) // ['1:00']
If you pass no second argument it will just return all of the numbers in the array you give to the first argument. Also, if you define the availHours with let you can keep redefining itself by passing itself into the function to keep a running tally with one variable. The function could also be shorter and more straight forward if you only work in numbers and then add the :00 with map when you use the variable when displaying the hours, it's also only built to accept hours, not half hours.

Related

How to average values in JSON after grouping within a group in javascript

I have a json file with the following data which I would like to group by Year , Theme and then average values on each theme. Can someone help me with this? I have grouped by date and then theme but struggling after that.
[
{"Bid":"BidTest1","QNo":"1","Score":"0.7","Theme":"Social Value","QDate":"01/01/2021"},
{"Bid":"BidTest1","QNo":"2","Score":"0.5","Theme":"Tech Mgt","QDate":"01/01/2021"},
{"Bid":"BidTest1","QNo":"3","Score":"0.8","Theme":"Agile","QDate":"01/01/2021"},
{"Bid":"BidTest1","QNo":"4","Score":"0.7","Theme":"Social Value","QDate":"01/01/2021"},
{"Bid":"BidTest456","QNo":"1","Score":"0.5","Theme":"Tech Mgt","QDate":"03/04/2021"},
{"Bid":"BidTest456","QNo":"2","Score":"0.7","Theme":"Social Value","QDate":"03/04/2021"},
{"Bid":"BidTest456","QNo":"3","Score":"0.5","Theme":"Agile","QDate":"03/04/2021"},
{"Bid":"BidHO","QNo":"1","Score":"0.8","Theme":"Agile","QDate":"06/10/2021"},
{"Bid":"BidHO","QNo":"2","Score":"0.7","Theme":"Social Value","QDate":"06/10/2021"}
]
The code I have used for grouping it is
let result1 = dataQ1.reduce((state1, current1 ) => {
let {QDate, Theme} = current1;
let date = state1[QDate] || (state1[QDate] = {});
let themeArr = date[Theme] || (date[Theme] = []);
// let monthArr = yearObj[month] || (yearObj[month] = []);
themeArr.push(current1);
return state1;
}, {});
This gives me an object with objects containing arrays for each Theme. I need to create a chart grouped by Month, Theme and mark the respective average values per theme.Final Chart.Final Data Sample
Could someone please guide me on how to go about this ? Thanks
I would slightly change your reducing function to make it easier to compute averages:
let groupedDataQ1 = dataQ1.reduce((accumulator, item) => {
let {QDate, Theme} = item;
accumulator[Theme] = accumulator[Theme] || {};
// i'm assuming your dates are in dd/mm/yyyy format -> we extract mm/yyyy
let month = QDate.slice(-7);
(accumulator[Theme][month] = accumulator[Theme][month] || []).push(item);
return accumulator;
}, {});
Now you have as keys the Themes, and each Theme is an object that, for each month, has an array of corresponding values. So, if you want to compute the average Scores for Theme "Agile" in April 2021 you would simply do
let agile_april_data = groupedDataQ1['Agile']['04/2021'];
let agile_april_avg_score = agile_april_data.reduce((val, item) => val + parseFloat(item.Score),0) / agile_april_data.length
The reason why I decided to change the first term of the grouping function (you group first by date [leaving aside the fact that you keep the day in the key], and then by Theme, while I do the other way around) is because in the screenshot examples you provided, the Theme is the series over which the computations are performed - so, for each Theme, one line in the plot and one table in the tabular data. What happens if, for a month, you don't have any data for a specific Theme? You would need to perform a big amount of checks, while in this approach you only need to iterate over the object keys.

What is the simplest way of giving Tone.js arrays of notes and durations in seconds to play back?

I would like to give Tone.js a list of notes and corresponding durations for each note and have it play back the sequence. As far as I can see, there is no easy way to do this.
In the following, the corresponding time values are not the ones I entered (i.e 0.25, 0.5, 0.25), as evidenced by the console.log:
var part = new Tone.Part(function(time, note){
console.log(time);
console.log(note);
synth.triggerAttackRelease(note, time);
}, [[0.25, "C2"], [0.5, "C3"], [0.25, "G2"]]);
part.start(0).loop = false;
Tone.Transport.start();
How can I give Tone.js notes and corresponding ms for playback?
I'm not familiar with Tone.js, so there's probably a better way of doing this. The official example for the array shorthand that you're using doesn't seem to work, so it might be a library issue.
As for what you're trying to achieve, I fiddled with it out of curiosity and here's what I've come to:
function timeFromDurations(value, i, arr) {
const prevTime = arr[i - 1]?.time;
value.time = prevTime + arr[i - 1]?.duration || 0;
return value;
}
const notesAndDurations = [
{ note: 'C3', duration: .25 },
{ note: 'C4', duration: .5 },
{ note: 'G2', duration: 1 },
].map(timeFromDurations);
console.log(notesAndDurations);
const synth = new Tone.Synth().toDestination();
// use an array of objects as long as the object has a "time" attribute
const part = new Tone.Part((time, value) => {
// the value is an object which contains both the note and the velocity
synth.triggerAttackRelease(value.note, value.duration, time);
}, notesAndDurations).start(0);
Tone.Transport.start();
The idea is that you need to set start time of each note based on previous note start time + duration. That removes the need to set the start time(not optional) manually.
Edit
For your second case where the durations and the notes come in separate arrays you can use the following reduce function:
const notes = ['C3', 'C4', 'G2'];
const durations = [0.25, 0.5, 1];
const noteDurationTime = notes.reduce((acc, note, i) => {
const prevTime = acc[i - 1]?.time;
const time = prevTime + acc[i - 1]?.duration || 0;
const duration = durations[i];
acc.push({ note, duration, time });
return acc;
}, []);
The idea is the same, you're building an array of objects that have all the needed properties(note, duration, time), but this time from different sources(notes array and durations array).
You want to make sure that both these arrays are the same length.

Javascript loop through array recursively

I've run into a bit of an issue trying to map a list of items in javascript. I have the following array that I'm trying to map:
[{"project_id":"EGNL1701","title":"Test Energy Project",
"reservations":
[{"start_time":"1519887600000"},{"start_time":"1519891200000"},
{"start_time":"1519938000000"},{"start_time":"1519898400000"},
{"start_time":"1519902000000"},{"start_time":"1519905600000"},
{"start_time":"1519909200000"},{"start_time":"1529683200000"},
{"start_time":"1529686800000"},{"start_time":"1531893600000"},
{"start_time":"1531897200000"},{"start_time":"1531900800000"},
{"start_time":"1531904400000"}]},
{"project_id":"LENL1701","title":"Vive","reservations":[]}]
Basically it's a list of reservations per project. All the times are in unix code in miliseconds. I have no issue trying to convert the times to the right time, however what I do need is to check for reservation blocks of consecutive hours. A reservation is always an hour, so I want to map reservations that are directly after the previous reservation, to be included into the previous reservation so that it forms 1 reservation of multiple hours. So if the list would contain 3 reservations for 10-07-2018 at 10, 11 and 12 o'clock then it should combine those in one object.
The new array should look like this:
[
{
title: 'Energy Project',
startTime: 1530631437,
endTime: 1530638640
},
{
title: 'HTC VIVE',
startTime: 1530794845,
endTime: 1530797390
}
];
I'm not sure what the best way is to go about this, I was trying to mess with while and for loops to get it done, but I keep getting stuck in an infinite loop.
Here's the code I have now for checking if the next reservation is an hour after the current reservation in the loop:
while (moment.duration(getUnixTime(reservations[index + nextIndex].start_time).diff(getUnixTime(reservation.start_time))).asHours() === 1) {
nextIndex++;
}
I was hoping one of you might have a good idea on how to do this? I feel like I'm going about this all wrong. It's for a React project, I can use ES6 functions etc.
This might not be complete answer so try this
Create an empty reservations object R
Loop through (for each or for ) upto second last item. You can check if next item exists or not.
Check if the criteria for example next one is one hour after the current one then add them together and add it to R.
End time of R will become end time of current reservation or the one next to it.
Move to next one or skip based on what you did in previous step.
When you reach last item simply add it to R if its not being added in previous step.
One of the assumption is that all reservations are in order. You can order them via sorting functions to avoid loop and check where it fits.
I've fixed the issue using #Farrukh Subhani's suggestion.
First had to sort the list, then loop through it twice. Resulting code:
export const mapReservationTimes = reservations => {
const upcomingReservations = [];
reservations.sort((x, y) => {
return getUnixTime(x.start_time).toDate() - getUnixTime(y.start_time).toDate();
});
for (let index = 0; index < reservations.length; index++) {
let newReservation = { startTime: '', endTime: '' };
let reservation = reservations[index];
newReservation.startTime = getUnixTime(reservation.start_time).format('DD-MM-YYYY HH:MM');
newReservation.endTime = getUnixTime(reservation.start_time)
.add(1, 'hours')
.format('DD-MM-YYYY HH:MM');
for (let i = index; i < reservations.length - 1; i++) {
reservation = reservations[index];
const nextReservation = reservations[i + 1];
if (
moment
.duration(
getUnixTime(nextReservation.start_time).diff(
getUnixTime(reservation.start_time)
)
)
.asHours() === 1
) {
index++;
newReservation.endTime = getUnixTime(nextReservation.start_time)
.add(1, 'hours')
.format('DD-MM-YYYY HH:MM');
} else {
break;
}
}
upcomingReservations.push(newReservation);
}
return upcomingReservations;
};
Thanks a lot for the suggestion, it got me on the right track :)
I feel this might be a good fit for an array.reduce, though this might stem from my personal taste. This solution can and should be simplified and cleaned, but I tried to convey the general idea so I made it a little more verbose then I think production code should be.
const input = [{"project_id":"EGNL1701","title":"Test Energy Project","reservations":
[{"start_time":"1519887600000"},{"start_time":"1519891200000"},
{"start_time":"1519938000000"},{"start_time":"1519898400000"},
{"start_time":"1519902000000"},{"start_time":"1519905600000"},
{"start_time":"1519909200000"},{"start_time":"1529683200000"},
{"start_time":"1529686800000"},{"start_time":"1531893600000"},
{"start_time":"1531897200000"},{"start_time":"1531900800000"},
{"start_time":"1531904400000"}]},
{"project_id":"LENL1701","title":"Vive","reservations":[]}]
const hourInMilliseconds = 3600000
console.log(input.reduce((acc, project) => {
const title = project.title
const sortedReservations = project.reservations.map(r => r.start_time).sort()
const reservationCount = sortedReservations.length
const earliest = reservationCount > 0 ? sortedReservations[0] : "none"
const latest = reservationCount > 0 ? sortedReservations[reservationCount - 1] : "none"
const projectObject = {
title,
startTime: earliest,
endTime: isNaN(latest) ? latest : (Number(latest) + hourInMilliseconds).toString()
}
acc.push(projectObject)
return acc
}, [])
)
Disclaimer: This assumes that all the reservations in one array item are consecutive, which seems to be the case in your sample data. If this is not the case the function would have to be altered a bit.

Javascript function to return value between threshold of array objects

I've got the following scores object in JavaScript:
[
{
"id":37,
"title":"Over achieving",
"description":"Exceeding expectations",
"threshold":10,
},
{
"id":36,
"title":"Achieving",
"description":"Achieving expectations",
"threshold":6,
},
{
"id":35,
"title":"Under achieving",
"description":"Not achieving expectations",
"threshold":3,
}
]
I'm trying to figure out how to create a method that will return the score object based on a value determined by the score threshold.
I've tried the following but it only returns the score if the value is equal to the score threshold, not between it.
scores.find(o => o.threshold <= progress && o.threshold >= progress)
So the scenario is, a person has a progress value of 5, I'd like the method to return the score array item with the id of 35 because 5 is between 3 and 6. Similarly, if the progress value is 7 then I'd like the method to return the score array item with the id of 36 because 7 is between 6 and 10.
I'm sure I'm not far off.
You seem to be looking for the first item in the array whose threshold is below or equal to the progress. The expression
scores.find(o => o.threshold <= progress)
will do that.
If you sort your scores array in reverse order first, you can then adjust your callback to find the first score with threshold that's merely less than the progress.
// doing it this way solely to keep it on a single line.
const scores = JSON.parse('[{"id":37,"title":"Over achieving","description":"Exceeding expectations","threshold":10},{"id":36,"title":"Achieving","description":"Achieving expectations","threshold":6},{"id":35,"title":"Under achieving","description":"Not achieving expectations","threshold":3}]');
const getScore = (progress) => scores.sort((a, b) => b.threshold - a.threshold).find(score => score.threshold <= progress);
const showScore = (progress) => {
const lowestThreshold = scores.sort((a, b) => a.threshold - b.threshold)[0];
const score = getScore(progress) || lowestThreshold;
console.log(`${progress} returns`, score.id);
};
const allValues = [...Array(15).keys()].map(showScore);
Even if your scores array is NOT sorted by threshold.
let progress = 5;
let scores = [{"id":37, "title":"Over achieving", "description":"Exceeding expectations", "threshold":10,}, {"id":36, "title":"Achieving", "description":"Achieving expectations", "threshold":6,}, {"id":35, "title":"Under achieving", "description":"Not achieving expectations", "threshold":3,}]
let item = scores.filter(o => (o.threshold <= progress)).reduce((acc, curr) => (curr.threshold >= acc.threshold)? curr: acc)
console.log(item);
console.log(item.id);
I hope this will help ;)

Calculating time difference for sets of streaming data

I have the following function that renders a row based on streaming SignalR data coming from another component:
let prev
let next
renderRow = () = {
this.props.incomingData.map(item => {
return (
<tr>
<td>{item.time}</td> //00:00:00 format
<td>{this.calculateSecondsTaken()}</td> //should show the time difference in seconds
<td>{item.event}</td>
<td>{item.message} <br/>{item.outputURL}</td>
</tr>
)
})
}
I'm trying to get the time difference for each data row based on the item.time attribute value by subtracting current time from previous time (in the row).
//calculates the difference between the previous and the incoming (next) time value to give the time taken
//for the current row's data to come in.
calculateSecondsTaken = (prev, next) => {
next - prev;
}
I know in the above function it has to be some sort of a subtraction based on previous and next values of time but I can't quite pin down the logic of it. For instance, what would the difference be for the very first row etc.
Edit:
The data streaming component is as follows
deploymentEventMessageReceived = (item: DeploymentEvent) => {
let dataset = {
time: item.UtcNowPrettyText, //this is the time value
event: item.EventName,
message: item.Message,
outputURL: item.OutputUrl
}
let newDataArray = this.state.receivedData.concat(dataset);
this.setState({
receivedData: newDataArray
});
}
deploymentEventMessageReceived() in turn gets called inside the function where I'm connecting to SignalR.
Edit 2 updated (working version):
deploymentEventMessageReceived = (item: DeploymentEvent) => {
let lastEntryTime;
newArray = newArray.concat(item);
if (newArray.length == 1) {
lastEntryTime = moment(newArray[newArray.length - 1].UtcNowPrettyText)
} else {
lastEntryTime = moment(newArray[newArray.length - 2].UtcNowPrettyText)
}
timeDiff = moment(item.UtcNowPrettyText).diff(lastEntryTime, "seconds");
//create new object with the addition of TimeDifference attribute
let dataset = {
UtcNowPrettyText: item.UtcNowPrettyText,
EventName: item.EventName,
Message: item.Message,
OutputUrl: item.OutputUrl,
TimeDifference: timeDiff
}
//store it in a new array
finalDatasetArray = finalDatasetArray.concat(dataset);
this.setState({
receivedData: finalDatasetArray
});
}
Inside render() of data streaming component
<RenderRow receivedData={this.state.receivedData} />
Any help is appreciated.
I would implement a time difference calculation this way: I keep track of Date.now(), which I don't render but keep it in each entry so I can perform math on it. I don't know how you're planning on rendering the time difference, but I put it in this.state in my example.
deploymentEventMessageReceived = (item) => {
let dataset = {
time: item.UtcNowPrettyText, //this is the time value
event: item.EventName,
message: item.Message,
outputURL: item.OutputUrl,
date: Date.now()
}
let lastEntry = this.state.receivedData[this.state.receivedData.length - 1]
let timeDifference = dataset.date - lastEntry.date //time in ms
let newDataArray = this.state.receivedData.concat(dataset);
this.setState({
receivedData: newDataArray,
timeDifference: timeDifference
});
}
edit: My personal implementation would be to actually split up the two concepts. I keep Date.now() and make a different function to actually build the array of time differences. Never know if you will need Date.now() later on, and it just makes more sense to have a timestamp in its entirety in your data. You don't need to add the item immediately. You can concat it later. I use object destructuring to make the assignment cleaning as 2 one-liners.
deploymentEventMessageReceived = (item) => {
let { UtcNowPrettyText, EventName, Message, OutputUrl } = item
let dataset = { UtcNowPrettyText, EventName, Message, OutputUrl, Date: Date.now() }
this.setState({receivedData: this.state.receivedData.concat(dataset)})
}
buildTimeDifferences = (entries) => {
let newArr = []
for (let i = 0; i < entries.length; i++) {
if (i !== entries.length - 1) {
newArr.push(entries[i + 1].Date - entries[i].Date)
}
}
return newArr
}
d = [{Date: 1},{ Date: 2}, {Date: 4}, {Date: 7}]
console.log(buildTimeDifferences(d))

Categories