Eloquent JavaScript looping a triangle exercise solution question - javascript

I had to check the solution for the first exercise in the book, and as I understand it, it's almost identical to my answer.
The exercise:
Write a loop that makes seven calls to console.log to output the following triangle:
the solution, that is given by the book:
for (let line = "#"; line.length < 8; line += "#")
console.log(line);
and my solution:
for (let hash = '#'; hash.length <= 7; hash++) {
console.log(hash);
};
My question is, why my loop doesn't loop? As it is explained in the book:
For counter += 1 and counter -= 1, there are even shorter equivalents: counter++ and counter--.
So by this logic, it should work.

You are trying to increment a character. This doesn't concatenate to the character as you want it to and instead increases the ASCII value of the character.
Modify your code a bit:
for (let hash = "#"; hash.length <= 7; hash += "#") {
console.log(hash);
};
hash should be a string so that you can concatenate to it; furthermore, you shouldn't try to increment hash but rather concatenate to it on every iteration of the loop.

Related

Better big O worse real time execution

So I'm following a algorithms course and it asked to implement the following function:
Write a function called findLongestSubstring, which accepts a string and returns the length of the longest substring with all distinct characters.
findLongestSubstring('') // 0
findLongestSubstring('bbbbbb') // 1
findLongestSubstring('longestsubstring') // 8
And said it has to have a time complexity of o(n).
So I could not come up with a o(n) solution, but a supposed (n^2)... my naive solution:
function findLongestSubstring(str: string): number {
const array = str.split("");
let maxLength = 0;
for (let i = 0; i < array.length; i++) {
const letterArray = [array[i]];
for (let j = i + 1; j < array.length; j++) {
if (letterArray.find(e => e === array[j])) {
if (letterArray.length > maxLength) maxLength = letterArray.length;
break;
} else {
letterArray.push(array[j])
if (j === array.length - 1) {
if (letterArray.length > maxLength) maxLength = letterArray.length;
break;
}
}
}
}
return maxLength;
}
Then I went to check his solution:
function findLongestSubstring(str: string) {
let longest = 0;
let seen = {};
let start = 0;
for (let i = 0; i < str.length; i++) {
let char = str[i];
if (seen[char]) {
start = Math.max(start, seen[char]);
}
// index - beginning of substring + 1 (to include current in count)
longest = Math.max(longest, i - start + 1);
// store the index of the next char so as to not double count
seen[char] = i + 1;
}
return longest;
}
So I do agree that my solution which has a nested for loop appears to be O(n2) and his solution o(n). But when I tested both algorithms with a really big string to my surprise my algorithm was acctually faster and i have NO idea why... can someone enlight me?
The way Im testing:
test("really big string to test bigO", () => {
const alphabet = "abcdefghijklmnopqrstuvwxyz"
const randomLettersArray = Array.from({ length: 100000000 }, () => alphabet[Math.floor(Math.random() * alphabet.length)]);
const randomBigString = randomLettersArray.join("")
const start = Date.now();
findLongestSubstring(randomBigString);
const end = Date.now();
console.log(`Execution time: ${end - start} ms`);
})
Your nested loop solution looks like it could take O(n2) time, but it's actually limited by the longest possible valid substring, and that is limited by the size of the alphabet.
Your test uses a small alphabet -- only 26 characters. Your inner loop has a maximum of 26 iterations in this case, and that just might be faster than the other algorithm in some environments.
If you were to use a much larger alphabet -- say 10000 characters or so, then your algorithm would be much slower, but the other one would not slow down much at all.
Let us condider all longest subtrings (meeting the distinctness condition) that end at the successive positions:
l -> l
lo -> lo
lon -> lon
long -> long
longe -> longe
longes -> longes
longest -> longest
longests -> ts
longestsu -> tsu
longestsub -> tsub
longestsubs -> ubs
longestsubst -> ubst
longestsubstr -> usbtr
longestsubstri -> ubstri
longestsubstrin -> ubstrin
longestsubstring -> ubstring
You will notice that the index of the first character never descreases. Every time one appends a new character, the starting index either does not change, or it moves past the position of the same character in the substring.
So the following loop could do:
the substring is empty
for all ending positions in the string:
append the new character to the substring
move the starting position past the same character in the substring, if any
But we are not done yet: if the new character is found in the substring, the starting cursor will stop there; but if it is not found, we need to traverse the whole substring for nothing, and this can make the complexity quadratic.
So we need an extra device to tell us if the substring contains the new character without traversing the substring. As the alphabet has a finite size, we can do with an array of booleans which will act as counters. Initially all booleans are zero; when a character enters/exits the substring, its entry in the array is set/reset.
Hence the whole search can be performed in linear time. Of course the solution is the length of the longest string found during this process.
all booleans are initially reset
the substring is empty
for all ending positions in the string:
append the new character to the substring
if the boolean for this character is set:
move the starting position past the same character in the substring,
while resetting the booleans of all characters met
set the boolean for this character
update the longest length so far

Why is one of the following algorithms faster than the other if you do more processing?

function reverseString(str) {
let newStr = ''
for (let i = (str.length - 1); i >= 0; i--) {
newStr += str[i]
}
return newStr
}
// This algorithm is faster
function reverseString2(str) {
str = str.split('')
let left = 0
let right = str.length - 1
while (left < right) {
const tmp = str[left]
str[left] = str[right]
str[right] = tmp
left++
right--
}
return str.join('')
}
Why is reverseString2 faster than reverseString if the function does more processing, converting the string to an array and then concatenating the whole array? The advantage is that the main algorithm is O(n/2) but the rest is O(n). Why does that happen?
The results are the following:
str size: 20000000
reverseString: 4022.294ms
reverseString2: 1329.758ms
Thanks in advance.
In the first function reverseString(), what is happening is that the loop is running 'n' times. Here 'n' is meant to signify the length of the string. You can see that you are executing the loop n times to get the reversed string. So the total time taken by this function depends on the value of 'n'.
In the second function reverseString2(), the while loop is running only n/2 times. You can understand this when you look at the left and the right variable. For one execution of the loop, the left variable is increasing by 1 and the right variable is decreasing by 1. So you are doing 2 updation at once. So for a n-length string, it only executes for n/2 times.
Although there are much more statements in the second function, but they are being executed much less time than those statements in the first function. Hence the function reverseString2() is faster.

Write a JavaScript function to find longest substring in a given a string without repeating characters

"Write a JavaScript function to find longest substring in a given a string without repeating characters."
Here's what I tried, but it doesn't print anything
function sort(names) {
let string = "";
let namestring = names.split("");
for(let i = 0; i < namestring.length; i++) {
for(let j = 0; j < string.length; j++) {
if(string[j] != namestring[i]) {
string = string + namestring[i];
}
}
}
return string;
}
console.log(sort("google.com"));
What's wrong?
function sort(names)
{
string="";
ss="";
namestring=names.split("");
for(j=0;j<namestring.length;j++) {
for(i=j;i<namestring.length;i++) {
if(string.includes(namestring[i]))
break;
else
string+=namestring[i];
}
if(ss.length<string.length)
ss=string;
string="";
}
return ss;
}
console.log(sort("google.com"));
It's o(n^2) complexity but try this(may be o(n^3) if contains function take o(n) complexity)
function sort(names)
{
string="";
ss="";
namestring=names.split("");
for(j=0;j<namestring.length;j++) {
for(i=j;i<namestring.length;i++) {
if(string.includes(namestring[i])) // if contains not work then
break; //use includes like in snippet
else
string+=namestring[i];
}
if(ss.length<string.length)
ss=string;
string="";
}
return ss;
}
console.log(sort("google.com"));
What are you expecting the answer to be here? Should it be "ogle.com" or "gle.com"? If the first, the below should get you there, if the latter, update the tested = name.charAt(i) in the else to tested = "".
So a few things to note, though you're more than welcome to do as you wish:
1) the function name. This isn't doing a "sort" as far as I can tell, so if this is for your use (or any reuse. Basically, anything more than a one off homework assignment), you may want to rename it to something you'd actually remember (even the example I give is probably not completely best as "pick longest substring" is non-descriptive criteria).
2) variable naming. string and namestring may mean something to you here, but considering we're trying to find the longest substring (with the no double characters) in a string, I felt it was better to have the one we're checking against (tested) and the one we're storing to return later (longest). It helps make sense as you're reading through the code as you know when you are done with a checked string (tested), you want to compare if it is greater than the current longest substring (longest) and if it is bigger, you want it to be the new longest. This will save you a ton of headache to name variables to things that'll help when designing your function as you can get it as close to requirements written down as possible without trying to do some form of substitution or worse, forgetting which variable holds what.
I don't know what you want the result to be in the event that tested length is the same as longest length. Currently I have it set to retain, if you want the most recent, update the check to >=.
Beyond that, I just iterate over the string, setting to the currently tested string. Once double characters are met, I then see if what I just generated (tested) is larger than the current longest and if it is, it is now the longest. Once I finish looping across the string, I have to do the current vs longest check/set again as otherwise, it'd make the final tested meaningless (it went outside the loop before another double character situation was hit).
function pickLongestSubstring(name) {
let tested = "";
let longest = "";
for (let i = 0; i < name.length; i++) {
if (tested.length == 0 || tested.charAt(tested.length - 1) != name.charAt(i)) {
tested += name.charAt(i);
}
else {
if (tested.length > longest.length) {
longest = tested;
tested = "";
}
}
}
if (tested.length > longest.length) {
longest = tested;
}
return longest;
}
console.log(pickLongestSubstring("google.com"))
console.log(pickLongestSubstring("example.com"))
This is a recursive loop that should get the longest string. Uses sort to determine longest string. Works, even if multiple instances of same repeat char.
function longestWithoutRepeat(testString, returnString){
var returnString = returnString || "";
for(var i = 0; i < testString.length; i++) {
if(i > 0){
if(testString[i] == testString[i-1]) {
var testStringArray = testString.split(testString[i] + testString[i-1]);
testStringArray.sort(function(firstString, nextString){ return nextString.length - firstString.length})
returnString = testStringArray[0];
longestWithoutRepeat(testStringArray[0], returnString);
}
} else {
returnString = testString
}
}
return returnString;
}
console.log(longestWithoutRepeat("oolong"));
console.log(longestWithoutRepeat("google.com"));
console.log(longestWithoutRepeat("diddlyougotoofarout"));

Hacker Rank Annagrams

I am trying to solve the problem described here with JavaScript...
https://www.hackerrank.com/challenges/ctci-making-anagrams
I have to output the number of letters that would need to be removed from two strings in order for there to only be matching letters (compare the two strings for matching letters and total the letters that don't match)
for example...
string a = cbe
string b = abc
the only matching letter is between both strings is the two c's so I would be removing 4 letters (beab).
My code works, but it seems to keep timing out. If I download the individual test case instances, I seem to fail when variables a and b are set to large strings. If I test these individually, I seem to get the right output but i still also get the message "Terminated due to timeout".
I'm thinking it might be obvious to someone why my code timesout. I'm not sure it's the most elegant way of solving the problem but I'd love to get it working. Any help would be much appreciated...
function main() {
var a = readLine();
var b = readLine();
var arraya = a.split('');
var arrayb = b.split('');
var arraylengths = arraya.length + arrayb.length;
//console.log(arraylengths);
if (arraya.length <= arrayb.length) {
var shortestarray = arraya;
var longestarray = arrayb;
} else {
var shortestarray = arrayb;
var longestarray = arraya;
}
var subtract = 0;
for (x = 0; x < shortestarray.length; x++) {
var theletter = shortestarray[x];
var thenumber = x;
if (longestarray.indexOf(theletter, 0) > -1) {
var index = longestarray.indexOf(theletter, 0);
longestarray.splice(index, 1);
subtract = subtract + 2;
}
}
var total = arraylengths - subtract;
console.log(total);
}
Your algorithm is good. It's straight forward and easy to understand.
There are certain things you can do to improve the performance of your code.
You don't have to calculate the indexOf operation twice. you can reduce it to one.
the splice operation is the costliest operation because the JS engine has to delete the element from an array and reassign the indexes of all the elements.
A point to be noted here is that the JS engine does an extra step of correcting the index of the array, which is not required for your purpose. So you can safely remove longestarray.splice(index, 1); and replace it with delete longestarray[index]
Here is a snippet which will increase your performance of the code without changing your logic
for (var x = 0; x < shortestarray.length; x++) {
var theletter = shortestarray[x];
var thenumber = longestarray.indexOf(theletter, 0); // <-- check only once
if (thenumber > -1) {
var index = thenumber;
delete longestarray[index]; // <-- less costlier than splice
subtract = subtract + 2;
}
}
Note: I am not suggesting you to use delete for all the cases. It's useful here because you are not going to do much with the array elements after the element is deleted.
All the best. Happy Coding
I would suggest you hashing. make the characters of string key and its numbers of occurrences value. Do the same for both strings. After that take string 1 and match the count of its every character with the count of same character in string then calculate the difference in the number of occurrences of the same character and delete that character till the difference becomes 0 and count that how many times you performed delete operation.
ALGORITHM:
step 1: Let arr1[255]= an integer array for storing the count of string1[i]
and initialized to zero
ex: string1[i]='a', then arr1[97]=1, because ASCII value of a is 97
and its count is 1. so we made hash table for arr1 where key is
ASCII value of character and value is its no of occurrences.
step 2: Now declare an another array of same type and same size for string 2
step 3: For i=0 to length(string1):
do arr1[string1[i]]++;
step 4: For i=0 to length(string2):
do arr2[string2[i]]++;
step 5: Declare an boolean char_status[255] array to check if the
character is visited or not during traversing and initialize it to
false
step 6: set count=0;
step 7: For i=0 to length(string1):
if(char_status[string1[i]]==false):
count=count+abs(arr1[string1[i]]-arr2[string1[i]])
char_status[string1[i]]=true
step 8: For i=0 to length(string2):
if(char_status[string2[i]]==false):
count=count+abs(arr1[string2[i]]-arr2[string2[i]])
char_status[string2[i]]=true
step 9: print count
I have applied this algo just now and passed all test cases. You may improve this algo more if you have time.

Recursion and Loops - Maximum Call Stack Exceeded

I'm trying to build a function that adds up all the numbers within a string... for example, 'dlsjf3diw62' would end up being 65.
I tried to be clever and put together a recursive function:
function NumberAddition(str) {
var numbers='1234567890';
var check=[];
str=str.split[''];
function recursive(str,check) {
if (str.length==0)
return check;
else if (numbers.indexOf(str[0])>=0)
{
for (i=0;i<str.length;i++){
if (numbers.indexOf(str[i])<0)
check.push(str.slice(0,i));
str=str.slice(i);
return recursive(str,check);
}
}
else
str.shift();
return recursive(str,check);
}
You'll see that I'm trying to get my numbers returned as an array in the array named check. Unfortunately, I have a maximum call stack size exceeded, and I'm not sure why! The recursion does have a base case!! It ends once str no longer has any contents. Why wouldn't this work? Is there something I'm missing?
-Will
You can achieve the same thing with a far easier solution, using regular expressions, as follows:
var str = 'dlsjf3diw62';
var check = str.match(/\d+/g); // this pattern matches all instances of 1 or more digits
Then, to sum the numbers, you can do this:
var checkSum = 0;
for (var i = 0; i < check.length; i++) {
checkSum += parseInt(check[i]);
}
Or, slightly more compact:
var checkSum = check.reduce(function(sum, num){ return sum + parseInt(num) }, 0);
The reason your recursion doesn't work is the case where you do enter the for loop, because you've found a digit, but the digits continue to the end of the string. If that happens, the return inside the for loop never happens, and the loop ends. After that, the .shift() does not happen, because it's in that else branch, so you return re-process the same string.
You shouldn't solve this particular problem that way, but the code makes a good example of the anti-pattern of having return statements inside if bodies followed by else. Your code would be clearer (and would work) if it looked like this:
function recursive(str, check) {
if (str.length == 0)
return check;
if (numbers.indexOf(str[0]) >= 0) {
// Find the end of the string of digits, or
// the end of the whole thing
for (var i = 0; i < str.length && numbers.indexOf(str[i]) >= 0; i++);
check.push(str.slice(0, i));
str = str.slice(i);
return recursive(str, check);
}
// A non-digit character
str.shift();
return recursive(str, check);
}
In that version, there are no else clauses, because the two if clauses always involve a return. The for loop is changed to simply find the right value of "i" for the subsequent slicing.
edit — one thing this doesn't fix is the fact that you're pushing arrays into your "check" list. That is, the substring "62" would be pushed as the array ["6", "2"]. That's not a huge problem; it's solved with the addition of a .join() in the right place.

Categories