Optimal method for organizing individuals into teams using Google Apps Script

There are approximately 200 individuals in this particular department. Our goal is to form groups of 4, with each group consisting of members from different teams based in the same city. Each group must have one driver and three non-drivers, all sharing similar preferences. These criteria will be collected through a Google Form to gather the necessary data for creating the groups. My current challenge lies in my limited knowledge of Apps Script, so I will require high-level guidance on how to proceed.

My initial approach involves storing each column in separate arrays and using a For loop along with conditional statements to meet all the requirements. The first condition would identify drivers and assign them to a new array. Then, the next responses would be checked to find non-drivers with matching preferences, located in the same city, and from different teams to include them in another array. This process continues until a group of 4 is formed, which would then be recorded in a Google Sheet before moving on to create the next group following the same logic.

An alternative method could involve sorting people into two groups based on their respective cities, followed by further categorization within each group based on driving status and other criteria.

Regardless of the algorithm chosen, starting with storing each column value in an array seems like the most logical approach, allowing for easy management of the array values using various conditional statements.

This is the progress made so far:

function matchUsers() {
  //store each column into arrays for later use
  var responseSheet = SpreadsheetApp.getActive().getSheetByName("Responses");
  var Alast = responseSheet.getRange("A2:A").getValues().filter(String).length;
  Logger.log(Alast);
  var emails = responseSheet.getRange("A2:A").getValues();
  var userEmails = responseSheet.getRange(2,1, Alast,1).getValues();
  var tmName = responseSheet.getRange(2,2, Alast,1).getValues();
  var team = responseSheet.getRange(2,3, Alast,1).getValues();
  var city = responseSheet.getRange(2,4, Alast,1).getValues();
  var firstChoice = responseSheet.getRange(2,5, Alast,1).getValues();
  var secChoice = responseSheet.getRange(2,6, Alast,1).getValues();
  var thirdChoice = responseSheet.getRange(2,7, Alast,1).getValues();
  var driveStatus = responseSheet.getRange(2,8, Alast,1).getValues();
  var arrayResponses = [userEmails,tmName,team,city,firstChoice,secChoice,thirdChoice, driveStatus];

  //clear sheets
  SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Output").getRange(2, 1,20,50).clearContent();
  SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CitySort").getRange(2, 1,20,50).clearContent();

  //prepare arrays
  var edmTMs = [];
  var calTMs = [];
  var edmDrivers = [];
  var calDrivers = [];

  //loop through each row and sort team members by respective city and driving capability
  for (let i = 0; i < Alast;i+=1){
    if (arrayResponses[3][i] == "Edmonton" && arrayResponses[7][i] == "Yes"){
      edmDrivers.push(arrayResponses[1][i]);
    }else if (arrayResponses[3][i] == "Edmonton" && arrayResponses[7][i] == "No"){
      edmTMs.push(arrayResponses[1][i]);
    }else if (arrayResponses[3][i] == "Calgary" && arrayResponses[7][i] == "Yes"){
      calDrivers.push(arrayResponses[1][i]);
    }else if (arrayResponses[3][i] == "Calgary" && arrayResponses[7][i] == "No"){
      calTMs.push(arrayResponses[1][i]);
    }
  }

  Logger.log("\nEdmonton: "+edmTMs + "\nCalgary: "+calTMs );
  var range = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CitySort").getRange(2,1,edmTMs.length,1);
  range.setValues(edmTMs);
  var range = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CitySort").getRange(2,2,calTMs.length,1);
  range.setValues(calTMs);
  var range = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CitySort").getRange(2,3,edmDrivers.length,1);
  range.setValues(edmDrivers);
  var range = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CitySort").getRange(2,4,calDrivers.length,1);
  range.setValues(calDrivers);
}

Answer №1

Let's explore if this solution aligns with your requirements. I am aware that there may be more sophisticated and effective alternatives available, so any suggestions are welcomed.

After testing the script with 34 users, the results showed 8 teams of 4 members each and 2 teams consisting of only 1 member:

https://i.stack.imgur.com/m3F3I.png

Please disregard the duplicate names, as they represent distinct rows with varying Cities, Teams, and Choices. To differentiate them, certain names have been appended with a letter.

The functionality of the script is as follows:

It analyzes each user row and compares the City, Teams, and Choices with all other 33 users. Users who are already part of a team are excluded from consideration. The priority order is as follows: City > Team > Choice 1 > Choice 2 > Choice 3.

The script makes an effort to form teams comprising 4 members each. In case a team has only 3 members, it prioritizes finding the 4th member while adhering to the Choices and Cities criteria. If desired, you can deactivate this feature by setting the boolean ignoreTeams to false. During my test, only 3 groups ended up with a repeated team.

User from different cities will never be grouped together by the script.

function main() {

  var sprsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet_1 = sprsheet.getSheetByName("Members");
  var sheet_2 = sprsheet.getSheetByName("Output");

  var number = sheet_1.getLastRow() - 1;
  var members = []; //Each row of Members will be stored here

  getMembers(sheet_1, members, number);
  orderMembers(sheet_2, members);

}

function getMembers(sheet_1, members, number){
  for (var i = 0; i < number; i++){
    var values = sheet_1.getRange(i+2, 1, 1, 8).getValues();
    members.push(new Person(values[0][0],values[0][1],values[0][2],values[0][3],values[0][4],values[0][5],values[0][6],values[0][7]));

  }

}

function orderMembers(sheet_2, members){
  var col = 1;
  var counter = 0; //Number of Persons in each group column [0-3] 

  loop1:
  for (var i = 0; i < members.length; i++){
    var loopbreaker = 0; //Avoids an endless loop if ignoreTeams = true
    var ignoreTeams = false; 
    var row = 2;
    if (!members[i].assigned || i == 0){
      var teams = []; //Array of teams of each group
      sheet_2.getRange(row, col).setValue(members[i].name);
      members[i].assigned = true;
      teams.push(members[i].team);

      loop2:
      for (var loop = 0; loop < 9; loop++){
        var value = order_params(i, loop, members, teams, ignoreTeams);

        if (value != null){
          sheet_2.getRange(row + 1, col).setValue(value); //Writes in sheet Output     
          counter++;
          row++;

        }
        //If the group is smaller than 4 and the loop is over, it will ignore the teams and start again in order to complete the group.
        if (counter < 3 && loop == 8 && loopbreaker < 1){
          loop = 0; 
          ignoreTeams = true; //Set to false to never ignore teams
          loopbreaker++;//The loopbreaker avoids an endless loop
        } else if (counter == 3 || loop == 8){
          col++;
          counter = 0;
          break loop2;
        }

      }
    }
  }


}

function order_params(i, loop, members, teams, ignoreTeams){
  for (var x = 0; x < members.length; x++){

    if (!members[x].assigned && members[x].email != members[i].email && members[x].city == members[i].city  && (!checkTeams(members[x].team, teams) || ignoreTeams)) {
      if (members[x].choice1 == members[i].choice1 && loop < 3) {

        members[x].assigned = true;
        teams.push(members[x].team);
        return members[x].name
      } else if (members[x].choice2 == members[i].choice1 && loop >= 3 && loop < 6) {

        members[x].assigned = true;
        teams.push(members[x].team);
        return members[x].name

      } else if (members[x].choice3 == members[i].choice1 && loop >= 6 && loop < 9) {

        members[x].assigned = true;
        teams.push(members[x].team);
        return members[x].name

      } 
    }
  }

}
//Checks for duplicate teams in the group
function checkTeams(member_t, teams){
  for (var i = 0; i < teams.length; i++){
    if (member_t == teams[i]){

      return true; 
    }
  }
}
//Converts the data from the Sheet into Person objects.
function Person(email, name, team, city, choice1, choice2, choice3, driver){
  this.email = email;
  this.name = name;
  this.team = team;
  this.city = city;
  this.choice1 = choice1;
  this.choice2 = choice2;
  this.choice3 = choice3;
  this.driver = driver;
  this.assigned = false; //Assigned = true means the Person is already in a group

}

The Driver field is not utilized in the current version of the script, allowing flexibility for customized modifications based on individual needs. Should a lack of drivers become a critical issue, adjustments can be made to address this concern within the code.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Transfer responsibilities of events to the canvas and then fetch the Element in the handler

Currently, I am utilizing the Raphaël library to create a network graph, where nodes are depicted as circles. Users have the ability to dynamically add nodes by clicking on the canvas. When a new node is added, an Element object is pushed into both a Set ...

Convert the existing jQuery function to Angular 9 syntax

I have just started learning Angular and am finding it challenging to rewrite a jQuery code that I use for loading the "classycountdown" script. Below is the jQuery function that I need to convert: $(document).ready(function() { var remainingSec = $(& ...

Add a new key-value pair to the mock data by clicking on it

Hey there! I'm currently tackling a task that involves toggling the value of a boolean and then appending a new key-value pair on click. I've been attempting to use the . operator to add the new key-value pair, but it keeps throwing an error. In ...

The displayed value in the text field remains constant even when the object's attribute is modified

My issue involves the Date Text Field component of Material UI. Whenever I pass an attribute from an object, the displayed value in the Text Field does not update even when the object attribute changes. const [data, setData] = useState({ title: new Da ...

Error in routing of submit button in Express.js

While attempting to create a basic "to-do list" using HTML requests, I encountered an issue with the PATCH request. Instead of redirecting to "/", it redirected to "/posts/2" and displayed the message "Cannot POST /posts/2", without updating the array elem ...

Looping through a single object with an Ajax call

When I make this ajax call, it only displays the last object from the JSON file instead of all objects. Can someone help me understand why? Ajax Call var ajax = new XMLHttpRequest(); var data = 'data.json'; var url = 'http://localhost: ...

Is it feasible to obtain multiple tag-name indexes using JavaScript?

Exploring the table search function provided by W3Schools has brought up an interesting question in my mind. Is it feasible to simultaneously retrieve multiple indexes using getElementsByTagName and conduct a search across the entire table instead of just ...

JavaScript popup cannot be shown at this time

I'm encountering an issue with displaying popups using JavaScript. Currently, only the div with class "popup" is being shown. When a user takes action, both popup and popup2 should be displayed but for some reason, it's not working as expected. ...

How come the back button does not initiate client-side navigation in a Next.js application?

In my Next.js application utilizing GraphQL to fetch articles from a server, I encountered an issue with dynamic routing when reloading the page while on an article and attempting to navigate back. The usual scenario works as expected: Index -> [slug] ...

The issue of "undefined is not a function" is arising when trying to use the session in NHibernate with a mongo store

In my MongoDB database, I have a collection named 'Sessions' within the 'SessionStore' to store session state. To manage sessions, I am using express-session. Below is the code snippet used to set up sessions: var session = requi ...

Blazor combines the power of both C# and JavaScript by allowing them to be executed in a single

There is a button that triggers a JavaScript function: <button class="btn-orange btn" onclick="expand(this.closest('.profile'))">JavaScript</button> And there is another button that executes C# code and toggles ic ...

Every time Chrome on Android returns a keyCode of 229

Here is a snippet of code that I am having trouble with: ... @HostListener('keydown', ['$event']) onKeyDown(evt: KeyboardEvent) { console.log('KeyCode : ' + evt.keyCode); console.log('Which : ' + evt.which); ...

Is it necessary to re-export a module after modifying an attribute in it in JS/ES6?

From my understanding of the module system, when I use import 'some_module' in a file, I will always receive the same instance of that module and not a new instance each time. However, I am a bit puzzled by a pattern I have observed in certain a ...

Utilizing Mantine dropzone in conjunction with React Hook Form within a Javascript environment

Can Mantine dropzone be used with React hook form in JavaScript? I am currently working on a modal Upload using Tailwind components like this import { useForm } from 'react-hook-form'; import { Group, Text, useMantineTheme } from '@mantine/c ...

Utilize node.js on your local machine and leverage gulp to monitor any modifications

I recently copied a repository from https://github.com/willianjusten/bootstrap-boilerplate and followed these steps. git clone git://github.com/willianjusten/bootstrap-boilerplate.git new_project cd bootstrap-boilerplate npm install gulp The gulp comman ...

Adding a .PHP File to Two Separate DIVs

I am using wordpress to create a custom theme. I'm interested in placing all the content from my slider.php file inside a div box on my index.php page. How would I go about doing this? SLIDER.PHP <div class="slider"> // All the image tags wit ...

How to organize initial, exit, and layout animations in Framer Motion (React) tutorial?

Currently, I am utilizing framer-motion library for animating a change in grid columns. This is the objective: Within the grid container (#db-wrapper), there are nine buttons arranged. https://i.stack.imgur.com/61pQqm.png When the user switches to the ...

Webpack has issues with loading HTML files

I encountered a 404 not found error while attempting to load the HTML page using webpack. Here are my configurations: Webpack.config.js: const path = require('path'); module.exports= { devServer: { // contentBase static : { ...

Back up and populate your Node.js data

Below is the Course Schema I am working with: const studentSchema = new mongoose.Schema({ name: { type: String, required: true }, current_education: { type: String, required: true }, course_name: { ...

This error message occurs when trying to access JSON keys from an object with an invalid operand in the 'in' operation

Check out the fiddle I created here: http://jsfiddle.net/kc11/h6nh1gvw/2/ I'm attempting to extract keys from a JSON string using this code: var keys = $.map(a, function(element,key) { return key; }); . But unfortunately, I keep encountering the er ...