I started learning to code just a couple days ago, and I'm almost finished with my first program. However, I have ran into a problem now at the end, and I can't seem to figure out what's wrong myself.
The program is supposed to help me sort a bunch of dates by week day and time of day. The dates are written "YYMMDD-" and then the time of the day (only hour), e.g. "170109-06".
I have all the dates I want to sort in an array (time), and the following function and loop are supposed to delete all the dates that's not matching a specific year.
Variable clarification:
time - array consisting of dates and time (all the same months but different years. ( e.g. ["161102-03", "151127-11"]
yearDate - the first 4 numbers of the dates that's going to stay in the array.
function checkDate (date) {
var yearMonth = date.slice(0, 4);
var index = time.indexOf(date)
if (yearMonth != yearDate) {
time.splice(index, index + 1);
}
else {
}
}
for (var i = 0; i <= time.length; i++) {
checkDate(time[i]);
}
This code seems to work occasionally, but sometimes a few strings from the array stay there when they're supposed to be deleted, so there's clearly something wrong with the code.
I hope that you'll be able to understand what I mean. I do realize that this post ended up kind of wooly.
Standard mistake. You are deleting items from array while iterating over it. Every time an item is deleted, incrementing the iterator will jump you over the next element, adjacent to the deleted.
Go backwards:
for (var i = time.length - 1; i >= 0; i--) {
checkDate(time[i]);
}
Related
I am (VERY) new to Apps Script and JS generally. I am trying to write a script that will automatically tally the difference between student entry time and start time of a course to deliver total minutes missed.
I have been able to get a function working that can do this for a single cell value, but am having trouble iterating it across a range. Doubtless this is due to a fundamental misunderstanding I have about the for loop I am using, but I am not sure where to look for more detailed information.
Any and all advice is appreciated. Please keep in mind my extreme "beginner status".
I have tried declaring a blank variable and adding multiple results of previously written single-cell functions to that total, but it is returning 0 regardless of given information.
I am including all three of the functions below, the idea is that each will do one part of the overall task.
function LATENESS (entry,start) {
return (entry-start)/60000
}
function MISSEDMINUTES(studenttime,starttime) {
const time = studenttime;
const begin = starttime;
if (time=="Present") {
return 0
} else if (time=="Absent") {
return 90
} else {
return LATENESS(time,begin)
}
}
function TOTALMISSED(range,begintime) {
var total = 0
for (let i = 0; i < range.length; i++) {
total = total + MISSEDMINUTES(i,begintime)
}
}```
If you slightly tweak your layout to have the 'missing minutes' column immediately adjacent to the column of names, you can have a single formula which will calculate the missing minutes for any number of students over any number of days:
Name
*
2/6
2/7
2/8
2/9
John Smith
-
Present
Present
Absent
10:06
Lucy Jenkins
-
Absent
Absent
Absent
Absent
Darren Polter
-
Present
Present
Present
10:01
With 'Name' present in A1, add the following to cell B1 (where I've marked an asterisk):
={"mins missed";
byrow(map(
C2:index(C2:ZZZ,counta(A2:A),counta(C1:1)),
lambda(x,switch(x,"Present",0,"Absent",90,,0,1440*(x-timevalue("10:00"))))),
lambda(row,sum(row)))}
We are MAPping a minute value onto each entry in the table (where 'Present'=0, 'Absent'=90 & a time entry = the number of minutes difference between then and 10am), then summing BYROW.
Updated
Based on the example, you could probably have a formula like the below one to conduct your summation:
=Sum(ARRAYFORMULA(if(B2:E2="Absent",90,if(isnumber(B2:E2),(B2:E2-$K$1)*60*24,0))))
Note that k1 has the start time of 10:00. Same sample sheet has working example.
Original Answer
I'm pretty sure you could do what you want with regular sheets formulas. Here'a sample sheet that shows how to get the difference in two times in minutes and seconds... Along with accounting for absent.
Here's the formula used that will update with new entries.
=Filter({if(B2:B="Absent",90*60,Round((C2:C-B2:B)*3600*24,0)),if(B2:B="Absent",90,Round((C2:C-B2:B)*3600*24/60,1))},(B2:B<>""))
This example might not solve all your issues, but from what I'm seeing, there's no need to be using an app script. If this doesn't cover it, post some sample data using Mark down table.
I'm trying to calculate data for burndown chart for a course. The course has a start and end dates, exercises count and actual students start and finish dates. I have data JSON from the server with a course data. I process it. First of all, I'm calculating totalExcercisesCount then counting the number of days student havs to finish the course. After all I get the next data object:
const chartDataObj = {
idealBurn: [],
actualBurn: [],
idealIncrement: 0,
totalExcercisesCount: 12,
totalExercisesDoneCount: 4,
timeLine: {
courseFrom: "2018-09-10",
courseTo: "2019-06-21",
start: "2018-09-11",
finish: "2018-10-01",
totalDays: 20,
}
}
After I'm building an ideal line and here comes the first problem. I'm trying to do next,
chartDataObj.idealIncrement = Math.floor(
chartDataObj.timeLine.totalDays / chartDataObj.totalExcercisesCount
);
for (i = 0; i <= chartDataObj.timeLine.totalDays - 1; i++) {
chartDataObj.idealBurn.push(chartDataObj.idealIncrement * (i + 1));
}
chartDataObj.idealBurn.reverse();
The problem is if the count of days much more then exercises I have a wrong ideal burn.
I have 12 exercises to complete but on the second day, it shows like 19. What am I doing wrong here?
And then I need to fill actual burn data. But the problem is, how to fill it according to dates exercises was complete and show it on the graph? I mean in my final dataObject I have just totalExercisesDoneCount but in initial JSON I have info about dates when exercises was finished. Should I group them by dates or not?
I also have a codepen prepared with chart and all the code. Any help will be appreciated. Thanx
I am a "new" developer into the foray of Web Development and I have come across an issue I was hoping that you fine people on Stack Overflow would be able to help me with. I have asked several Cadre and Instructors in my class and we are all stumped by it.
To start with I have decided to put all of my code on a Gitlab repo so if you want to look at the whole thing (or if you want to add to it let me know): Link to Github Repo. I fiqured you guys don't want the whole thing posted as a wall of text and rather some snip-its of what in the file I specifically. But it is relitively small file
I am useing simple JavaScript as well as Node.Js to be able to build a working calculator in the back end that I can use as a template for any other project I will need to work on in the future. For now I am trying to just get it working by imputing things via the console.
I have made a way for what is imputed in Node and to an imputArray var I have set up and the Array goes something like this:
[(command), (num1), (num2), (num3), ...]
I set up a switch function that runs a block of code based on what command was given (add, subtract, divide, etc..). As well as separating the command from the number and putting them inside another array.
The part I need some help with is with getting the block of code to work for what I want it to do. I have got it set up to run rather easily on two numbers but I want it to handle as many numbers as I want to throw at it. I tried various forms of for loops as well as forEach loops and I cant seem to get it working.
case 'divide':
for (i = 1; i < numArray.length; i++) { // If any number besides the first is 0 spit out this
if (numArray[i] === 0) {
consol.log("You canot divide by zero!");
}
else {
var previousTotal = numArray[0]; //Inital number in array
for (i = 1; i < numArray.length; i++) {
previousTotal = previousTotal / numArray[i]; // for each number in array divide to the previous number
}
}
result = previousTotal // Pushes end total to result
}
break;
I have gone through several different versions of the above code (such as using for loops instead) but this is pretty much what I ended up with. I'm sure there is an easier way and more sane way to do what I am trying to do, but if I knew how I wouldn't be here.
Essentially this is the ideal thing I want to do but I cant find a way to do it: I want to run a small block of code the index of the number array, minus one. In this case it is dividing the previous number by the next number in the array.
So it only runs if there are more then one in the array and it does the function to the previous number, or total from the last one in the array.
This is pretty much the only thing holding me back from finishing this so if someone can take the time to look at my crapy code and help it do what I want it to do that would be awesome.
Your code is reseting result each time the outer loop iterates so it will just equal what ever the last prev Total is. Basically every loop but the last is irrelevant. Do you want to add them to result? If so you want:
result += previousTotal
Or if you want an array of the answers you want:
result.push(reviousTotal)
Sorry not 100% what you want. Hope this helps!
You just need one loop, and you probably want to stop iterating if a 0 occurs:
result = numArray[0]; //no need for another variable
for (var i = 1; i < numArray.length; i++) { // declare variables!
if (numArray[i] === 0) {
console.log("You canot divide by zero!"); // typo...
break; // exit early
}
result = result / numArray[i];
}
For sure that can be also written a bit more elegantly:
const result = numArray.reduce((a, b) => a / b);
if(isNaN(result)) {
console.log("Can't divide by zero!");
} else {
console.log(`Result is ${result}`);
}
I assume you want the divide command to do ((num1/num2)/num3)/...
There are couple of issues in the code you posted, I will post a version that does the above. You can inspect and compare it your version to find your mistakes.
// divide, 10, 2, 5
case 'divide':
if (numArray.length < 2) {
console.log("no numbers in array")
break;
}
// previousTotal starts with 10
var previousTotal = numArray[1];
// start from the second number which is 2
for (i = 2; i < numArray.length; i++) {
if (numArray[i] === 0) {
console.log("You canot divide by zero!");
}
else {
previousTotal = previousTotal / numArray[i]; // for each number in array divide to the previous number
}
}
result = previousTotal;
// result will be (10/2)/5 = 1
break;
I'm trying to make a function to set some contents on a cell of a spreadsheet from a data set by user through a prompt.
There are some parts working well and some problems
To put in context, we have a spreadsheet with a "calendar" as follows: every cell in the A:A range is a day of the school year, starting from September/1 in A1, September/2 in A2, and so. In the B column we have for now the holidays, vacation period and other. There are many "stable" days in a school year, such as Christmas day: every year is the same day and month (only changes year). Besides this, there are some days every school (almost in Spain) choose to get a little break.
First, we set function, input range (A column to compare) and output range (B columne, where we want to write new content). (the var year is given as an argument of the function, but is set here for testing)
function setSpecialHolidays() {
var year = 2017;
I had to make a parseInt for this data, because when given as an argument detects it as a string and doesn't work well. But now is ok.
var yearNumber = parseInt(year);
var sheet = SpreadsheetApp.getActive().getSheetByName("Calendar"+year);
var inputRange = sheet.getRange('A1:A'+sheet.getLastRow()).getValues();
var outputRange = sheet.getRange('B1:B'+sheet.getLastRow());
Here start a loop where we can input different days. Every day I set in the prompt the script has to set it in a cell corresponding the date and the content of the A column
I set this ButtonSet before the loop because I need it to start the while, as it will be still working until the user clicks "No" button.
var specialHoliday = ui.alert('Click «Yes» to set a special holidays', ui.ButtonSet.YES_NO);
while (specialHoliday == ui.Button.YES) {
var dayPointer = ui.prompt('Please enter the special holiday date (DD/MM)');
var specialHolidayDate = dayPointer.getResponseText();
var specialHolidayDateParts = specialHolidayDate.split("/");
var date = new Date();
date.setMonth(specialHolidayDateParts[1] - 1, specialHolidayDateParts[0]);
date.setHours(0, 0, 0, 0);
As the school year comprend two different natural years, to make possible Date compare, we need to ensure that if the month is from September to December is the current year, but if the month is from January to July or so is the *current year + 1", this is, the next year. Because of that I make this conditional. This is working well too. I've made some tests and ui.alert to be ensured and detects well the year, if current or next.
if (specialHolidayDateParts[1] <= 8) {
date.setYear(yearNumber + 1);
} else if (specialHolidayDateParts[1] > 8) {
date.setYear(yearNumber);
}
Here is were doesn't work. Once we have set correctly the input date we can start compare it with all values in the inputRange. Then, look every A column cell and, when find a coincidence set in the outputRange the string.
I think there's probably a better solution to this problem, maybe doesn't making a loop, but this is a proccess I've followed before in other functions of this script and that's well for now. This specific date setting is the last step of our program (for now), but I'm
for (var i = 0; i < inputRange.length; i++) {
var dateCompare = new Date(inputRange[i][0]);
if (dateCompare == date) {
outputRange[i][0].setValue("Holiday: special holiday");
}
}
Finally, if user wants to set another day, ButtonSet allows it. When users finish click "NO" instead of yes and everything stops. That's working too.
var specialHoliday = ui.alert('Do you want to set a new special holiday?', ui.ButtonSet.YES_NO);
}
Well, thanks in advance for help.
(Posted on behalf of the OP).
I've made some tries and finally I've found the correct way to do what I want, to set a value in a cell next to a cell that have a date I've compared from a user input date. There was a mistake in the statement:
In a range I can't setValues (but I was trying...)
Is needed to use the .getTime() method on compare the date values
Here is the (I think!) corrected code for the for loop:
for (var i = 0; i < inputRange.length; i++) {
var dateCompare = new Date(inputRange[i][0]);
if (dateCompare.getTime() == date.getTime()) {
outputRange[i][0] = "Holiday: special holiday";
}
}
I finally understood the problem in outputRange: as a range, I can't setValues, because is not a cell, then, I've to assign the value as is. What I'm not sure to get is why I need the .getTime() after every compared value. But it works!
Through my searching I've found the following simple logic works really well for ensuring two time ranges don't overlap.
(StartA <= EndB) and (EndA >= StartB)
But it starts to get cumbersome (with my current approach) if I need to use many time ranges. Lots of If statements and such.
So what is a better approach? I've thought about filling arrays corresponding to 15 minute intervals of 'time' and running a check if those array places are 'occupied' when a new time range is added.
This will be using javascript, so I don't know if arrays are the most appropriate/lightweight however.
A straightforward way would be to store the dates in an array, sort it by start date, then check if the dates overlap. Here's a sample:
var dates = [];
var addDate = function(start, end){
dates.push({start: start, end: end});
};
var datesOverlap = function(){
var i;
dates.sort(function(a, b){ return a.start-b.start;});
for(i=0; i<dates.length-1; i++){
if(dates[i].end >= dates[i+1].start){
return true; // dates overlap
}
}
return false; // no dates overlap
};
Add all the dates you want to check with the addDate function. Then you'd call the datesOverlap function to check if any dates overlap.