I've dataset which look like this(Raw Data) :-
And I want to jumble or sort the data like Book 1 - Topic A and next row Book 2 - Topic B, so one book-chapter and next book and chapter, if we does that, data should look like this :-
I somehow made it, but it's not getting data which has more chapter, like Book 3 has 3 Chapter and Book 4 has 4 chapter, so it's not extracting that.
Using this code now:-
function jumbling() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const ssname = ss.getSheetByName('Raw')
const data = ssname.getRange(2,1,ssname.getLastRow()-1,8).getValues()
const lessons = [...new Set(data.map(function(r){return r.slice(3,4)}).flat())]
const arr = []
let j = 0
console.log(lessons)
let thisLesson
let to_Len = 0
for(let i = 0 ; i < lessons.length ; i++)
{
let k = 0
while(k < lessons.length)
{
let thisLesson = data.filter(r=> r[3] === lessons[k])
arr.push(thisLesson[j])
k += 1
}
j += 1
}
console.log(arr)
ss.getSheetByName('Processed').getRange(1,1,arr.length,arr[0].length).setValues(arr)
}
You can try with this formula, if it fits with your intention:
=SORT(A2:H,BYROW(A2:A,LAMBDA(each,IF(each="","",COUNTIF(A2:each,each)))),1)
With BYROW it makes a cumulative count of each time an ID appears (I used A column to configure it, because I guessed that's the value of uniqueness, but can change it to column D if needed).
Related
I've been trying to loop over a range of data and bring the data to another sheet until the loop hits one of the following words (Tools, Painting) and stop there.
I was going for a for loop + while, but I'm not sure this would be the best way to do that.
Here's the code draft I'm working on:
function importPipeworkData() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const input4File = ss.getSheetByName('Input Files Mapping').getRange('B4').getValue()
const pipeworkSheet = SpreadsheetApp.openByUrl(input4File).getSheetByName('Sheet1');
const inputPipeworkRng = pipeworkSheet.getRange(2, 1, pipeworkSheet.getLastRow(), 4);
const inputPipeworkData = inputPipeworkRng.getValues();
const boqPipeworkSheet = ss.getSheetByName('BOQ Pipework');
const boqPipeworkRng = boqPipeworkSheet.getRange(4, 1, boqPipeworkSheet.getLastRow(), 4);
//boqPipeworkRng.clear(); //Clears the sheet to receive updated data;
const pipeworkData = [];
for (let a = 0; a < inputPipeworkData.length; a++) {
while (inputPipeworkData[a][0] != 'Valves') //This is one of the unclear parts
Logger.log(inputPipeworkData[a][1])
}
}
Here's an example of the dataset: https://docs.google.com/spreadsheets/d/1W9zPt1nYKv59j9kjgFqy5Z4KPgvXZUoCusTk06kRQUk/edit?usp=sharing
I'd appreciate if any help could be given here.
Cheers,
Antonio
In your situation you should use only one loop
Thereby,
in case a for loop an additional if statement is necessary .
in case of a while loop, you should make sure to increment your counter variable.
Sample with for loop:
const keyWords = ["Tools", "Painting"];
for (let a = 0; a < inputPipeworkData.length; a++) {
if (keyWords.indexOf(inputPipeworkData[a][0]) > - 1){
break;
}
Logger.log(inputPipeworkData[a][1]);
pipeworkData.push(inputPipeworkData[a]);
}
}
Sample with while loop:
const keyWords = ["Tools", "Painting"];
var a = 0;
while (keyWords.indexOf(inputPipeworkData[a][0]) == - 1){
Logger.log(inputPipeworkData[a][1]);
pipeworkData.push(inputPipeworkData[a]);
a++
}
}
Object property value differentiates from the value I'm assigning to it,even though none other changes are made to the property which I checked with implementation of Object. watch I found at this link Watch for object properties changes in JavaScript.
From sofascore I'm getting an array with weeks (objects with start and end time) and I want to add home and away standings to each week as they were at the end of that week.I'm getting matches and their start time and result also from sofascore.
My sorting algorithm which sorts the table and pushes teams to their index in standings.home or standings.away array is working as expected.
I've worked on this for a couple hours now and a couple of hours last night and I think that I've already tried every possible solution that I can think of.
const https = require('https');
function get(url) {
return new Promise( resolve => {
https.get(url , res => {
res.setEncoding("utf8");
let body = "";
res.on("data", data => {body += data;});
res.on("end", () => {resolve(body)});
});
});
}
function standingsByWeek(tournamentID,seasonID) {
const baseURL = 'https://www.sofascore.com/';
var league,weeks,matches,teams,teamsIDs,standings,currentWeekIndex = 0,table = {},possiblePoints = [3,1,0];
get( baseURL + 'u-tournament/' + tournamentID + '/season/' + seasonID + '/json').then(function(league) { //get data for league
league = JSON.parse(league);
weeks = league.events.weeks;
teams = league.teams;
for (let i = 0; i < teams.length; i++) { //fill up table with teams
table[teams[i].id] = { id : teams[i].id, home : [0,0,0,0,0,0,teams[i].id], away : [0,0,0,0,0,0,teams[i].id]} ; // wins,draws,losses,score,conceded,points
};
teamsIDs = Object.keys(table); //extract keys (teamsIDs) from table
return get( baseURL + 'u-tournament/'+tournamentID+'/season/'+seasonID+'/matches/week/'+weeks[0].weekStartDate+'/'+weeks[weeks.length-1].weekEndDate)// get data for matches
}).then(function(matches) {
matches = JSON.parse(matches).weekMatches.tournaments[0].events; // assign matches to matches
for (let i = 0; i < matches.length; i++) { //update the table for every finished match
with (matches[i]){
if(status.code != 100){continue} // continue if match not finished
var outcome = Math.sign(homeScore.normaltime - awayScore.normaltime); // get outcome 1 = win , 0 = draw , -1 lose (for home team)
table[homeTeam.id].home[Math.abs(outcome - 1)] ++; // incrementing number at index for outcome
table[awayTeam.id].away[outcome + 1] ++; // -||-
table[homeTeam.id].home[3] += homeScore.normaltime; // adding scored goals in home team's home table
table[homeTeam.id].home[4] += awayScore.normaltime; // adding conceded goals in home team's home table
table[awayTeam.id].away[3] += awayScore.normaltime; // adding scored goals in away team's away table
table[awayTeam.id].away[4] += homeScore.normaltime; // adding conceded goals in away team's away table
table[homeTeam.id].home[5] += possiblePoints[Math.abs(outcome - 1)]; // adding earned points to home team's home table
table[awayTeam.id].away[5] += possiblePoints[Math.abs(outcome + 1)]; // adding earned points to away team's away table
}
// if this is the last match of the season or current week ended ,sort the table and push standings to that week and increase current week index
if ( (i + 1) == matches.length || weeks[currentWeekIndex].weekEndDate < matches[i + 1].startTimestamp ) {
standings = {home : [], away:[] };
for (let j = 0; j < 2; j++) { // loop through both keys in standings
var HoA = Object.keys(standings)[j]; //home or away
k : for (let k = 0; k < teamsIDs.length; k++) { // loop through all teams in the table
for (let l = 0; l <= standings[HoA].length; l++) { // loop through all teams added to standings
var EoL = Boolean(l == standings[HoA].length) // empty or last
var tableTeam = table[teamsIDs[k]][HoA] ;
var standingsTeam = EoL ? null : standings[HoA][l]; //standings team or null if standings is empty
var pointDiff = l == standings[HoA].length ? null : Math.sign(tableTeam[5] - standingsTeam[5]) ; // point difference 1 = table team has more, 0 = equal # of points, -1= standings team has more points
// standings empty(or last) or more points or equal points and better goal difference
if ( EoL || (pointDiff == 1) || !pointDiff && (tableTeam[3] - tableTeam[4]) > (standingsTeam[3] - standingsTeam[4] ) ) {
standings[HoA].splice(l,0,table[teamsIDs[k]][HoA]); // put team from table to index at which team which it was compared to was
continue k // go to another team
}
}
}
}
// USING THIS INSTEAD OF LINE BELOW IT YOU CAN SEE WHAT VALUE IS BEING ASSIGNED TO STANDINGS PROPERTY OF WEEK OBJECT
//Object.defineProperty(weeks[currentWeekIndex],'setter',{set: function (value) {this.standings = value;console.log(currentWeekIndex,value);}})
// weeks[currentWeekIndex].setter = Object.assing({},standings);
weeks[currentWeekIndex].standings = Object.assign({},standings);// assigning standings clone to standing property of current week
currentWeekIndex++;// increase week index
}
}
console.log(weeks[0].standings);
}
)
}
standingsByWeek(17,13380); // premier league , season 17/18
I expect object property to have the same value as the assigned value if none other changes to property are made.
EDIT
link to tidied and runnable version of the code https://repl.it/#mwittig/objectPropertiesChange
(thanks to Marcus)
I'm trying to optimise a whole school timetable. I have the timetable currently organised in a sheet. Teacher initials are the headings for each column and each row corresponds to a single teaching period in a 30 lesson week. Each cell contains the name of a class.
Currently I am looking for classes that are split between 2 teachers.
I am trying to make an appscript that will log the classname if it appears anywhere outside the current column (i.e. the same class is being taught by 2 or more different teachers at different times)
I'm aware that nesting loops is not the least efficient way of doing this but I just wanted to hack something together quickly to get the job done. Unfortunately this code is taking longer than the maximum permitted time. The array is only 30 rows by about 56 columns so I dont see why it's taking such a long time. (Cant see anything that's obviously infinite about my loops either)
Can anyone help? :)
function splitClassLocator()
{
//copy the sheet to a 2d array.
//(1)descend through each column from vertical idx 3 to period6 idx36
//(2)start at horiz idx 1, descend through each item vertically.
//if item from loop 1 matches item from loop 2 and loop 1 vertical index != loop 2 vertical index
//log the item (split class)
//GET THE DATA
var sh0 = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var range = sh0.getDataRange();
var data = range.getValues();
//COMPARE
//main row mr, main col mc V compare row cr, compare col cc
Logger.log("Rows = " + data.length + "cols = " + data[0].length);
for (var mr = 2; mr < data.length; mr ++)
{
for (var mc = 1; mc < data[0].length; mc ++)
{
for (var cr = 2; cr < data.length; cr ++)
{
for (var cc = 1; cc, data[0].length; cc ++)
{
if (mc != cc) // if it's not comparing classes belonging to the same teacher
{
if ((data[mr][mc] != undefined) || (data[mr][mc] != null) || (data[mr][mc] != ""))
{
if (data[mr][mc] == data[cr][cc])
{
Logger.log(data[mr][mc]);
}
}
}
}
}
}
}
}
Rather than discard the information after your comparison, store it and use it! Welcome to the world of not-Arrays!
Your stated goal is to find classes that have more than two teachers. Thus, the minimum you need to do is tally this information on a single trip through the array.
function countTeachersPerClass() {
const schedule = SpreadsheetApp.getActive().getSheetByName("somename").getDataRange().getValues();
const headers = schedule.shift();
const numTeachers = headers.length;
// Store classes as properties in an Object.
const classes = {};
// Read the schedule array.
// Assumption: time is in index 0, classes in all other.
schedule.forEach(function (period) {
var name = period[0];
// Store each class in this period.
for (var teacherIndex = 1; teacherIndex < numTeachers; ++teacherIndex) {
// Have we seen this class? If no, initialize it.
var classID = period[teacherIndex];
if (!classes[classID])
classes[classID] = {teachers: {} };
// Add this teacher to its list of teachers.
var tID = headers[teacherIndex];
if (!classes[classID].teachers[tID])
classes[classID].teachers[tID] = {periods: []};
// Add this period for this teacher.
classes[classID].teachers[tID].periods.push(name);
} //End for loop over columns in a row.
}); // End forEach over schedule's rows.
// Log the object (in Stackdriver, for easy review and interactivity).
console.log({message: "Built classes object", input: schedule, result: classes, teachers: headers});
// Make a report from it.
const report = [["Class ID", /* other headers */]];
for (var cID in classes) {
// This particular report is for class with more than two teachers.
if (Object.keys(classes[cID].teachers).length > 2) {
var row = [cID];
/** Push info to the row, perhaps with just the names
of the teachers, or also including number of
periods per each teacher, etc. */;
// Add the completed report row to the report.
report.push(row);
}
}
// Log the report.
console.info({message: "Report", report: report});
}
You could certainly get fancier by adding more properties to each classes object than just teachers, such as tracking the average consecutive taught time (i.e. which classes alternate teachers often), but I leave that as an exercise to the reader :)
This works a bit better, flattening to make 2 single dimensional arrays, one is the teacher to check and the other array is all the other timetables combined.
Still takes ages though:
function splitClasses()
{
var sh0 = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
var range = sh0.getDataRange();
var data = range.getValues();
var rows = data.length;
var cols = data[0].length;
for (var col = 1; col < cols; col ++)
{
var teacherTT = [];
var othersTT = [];
for (var row = 2; row < rows; row ++)
{
teacherTT.push(data[row][col]);
}
for (var oCol = 1; oCol < cols; oCol ++)
{
for (var oRow = 2; oRow < rows; oRow ++)
{
if (col !=oCol)//dont add current teacher TT to others TT
{
othersTT.push(data[oRow][oCol]);
}
}
}
//Logger.log(othersTT);
var tLength = teacherTT.length;
var oLength = othersTT.length;
Logger.log("tL" + tLength);
Logger.log("oL" + oLength);
for (var t = 0; t < tLength; t ++)
{
//Logger.log("t "+t);
for (var o = 0; o < oLength; o ++)
{
if (teacherTT[t] != undefined ||teacherTT[t] != null || teacherTT[t] != "" || teacherTT[t] != " ")
{
if (teacherTT[t])
{
if (teacherTT[t] == othersTT[o])
{
//Logger.log("o "+o);
Logger.log(teacherTT[t]);
}
}
}
}
}
}
}
One of our high schools is trying to create weekly reports for students who are currently failing one or more classes. The reports are in the format linked below.
If a student is failing multiple classes, the information is on different rows. The information needs to be combined, with each student on one column - column 1 is the name, and the subsequent columns are for the grade/class/teacher information.
Currently, I have code that deletes blank sheets, creates a new sheet titled "Output", writes a header row, and writes unique names in column 1 (portions commented out so it doesn't create a duplicate "Output" sheet every time):
function copyRows() {
//Initialize variables
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheetByName("Sheet1");
/*
//Create output Sheet
ss.insertSheet("Output");
*/
var writeSheet = ss.getSheetByName("Output");
/*
//Delete unwanted columns (1, 3, 4, 7, 9)
dataSheet.deleteColumn(1); //Deletes first column
dataSheet.deleteColumn(2); // Deletes third column (because it shifts after every deletion)
dataSheet.deleteColumn(2); //Deletes fourth column
dataSheet.deleteColumn(4); //Deletes 7th column
dataSheet.deleteColumn(5);//Deletes 9th column
//Delete unwanted Sheets
var deleteSheet2 = ss.getSheetByName("Sheet2");
var deleteSheet3 = ss.getSheetByName("Sheet3");
ss.deleteSheet(deleteSheet2);
ss.deleteSheet(deleteSheet3);
//Write data to header row
var headerRange = writeSheet.getRange("A1:V1");
var headerValues = [
["name", "class1", "grade1", "teacher1","class2", "grade2", "teacher2","class3", "grade3", "teacher3","class4", "grade4", "teacher4","class5", "grade5", "teacher5","class6", "grade6", "teacher6","class7", "grade7", "teacher7"]
]
headerRange.setValues(headerValues);
*/
var lastRow = dataSheet.getLastRow();
var mainArray = dataSheet.getRange(1, 1, lastRow, 4).getValues();
var allNames = []; //List of all names, including duplicates
var uniqueNames = []; //List of all unique names
for (i = 0; i < mainArray.length; i++) { //Sets allNames
allNames.push(mainArray[i][0]);
}
for (i = 0; i < allNames.length; i++) { //Sets uniqueNames
if (allNames[i+1] != allNames[i]) {
uniqueNames.push(allNames[i]);
}
}
var uniqueNamesArray = uniqueNames;
//New method that converts 1d array to 2d array
Array.prototype.reshape = function(rows, cols) {
var copy = this.slice(0); // Copy all elements.
this.length = 0; // Clear out existing array.
for (var r = 0; r < rows; r++) {
var row = [];
for (var c = 0; c < cols; c++) {
var i = r * cols + c;
if (i < copy.length) {
row.push(copy[i]);
}
}
this.push(row);
}
};
var uniqueNamesRow = uniqueNames;
uniqueNames.reshape(uniqueNames.length, 1); //Changing uniqueNames from row to column
var writeNamesRange = writeSheet.getRange(2,1,uniqueNames.length,1); //writeSheet column 1
writeNamesRange.setValues(uniqueNames);
Example data:
John Doe 50 Band Mr. Dean
Mary Smith 60 US History Ms. Jones
Mary Smith 25 Chemistry Ms. Dyar
Mary Smith 40 Algebra 2 Ms. Harris
Bob Miller 55 Band Mr. Dean
Larry Jones 22 Algebra 2 Ms. Harris
With the output of:
John Doe 50 Band Mr. Dean
Mary Smith 60 US History Ms. Jones 25 Chemistry Ms. Dyar 40 Algebra 2 Ms. Harris
Bob Miller 55 Band Mr. Dean
Larry Jones 22 Algebra 2 Ms. Harris
Note that Mary Smith's data has been combined into one row.
I just cannot figure out how to iterate through the rows and append the data to the end of the appropriate row.
Sorry for the previous lack of details, and thanks for the feedback.
I wrote some codes based on your input and output (not on your code). Since student's name is a primary key, I think dictionary data structure is suitable here. Below code is not so elegant and many literals were hard-coded but I guess you'll be able to grasp the idea. Sample sheet is here.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('input');
var rows = 6;
var cols = 4;
var input = ss.getRange(1,1, rows,cols).getValues();
var students = [];
for(var i = 0; i < input.length; i++) {
var name = input[i][0];
var class = {
score: input[i][1],
class: input[i][2],
teacher: input[i][3]
};
var j;
for(j = 0; j < students.length; j++) {
if(students[j].name == name) {
students[j].classes.push(class);
break;
}
}
if(j == students.length) {
students.push({
name: name,
classes: [class]
});
}
}
var ts = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('output');
var output = [];
for(var i = 0; i < students.length; i++) {
output[i] = [students[i].name];
for(var j = 0; j < students[i].classes.length; j++) {
output[i].push(students[i].classes[j].score,
students[i].classes[j].class,
students[i].classes[j].teacher);
}
ts.getRange(i+1,1, 1,output[i].length).setValues([output[i]]);
}
}
I tried this for a while, and came up with a different approach. Below in case someone finds it useful, I also added some comments
function appendToRecords2() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetWithNewData = ss.getSheetByName("New");
var sheetWithExistingData = ss.getSheetByName("Existing")
// Defines the range where the NEW DATA is located
var newData = sheetWithNewData.getRange("a2:b25")
.getValues();
var lastRow = sheetWithExistingData.getLastRow();
sheetWithExistingData.getRange(lastRow + 1, 1, 24, 2)
.setValues(newData);
/*
Parameters for the last to one row
1st = +1, this is always the same. This indicates that it's last row + 1
2nd = The column where to copy the data, normally 1
3rd = The number of rows that contains the new data. This is pretty much the same as the rage defined in getRange
4th = The number of columns that contains the data, same as above
*/
}
IMPORTANT: All credit goes to #jvdh from his answer at Script to append range of data in Google Sheets. I only made some minor changes and added the comments clarifications
So I have csv files that basically include lists of students, what school they are in, and what subjects they are taking(ex. chem, spanish, biology, etc). I want to have my program let the user type in a subject area(s) and have the page return the amount of times each subject is being taken.
I then have some javascript that basically takes in some user input from a text field, parse that, and puts it into an array. Then, it imports the csv file and compares the subject columns to what the user inputs, and calculates the amount of time each subject is taking.
My javascript looks like this:
var globalArray = [];
var schoolList = [];
var splitTextInput = [];
var count = 0;
var splitSubjectArea = [];
function myFunction()
{
var textInput = document.getElementById('numb').value;
var needsTrimTextInput = textInput.split(","); //creating an array to store user input
for( var q = 0; q < needsTrimTextInput.length; q++) //getting rid of whitespace in user input
{
splitTextInput[q] = needsTrimTextInput[q].trim();
}
for(var j = 0; j< splitTextInput.length; j++)
{
var sSubjectArea = {};
sSubjectArea[ splitTextInput[j] ] = 0; //assigning the value to 0 to store the count of each subject
}
var fileName = document.getElementById("UniversitySelect").value;
if( fileName.indexOf(".csv") > 0 )
{
d3.csv( "./" + document.getElementById("UniversitySelect").value, bob, counting);
}
function bob(d){
return { Area: d.Area };
}
function counting(error, rows)
{
globalArray = rows;
for( var i = 0; i < rows.length; i++ ) //for the row in the CSV file
{
for( var k = 0; k < splitTextInput.length; k++ ) // loop to go through the different inputed subject areas
{
if( rows[i].Area.toLowerCase().indexOf( splitTextInput[k].toLowerCase() ) > -1)
{
count++; //stores the overall count
sSubjectArea[splitTextInput[k]] += 1;
//console.log(sResearchArea[splitTextInput[k]]);
}
}
}
console.log(rows);
for(r = 0; r < splitTextInput.length; r++)
{
console.log( sSubjectArea[ splitTextInput[r] ] );
}//for
}//function
}//function
I know it partially works, because if I enter in 2 subjects, say Chemistry and Biology, which are taken 3 times each, then the count will be 6. However, I cannot get sSubjectArea to hold the count for each individual subject. What am I doing wrong? When I added the console.log(sSubjectArea[splitTextInput[k]]) line, the output I get is:
1
NaN
2
3
4
NaN
5
6
Array [ Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, 86 moreā¦ ]
NaN
6
And I don't really understand why I get those numbers & especially NaN.... can anyone help? I'm really new to javascript, so I might have made some sort of fundamental error in understanding objects, but I can't figure it out. Any help would be much appreciated, thanks!!