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
Related
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);
});
});
}
integrityCheck = function () {
var check = ['098f6bcd', '4621d373', 'cade4e83', '2627b4f6'];
for (var i = 0; i <= check.length; i++) {
checkIntegrityOfData(
i,
check[i]
);
}
}
There is probably a very obvious error in my part of the code but im new at JavaScript so i cant really see it.
Array index starts from 0, meaning if an array has 5 elements, the length would be 5 but the max index would be 4.
With your condition logic
You will need to fix your condition logic i <= check.length, last iteration of the loop will try to access check[4] but the last element in check will be in check[3].
Change your loop condition code to
for (var i = 0; i < check.length; i++) {
The problem is your loop goes up to and including the length of the array, however array length starts from 1 whereas array indexes start from 0. Change it to this:
for (var i = 0; i < check.length; i++) {
And it will work. (All I did was change the i<=check.length to i<check.length so it won't iterate over the last index, which is the thing making the error.
//====================================================
function getPermutations(str){
//Enclosed data to be used by the internal recursive function permutate():
var permutations = [], //generated permutations stored here
nextWord = [], //next word builds up in here
chars = [] //collection for each recursion level
;
//---------------------
//split words or numbers into an array of characters
if (typeof str === 'string') chars = str.split('');
else if (typeof str === 'number') {
str = str + ""; //convert number to string
chars = str.split('');//convert string into char array
}
//============TWO Declaratives========
permutate(chars);
return permutations;
//===========UNDER THE HOOD===========
function permutate(chars){ //recursive: generates the permutations
if(chars.length === 0)permutations.push(nextWord.join(''));
for (var i=0; i < chars.length; i++){
chars.push(chars.shift()); //rotate the characters
nextWord.push(chars[0]); //use the first char in the array
permutate(chars.slice(1)); //Recurse: array-less-one-char
nextWord.pop(); //clear for nextWord (multiple pops)
}
}
//--------------------------------
}//==============END of getPermutations(str)=============
How is nextWord.pop() getting called multiple times?
Won't permutate(chars.slice(1)); not let nextWord.pop() execute since it will take you back to the top of the permutate function?
Also, when chars becomes empty from calling slice on it permutate(chars.slice(1)); who is populating chars once again? Is chars being populated by nextWord.pop(); since pop is returning the value to the permutate function?
Stepping through this code in chrome debugger it wasnt clear.
Recursive call permutate is inside a loop and each time it is executed it is put on the call stack. The nextWord.pop is called multiple times to finish executing each of the recursive calls on the stack. You can visualize the recursion with this tool http://visualgo.net/recursion.html. If you have a tool such as Webstorm, you can run it in the debugger to see that there are three permutate() calls in the stack when the first time nextWord.pop is called.
It would execute after premutate() returns. But I guess that's obvious from looking at the code. I think your question is how can premutate ever reutrn? The answer to that is to look at the for loop.
Since we're calling premutate() with one fewer character every time. It makes sense that at some point one of our calls to premutate() will be called with an empty array:
premutate([]); // happens when chars.slice(1) gives us an empty array
Now, let's see what happens when that happens:
function permutate(chars){
// The following gets executed:
if(chars.length === 0)permutations.push(nextWord.join(''));
// This for loop is skipped because 0 < 0 is false
for (var i=0; i < chars.length; i++){/*...*/}
// we return because we skipped the for loop
}
Now that the base-case have returned, all the other calls to premutate() also returns:
function permutate(chars){
if(chars.length === 0)permutations.push(nextWord.join(''));
for (var i=0; i < chars.length; i++){
chars.push(chars.shift());
nextWord.push(chars[0]);
permutate(chars.slice(1)); // This have returned..
nextWord.pop(); // so execute this line
}
// and return so that other calls to us can also execute pop()
}
chars.slice(1) is the character array chars starting at position 1, i.e. the second character, so calling permutate(chars.slice(1)); recurses with 1 fewer characters.
Eventually, chars.slice(1) will return zero characters, at which point permutations.push(nextWord.join('')); is executed and for (var i=0; i < chars.length; i++) will not execute the loop block inside because its terminating condition i < chars.length will already be false when the initial value of i is 0 and chars.length is also zero.
So the terminating condition of this recursive function is when it runs out of characters in the current word.
When the the permutate function finally returns, nextWord.pop() is called once for each time permutate was called.
Calling permutate will not reset you at the top of permutate. (as was mentioned by joe) It will simply wait for this child call to complete and then continue executing the method.
I think your recursive permutate could be simplified:
// suppose input = "abc"
permutate(chars) {
if(chars.length === 2) return [chars, chars[0] + chars[1]};
var arr = permutate(chars.slice(1)) // returns ["bc", "cb"]
var out = []
for (var i=0; i < arr.length; i++) {
for (var j = 0; j < chars.length - 1; ++j) {
out.push(arr[i].split(0,j) + chars[0] + arr[i].split(j)) // "a" gets inserted at every possible position in each string
}
}
// result here is ["abc", "bac", "bca", "acb", "cab", "cba"]
return out
}
function permutate(left, used, result) {
// If there are no more characters left to permute
if (0 == left.length) {
result.push(used);
}
// Iterate over all characters in the 'left' string
for (var i = 0; i < left.length; ++i) {
// Read the character we are going to work with in this iteration
var iObject = left[i];
// Create a new_left string that contains all the characters
// of the 'left' string without the one we are going to use now
var new_left = '';
for (j = 0; j < left.length; ++j) {
if (j != so.i) new_left += so.left[j];
}
// Create a new_used string that has all the characters of 'used'
// plus the one we are going to use now
var new_used = so.used + iObject;
// Call permute with new_left and new_used strings
permutate(new_left, new_used, result);
}
}
To run the function on some string you need to call it like this:
permutate('abcd', '', []);
For those who doesn't get this because of the recursion involved I found a page that illustrates the algorithm flow using interactive animations.
http://learntocode.guru/code/generate-permutations-recursively-string
Just click play and observe the variables change while the permutation function keeps calling itself. Also observe when a new permutation has been found and when it's added to the resulting permutations array.
i am newbie learner and i am learning basic javaScript from codecademy.I stuck at "Search Text for Your Name" tutorial 5/7.
here is my question:
your loop should stop when it hits the value of the first iterator (say, i)
plus the length of your myName variable.
here is some informations from to tutorial:
Your second "for" loop
Okay! Last loopy step: add another for loop, this time inside the body of your if statement (between the if's {}s).
This loop will make sure each character of your name gets pushed to the final array. The if statement says: "If we find the first letter of the name, start the second for loop!" This loop says: "I'm going to add characters to the array until I hit the length of the user's name." So if your name is 11 letters long, your loop should add 11 characters to hits if it ever sees the first letter of myName in text.
For your second for loop, keep the following in mind:
First, you'll want to set your second loop's iterator to start at the first one, so it picks up where that one left off. If your first loop starts with
> for(var i = 0; // rest of loop setup
your second should be something like
> for(var j = i; // rest of loop setup Second
think hard about when your loop should stop.
Finally, in the body of your loop, have your program use the .push() method of hits. Just like strings and arrays have a .length method, arrays have a .push() method that adds the thing between parentheses to the end of the array. For example,
newArray = [];
newArray.push('hello');
newArray[0]; // equals 'hello'
and here is my code:
multistr:true
var text = "Hey, how are you \
doing? My name is Emily.";
var myName = "Emily";
var hits = [];
for (var i = 0; i > text.length; i++)
{
if (text[i] === 'E')
{
for(var j = i; j > text.length; j++){
};
};
};
ps: i don't want to pass this tutorial without understand it. please help me. teach me.
for (var i = 0; i > text.length; i++) should be
for (var i = 0; i < text.length; i++)
otherwise it won't ever meet the criteria to even start the loop.
Welcome on board! You confused > with <. Your loops won't run because for the first check when i = 0 it certainly does not hold that 0 > text.length, because text.length is at least 0 (there are no strings shorter than the empty string).
You should make a habit of manually going through your loops for the first two steps and then check what happens just before the loop ends.
Here is what I got for my code:
for ( i = 0; i < text.length; i++)
{
if ( text[i] === "E")
{
for( var j = i; j < (myName.length + i ); j++)
{
hits.push(text[j]);
}
}
};
It looks like you were missing the " + i " part in your second for loop. That seems to make sure that the first loop will be included. I tried it without the "+ i" and it does not work.
I tried continuing directly from the second for loop using a "+ j" and that only crashes the browser.
The following is a function for calculating all the possible combinations of a given array:
function combinations(arr, k) {
var i, subI, sub, combinationsArray = [], next;
for (i = 0; i < arr.length; i++) {
if (k === 1) {
combinationsArray.push([arr[i]]);
} else {
sub = combinations(arr.slice(i + 1, arr.length), k - 1);
for (subI = 0; subI < sub.length; subI++) {
next = sub[subI];
next.unshift(arr[i]);
combinationsArray.push(next);
}
}
}
return combinationsArray;
};
For example:
combinations([1,2,3],2);
returns:
[[1,2],[1,3],[2,3]]
I have a nested for loop which modifies a copy of an array of 12 objects (splicing certain elements,depending on the iteration of the loop), before using it as a parameter to the combinations function and storing certain elements of the array returned.
var resultArray = [];
var paramArray = [obj1,obj2,obj3,obj4,obj5,obj6,obj7,obj8,obj9,obj10,obj11,obj12];
for(i=0;i<length1;i++){
for(n=0;n<length2;n++){
paramArray.splice(...);//modifying array
resultArray[n] = combinations(paramArray,2)[i].slice();//storing an element, there are multiples of each element in the resultArray obviously
}
}
The browser crashes with the above type of code
(firefox returns the messege: "A script on this page may be busy, or it may have stopped responding. You can stop the script now, open the script in the debugger, or let the script continue.") The breakpoint is always the part where the combinations function thats being called.
Because the array parameter is different in each iteration, I cant assign the combinations function call to a variable to optimize the code. Is there a more efficient way of writing this?