Parse through rotation choices to produce class schedules - for-loop

I have a Sheets project with 6 worksheets. I'm using student choices to generate a series of 4 rotations in which students can visit classes.
Form Responses 1 lists names in Column D; Columns I-N list a set of six possible rotation choices
6Rot1, 6Rot2, 6Rot3, and 6Rot4 correspond to each of the four possible rotations
mainRoster6 is a master roster produced to reflect names and rotation choices
I'm trying to parse through Form Responses 1, with the following conditions:
Start by looking at Column I and see if there are "open spots" (<47) in the corresponding column of 6Rot1. If so, add the name to that column. Otherwise, check 6Rot2 and try to perform the same operation. Etc., until all 4 rotations have been checked.
If there are no open spots in either of the 4 rotations, then check Column J and try to fit the student in the correct class's name, etc.
The intention is to assign the student's top unique choices to 4 rotations. If a student has already chosen a class for one rotation, then that class should not appear again in a different rotation.
I'm trying to solve two bugs:
If a class has already been added for a student for a rotation, he should not have the same class in a newer rotation (I tried to solve this in lines 62-67 but it didn't work). In the example sheet, for the student in row 3, once the student has been placed in "Spanish I" for rotation 2, it should skip his third choice (a repeated "Spanish I" and proceed trying to place him in "Coding" for his third rotation.
The program should assign in rotations in the order of preference (first, then second, then third, etc.). Right now, if a rotation is full for a specific class, it simply ignores that class (even if the next rotation may be available) and moves on to the next choice. In the example sheet, the first student has "coding" as third choice. Although that class is not available in the 3rd rotation, it's available in the 4th rotation. The program should try to place coding in the 4th rotation and then look at the fourth choice and try to place the student into any available rotation (in this case, rotation 3 would be available).
This is what I have currently:
function myFunction() {
var mainSheet = SpreadsheetApp.openById('1ki0Cya3IWNwdLIBe0fR1vwfz_ansmfa52NilmdFBrd0');
var firstRotation = mainSheet.getSheetByName('6Rot1');
var secondRotation = mainSheet.getSheetByName('6Rot2');
var thirdRotation = mainSheet.getSheetByName('6Rot3');
var fourthRotation = mainSheet.getSheetByName('6Rot4');
var mainRoster = mainSheet.getSheetByName('mainRoster6');
var responsesSheet = mainSheet.getSheetByName('Form Responses 1');
var destinationCell;
var column;
var columnValues;
var columnContainsStudent;
var currentSheet;
var mainRosterLastRow;
var studentName;
var compassTeacher;
var classChoice;
var rotationCounter;
var choicesCounter;
var successCounter;
var rotationOneSuccess;
var rotationTwoSuccess;
var rotationThreeSuccess;
var rotationFourSuccess;
var responsesLastRow = responsesSheet.getLastRow();
var currentRotationsArray;
// var y;
var z;
for (z = 2; z < responsesLastRow + 1; z++) {
studentName = responsesSheet.getRange(z, 4).getValues();
mainRoster.getRange(z, 2).setValue(studentName);
compassTeacher = responsesSheet.getRange(z, 6).getValues();
mainRoster.getRange(z, 1).setValue(compassTeacher);
choicesCounter = 1;
successCounter = 0;
rotationCounter = 1;
rotationOneSuccess = false;
rotationTwoSuccess = false;
rotationThreeSuccess = false;
rotationFourSuccess = false;
while (choicesCounter < 7) {
if (successCounter > 4) {
break;
}
classChoice = responsesSheet.getRange(z, 8 + choicesCounter).getValues();
//test for includes in previous choices
var currentRotationsArray = mainRoster.getRange(z, 4, 1, 4).getValues();
// for (y = 0; y < 4; y++) {
// if (currentRotationsArray[0][y] == classChoice) {
// choicesCounter++;
// break;
// }
// }
while (successCounter < 4) {
if (rotationOneSuccess == false) {
currentSheet = firstRotation;
} else if (rotationTwoSuccess == false) {
currentSheet = secondRotation;
} else if (rotationThreeSuccess == false) {
currentSheet = thirdRotation;
} else {
currentSheet = fourthRotation;
}
// Set column number and get values as an array, depending on class choice
if (classChoice == "Guitar 7") {
column = 1;
columnValues = currentSheet.getRange("A1:A").getValues();
} else if (classChoice == "Communications - Video Production") {
column = 2;
columnValues = currentSheet.getRange("B1:B").getValues();
} else if (classChoice == "Communications - Journalism") {
column = 3;
columnValues = currentSheet.getRange("C1:C").getValues();
} else if (classChoice == "Coding") {
column = 4;
columnValues = currentSheet.getRange("D1:D").getValues();
} else if (classChoice == "Art 7") {
column = 5;
columnValues = currentSheet.getRange("E1:E").getValues();
} else if (classChoice == "Latin I") {
column = 6;
columnValues = currentSheet.getRange("F1:F").getValues();
} else if (classChoice == "Spanish I") {
column = 7;
columnValues = currentSheet.getRange("G1:G").getValues();
} else if (classChoice == "French I") {
column = 8;
columnValues = currentSheet.getRange("H1:H").getValues();
} else {
column = 9;
columnValues = currentSheet.getRange("I1:I").getValues();
}
// Find column's current last row
var columnLength = columnValues.filter(String).length;
//Add the student to the class's rotation, if the student isn't already there and there's >46 people already in
if (columnLength < 48) {
for(var n in columnValues){
if(columnValues[n][0] == studentName){
columnContainsStudent = true;
rotationCounter++;
break;
}
}
if (columnContainsStudent != true) {
destinationCell = currentSheet.getRange(columnLength + 1, column);
destinationCell.setValue(studentName);
if (currentSheet == firstRotation) {
rotationOneSuccess = true;
} else if (currentSheet == secondRotation) {
rotationTwoSuccess = true;
} else if (currentSheet == thirdRotation) {
rotationThreeSuccess = true;
} else {
rotationFourSuccess = true;
}
mainRoster.getRange(z, rotationCounter + 3).setValue(classChoice);
successCounter++;
rotationCounter++;
break;
}
rotationCounter++;
} else {
break
}
rotationCounter++;
}
choicesCounter++;
}
}
}
If you look at the linked sample spreadsheet, the logic is as follows:
We're looking at Form Responses 1 row by row, with the intention of adding 4 "choices" for that person in 4 rotations (6Rot1, 6Rot2, etc.). Columns I-N list 6 possible choices, but ideally I want to put the student in his top 4 choices.
I want the program to first look at Form Responses 1 Column I. In the sample sheet it's "Guitar 7". Then the program should look at 6Rot1 and see if the column for "Guitar 7" (Column A) has any open spaces (i.e. if it has less than 48 rows already filled). In the sample sheet, there are 36 "Placeholder Names" so it adds the first student to the first blank row in Column A (row 37). Once the name has been added to one of the rotations, mainRoster6 reflects that same information (column D in this case reflects "Guitar 7", which contains the same information for 6Rot1).
The program should next look back at Form Responses 1 and consider the student's next choice: in this case it would be the "second choice" (column J, "French I"). I want to look at the first possible rotation (6Rot1, 6Rot2, etc) that doesn't include the student AND has less than 48 people already in it, so that we can put his choice there. In this case6Rot2had already 39 names in it, so it accepted the student into row 40 of column H. After adding the student name in6Rot2, the program adds "French I" to Column E ofmainRoster6` ("Rotation 2")
It next looks back at Form Responses 1 and consider the "third choice" in column K, and repeats the process.
If a class is already "full" (has more than 47 names listed already in the column), then the program should try to find the first available rotation. For example, the student in Form Responses 1 has "Coding" as his third choice. 6Rot3 already has no space for column D ("Coding"), so it should first try to add the student's name ("Billy Kid") in Column D of 6Rot4 and then try to put in the student's name in column I of 6Rot3 for "German I".
What I'm trying to do with the code is parsing row by row of Form Responses 1 and putting the first 4 possible "choices" into the 4 possible rotations, and having mainRoster6 reflect that same information as a kind of abbreviated master list.
If a student lists two or more similar choices in Form Responses 1 columns I-N, then the program should ignore the repeated values. For example, in row 3 of Form Responses 1 it should overlook the second "Spanish" in K3 and move on to the next choice for that student.
If there are no more possible choices or rotations that can account for that choice, then the space can be left blank (see for example Form Responses 1 row 4: based on the student's choices and available spots, we were only able to honor the first three choices ("French" was already filled completely in 6Rot4; "Latin I" and "Art 7" were already in the schedule).
Under these conditions, I would have expected mainRoster6 to have this intended output:

Related

Google Spreadsheet Loop & IF statement: Loop through co-worker list and add them to a rotation schedule

EDIT - SOLVED
I have to make a rotation schedule for coworkers for the next year. Some coworkers have standard days off and I do not want to schedule them on those days.
This is the manual outcome I would like to get.
Example: Consultant A does not work on mondays, so I do not want Consultant A to be added to the schedule on a monday.
I then want consultant B to be added to the schedule as a fill-up. Consultant A would be next in line on a tuesday etc. Next would be consultant C but consultant C does not work on wednesdays. Therefore, we need to take consultant D for wednesday and consultant C on a thursday, and so on. When we are at the last consultant of the F column, it needs to start again at consultant A.
I have tried all kinds of formulas, like if statements and arrayformula. But there is no way that I know of to loop through the F column just with formulas.
I am not sure if this is at all clear what I want to achieve here, I am stuck 😄
I am using additionally an add-on to send the schedule to everyone's agenda, thats also the reason why i'd love to automate this, because it would help me SO much.
I did try myself on some coding, but I am no coder and I am not sure if it would be helpful at all to share my failure 😄 But this is what I've tried so far:
function Loop() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var EndRow = ss.getLastRow();
for (var i = 2; i <= EndRow; i++) {
var Day = ss.getRange(i,2).getValue();
var Consultants = ss.getRange(i,6).getValue();
var Off = ss.getRange(i,7).getValue ();
var Count = ss.getRange(i,8).getValue();
if(Day == Off){
ss.getRange(i, 3).setValue(Consultants)
}else{
ss.getRange(i, 3).setValue(Consultants)
}
}
}
EDIT:
I found a way without using apps scripts, costs me some more work manually and first tried it with a shorter team list.
The highlighted yellow cells are the cells in which the day off was identical to the work-day cell. So they got switched.
I did have to copy paste my input list of consultants but if this is the only manual way, its fine :)
Try this:
Code:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName("Sheet1");
var dayCol = sh.getRange("B2:B343").getValues().flat(); //get day column values and convert it to 1d array
var dayOffCol = sh.getRange("G2:G9").getValues().flat(); //get day off values and convert it to 1d array
var dayOffColCopy; //initialize copy
var consCol = sh.getRange("F2:F9").getValues().flat(); //get consultants values or column F
var consColCopy; //initialize copy
var tempArray = []; //storage of final value for column C
for(var i = 0; i < dayCol.length; i++){ //loop through dayCol values
var ctr = (i % consCol.length); //used modulo as counter. the value will return to 0 if the value of i is divisible to the length of consCol or in example 8
//The if statement below will help up reset the value of
//consColCopy and dayOffColCopy once the values are emptied because of the splice()
if(ctr == 0){
consColCopy = consCol.slice();
dayOffColCopy = dayOffCol.slice();
}
//the loop below will get the first non-matching values of dayCol and dayOffColCopy,
//the first non-matching values will be removed to the copy variables using splice()
//and insert it to tempArray using push()
for(var j = 0; j < dayOffColCopy.length; j++){ //loop through dayOffColCopy values
if(dayCol[i] != dayOffColCopy[j]){
tempArray.push(consColCopy.splice(j, 1));
dayOffColCopy.splice(j, 1);
break; //exit loop
}
}
}
sh.getRange(2, 3, tempArray.length, 1).setValues(tempArray); //set the values of temp array to column C
}
Example Data & Output:
Note: Make sure to use the cell that has data in your range and change the sheet name. I also added comments in my code to explain the process.
References:
Array.prototype.push()
Array.prototype.slice()
Array.prototype.splice()
Class Range

App Script For Loop is Slow Need to optimize code for faster update

This code works perfectly but its slow like turtle. Generally I don't take this approach but I was not able to find any other option.
Well my requirement is to where ever in the column code finds 1 get the key and value of the that index defined range and go to To sheet find the key and paste the value in the required columns.
If I can jump to cells where 1 is in selected column and get the index to find the key and value and then jump to To page key column where key is instead of going to every cell and checking for it whether it is there or not.
I am new to app script and little help would be great.
Thank you in advance.
function Data_Update(){
//assigning sheet name to variables
var ss = SpreadsheetApp.getActive()
var from = ss.getSheetByName("From Sheet")//update here from sheet name(Updated)
var To= ss.getSheetByName("To Sheet")//update here to sheet name(Updated)
// Creating Loop
for (var i = 4;i<=7000;i++){//Update here from which row to start
//assigning values to check if there is 1 in column K
var udaterang = from .getRange("K"+i).getValue()//update here from which column to check for
Logger.log(i)
// Checking condition if the value is diffrent from the value already is
if (udaterang == 1) {
//creating key to find the value
var name1 = from .getRange("A"+i).getValue()//update here the key column 1
var name2 = from .getRange("B"+i).getValue()//update here the key column 2
var name3 = from .getRange("C"+i).getValue()//update here the key column 3
var name = name1.trim()+name2.trim()+name3.trim()
var rng = from .getSheetValues(i,4,1,7)//start row, start column, # rows, # columns
// Looping through each cell to check if the data needs update
for(var j=2;j<=12500;j++){
var key = To.getRange("AP"+j).getValue()
if(key == name){ //[1] because column B
To.getRange("AI"+j+":"+"AO"+j).setValues(rng)
break
}
}
}
}
}
Try this:
function Data_Update() {
const ss = SpreadsheetApp.getActive();
const fsh = ss.getSheetByName("From Sheet");
const fkvs = fsh.getRange(4, 11, fsh.getLastRow() - 3).getValues().flat();
const fvs = fsh.getRange(4, 1, fsh.getLastRow() - 3, fsh.getLastColumn()).getValues();
const tapvs = tsh.getRange(2, 42, tsh.getLastRow() - 1).getValues().flat();
fkvs.forEach((k, i) => {
let udaterang = k;
if (k == 1) {
let name = fvs[i][0].toString().trim() + fvs[i][1].toString().trim() + fvs[i][2].toString().trim();
let idx = tapvs.indexOf(name);
if (~idx) {
tsh.getRange(idx + 2, 35, 1, 7).setValues(tsh.getRange(i + 2, 4, 1, 7).getValues());
}
}
});
}
This may take some tweaking because I have not tested this as I don't have the data to do so.

For loop to check values from two spreadsheets

I have two spreadsheets:
Column A on sheet 6th&7thRoster lists all IDs in a sample, contains 853 items.
Column C on sheet alreadySubmitted contains the IDs of users who've completed a task. Contains 632 items.
I'm trying to parse through both columns. If a user from Column A of sheet 6th&7thRoster matches a user from Column C of sheet sandboxAlreadySubmitted, I want to write the word "Yes" on Column I of the current row of sheet 6th&7thRoster. When using the code below, I'm not seeing not seeing any instances of the word "Yes" on Column I of 6th&7thRoster, even though I know there's multiple places where that should be the case.
function checkRoster() {
var mainSheet = SpreadsheetApp.openById('XXXXXXX');
var roster = mainSheet.getSheetByName('6th&7thRoster');
var submissions = mainSheet.getSheetByName('alreadySubmitted');
var rosterLastRow = roster.getLastRow();
var submissionsLastRow = submissions.getLastRow();
var rosterArray = roster.getRange('A2:A853').getValues();
var submissionsArray = submissions.getRange('C2:C632').getValues;
var i;
var x;
for (i = 1; i < 853; i++) {
for (x = 1; x < 632; x++){
if (rosterArray[i] == submissionsArray[x]){
roster.getRange(i, 9).setValue("Yes");
}
}
}
}
Feedback on how to solve and achieve this task will be much appreciated. For confidentiality, I cannot share the original sheets.
You want to compate the values of A2:A853 of 6th&7thRoster and C2:C632 of alreadySubmitted.
When the values of C2:C632 of alreadySubmitted are the same with the values of A2:A853 of 6th&7thRoster, you want to put Yes to the column "I".
If my understanding is correct, how about this modification? Please think of this as just one of several possible answers.
Modified script:
function checkRoster() {
var mainSheet = SpreadsheetApp.openById('XXXXXXX');
var roster = mainSheet.getSheetByName('6th&7thRoster');
var submissions = mainSheet.getSheetByName('alreadySubmitted');
var rosterLastRow = roster.getLastRow();
var submissionsLastRow = submissions.getLastRow();
var rosterArray = roster.getRange('A2:A853').getValues();
var submissionsArray = submissions.getRange('C2:C632').getValues(); // Modified
// I modified below script.
var obj = submissionsArray.reduce(function(o, [v]) {
if (v) o[v] = true;
return o;
}, {});
var values = rosterArray.map(function([v]) {return [obj[v] ? "Yes" : ""]});
roster.getRange(2, 9, values.length, values[0].length).setValues(values);
}
Flow:
Retrieve values from A2:A853 of 6th&7thRoster and C2:C632 of alreadySubmitted.
Create an object for searching the values from the values of alreadySubmitted.
Create the row values for putting to 6th&7thRoster.
References:
reduce()
map()
If I misunderstood your question and this was not the direction you want, I apologize.

Dropdown list with conditions on other dropdown list

How can I make a drop-down list from several lists with a condition from another drop-down list?
Using my image above, let's say I on the first drop-down list (this one is a simple one) select "Furniture"... I would like the second drop-down list to only show the furniture. Same thing with the third drop-down list, would like only the color of my second choice to be shown there.
Did try to place on the criteria "Custom formula" in the "Data validation" one of this two formulas but does not work...
=FILTER(Object,Type = E2)
or
=QUERY(A:C,"SELECT B WHERE A='"&E2&"' ", 0)
Did read in some other topic here that it was not possible with formulas and I could not find an app script for it. How can i place conditional rules and make only appear the values i want on the drop-down menu instead all of them ? I think it has something to do with the "withCriteria(criteria, args)" however i am not understanding how to apply it.
About the list ... it will be compose maybe with 2k lines (each line 3 columns). First column will only have (maybe) 6 or 7 different values. Second about 70 or 80 and third all different. The order will be random cause new values could be added and i can be adding a new Furniture or Animal ...
This is the code i have now
function onEdit(e) {
var range = e.range;
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
if ( range.getRow() > 1) {
if ( range.getColumn() == 5) {
var cell_Range = ss.getRange( range.getRow(), range.getColumn() + 1);
var cell = cell_Range.getCell( 1, 1);
var rangeV = SpreadsheetApp.getActive().getRange('B2:B13');
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(rangeV).build();
cell.setDataValidation(rule);
}
else if ( range.getColumn() == 6 ) {
var cell_Range = ss.getRange( range.getRow(), range.getColumn() + 1);
var cell = cell_Range.getCell( 1, 1);
var rangeV = SpreadsheetApp.getActive().getRange('C2:C13');
var rule = SpreadsheetApp.newDataValidation().requireValueInRange(rangeV).build();
cell.setDataValidation(rule);
}
}
}
Example sheet at https://docs.google.com/spreadsheets/d/1aLpYd8fC0jpwvQOPVTj_yvY7DVKeFFnPvpJSF27if6w/edit?usp=sharing
Thanks in advance
Solution:
Use Datavalidation requireValueInList
Flow:
Get All options A1:C as a array
Filter A(Col1) if edited value in E(Col5) is present in it
Retrieve corresponding Col2(B) as options and build data validation based on it
Offset the edited range by 1 column and set DataValidation
Sample Script:
function onEdit(e) {
const SETTINGS = {
//Edited Column : Column to Check(First col in optionsDataRange is considered 1)
5: 1,
6: 2,
};
var editedRange = e.range,
editedSheet = editedRange.getSheet(),
val = e.value,
col = editedRange.columnStart,
row = editedRange.rowStart;
/*Exit clause(s)*/
if (
Object.keys(SETTINGS).indexOf(col.toString()) === -1 || //If edited col is not in settings
editedSheet.getName() !== 'Sheet1' ||
row > 5
)
return;
var optionsDataRange = editedSheet
.getRange(1, 1, editedSheet.getLastRow(), 3)
.getValues();
/*Only get options where val is present in optionsDataRange*/
var options = optionsDataRange
.map(function(e) {
return e[SETTINGS[col] - 1] == val ? e[SETTINGS[col]] : null;
})
.filter(function(e, i, a) {
return e !== null && a.indexOf(e) === i;
});
var dv = SpreadsheetApp.newDataValidation()
.requireValueInList(options)
.build();
editedRange.offset(0, 1).setDataValidation(dv);
}
References:
Range
Array

Looping Down A Column in Google Sheets Using Offset and Subtracting 2 Values in a Cell

I'm writing an inventory management script of Google Sheets. What i need to do...
I'm trying to loop down Column 'A' and check to see if a cell in column 'A' has a value. If that row in Column 'A' does have a value then i want to subtract the Value in Column 'E' from the Value in Column 'A' and then store that result of the difference in Column 'E'. After the program loops all down to the last cell of column A (which is hard coded at 327) then i want all values in Column 'A' to be cleared.
Currently what is happening is the that first row where column 'A' has a value works but after that its all over the place. I think my loop iterations might be wrong but i cant figure it out.
function myFunction4() {
var mySheet = SpreadsheetApp.getActiveSheet();
var high = mySheet.getRange("E10");
var low = mySheet.getRange("A10");
for (var i = 0; i < 327; i++) {
if (low.getValue() == 0 ) {
i = i + 1;
} else {
high = high.offset(i, 0);
low = low.offset(i, 0);
high.setValue(high.getValue() - low.getValue());
}
mySheet.getRange("A10:A328").clearContent();
}
}
Any help would be much appreciated. Trying to get the program to run as fast as possible!

Resources