Javascript - Associative Arrays not working? - javascript

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!!

Related

Log repeated values in 2d array

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]);
}
}
}
}
}
}
}

Service invoked too many times in a short time

I'm getting this error message to the following script. "Service invoked too many times in a short time: exec qps. Try Utilities.sleep(1000) between calls."
I've given my code below. Can you help me to stop this error message? Note: I'm using an array formula imported from another sheet. The trigger is set to work on Change.
function onEdit(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Direct');
var range = sheet.getDataRange();
var values = range.getValues();
var rows_deleted = 0;
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
var value = values[i][j];
//row numbers are 1-based, not zero-based like this for-loop, so we add one AND...
//every time we delete a row, all of the rows move down one, so we will subtract this count
var row = i + 1 - rows_deleted;
//if the type is a number, we don't need to look
if (typeof value === 'string') {
var result = value.search("Remove");
//the .search() method returns the index of the substring, or -1 if it is not found
//we only care if it is found, so test for not -1
if (result !== -1) {
sheet.deleteRow(row);
rows_deleted++;
}
}
}
}
};

javascript thinks the values in two arrays don't match even though they do

This snippet of code is throwing me for a loop.
if(colList[i] != checkList[i]) {
var colTest = colList[i];
var checkTest = checkList[i];
As you can see from this screenshot from the debug the values are identical.
ScreenShot
Any hints as to why the if statement thinks these values are different?
EDIT: Here is a screenshot showing the full arrays.
Again, I'm not sure why this matters. In fact for testing purposes I have both arrays pulling from the exact same source data.
2nd Edit:
Here is all the relevant code. Again, as you can see the arrays are identical.
var colList = sheet.getRange(startRow,watchCol,lastRow,1).getValues(); // Data set with all values to watch
var checkList = sheet.getRange(startRow,watchCol,lastRow,1).getValues(); // Data set with all the check values
function timeStamp() {
for(var i = 0; i <= colList.length; i++)
if(colList[i] != checkList[i]) {
return colList
return checkList
Here is the full code that is trying to treat it as a multidimensional array. This code does not work and returns "Cannot read property "0" from undefined. (line 13,"
var sheet = SpreadsheetApp.getActive().getSheetByName("Sheet1");
var startRow = 2; // First row with Data
var lastRow = sheet.getLastRow() - startRow;
var watchCol = 2; // Column to check for changes
var checkCol = 7; // Column to check against
var timeCol = 3; // Column to put the time stamp in
var colList = sheet.getRange(startRow,watchCol,lastRow,1).getValues(); // Data set with all values to watch
var checkList = sheet.getRange(startRow,watchCol,lastRow,1).getValues(); // Data set with all the check values
function timeStamp() {
for(var i = 0; i <= colList.length; i++)
for(var j = 0; j < checkList.length; j++){
if(colList[i][j] != checkList[i][j]) {
return colList
return checkList
sheet.getRange(i + startRow,checkCol).setValue(colList[i]);
sheet.getRange(i + startRow,timeCol,1,1).setValue(new Date());
}
}
}
According to your screenshots it's simple.
Your's arrays doesn't contain strings, they contain array that contain string, and thus to compare is true, because two arrays will always be different, that because arrays in js are objects and when you try to compare objects it compares that references of them , not the value.
So you should make array of strings, or just to add [0] to each side in the if
As I said in the comment, you have that one index arrays inside another array, so yours are multi-dimensional arrays and you have to use 2 indexes to access its values, i = row and j = column
var checkList = [["Beef"], ["Red"], ["Career"], ["Chicken"], ["Red"], ["Kids"], ["Beef"], ["Red"]];
var colList = [["Beef"], ["Red"], ["Career"], ["Chicken"], ["Red"], ["Kids"], ["Beef"], ["Red"]];
function timeStamp() {
for(var i = 0; i < colList.length; i++){
for(var j = 0; j < checkList[i].length; j++){
if(colList[i][j] != checkList[i][j]) {
console.log('not equal');
} else{
console.log('equal');
}
}
}
}
timeStamp();
As it turns out adding .String() at the end of the function creating the arrays fixed the issue and allowed them to compare correctly.

How can I remove rows with unique values, keeping rows with duplicate values?

I have a spreadsheet of surveys, in which I need to see how particular users have varied over time. As such, I need to disregard all rows with unique values in a particular column. The data looks like this:
Response Date Response_ID Account_ID Q.1
10/20/2011 12:03:43 PM 23655956 1168161 8
10/20/2011 03:52:57 PM 23660161 1168152 0
10/21/2011 10:55:54 AM 23672903 1166121 7
10/23/2011 04:28:16 PM 23694471 1144756 9
10/25/2011 06:30:52 AM 23732674 1167449 7
10/25/2011 07:52:28 AM 23734597 1087618 5
I've found a way to do so in Excel VBA:
Sub Del_Unique()
Application.ScreenUpdating = False
Columns("B:B").Insert Shift:=xlToRight
Columns("A:A").Copy Destination:=Columns("B:B")
i = Application.CountIf(Range("A:A"), "<>") + 50
If i > 65536 Then i = 65536
Do
If Application.CountIf(Range("B:B"), Range("A" & i)) = 1 Then
Rows(i).Delete
End If
i = i - 1
Loop Until i = 0
Columns("B:B").Delete
Application.ScreenUpdating = True
End Sub
I'd like to do it in Google Spreadsheets with a script that won't have to be changed. Closest I can get is retrieving all duplicate user ids from the range, but can't associate that with the row. That code follows:
function findDuplicatesInSelection() {
var activeRange = SpreadsheetApp.getActiveRange();
var values = activeRange.getValues();
// values that appear at least once
var once = {};
// values that appear at least twice
var twice = {};
// values that appear at least twice, stored in a pretty fashion!
var final = [];
for (var i = 0; i < values.length; i++) {
var inner = values[i];
for (var j = 0; j < inner.length; j++) {
var cell = inner[j];
if (cell == "") continue;
if (once.hasOwnProperty(cell)) {
if (!twice.hasOwnProperty(cell)) {
final.push(cell);
}
twice[cell] = 1;
} else {
once[cell] = 1;
}
}
}
if (final.length == 0) {
Browser.msgBox("No duplicates found");
} else {
Browser.msgBox("Duplicates are: " + final);
}
}
This is maybe not very efficient, but I think it's what you want:
var ar=[1,3,3,5,6,8,6,6];
console.log("Before:");
display(ar);//1 3 3 5 6 8 6 6
var index=[];
var ar2=[];
for(var a=0;a<ar.length;a++)
{
var duplicate=false;
for(var b=0;b<ar.length;b++)
{
if(ar[a]==ar[b]&&a!=b)
{
duplicate=true;
}
}
if(!duplicate)
{
index.push(a);
}
}
for(var a=0;a<index.length;a++)
{
ar[index[a]]=null;
}
for(var a=0;a<ar.length;a++)
{
if(ar[a]!=null)ar2.push(ar[a]);
}
console.log("After:");
display(ar2);//3 3 6 6 6
function display(x)
{
for(var a=0;a<x.length;a++)console.log(x[a]);
}
The fiddle : http://jsfiddle.net/mageek/6AGQ4/
And a shorter version that is as a function :
var ar=[1,3,3,5,6,8,6,6];
function removeUnique(x)
{
var index=[];
var ar2=[];
for(var a=0;a<ar.length;a++)
{
var duplicate=0;
for(var b=0;b<ar.length;b++)if(ar[a]==ar[b]&&a!=b)duplicate=1;
if(!duplicate)index.push(a);
}
for(var a=0;a<index.length;a++)ar[index[a]]=null;
for(var a=0;a<ar.length;a++)if(ar[a]!=null)ar2.push(ar[a]);
return x;
}
ar=removeUnique(ar);
The fiddle : http://jsfiddle.net/mageek/6AGQ4/2
I'd suggest going for something simple.
Create a short script that flags duplicates
Write the formula directly into the cell "=flagDuplicate(C2,C$2:C$10)"
Copy the forumla down the column
Use Spreadsheet's built in QUERY formula to pull the information you need
"=QUERY(A1:E10; "SELECT * WHERE E = TRUE"; 1)"
Here is a simple function to flag duplicates
function flagDuplicate(value, array) {
var duplicateCounter = 0;
for (var i=0; i<array.length; i++){
if (array[i] == value){ // I avoid === in Spreadsheet functions
duplicateCounter++;
}
}
if (duplicateCounter > 1){
return true;
}else{
return false;
}
}
Too many functions on a large table can slow things down. If it becomes a problem, you can always copy and "paste values only" - that will retain the information but remove the functions.
Best of luck.
Note: When I tested this I noticed that can take a while before the spreadsheet recognizes the new custom function (gives error like can't find function FLAGDUPLICATE)
You could also do it using arrays to handle the whole sheet at once :
function removeUnique(){
var col = 2 ; // choose the column you want to check for unique elements
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data=ss.getDataRange().getValues();// get all data
data.sort(function(x,y){
// var xp = Number(x[col]);// use these to sort on numeric values
// var yp = Number(y[col]);
var xp = x[col];// use these for non-numeric values
var yp = y[col];
Logger.log(xp+' '+yp); // just to check the sort is OK
return xp == yp ? 0 : xp < yp ? -1 : 1;// sort on column col numeric ascending
});
var cc=0;
var newdata = new Array();
for(nn=0;nn<data.length-1;++nn){
if(data[nn+1][col]==data[nn][col]||cc>0){
newdata.push(data[nn]);
++cc;
if(cc>1){cc=0}}
}
ss.getDataRange().clearContent(); // clear the sheet
sh.getRange(1,1,newdata.length,newdata[0].length).setValues(newdata);// paste new values sorted and without unique elements
}
EDIT : here is the version that keeps all duplicates (the working one)
function removeUnique(){
var col = 2 ; // choose the column you want to check for unique elements
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data=ss.getDataRange().getValues();// get all data
data.sort(function(x,y){
// var xp = Number(x[col]);// use these to sort on numeric values
// var yp = Number(y[col]);
var xp = x[col];// use these for non-numeric values
var yp = y[col];
Logger.log(xp+' '+yp); // just to check the sort is OK
return xp == yp ? 0 : xp < yp ? -1 : 1;// sort on column col numeric ascending
});
var newdata = new Array();
for(nn=0;nn<data.length-1;++nn){
if(data[nn+1][col]==data[nn][col]){
newdata.push(data[nn]);
}
}
if(data[nn-1][col]==data[nn][col]){newdata.push(data[nn])}
ss.getDataRange().clearContent(); // clear the sheet
sh.getRange(1,1,newdata.length,newdata[0].length).setValues(newdata);// paste new values sorted and without unique elements
}

Sorting parallel arrays in javascript

I have a couple of parallel arrays called names and sales. I have the user enter up to 100 salespeople (names, obviously) and their sales. I have no problem printing these to a table. The catch (for me, anyway) is that they need to be sorted in descending order according to sales. I have made a function called sort which is coded (poorly - as I am just beginning to learn JavaScript) as:
function sort(names, sales) {
var i = 0;
var j = 0;
var temp = 0;
for (var i = 0; i < sales.length - 1; i++) {
var min = i;
for (var j = i + 1; j < array.length; j++)
if (sales[j] < (sales[min])) min = j;
temp = sales[i];
sales[i] = sales[min];
sales[min] = temp;
temp = names[i];
names[i] = names[min];
names[min] = temp;
}
}
I am in need of some help here, obviously. Can anyone lend a hand to point out the (no doubt numerous) errors?
We have been instructed to write our own sort. Sales and names are input through two different functions (getName() and getSales()) using prompts.
First, why not use a single two-dimensional array, say, SalesArray, for example:
[ ['someName', 'someSale'],
['someName2', 'someSale2'],
]
Next, simply inspect SalesArray[i][1] while sorting.
As for sorting, try implementing bubblesort, especially if you're new to sorting algorithms.
Why not just store both the name and sales in a single object? Then everything is in one array.
// Declare array
var people = new Array();
// Somewhere in a loop to add people...
var person = {
name: "jdmichal",
sales: 1000
};
people.push(person);
// Now sort based on the sales property in each object.

Categories

Resources