Iterate over a Range fast in Excelscript for web - javascript

I want to check that a range of cell are empty or has any values in them, I use this for loop :
for (let i = 0; i <= namesRange.getCellCount(); i++) {
if (namesRange.getCell(i,0).getText() == "")
{
break;
}
bookedCount += 1;
}
However this iteration is extremely slow (as is the use of Range.getValue, but the console warns you that iterating with .getValue is slow, does not warn you with getText) It takes several seconds to iterate over a very short list of 10 elements.
Is there any way to check for the values of a cell in a speedy manner using ExcelScripts?
Does this mean that, even if I develop a UDF or a ribbon Add-In with office.js and Node.js it will also be this extremely slow for iterating over cells?
Is there any way to make this faster?

The reason your code is likely performing slowly is that the calls to getCell() and getText() are expensive. Instead of performing these calls every time in the loop you can try a different approach. One approach is to get an array of the cell values and iterate over that. You can use your namesRange variable to get the array of values. And you can also use it to get the row count and the column count for the range. Using this information, you should be able to write nested for loops to iterate over the array. Here's an example of how you might do that:
function main(workbook: ExcelScript.Workbook) {
let namesRange: ExcelScript.Range = workbook.getActiveWorksheet().getRange("A1");
let rowCount: number = namesRange.getRowCount();
let colCount: number = namesRange.getColumnCount();
let vals: string[][] = namesRange.getValues() as string[][];
for (let i = 0; i < rowCount; i++) {
for (let j = 0; j < colCount; j++) {
if (vals[i][j] == "") {
//additional code here
}
}
}
}

Another alternative to the first answer is to use the forEach approach for every cell in the range of values.
It can cut down the amount of variables you need to achieve the desired result.
function main(workbook: ExcelScript.Workbook)
{
let worksheet = workbook.getActiveWorksheet();
let usedRange = worksheet.getUsedRange().getValues();
usedRange.forEach(row => {
row.forEach(cellValue => {
console.log(cellValue);
});
});
}

Related

Infinite Loop for finding a power set for a string

I'm working on a problem where I need to find all the power set of a given string which are all the possible subsets. I feel like I'm close with my current code but I can't figure out why I'm getting stuck on an infinite loop for my second iteration. I ran it through the debugger but I still can't seem to figure it out even though I'm sure it's very simple. When i = 0 then it goes to the second loop where j = 0 && j < 1 so for example if help is my given str argument then I would expect it to add j + '' and push it into my allSubsets array. The problem is that the j iteration will keep looping and doing j++ and will never stop. I'm not sure why this is. One particular question even if I solve this infinite loop - do I need to update the allSubsets.length in the iteration to keep it updated with the pushed in strings?
var powerSet = function(str) {
let allSubsets = [''];
for (let i = 0; i < str.length; i++) {
debugger;
for (let j = 0; j < allSubsets.length; j++) {
allSubsets.push(sortLetters(str[i] + allSubsets[j]));
}
}
return allSubsets;
};
var sortLetters = (word => {
//convert string to an array
//use the sort to sort by letter
//convert array back to string and return
return word.split('').sort().join('');
})
Everytime you push to allSubSets, the length increases, and thus, your loop never ends. A declarative loop runs on the range of the initial loop. See below for a fix based on your code:
var powerSet = function(str) {
let allSubsets = [''];
for (let i = 0; i < str.length; i++) {
allSubsets.forEach( (_char, j) => { // declarative loop here
allSubsets.push(sortLetters(str[i] + allSubsets[j]));
})
}
return allSubsets;
};
var sortLetters = (word => {
return word.split('').sort().join('');
})
From MDN web docs:
The range of elements processed by forEach() is set before the first invocation of callback. Elements which are appended to the array after the call to forEach() begins will not be visited by callback. If existing elements of the array are changed or deleted, their value as passed to callback will be the value at the time forEach() visits them; elements that are deleted before being visited are not visited. If elements that are already visited are removed (e.g. using shift()) during the iteration, later elements will be skipped. (See this example, below.)
See the fourth paragraph under descriptions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#Description

How to optimize a long running script

In my application a user is able to update multiple rows of my Kendo grid. After that information gets saved in my data base i have to update the rows. But the only way i can do that is to iterate through my partitions and every row of the grid to update these records.
This causes long running script errors and takes forever. and locks up the browser. My question is; is there a way i can optimize my iteration to happen either in another thread or in the background. Or is there a quicker way i can update the records.
//maingrid contains 400 records
//10 partitions containing 10 rows changed by user
//100 records needing to be updated.
function updateAssignmentsInUI(partitions) {
for (var i = 0; i < partitions.length; i++) {
for (var j = 0; j < partitions[i].length; j++) {
var mainGrid = $("#mainGrid").data("kendoGrid");
$.each(mainGrid.dataSource.data(), function () {
if (this.RowSelected === true) {
if (this.ID === partitions[i][j].ID) {
var row = mainGrid.dataSource.getByUid(this.uid);
row.set("Changed", "Yes");
}
}
});
}
}
}
this loops through 10 partitions, then loops through 10 records, the looks for the record in the entire list of 400 records in the maingrid. So you can imagine how long this takes before the user gets control again.
You can just try to change / cache your queries. I don't know this will help much but try to use your code like this
//maingrid contains 400 records
//10 partitions containing 10 rows changed by user
//100 records needing to be updated.
function updateAssignmentsInUI(partitions) {
var mainGrid = $("#mainGrid").data("kendoGrid");
$.each(mainGrid.dataSource.data(), function () {
if (this.RowSelected === true) {
for (var i = 0; i < partitions.length; i++) {
for (var j = 0; j < partitions[i].length; j++) {
if (this.ID === partitions[i][j].ID) {
var row = mainGrid.dataSource.getByUid(this.uid);
row.set("Changed", "Yes");
}
}
}
}
});
}
This way your mainGrid will be cached and you will not query it multiple times and also wont search for data of it, also you will not run expensive n^2 loops if row is not selected.
Here is a working demo (simplified) :
https://plnkr.co/edit/n3ZHmlqtoKZP4UQikw2A?p=preview
For the n^2 loops. I dont know much to optimize it, ask it for more algorithm gurus.

javascript loop string comparison headache

console.log(previousCompetitors);
console.log(competitors);
if(data.isVisible) {
var moveIndexTo = [];
for(var q=0; q<competitors.length;q++) {
moveIndexTo.push(-1);
}
for(var i = 0; i<competitors.length; i++) {
for(var j = 0; j<previousCompetitors.length; j++) {
console.log(competitors[i].name);
console.log(previousCompetitors[j].name);
if(competitors[i].name === previousCompetitors[j].name) {
moveIndexTo[j]= i;
break;
}
}
}
console.log(moveIndexTo);
}
I'm slowly going insane trying to figure out what is happening here. I have an array of competitor data that updates in order. They are both arrays and I want to track the changes from the previous ordering.
I console.log the data and can see that the data order has been changed yet every single time the moveIndexTo array ends up being [0,1,2,3,4,5] implying that previousCompetitors an Competitors have equal order. How can they be changed between when I console.log them at the top of the code block to when I perform the string comparison?
Competitors and previousCompetitors take roughly the form
[{name:'name1'},{name:'name2'},{name:'name3'},{name:'name4'},{name:'name5'},{name:'name6'}]
with a lot more going on in each object. So If that was previousCompetitors then competitors would be something like
[{name:'name6'},{name:'name2'},{name:'name3'},{name:'name4'},{name:'name5'},{name:'name1'}].
Note the switch of name1 and name6. So I would expect moveIndexTo to be [5,1,2,3,4,0].
Just try this : moveIndexTo[i] = j;
fiddle at : https://jsfiddle.net/c9mbbpjj/

How to run the loop inside the loop faster in javascript

I have two array objects which is of different lengths in which data.rows is of length 955 and place_names is of length 287. I am running loop inside the loop which takes around 3 minutes to run. Is there any easy and fastest way to run the code below?
for (i = 0; i < place_names.length; i++) {
for (j = 0; j < data.rows.length; j++) {
if (place_names[i].name === data.rows[j].name) {
geom.push(data.rows[j].st_asgeojson);
geom1.push({
name: data.rows[j].name,
geometry: data.rows[j].st_asgeojson
});
matched_average_value.push(place_names[i].average);
matched_sum_value.push(place_names[i].sum);
matched_minimum_value.push(place_names[i].minmum);
matched_maximum_value.push(place_names[i].maximum);
}
else {
console.log("no matches found");
}
}
}
You have two lists place_names and data.rows. The field you're interested in is the name field.
Depending on where you get this data from, it might be better to use a dictionary for this:
var dict = {};
place_names.forEach(
function(place_name) {
dict[place_name.name]=place_name;
}
);
Now you can loop through your data.rows:
data.rows.forEach(
function(row) {
if(row.name in dict) {
var place_name = dict[row.name];
// Do what needs to be done with "row" and "place_name"
}
}
);
This should reduce your n^2 algorithm to a n log n or even n (Depending on JavaScript's dictionary indexing complexity, I'm not sure). This solution assumes that all place_names[i].name values are unique. If they aren't, then you might want to store a list of all matching place_names in your dict.

Javascript sort string or number

EDIT: Pete provided a really good solution that works when the fields contain numbers, however I need to be able to sort strings too - any ideas?
I'm trying to write a javascript sorting algorithm that will sort a table based on the column clicked - I know this is semi-reinventing the wheel but the design is too complex for me to try and insert some other plugin etc.
Some columns are text, some columns are numbers.
Clicking a column calls: sort(X,Y). X is the column number so we know which cells to compare for the sort. Y is the mode, i.e. ascending or descending.
The code for the sort function is:
function sort(field, mode) {
var tabrows = 0;
$(".data tr").each(function() { if($(this).hasClass("hdr")) { } else {tabrows++;} });
var swapped;
do {
swapped = false;
for (var i=0;i< tabrows; i++) {
var j = i + 3;
var k = i + 4;
var row1 = $(".data tr:nth-child("+j+")");
var row2 = $(".data tr:nth-child("+k+")");
var field1 = row1.find("td:eq("+field+")").text();
var field2 = row2.find("td:eq("+field+")").text();
if(shouldswap(field1, field2, mode)) {
swaprows(row1, row2);
swapped = true;
}
}
} while (swapped);
}
The shouldswap function is as follows:
function shouldswap(field1, field2,mode) {
if(field1 > field2) {
if(mode==1) {
return true;
} else {
return false;
}
}
return false;
}
Code for swaprows function:
function swaprows(row1, row2) {
row2.insertBefore(row1);
}
Can anyone see why this would cause the browser to freeze/lockup. I've been working on this for quite a while so I think a fresh pair of eyes may point out something silly! Any help is appreciated :)
The problem might be that you're calling the jQuery constructor a bunch of times and doing heavy operations on it (e.g. using .find() with complex selectors). Therefore, your function is just slow and that's probably the issue.
The good news is that JavaScript has a native implementation of QuickSort (a very fast sorting function) that will probably take care of your needs. When combined with a reduction in expensive calls, your code should end up being enormously more efficient. I'd change your code to look like this:
var sortByField = function(field, mode) {
var numExp = /^-?\d*\.?\d+$/;
var $rows = $(".data tr:not(.hdr)"), $table = $(".data");
$rows.each(function () {
this.fieldVal = $(this).find("td:eq("+field+")").text();
if(numExp.test(this.fieldVal)) { //if field is numeric, convert it to a number
this.fieldVal = +this.fieldVal;
}
}).sort(function (a, b) {
if (mode === 1) {
return (a.fieldVal > b.fieldVal) ? -1 : 1;
}
return (a.fieldVal < b.fieldVal) ? -1 : 1;
}).detach().each(function () {
$(this).appendTo($table);
});
};
This won't work well with multiple tables on one page (because it assumes everything is on the same table). So if you want to do that, you should pass in the table or table selector as a parameter. But that's an easy fix to make. You can see my solution in action here:
http://jsfiddle.net/r8wtK/ (updated)
It should be far more efficient than your code and should reduce "freezing" by quite a bit (ore even entirely).
UPDATE:
The OP noted that some fields may contain strings. Doing a string comparison on numbers is bad because it returns a lexicographical ordering (e.g. "10" < "2"). So I added a test to see if the data appear to be numeric before doing the sort.
Could it be that you're adding 3 and 4 to i in order to get your row indices? So when i gets to (tabrows-1), it appears that it will be trying to access rows with index of (tabrows+2) and (tabrows+3). If I understand your logic correctly, these are out of bounds, so row1, row2, field1 and field2 will be empty. Therefore, if you're in mode==1, I think this will make it so that your algorithm attempts to swap these two non-existent rows and keeps comparing for infinity. Does this make sense, or am I misunderstanding your logic?
If that's the case, I think you just need to change your for loop to:
for (var i=0;i< tabrows-4; i++) {
// your code
}
What is the purpose of adding 3 to j and 4 to k anyway? Do you have 3 rows of data at the top that you don't want to compare?

Categories