Why is my JavaScript loop jumping 10 index places at a time? - javascript

I have a for loop that is returning an unexpected result. Here is the code:
var matArray = scrolls;
var offset = $scope.offset;
for(var i = 0; i < matArray.length; i++) {
var pointer = (i + offset) % matArray.length;
console.log(matArray[pointer]);
}
What I am expecting to get is the loop to begin at a specific index, and then continue in a normal loop until all the way around the results. But instead, THE LOOP JUMPS 10 INDEX SPOTS EACH TIME. If I hardcode in the offset, for example:
var matArray = scrolls;
var offset = $scope.offset;
for(var i = 0; i < matArray.length; i++) {
var pointer = (i + 1) % matArray.length;
console.log(matArray[pointer]);
}
It works as expected, beginning at the second index.
Any thoughts?

Seems your problem in that you add the string to a number, in JavaScript it will be concatenated into a string, e.g.:
'9' + 1 // will be '91'
You could use parseInt function:
var offset = parseInt($scope.offset, 10);

$scope.offset is probably the string '1' when you expect it to be a number.
Note that +(0 + '1') is 0, and +(1 + '1') is 11.
Change i + offset to i + +offset to convert offset to a number before adding it to i:
var matArray = scrolls;
var offset = $scope.offset;
for(var i = 0; i < matArray.length; i++) {
var pointer = (i + +offset) % matArray.length;
console.log(matArray[pointer]);
}

Use
var pointer = (i + parseInt(offset, 10)) % matArray.length;

Related

How to step through google script

I am trying to write a Google Sheets script macro. How do I step through it to understand why the time is being exceeded?
My problem is the loop, when I set the iteration max(scenarios) to 46, the code seems to run fine, taking about 1-2 seconds. When the max is set to 47, it dies with max execution time exceeded (4-5 minutes). Whats going on?
function testing() {
var aa = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Run
Program");
var scenarios = aa.getRange("H19").getValue();
for (i = 1; i <= scenarios; i++){
var ss =
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet6");
var range = ss.getRange("b6:u6");
var min = 1 ;
var max = 20 ;
var numbers = []
for (var i = min; i <= max; i++) {
numbers.push(i);
}
shuffleArray(numbers)
var counter = 0;
for (var x = 1; x <= range.getWidth(); x++) {
for (var y = 1; y <= range.getHeight(); y++) {
range.getCell(y, x).setValue(numbers[counter]);
counter++;
}
}
var range = ss.getRange("v6:ao6");
var min = 21 ;
var max = 40 ;
var numbers = []
for (var i = min; i <= max; i++) {
numbers.push(i);
}
shuffleArray(numbers)
var counter = 0;
for (var x = 1; x <= range.getWidth(); x++) {
for (var y = 1; y <= range.getHeight(); y++) {
range.getCell(y, x).setValue(numbers[counter]);
counter++;
}
}
var range = ss.getRange("ap6:at6");
var min = 41 ;
var max = 45 ;
var numbers = []
for (var i = min; i <= max; i++) {
numbers.push(i);
}
shuffleArray(numbers)
var counter = 0;
for (var x = 1; x <= range.getWidth(); x++) {
for (var y = 1; y <= range.getHeight(); y++) {
range.getCell(y, x).setValue(numbers[counter]);
counter++;
}
}
}
// function Chart4() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A1').activate();
spreadsheet.getSheetByName('Chart4').showSheet()
.activate();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('Chart4'),
true);
};
Your code needs a lot of help. Here's a tip to get you started.
As others have mentioned, you should call .getValues() on the whole range, which gives you a 2 dimensional array of cell values [[A1value,B1value,...],[A2val,B2val...],...]. You should grab the width and length first and assign to a variable. Reference that variable instead of calling the API in the loop conditions. In fact, since you know the ranges ahead of time, you should define all the ranges you'll be needing outside of the main for loop, as well as the spreadsheet (ss):
var aa = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Run Program");
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet6");
var range1 = ss.getRange("b6:u6");
var range2 = ss.getRange("v6:ao6");
for (i = 1; i <= scenarios; i++) { ...
Scripts start taking a long time if you make repeated calls to the API.
Not sure exactly what your macro is trying to do, but here is a template for running this kind of loop and how you should think about it.
var range = ss.getRange(A1-style-address);
var values = range1.getValues();
var width = range1[0].length;
var height = range1.length;
var new_values = new Array();
for (var i = 0; i < height; i++) {
for (var j = 0; j < width; j++) {
//do something with values[i][j], like push to new_values
}
}
//something like:
//var new_range = ss.getRange(different A1-style address)
//new_range.setValues(new_array)
You'll have to make sure that the new array contains values in a 2 dimensional array that has the same dimensions as the range you're putting them into or it will throw an error.
I'll leave it to you to figure out how to modify the new_values array inside the loop to make sure it is the right size.
Here's a link to an overview of arrays in javascript if this is new for you, and here's some array documentation.
Range.getCell() call can be expensive sometimes and should be avoided. Use Range.getValues() and iterate over the two dimensional array. Also, instead of using active spreadsheet open spreadsheet by Id for unit testing. This way you will be able to debug/run from GAS editor directly. GAS editor usually catches such performance issues in the code and displays warnings pointing to the line number in the code.

Make a link count from 1 to 32 and reset back to 1?

So I have something like this, but it just outputs the link with 1 at the end of it.
var pagelink = "http://www.roblox.com/catalog/browse.aspx?CatalogContext=1&Subcategory=2&Keyword=&CurrencyType=0&pxMin=0&pxMax=0&SortType=4&SortAggregation=3&SortCurrency=0&PageNumber=";
var num = 1;
var maxnum = 32;
for(var i = num; i <= maxnum; i++){
pagelink + num
}
So the output just returns:
http://www.roblox.com/catalog/browse.aspx?CatalogContext=1&Subcategory=2&Keyword=&CurrencyType=0&pxMin=0&pxMax=0&SortType=4&SortAggregation=3&SortCurrency=0&PageNumber=1
First - you need to do some js training. Check out eloquentjavascript.net and go to town.
Answer:
var pagelink = "http://www.roblox.com/catalog/browse.aspx?CatalogContext=1&Subcategory=2&Keyword=&CurrencyType=0&pxMin=0&pxMax=0&SortType=4&SortAggregation=3&SortCurrency=0&PageNumber=";
var num = 1;
var maxnum = 32;
// You need something to put the output in. Here's an array.
var links = [];
for(var i = num; i <= maxnum; i++) {
links.push(pagelink + i);
}
return links;
var pagelink = "http://www.roblox.com/catalog/browse.aspx?CatalogContext=1&Subcategory=2&Keyword=&CurrencyType=0&pxMin=0&pxMax=0&SortType=4&SortAggregation=3&SortCurrency=0&PageNumber=";
var results = [];
for(var i = 0; i <= **however many links you want**; i++){
results.push(pagelink + (i % 32 + 1));
}
results // or if you want it delimited, results.join('\n') or whatever other delimiter you want
Your results will be stored in results and you can return it (if in a function) or use it for what you need

Fill pdf form with javascript (client-side only)

I need to fill a pdf form automatically in my angularjs webapp. The pdf form is generated outside the app so I can configure it as I want.
In my app, I just need to load the pdf, modify the form fields and flatten the file so it doesn't look like a form anymore.
Do you know any way to do it?
Edit:
I've found iText but it's a java library which won't work for my project (the app runs on tablets so I'm looking for something 100% HTML5)
There is now another solution that worked for me.
It's this JS package: https://github.com/phihag/pdfform.js
Also available on NPM: https://www.npmjs.com/package/pdfform.js
If you have troubles opening your PDF files, the best bet is to use the mozilla's PDF.js library which works with every PDF files I tried.
The only drawback as of today is that it's not working as expected with webpack. You have to import the JS files with a script tag.
I've found a solution...not perfect but it should fit most requirements. It doesn't use any server (perfect for privacy requirements) or library! First of all, the PDF must be version 1.5 (Acrobat 6.0 or later). The original pdf can be another version but when you create the fields you must save it as compatible for Acrobat 6.0 or later. If you want to make sure the format is right, you can check there
So let's say I have 'myform.pdf' file (with no form fields); I open it with Acrobat Pro (I have Acrobat Pro 11 but it should work with other versions). I add fields and pre-fill the field's value (not the field name!) with a 'code' (unique text string). This code will be find/replace with the string you want by the javascript function below so let say '%address%' (you can add multiple fields but use a different code to differenciate fields). If you want to have a flatten look of the field, set the field to read-only. To save it, go to File -> Save as other... -> Optimized PDF and choose "Acrobat 6.0 and later" under Make compatible with (top right of the pop-up).
Once you have your file saved, you can check that the format is right by opening it in a text editor and looking for your codes (in my case '%address%'). Count number of occurences, it should appear three times.
The following function does three things:
- Changes the fields content
- Recalculate the content's length
- Fix the cross reference tables
So now the function (look at the end for the final pdf blob):
#param certificate: your pdf form (format of this variable must be compatible with FileReader)
#param changes: the field changes, [{find: '%address%', replace: '2386 5th Street, New York, USA'}, ...]
// Works only for PDF 1.5 (Acrobat 6.0 and later)
var fillCertificate = function (certificate, changes) {
// replace a a substring at a specific position
String.prototype.replaceBetween = function(start, end, what) {
return this.substring(0, start) + what + this.substring(end);
};
// format number with zeros at the beginning (n is the number and length is the total length)
var addLeadingZeros = function (n, length) {
var str = (n > 0 ? n : -n) + "";
var zeros = "";
for (var i = length - str.length; i > 0; i--)
zeros += "0";
zeros += str;
return n >= 0 ? zeros : "-" + zeros;
}
// Create the reader first and read the file (call after the onload method)
var reader = new FileReader();
// To change the content of a field, three things must be done; - change the text of the field, - change the length of the content field, - change the cross table reference
reader.onload = function(aEvent) {
var string = aEvent.target.result;
// Let's first change the content and the content's length
var arrayDiff = [];
var char;
for(var foo = 0; foo < changes.length; foo++) {
// Divide the string into a table of character for finding indices
char = new Array(string.length);
for (var int = 0; int < string.length; int++) {
char[int] = string.charAt(int);
}
// Let's find the content's field to change and change it everywhere
var find = changes[foo].find;
var replace = changes[foo].replace;
var lengthDiff = replace.length - find.length;
var search = new RegExp(find, "g");
var match;
var lastElements = [];
var int = 0;
var objectLenPos;
var objectLenEnd;
// Each time you change the content, compute the offset difference (number of characters). We'll add it later for the cross tables
while (match = search.exec(string)) {
arrayDiff.push({index: match.index, diff: lengthDiff});
lastElements.push({index: match.index, diff: lengthDiff});
// Find length object
if(int == 0){
var length = 0;
var index;
while(char[match.index - length] != '\r'){
index = match.index - length;
length++;
}
objectLenPos = index + 10;
length = 0;
while(char[objectLenPos + length] != ' '){
length++;
objectLenEnd = objectLenPos + length;
}
}
int++;
}
var lengthObject = string.slice(objectLenPos, objectLenEnd) + ' 0 obj';
var objectPositionStart = string.search(new RegExp('\\D' + lengthObject, 'g')) + lengthObject.toString().length + 2;
var length = 0;
var objectPositionEnd;
while(char[objectPositionStart + length] != '\r'){
length++;
objectPositionEnd = objectPositionStart + length;
}
// Change the length of the content's field
var lengthString = new RegExp('Length ', "g");
var fieldLength;
var newLength;
string = string.replace(lengthString, function (match, int) {
// The length is between the two positions calculated above
if (int > objectPositionStart && int < objectPositionEnd) {
var length = 0;
var end;
while (char[int + 7 + length] != '/') {
length++;
end = int + 7 + length;
}
fieldLength = string.slice(end - length, end);
newLength = parseInt(fieldLength) + lengthDiff;
if (fieldLength.length != newLength.toString().length) {
arrayDiff.push({index: int, diff: (newLength.toString().length - fieldLength.length)});
}
// Let's modify the length so it's easy to find and replace what interests us; the length number itself
return "Length%";
}
return match;
});
// Replace the length with the new one based on the length difference
string = string.replace('Length%' + fieldLength, 'Length ' + (newLength).toString());
string = string.replace(new RegExp(find, 'g'), replace);
}
// FIND xref and repair cross tables
// Rebuild the table of character
var char = new Array(string.length);
for (var int = 0; int < string.length; int++) {
char[int] = string.charAt(int);
};
// Find XRefStm (cross reference streams)
var regex = /XRefStm/g, result, indices = [];
while ( (result = regex.exec(string)) ) {
indices.push(result.index);
}
// Get the position of the stream
var xrefstmPositions = [];
for(var int = 0; int < indices.length; int++){
var start;
var length = 0;
while(char[indices[int] - 2 - length] != ' '){
start = indices[int] - 2 - length;
length++;
}
var index = parseInt(string.slice(start, start + length));
var tempIndex = parseInt(string.slice(start, start + length));
// Add the offset (consequence of the content changes) to the index
for(var num = 0; num < arrayDiff.length; num++){
if(index > arrayDiff[num].index){
index = index + arrayDiff[num].diff;
}
}
string = string.replaceBetween(start, start + length, index);
// If there is a difference in the string length then update what needs to be updated
if(tempIndex.toString().length != index.toString().length){
arrayDiff.push({index: start, diff: (index.toString().length - tempIndex.toString().length)});
char = new Array(string.length);
for (var int = 0; int < string.length; int++) {
char[int] = string.charAt(int);
};
}
xrefstmPositions.push(index);
}
// Do the same for non-stream
var regex = /startxref/g, result, indices = [];
while ( (result = regex.exec(string)) ) {
indices.push(result.index);
}
for(var int = 0; int < indices.length; int++){
var end;
var length = 0;
while(char[indices[int] + 11 + length] != '\r'){
length++;
end = indices[int] + 11 + length;
}
var index = parseInt(string.slice(end - length, end));
var tempIndex = parseInt(string.slice(end - length, end));
for(var num = 0; num < arrayDiff.length; num++){
if(index > arrayDiff[num].index){
index = index + arrayDiff[num].diff;
}
}
string = string.replaceBetween(end - length, end, index);
if(tempIndex.toString().length != index.toString().length){
arrayDiff.push({index: end - length, diff: (index.toString().length - tempIndex.toString().length)});
char = new Array(string.length);
for (var int = 0; int < string.length; int++) {
char[int] = string.charAt(int);
};
}
xrefstmPositions.push(index);
}
xrefstmPositions.reverse();
var firstObject, objectLength, end;
var offset;
// Updated the cross tables
for(var int = 0; int < xrefstmPositions.length; int++) {
var length = 0;
var end;
if(char[xrefstmPositions[int]] == 'x'){
offset = 6;
} else{
offset = 0;
}
// Get first object index (read pdf documentation)
while(char[xrefstmPositions[int] + offset + length] != ' '){
length++;
end = xrefstmPositions[int] + offset + length;
}
firstObject = string.slice(end - length, end);
// Get length of objects (read pdf documentation)
length = 0;
while(char[xrefstmPositions[int] + offset + 1 + firstObject.length + length] != '\r'){
length++;
end = xrefstmPositions[int] + offset + 1 + firstObject.length + length;
}
objectLength = string.slice(end - length, end);
// Replace the offset by adding the differences from the content's field
for(var num = 0; num < objectLength; num++){
if(char[xrefstmPositions[int]] == 'x'){
offset = 9;
} else{
offset = 3;
}
// Check if it's an available object
if (char[xrefstmPositions[int] + 17 + offset + firstObject.length + objectLength.length + (num * 20)] == 'n') {
var objectCall = (parseInt(firstObject) + num).toString() + " 0 obj";
var regexp = new RegExp('\\D' + objectCall, "g");
var m;
var lastIndexOf;
// Get the last index in case an object is created more than once. (not very accurate and can be improved)
while (m = regexp.exec(string)) {
lastIndexOf = m.index;
}
string = string.replaceBetween(xrefstmPositions[int] + offset + firstObject.length + objectLength.length + (num * 20), xrefstmPositions[int] + 10 + offset + firstObject.length + objectLength.length + (num * 20), addLeadingZeros(lastIndexOf + 1, 10));
}
if(num == objectLength - 1){
if (char[xrefstmPositions[int] + offset + firstObject.length + objectLength.length + ((num + 1) * 20)] != 't'){
xrefstmPositions.push(xrefstmPositions[int] + offset + firstObject.length + objectLength.length + ((num + 1) * 20));
}
}
}
}
// create a blob from the string
var byteNumbers = new Array(string.length);
for (var int = 0; int < string.length; int++) {
byteNumbers[int] = string.charCodeAt(int);
}
var byteArray = new Uint8Array(byteNumbers);
var blob = new Blob([byteArray], {type : 'application/pdf'});
// Do whatever you want with the blob here
};
reader.readAsBinaryString(certificate);
}
So the code is not clean at all but it works :)
Let me know if you have any question
pdf-lib was the best choice for me. Look, after trying many random solutions I found this lib that's very very simple to implement and, off-topic, has a great TS support (nowadays it means a lot 😬).
Fill form official example: https://pdf-lib.js.org/#fill-form
As far as I can see, there is no client-side application on tablets which would do that.
That means you will need server-side support, and iText is indeed one of the products out there. Another one is FDFMerge by Appligent, which does fill and can be set to flattening.

Function to count how many numbers are there with the digits and division value specified

I started making a function that will be able do the following: Count how many 6 digit numbers you can make with the digits 0,1,2,3,4 and 5, that can be divided by 6?
How I currently try to start, is I make an array of all the possible numbers, then take out every number that has any of the numbers' arrays elements in it, then remove the ones that are not dividable with 6.
I got stuck at the second part. I tried making 2 loops to loop in the array of numbers, then inside that loop, create an other one for the length of the allnumbers array to remove all matches.
Then I would use the % operator the same way to get every element out that doesn't return 0.
The code needs to be flexible. If the user asks for eg. digit 6 too, then the code should still work. Any way I could finish this?
My Code is:
var allnumbers = [],j;
var biggestnumber = "999999999999999999999999999999999999999999999999999999999999";
function howmanynumbers(digits,numbers,divideWith){
if (digits && numbers && divideWith){
for (var i = 0; i < 1+Number(biggestnumber.substring(0,digits)); i++ ){
allnumbers.push(i);
}
for (j = 0; j < numbers.length; j++ ){
var matchit = new RegExp(numbers[j]);
}
//not expected to work, I just had this in for reference
if ( String(allnumbers[i]).match(matchit) != [""]){
j = 0;
allnumbers.splice(i,1);
var matchit = new RegExp(numbers[j])
}
}
else {
return false;
}
}
This is my take on the entire solution:
var i;
var allowedDigitsPattern = /^[0-5]+$/i;
var numbers = [];
for (i = 100000; i < 555555; i++) {
if (allowedDigitsPattern.test(i.toString())
&& i % 6 === 0) {
numbers.push(i);
}
}
And you can look at your results like this:
document.write('There are ' + numbers.length + ' numbers<br>');
// write out the first ten!
for (i = 0; i < 10; i++) {
document.write(numbers[i] + '<br>');
}
Update based on comments...
The configurable version of this would be:
var i;
var lowestDigit = 0;
var highestDigit = 5;
var numberOfDigits = 6;
var allowedDigitsPattern = new RegExp('^[' + lowestDigit + '-' + highestDigit + ']+$', 'gi');
var smallestNumber = '1';
for (i = 1; i < numberOfDigits; i++) {
smallestNumber += '0';
}
var biggestNumber = '';
for (i = 0; i < numberOfDigits; i++) {
biggestNumber += highestDigit.toString();
}
var numbers = [];
for (i = smallestNumber; i < biggestNumber; i++) {
if (allowedDigitsPattern.test(i.toString())
&& i % 6 === 0) {
numbers.push(i);
}
}
document.write('There are ' + numbers.length + ' numbers<br>');
You need to change the smallest and largest numbers based on the configuration. I have made both the allowable digits and the length of the number configurable.

javascript generate a number of a variable length [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Generating random numbers in Javascript in a specific range?
If I input a length of say 4, I need to get back a number between 0 9999.
And I want it to be 0 padded (0004), but that may be another function.
Try this:
function getNumber(range){
var max = Math.pow(10,range);
var num = Math.Random() * max;
}
As for the zerofill you're better off trying what Nathan suggested on his comment this
for(var x = 5, i = 0, y = ""; i < x​;​ ++i, y +​​= "9");
Math.floor(Math.random()*parseInt(y)); //random number
​
In the for loop, x would be your input length. You could move the definition of x outside the loop also if you needed.
You could also try this and see which one runs faster (probably the first one, fyi):
for(var x = 5, y = "", i = 0; i < x; ++i, y += Math.floor(Math.random()*9));
parseInt(y); //random number
function getRandom(c){
var r = (Math.random() * Math.pow(10, c)).toFixed(0);
while(r.length != c)
r = "0" + r;
return r;
}
getRandom(3);
Well, here's the number part. You can probably figure out the padding part.
function makeNumber(number){
var nines = "";
var returnNumber = "";
for(var i = 0; i < number; i++)
{
nines+= "9";
}
for(var i = 0; i < number; i++)
{
var returnNumber += String(Math.Random() * parseInt(nines));
}
return returnNumber;
}
Kind of sloppy but works:
var length = 4; //<-- or get however you want
var big = "";
for (i=0; i<length; i++) {
big = big + "9";
}
var big = parseInt(big);
var random = Math.floor(Math.random() * big).toString();
var random_len = random.toString().length;
if (random_len < length) {
var random_padded = random.toString()
padding = length - random_len;
for (i=0; i<padding; i++) {
random_padded = "0" + random_padded;
}
random = random_padded
}
alert(random);
Since it's javascript, you may want to take advantage of its weak typing in this scenario. One particular hackerish way of doing this is to generate n numbers between 0-9 and use string concatenation.
This post seems to have the answer: JavaScript expression to generate a 5-digit number in every case and you can pad with concatenation.
EDIT: so here is my implementation of it:
function random(x){
var rand = Math.floor(Math.random() * Math.pow(10,x));
var dummy = rand;
while (dummy<Math.pow(10,x-1)){
rand = "0" + rand;
dummy = dummy*10;
}
return rand;
}

Categories