ScriptDB object size calculation - google-apps-script

I'm trying to estimate the limits of my current GAS project. I use ScriptDB to chunk out processing to get around the 6 min execution limit. If I have an object like
var userObj{
id: //user email address
count: //integer 1-1000
trigger: //trigger ID
label: //string ~30 char or less
folder: //Google Drive folder ID
sendto: //'true' or 'false'
shareto: //'true' or 'false'
}
How would I calculate the size that this object takes up in the DB? I would like to project how many of these objects can exist concurrently before I reach the 200MB limit for our domain.

Whenever you've got a question about google-apps-script that isn't about the API, try searching for javascript questions first. In this case, I found JavaScript object size, and tried out the accepted answer in apps-script. (Actually, the "improved" accepted answer.) I've made no changes at all, but have reproduced it here with a test function so you can just cut & paste to try it out.
Here's what I got with the test stud object, in the debugger.
Now, it's not perfect - for instance, it doesn't factor in the size of the keys you'll use in ScriptDB. Another answer took a stab at that. But since your object contains some potentially huge values, such as an email address which can be 256 characters long, the key lengths may be of little concern.
// https://stackoverflow.com/questions/1248302/javascript-object-size/11900218#11900218
function roughSizeOfObject( object ) {
var objectList = [];
var stack = [ object ];
var bytes = 0;
while ( stack.length ) {
var value = stack.pop();
if ( typeof value === 'boolean' ) {
bytes += 4;
}
else if ( typeof value === 'string' ) {
bytes += value.length * 2;
}
else if ( typeof value === 'number' ) {
bytes += 8;
}
else if
(
typeof value === 'object'
&& objectList.indexOf( value ) === -1
)
{
objectList.push( value );
for( i in value ) {
stack.push( value[ i ] );
}
}
}
return bytes;
}
function Marks()
{
this.maxMarks = 100;
}
function Student()
{
this.firstName = "firstName";
this.lastName = "lastName";
this.marks = new Marks();
}
function test () {
var stud = new Student();
var studSize = roughSizeOfObject(stud);
debugger;
}

Related

Script that would find and mark the same words in the paragraph

I'm a fiction writer and I used to do my writing in MS Word. I've written some macros to help me edit the fiction text and one of them check the paragraph and marks (red) the duplicate (or triplicate words, etc). Example:
"I came **home**. And while at **home** I did this and that."
Word "home" is used twice and worth checking if I really can't change the sentence.
Now I mostly use google documents for writing, but I still have to do my editing in MS Word, mostly just because of this macro - I am not able to program it in the google script.
function PobarvajBesede() {
var doc = DocumentApp.getActiveDocument();
var cursor = DocumentApp.getActiveDocument().getCursor();
var surroundingText = cursor.getSurroundingText().getText();
var WordsString = WORDS(surroundingText);
Logger.log(WordsString);
//so far, so good. But this doesn't work:
var SortedWordsString = SORT(WordsString[1],1,False);
// and I'm lost.
}
function WORDS(input) {
var input = input.toString();
var inputSplit = input.split(" ");
// Logger.log(inputSplit);
inputSplit = inputSplit.toString();
var punctuationless = inputSplit.replace(/[.,\/#!$%\?^&\*;:{}=\-_`~()]/g," ");
var finalString = punctuationless.replace(/\s{2,}/g," ");
finalString = finalString.toLowerCase();
return finalString.split(" ") ;
}
If I could only get a list of words (in uppercase, longer than 3 characters), sorted by the number of their appearances in the logger, it would help me a lot:
HOME (2)
AND (1)
...
Thank you.
Flow:
Transform the string to upper case and sanitize the string of all non ascii characters
After splitting the string to word array, reduce the array to a object of word:count
Map the reduced object to a 2D array [[word,count of this word],[..],...] and sort the array by the inner array's count.
Snippet:
function wordCount(str) {
str = str || 'I came **home**. And while at **home** I did this and that.';
var countObj = str
.toUpperCase() //'I CAME **HOME**...'
.replace(/[^A-Z ]/g, '') //'I CAME HOME...'
.split(' ') //['I', 'CAME',..]
.reduce(function(obj, word) {
if (word.length >= 3) {
obj[word] = obj[word] ? ++obj[word] : 1;
}
return obj;
}, {}); //{HOME:2,DID:1}
return Object.keys(countObj)
.map(function(word) {
return [word, countObj[word]];
}) //[['HOME',2],['CAME',1],...]
.sort(function(a, b) {
return b[1] - a[1];
});
}
console.info(wordCount());
To read and practice:
Object
Array methods
This is a combination of TheMaster answer and some of my work. I need to learn more about the way he did it so I spent some learning time today. This function eliminates some problems I was having the carriage returns and it also removes items that only appear once. You should probably pick TheMasters solution as I couldn't have done it without his work.
function getDuplicateWords() {
var str=DocumentApp.getActiveDocument().getBody().getText();
var countObj = str
.toUpperCase()
.replace(/\n/g,' ')
.replace(/[^A-Z ]/g, '')
.split(' ')
.reduce(function(obj, word) {
if (word.length >= 2) {
obj[word] = obj[word] ? ++obj[word] : 1;
}
return obj;
}, {});
var oA=Object.keys(countObj).map(function(word){return [word, countObj[word]];}).filter(function(elem){return elem[1]>1;}).sort(function(a,b){return b[1]-a[1]});
var userInterface=HtmlService.createHtmlOutput(oA.join("<br />"));
DocumentApp.getUi().showSidebar(userInterface);
}
function onOpen() {
DocumentApp.getUi().createMenu('MyMenu')
.addItem('Get Duplicates','getDuplicateWords' )
.addToUi();
}
And yes I was having problems with get the results to change in my last solution.

How to use a for loop with .createChoice in Google Apps Script to create a quiz from a sheet?

I am using Google Apps Script to generate Google Forms from a Sheet. Questions are in rows and question choices are in columns.
Here is a link to the Google sheet if needed.
It is a straightforward task when using .setChoiceValues(values)
if (questionType == 'CHOICE') {
var choicesForQuestion = [];
for (var j = 4; j < numberColumns; j++)
if (data[i][j] != "")
choicesForQuestion.push(data[i][j]);
form.addMultipleChoiceItem()
.setChoiceValues(choicesForQuestion);
}
However, when I try to use .createChoice(value, isCorrect), the parameters call for value to be a string and isCorrect to be Boolean.
An example without a loop looks like this:
var item = FormApp.getActiveForm().addCheckboxItem();
item.setTitle(data[3][1]);
// Set options and correct answers
item.setChoices([
item.createChoice("chocolate", true),
item.createChoice("vanilla", true),
item.createChoice("rum raisin", false),
item.createChoice("strawberry", true),
item.createChoice("mint", false)
]);
I can not figure out how to add the loop. After reading over other posts, I have tried the following:
if (questionType == 'CHOICE') {
var questionInfo = [];
for (var j = optionsCol; j < maxOptions + 1; j++)
if (data[i][j] != "")
questionInfo.push( form.createChoice(data[i][j], data[i][j + maxOptions]) );
form.addMultipleChoiceItem()
.setChoices(questionInfo);
}
optionsCol is the first column of questions options
maxOptions is how many options are allowed by the sheet (currently 5). The isCorrect information is 5 columns to the right.
However, this not working because the array questionsInfo is empty.
What is the best way to do this?
Probably your issue is related to the method you reference--Form#createChoice--not existing. You need to call MultipleChoiceItem#createChoice, by first creating the item:
/**
* #param {Form} formObj the Google Form Quiz being created
* #param {any[]} data a 1-D array of data for configuring a multiple-choice quiz question
* #param {number} index The index into `data` that specifies the first choice
* #param {number} numChoices The maximum possible number of choices for the new item
*/
function addMCItemToForm_(formObj, data, index, numChoices) {
if (!formObj || !data || !Array.isArray(data)
|| Array.isArray(data[0]) || data.length < (index + 2 * numChoices))
{
console.error({message: "Bad args given", hasForm: !!formObj, info: data,
optionIndex: index, numChoices: numChoices});
throw new Error("Bad arguments given to `addMCItemToForm_` (view on StackDriver)");
}
const title = data[1];
// Shallow-copy the desired half-open interval [index, index + numChoices).
const choices = data.slice(index, index + numChoices);
// Shallow-copy the associated true/false data.
const correctness = data.slice(index + numChoices, index + 2 * numChoices);
const hasAtLeastOneChoice = choices.some(function (c, i) {
return (c && typeof correctness[i] === 'boolean');
});
if (hasAtLeastOneChoice) {
const mc = formObj.addMultipleChoiceItem().setTitle(title);
// Remove empty/unspecified choices.
while (choices[choices.length - 1] === "") {
choices.pop();
}
// Convert to choices for this specific MultipleChoiceItem.
mc.setChoices(choices.map(function (choice, i) {
return mc.createChoice(choice, correctness[i]);
});
} else {
console.warn({message: "Skipped bad mc-item inputs", config: data,
choices: choices, correctness: correctness});
}
}
You would use the above function as described by its JSDoc - pass it a Google Form object instance to create the quiz item in, an array of the details for the question, and the description of the location of choice information within the details array. For example:
function foo() {
const form = FormApp.openById("some id");
const data = SpreadsheetApp.getActive().getSheetByName("Form Initializer")
.getSheetValues(/*row*/, /*col*/, /*numRows*/, /*numCols*/);
data.forEach(function (row) {
var qType = row[0];
...
if (qType === "CHOICE") {
addMCItemToForm_(form, row, optionColumn, numOptions);
} else if (qType === ...
...
}
References
Array#slice
Array#forEach
Array#map
Array#some
I am sure the above answer is very good and works but I am just a beginner and needed a more obvious (plodding) method. I am generating a form from a spreadsheet. Question types can include: short answer (text item), long answer (paragraph), drop down (list item), multiple choice, grid item, and checkbox questions, as well as sections.
I had to be able to randomize the input from the spreadsheet for multiple choice and sort the input for drop downs. I am only allowing one correct answer at this time.
The columns in the question building area of the spreadsheet are: question type, question, is it required, does it have points, hint, correct answer, and unlimited choice columns.
qShtArr: getDataRange of the entire sheet
corrAnsCol: index within the above of the column with the correct answer
begChoiceCol: index within the above of first column with choices
I hope this helps other less skilled coders.
/**
* Build array of choices. One may be identified as correct.
* I have not tried to handle multiple correct answers.
*/
function createChoices(make, qShtArr, r, action) {
// console.log('Begin createChoices - r: ', r);
let retObj = {}, choiceArr = [], corrArr = [], aChoice, numCol, hasCorr;
numCol = qShtArr[r].length - 1; // arrays start at zero
if ((qShtArr[r][corrAnsCol] != '') && (qShtArr[r][corrAnsCol] != null)) {
hasCorr = true;
choiceArr.push([qShtArr[r][corrAnsCol], true]);
for (let c = begChoiceCol ; c < numCol ; c++) {
aChoice = qShtArr[r][c];
if ((aChoice != '') && (aChoice != null)) { /* skip all blank elements */
choiceArr.push([aChoice, false]);
}
} //end for loop for multiple choice options
} else {
hasCorr = false;
for (let c = begChoiceCol ; c < numCol ; c++) {
aChoice = qShtArr[r][c];
if ((aChoice != '') && (aChoice != null)) { /* skip all blank elements */
choiceArr.push(aChoice);
}
} //end for loop for multiple choice options
}
if (action == 'random')
choiceArr = shuffleArrayOrder(choiceArr);
if (action == 'sort')
choiceArr.sort();
console.log('choiceArr: ', JSON.stringify(choiceArr) );
let choices = [], correctArr = [] ;
if (hasCorr) {
for ( let i = 0 ; i < choiceArr.length ; i++ ) {
choices.push(choiceArr[i][0]);
// console.log('choices: ', JSON.stringify(choices) );
correctArr.push(choiceArr[i][1]);
// console.log('correctArr: ', JSON.stringify(correctArr) );
}
make.setChoices(choices.map(function (choice, i) {
return make.createChoice(choice, correctArr[i]);
}));
} else { // no correct answer
if (action == 'columns' ) {
make.setColumns(choiceArr);
} else {
make.setChoices(choiceArr.map(function (choice, i) {
return make.createChoice(choice);
}));
}
}
}

Assign JSON value to variable based on value of a different key

I have this function for extracting the timestamp from two JSON objects:
lineReader.on('line', function (line) {
var obj = JSON.parse(line);
if(obj.Event == "SparkListenerApplicationStart" || obj.Event == "SparkListenerApplicationEnd") {
console.log('Line from file:', obj.Timestamp);
}
});
The JSON comes from a log file(not JSON) where each line represents an entry in the log and each line also happens to be in JSON format on its own.
The two objects represent the start and finish of a job. These can be identified by the event key(SparkListenerApplicationStart and SparkListenerApplicationEnd). They also both contain a timestamp key. I want to subtract the end time from the start time to get the duration.
My thinking is to assign the timestamp from the JSON where Event key = SparkListenerApplicationStart to one variable and assign the timestamp from the JSON where Event key = SparkListenerApplicationEnd to another variable and subtract one from the other. How can I do this? I know I can't simply do anything like:
var startTime = if(obj.Event == "SparkListenerApplicationStart"){
return obj.Timestamp;
}
I'm not sure if I understood, but if are reading rows and want get the Timestamp of each row I would re-write a new object;
const collection = []
lineReader.on('line', function (line) {
var obj = JSON.parse(line);
if(obj.Event == "SparkListenerApplicationStart" || obj.Event == "SparkListenerApplicationEnd") {
// console.log('Line from file:', obj.Timestamp);
collection.push(obj.Timestamp)
}
});
console.log(collection);
Where collection could be a LocalStorage, Global Variable, or something alike.
Additional info
With regard to my comment where I queried how to identify the start and end times, I ended up setting start as the smallest value and end as the largest. Here is my final code:
const collection = []
lineReader.on('line', function (line) {
var obj = JSON.parse(line);
if((obj.Event == "SparkListenerApplicationStart" || obj.Event == "SparkListenerApplicationEnd")) {
collection.push(obj.Timestamp);
if(collection.length == 2){
startTime = Math.min.apply(null, collection);
finishTime = Math.max.apply(null, collection);
duration = finishTime - startTime;
console.log(duration);
}
}
});

Referencing unused materials from a JSON scene

I'm trying to create different materials in a JSON scene, assign a default one to a mesh, and let the user switch between the different materials within the user interface.
Is it possible to reference every materials, even the unused ones, after parsing a JSON ?
After inspecting the code of THREE.ObjectLoader, it turns out that it parses the entire JSON but explicitly returns a single reference to the scene object. I solved this issue by monkey patching the parse method at runtime. The code is taken from THREE.ObjectLoader.parse, only the return value is different (returning an Object and not a THREE.Object3D anymore).
function objectLoaderParseOverride(json, onLoad) {
var ret = {};
ret.geometries = this.parseGeometries( json.geometries );
ret.images = this.parseImages( json.images, function () {
if ( onLoad !== undefined ) onLoad( ret );
} );
ret.textures = this.parseTextures( json.textures, ret.images );
ret.materials = this.parseMaterials( json.materials, ret.textures );
ret.object = this.parseObject( json.object, ret.geometries, ret.materials );
if ( json.animations ) {
ret.object.animations = this.parseAnimations( json.animations );
}
if ( json.images === undefined || json.images.length === 0 ) {
if ( onLoad !== undefined ) onLoad( ret );
}
return ret;
}
var loader = new THREE.ObjectLoader();
loader.parse = objectLoaderParseOverride;

Permutation of Actionscript 3 Array

Greetings,
This is my first post and I hope someone out there can help. I am an educator and I designed a quiz using Actionscript 3 (Adobe Flash) that is to determine all the different ways a family can have three children.
I have two buttons that enter either the letter B (for boy) or G (for girl) into an input text field named text_entry. I then have a submit button named enter_btn that checks to see if the entry into the input text is correct. If the input is correct, the timeline moves to the next problem (frame labeled checkmark); if it is incorrect the timeline moves to the end of the quiz (frame 62).
The following code works well for any particular correct single entry (ie: BGB). I need to write code in which all eight correct variations must be entered, but they can be entered in any order (permutation):
ie:
BBB,BBG,BGB,BGG,GBB,GBG,GGB,GGG; or
BGB,GGG,BBG,BBB,GGB,BGB,GGB,BGG; or
GGB,GGG,BBG,BBB,GGB,BGB,BGB,BGG; etc...
there are over 40,000 ways to enter these eight ways of having three children. Help!
baby_B.addEventListener(MouseEvent.CLICK, letterB);
function letterB(event:MouseEvent)
{
text_entry.appendText("B");
}
baby_G.addEventListener(MouseEvent.CLICK, letterG);
function letterG(event:MouseEvent)
{
text_entry.appendText("G");
}
enter_btn.addEventListener(MouseEvent.CLICK, check);
function check(event:MouseEvent):void {
var solution_S:Array=["BBB","BBG","BGB","BGG","GBB","GBG","GGB","GGG "];
if(solution_S.indexOf(text_entry.text)>=0)
{
gotoAndStop("checkmark");
}
else
{
gotoAndPlay(62);
}
}
If you know the correct code, please write it out for me. Thanks!
You will just need to keep a little bit of state to know what the user has entered so far. One possible way of doing that is to have a custom object/dictionary that you initialize outside all your functions, so that it is preserved during the transitions between frames/runs of the functions:
var solutionEntered:Object = {"BBB":false, "BBG":false, /*fill in rest */ };
Then in your function check you can perform an additional check, something like:
function check(event:MouseEvent):void {
var solution_S:Array=["BBB","BBG","BGB","BGG","GBB","GBG","GGB","GGG "];
if(solution_S.indexOf(text_entry.text)>=0) {
// We know the user entered a valid solution, let's check if
// then entered it before
if(solutionEntered[text_entry.text]) {
// yes they entered it before, do whatever you need to do
} else {
// no they haven't entered it, set the status as entered
solutionEntered[text_entry.text] = true;
}
// continue rest of your function
}
// continue the rest of your function
}
Note that this is not necessarily an optimal solution, but it keeps with the code style you already have.
Try this:
import flash.text.TextField;
import flash.events.MouseEvent;
import flash.display.Sprite;
var correctAnswers : Array = [ "BBB", "BBG", "BGB", "GBB", "BGG", "GGB", "GBG", "GGG" ];
var answersSoFar : Array;
var textField : TextField; //on stage
var submitButton : Sprite; //on stage
var correctAnswerCount : int;
//for testing
textField.text = "BBB,BBG,BGB,GBB,BGG,GGB,GBG,GGG";
//textField.text = "BGB,BBB,GGG,BBG,GBB,BGG,GGB,GBG,";
//textField.text = "BBB,BBG, BGB,GBB,BGG, GGB, GBG, GGG";
//textField.text = "BBB,BBG,BGB,GBB,BGG,GGB,GBG";
//textField.text = "BBB,BBG,BGB,GBB,BGG,GGB,GBG,GGG,BBG";
submitButton.addEventListener( MouseEvent.CLICK, onSubmit );
function onSubmit( event : MouseEvent ) : void
{
var answers : Array = getAnswersArray( textField.text );
answersSoFar = [];
correctAnswerCount = 0;
for each ( var answer : String in answers )
if ( answerIsCorrect( answer ) ) correctAnswerCount++;
if ( correctAnswerCount == correctAnswers.length ) trace( "correct" );
else trace( "incorrect" );
}
function getAnswersArray( string : String ) : Array
{
string = removeInvalidCharacters( string );
return string.split( "," );
}
function removeInvalidCharacters( string : String ) : String
{
var result : String = "";
for ( var i : int, len = string.length; i < len; i++ )
if ( string.charAt( i ) == "B" || string.charAt( i ) == "G" || string.charAt( i ) == "," )
result += string.charAt( i );
return result;
}
function answerIsCorrect( answer : String ) : Boolean
{
if ( answerIsADuplicate( answer ) ) return false;
else answersSoFar.push( answer );
if ( answerIsInListOfCorrectAnswers( answer ) ) return true;
return false;
}
function answerIsInListOfCorrectAnswers( answer : String ) : Boolean
{
if ( correctAnswers.indexOf( answer ) == -1 ) return false;
return true;
}
function answerIsADuplicate( answer : String ) : Boolean
{
if ( answersSoFar.indexOf( answer ) == -1 ) return false;
return true;
}
note that in the original code you pasted, you have an extra space in the last element of your correct answer list - "GGG " should be "GGG"
this works
baby_B.addEventListener(MouseEvent.CLICK, letterB);
function letterB(event:MouseEvent) {
text_entry.appendText("B");
}
baby_G.addEventListener(MouseEvent.CLICK, letterG);
function letterG(event:MouseEvent) {
text_entry.appendText("G");
}
var valid:Array = ["BBB","BBG","BGB","BGG","GBB","GBG","GGB","GGG"];
enter_btn.addEventListener(MouseEvent.CLICK, check);
function check(event:MouseEvent):void {
var parts:Array = text_entry.text.split(/,\s*/g); // split the text into component parts
var dup:Array = valid.slice(); // copy the correct list
while(parts.length){ // run through each answer component
var part:String = parts.pop(); // grab the last one
part = part.replace(/(^\s+|\s+$)/g, ''); // strip leading/trailing white space
var pos:int = dup.indexOf(part); // is it in the list of correct answers?
if(pos != -1){ // if it is...
dup.splice(pos, 1); // then remove that answer from the list
}
}
if(dup.length == 0) { // if it's 0, they got all the correct answers
gotoAndStop("checkmark");
} else { // otherwise, they didn't get one or more correct answers
gotoAndPlay(62);
}
}