Sliding Window - findLongestSubString (please explain this piece of code/answer) - javascript

Now I have solved this algorithmic challenge myself but I would like someone to explain the answer below line by line please as I took it from someone else. I do NOT understand it at all and how the answers come to be, even after using pythonTutor.
Challenge: Write a function called findLongestSubstring, which accepts a string and returns the length of the longest substring with all distinct characters.
Edit: I only do NOT understand the ABOVE CODE.
function findLongestSubstring(str) {
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;
}
// findLongestSubstring("thisisawesome"); // 6
// findLongestSubstring("thecatinthehat"); // 7
My solution:
function findLongestSubstring(str){
if (str.length === 0) return 0;
// track longest length
let longestLength;
// get first subArr
let subStrArr = str.split("").slice(0,1);
// get first subArrLength
let subStrLength = subStrArr.length;
longestLength = subStrLength;
// variable for checking every character
let j = 0;
// for loop
for ( let i = 1; i < str.length; i++ ) {
// if current element don't exist in subArr
if (!subStrArr.includes(str[i])) {
subStrArr = str.split("").slice(j,i+1);
subStrLength = subStrArr.length;
}
// does exist
else {
j++;
i = j;
subStrArr = str.split("").slice(i,i+1);
subStrLength = subStrArr.length;
}
if (subStrLength > longestLength) longestLength = subStrLength;
}
return longestLength;
}
findLongestSubstring("rithmschool"); // 7

A substring with unique characters doesn't obviously have duplicate letters. So as we move forward in our string str, we keep track of the greatest index of all the characters that we have already met in seen. The greatest index of each letter is obviously the one we have seen last since we move from left to right.
Now, in your iteration, when you reach a letter that you find in seen, you need to set start to that character's index tracked by seen to avoid including both letters in your substring. We take the max because start of substring may already be higher because of another double letter that we met earlier:
if (seen[char]) {
start = Math.max(start, seen[char]);
}
Then we just look how long our current substring is and retain it if it's longer than our longest seen.
longest = Math.max(longest, i - start + 1);
And at last we save the index of the character in seen
seen[char] = i + 1;
The order of these operations is important. If we updated seen with the index of the char first then we couldn't check seen for the character and set start based on it.

Related

How to create a letter scoring function that scores vowels differently from consonants

So I'm trying to create a word scoring algorithm that gives one point for every consonant and 3 point for every vowel. But every attempt I've tried has failed which means something isn't mentally clicking for me and I need help understanding what I'm doing wrong
I've searched all over the web and found numerous ways to check letters of a word and count vowels, which I've tried customizing and using for my scoring algorithm and I still keep getting the wrong scoring output when testing each approach. I've looked at using iterative approached and using regular expressions. Someone was telling me that my fault is in my second loop but I'm not sure how to correct it.
//The attempt where apparently the second loop is the problem:
vowelBonus= (word)=>{
let score = 0;
const vowels = ["a","e","i","o","u"];
word = word.toLowerCase();
for (letter in word){
vowels.forEach((value)=>{
if (letter === value){
score += 3
}else if(letter !== value){
score += 1
}
})
return score
}
}
// An attempt using a regular expression which I kind of prefer after reading up on them
vowelBonus = (word)=>{
let vowel = word.match(/[aeiou]/gi);
let vCount = 0
let consCount = 0
let score = vCount + consCount
if(vowel.length){
vCount += 3
}else if (!vowel.length){
consCount =+ 1
}
return score
}
//I have about 3-5 other versions I've toyed around with but I'd like to stay in the ballpark of something like the two snippets I've pasted above.
//One version keeps giving an output of 0. Another gives increments of 5, and I've had a few give a result of 1-5. I'm expecting the result to be 1pt for every consonant and 3pts for every vowel. Meaning if I type in the word "code", the result should be 8pts.
EDIT: Don't forget about edge cases, like if someone supplies a space in the word... Just something to consider...
EDIT 2: Added example using regex that ignores spaces if they are supplied..
Something like this should work.. I find it easiest to use the Array.includes method for things like this.
Let me know if you have any questions!
function scoreWord(word) {
// Ignores spaces
return word.replace(/\s/g,'').toLowerCase().split("").reduce((a, i) =>
a += ["a", "e", "i", "o", "u"].includes(i) ? 3 : 1, 0);
}
function scoreWordSimplified(word) {
// Ignores spaces; Simplified example
let wordArray = word.replace(/\s/g, "").toLowerCase().split("");
let vowels = ["a", "e", "i", "o", "u"];
let pointsForVowel = 3;
let pointsForConsonant = 1;
let finalScore = 0;
wordArray.forEach(letter => {
if (vowels.includes(letter)) {
finalScore += pointsForVowel;
} else {
finalScore += pointsForConsonant;
}
/** Or you could do:
* finalScore += vowels.includes(letter) ? pointsForVowel : pointsForConsonant;
*/
});
return finalScore;
}
let vowelBonus = (word)=>{
// credit: #Code Maniac
// Ignores spaces
let _word = word.replace(/\s/g,'');
let vowel = _word.match(/[aeiou]/gi);
let wordLength = _word.length;
let vowelLength = vowel && vowel.length;
return vowel ? (wordLength - vowelLength) + vowelLength *3 : wordLength
}
let score1 = scoreWord("some word");
let score2 = vowelBonus("some word");
let score3 = scoreWordSimplified("some word");
console.log("scoreWord: ", score1);
console.log("vowelBonus: ", score2);
console.log("scoreWordSimplified: ", score3);
It's simpler with regex:
(word)=>{
let vowel = word.match(/[aeiou]/gi);
vowelScore = 3 * vowel.length
consScore = word.length - vowel.length
return vowelScore + consScore
}
Well there are lots of problems to notice in you code.
In example 1
Do not use for .. in for looping over the strings,
when you iterate over string using for ... in it returns the index ( key ) of string not the value.
You're returning value inside for...in after first iteration itself, whereas you need to return value after loop,
Inside the loop you're looping over the vowels everytime and if the value is found you're adding 3 if not found you're adding 1, but for each single characters you're looping on vowels which adds extra count than it should
In example 2
You're matching with regex and than if the length is greater than zero you're adding value just once whereas it should be match.length * 3 and for the remaining length it should be (word.length - match.length)
so you can simply do this
let vowelBonus = (word)=>{
let vowel = word.match(/[aeiou]/gi);
let wordLength = word.length
let vowelLength = vowel && vowel.length
return vowel ? (wordLength - vowelLength) + vowelLength *3 : wordLength
}
console.log(vowelBonus("aeiou"))
console.log(vowelBonus("abcd"))
console.log(vowelBonus("aeibcd"))

Find Longest Prefix That is Also a Suffix in String - Javascript

Link to codewars challenge
I need to return the length of the longest prefix that is also a suffix of a string in Javascript.
As far as I understand, the prefixes in "abcd" are:
['a', 'ab', 'abc']
And the suffixes in "abcd" are:
[ 'bcd', 'cd', 'd' ]
So the length of the longest prefix that is also a suffix in "abcd" in this case is 0, because there are no prefixes that are also suffixes in "abcd".
So far I've been able to figure out how to get the suffixes into an array for comparison, but not the prefixes.
function returnLongestPrefixAndSuffix(string) {
let prefixes = [];
let suffixes = [];
for (let i = 0; i < string.length -1; i++) {
prefixes.push(string.slice(i));
}
for (let i = 1; i < string.length; i++) {
suffixes.push(string.slice(i));
}
return prefixes + " " + suffixes;
}
console.log(returnLongestPrefixAndSuffix("abcd"));
I'm not grasping the concept of how to start at the beginning of a string and add a larger element to the array each time by one character, excluding the element that would include the last one.
Please follow my current logic if possible.
EDIT: My code now looks like this:
function solve(string) {
let prefixes = [];
let suffixes = [];
let includedList = [];
for (let i = 1; i < string.length; i++) {
prefixes.push(string.slice(0, i));
}
for (let i = 1; i < string.length; i++) {
suffixes.push(string.slice(-i));
}
console.log(prefixes);
console.log(suffixes);
for (let i = 0; i < prefixes.length; i++) {
let element = prefixes[i];
if (suffixes.includes(element) === true) {
includedList.push(element);
}
}
console.log(includedList);
if (includedList.length === 0) {
return 0;
}
else {
let overlap = prefixes.filter(value => suffixes.includes(value));
console.log(overlap);
let longest = includedList.sort(function (a, b) { return b.length - a.length; })[0];
return longest.length;
}
}
console.log(solve("abcdabc"));
And this is passing 10049 test but failing 163 tests on codewars. I still do not know what to do with the overlap variable or how to exclude overlaps from the includedList array.
function solve(string) {
for (let i = Math.floor(string.length / 2); i > 0; i--) {
let prefix = string.slice(0, i);
let suffix = string.slice(-i);
if (prefix == suffix) {
return i;
}
}
return 0;
}
console.log(solve("abcdabc"));
To account for the overlap, initialize your for-loop like this:
let i = Math.floor(string.length / 2)
That will initialize the for-loop at the half-way point in your string, so that you can count down and compare whether or not the prefix == the suffix, starting with the longest.
You could return prefix.length, but that will be the same thing as i.
Also, be sure to return 0 outside of the for-loop. Because if you try:
if (prefix != suffix) {
return 0;
}
inside of the for-loop, it will stop counting right there.
To get the prefixes, you can use the second argument of .slice:
string.slice(0, i)
Note that to get the suffixes, you could also take the string from the end:
string.slice(-i)
There is no sense in collecting prefixes and suffixes in arrays, just search for the biggest i where the suffix equals the prefix.
Please see the documentation of the slice function, it may take a second argument: https://www.w3schools.com/jsref/jsref_slice_string.asp
So following your logic, one way to get prefixes would be to:
for (let i = 1; i <= string.length; i++) {
prefixes.push(string.slice(0, i));
}
EDIT:
Your newest code doesn't work because of two reasons:
You may end up with includedList being empty, but you still try to get first element out of it.
You don't take overlaps into consideration. For the input aaa the correct result is a since prefix aa overlaps with the corresponding suffix. In other words the result can't be longer than half the length of the input.

How to make element in Array change its' place

I'm beginner in JS. I've tried to understand Caesar Cipher ROT13, but it was too complicated for me. So I've tried to write my own code. Here it is below:
function encrip() {
var alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"];
var str = "Ni Hao";
var string = str.toUpperCase();
for (var i = 0; i < string.length; i++) {
for (var k = 0; k < alphabet.length; k++) {
if(string.charAt(i) == alphabet[k]) {
/* console.log(string.charAt(i) + ' ' + alphabet.indexOf(alphabet[k])); */
}
}
}
}
encrip();
But I am stuck. How to do:
1. Get value from var str and then access to var alphabet , after change each letter from var str value to next 3 from alphabet (var str each element's current position would be changed) For example: Input: Ni Hao ==> output: QL KDR
2. Create universal code, I mean, not only for changing position by 3, but when I give value '5', each element would be changed by next 5 positions from alphabet. So output can be changed when I change its' value
I hope I explained everything clearly. Thanks everyone in advance for help!!
you can use the following function to encrypt english words, the 1st parameter is the string to encrypt and the 2nd for shifting
function encryp(str,pos){
var alpha="ABCDEFGHIJKLMNOPQRSTUVWXYZ";
var strUC=str.toUpperCase();
var enc="";
for(var i=0;i<strUC.length;i++){
if(strUC.charAt(i)!=" "){
enc+=alpha.charAt((alpha.indexOf(strUC.charAt(i))+pos)%26)
}
else{
enc+=" "
}
// in your case pos=3
}
return enc;
}
console.log(encryp("NiHao",3));
You don't need two for loops to do this. Iterate over the input string and find the index of each character in the alphabet array, if found add the shift to it to get the encrypted character.
To handle overflow use the modulus operator to cycle through the array.
Also I assume that you are not going use any special symbols to do the encryption.
function encrip(string, shift) {
var alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"];
string = string.toUpperCase();
let arr = [];
for (var i = 0; i < string.length; i++) {
let char = alphabet.indexOf(string[i]) !== -1 ? alphabet[(alphabet.indexOf(string[i]) %26) + shift] : " ";
arr.push(char);
}
let encryp = arr.join("");
console.log(encryp);
return encryp;
}
encrip("Ni Hao", 3);
First of all, instead of your inner for loop scanning the whole alphabet array, you can use the built-in function indexOf:
alphabet.indexOf('K') // returns 10
Secondly, you'll want to build up your enciphered string in a separate variable. For each letter, get the index of that letter in the alphabet, add your cipher offset parameter to that index and add the resulting letter from the alphabet to your new string. An important step is that when you add to the index of the letter, you want to make sure the resulting index is within range for the alphabet array. You can do that using the % (modulo) operator, which will wrap high values back round to the start of the array. In full:
function encipher(input, offset) {
var alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"];
var str = input.toUpperCase();
var result = '';
for (var i = 0; i < str.length; i++) {
letterIndex = alphabet.indexOf(str.charAt(i));
if (letterIndex === -1) {
result += str[i]; // if the letter isn't found in the alphabet, add it to the result unchanged
continue;
}
cipheredIndex = (letterIndex + offset) % alphabet.length; // wrap index to length of alphabet
result += alphabet[cipheredIndex];
}
console.log(result);
}
encipher('Ni Hao', 5); // output: 'SN MFT'

How to improve performance of this Javascript/Cracking the code algorithm?

so here is the question below, with my answer to it. I know that because of the double nested for loop, the efficiency is O(n^2), so I was wondering if there were a way to improve my algorithm/function's big O.
// Design an algorithm and write code to remove the duplicate characters in a string without using any additional buffer. NOTE: One or two additional variables are fine. An extra copy of the array is not.
function removeDuplicates(str) {
let arrayString = str.split("");
let alphabetArray = [["a", 0],["b",0],["c",0],["d",0],["e",0],["f",0],["g",0],["h",0],["i",0],["j",0],["k",0],["l",0],["m",0],["n",0],["o",0],["p",0],["q",0],["r",0],["s",0],["t",0],["u",0],["v",0],["w",0],["x",0],["y",0],["z",0]]
for (let i=0; i<arrayString.length; i++) {
findCharacter(arrayString[i].toLowerCase(), alphabetArray);
}
removeCharacter(arrayString, alphabetArray);
};
function findCharacter(character, array) {
for (let i=0; i<array.length; i++) {
if (array[i][0] === character) {
array[i][1]++;
}
}
}
function removeCharacter(arrString, arrAlphabet) {
let finalString = "";
for (let i=0; i<arrString.length; i++) {
for (let j=0; j<arrAlphabet.length; j++) {
if (arrAlphabet[j][1] < 2 && arrString[i].toLowerCase() == arrAlphabet[j][0]) {
finalString += arrString[i]
}
}
}
console.log("The string with removed duplicates is:", finalString)
}
removeDuplicates("Hippotamuus")
The ASCII/Unicode character codes of all letters of the same case are consecutive. This allows for an important optimization: You can find the index of a character in the character count array from its ASCII/Unicode character code. Specifically, the index of the character c in the character count array will be c.charCodeAt(0) - 'a'.charCodeAt(0). This allows you to look up and modify the character count in the array in O(1) time, which brings the algorithm run-time down to O(n).
There's a little trick to "without using any additional buffer," although I don't see a way to improve on O(n^2) complexity without using a hash map to determine if a particular character has been seen. The trick is to traverse the input string buffer (assume it is a JavaScript array since strings in JavaScript are immutable) and overwrite the current character with the next unique character if the current character is a duplicate. Finally, mark the end of the resultant string with a null character.
Pseudocode:
i = 1
pointer = 1
while string[i]:
if not seen(string[i]):
string[pointer] = string[i]
pointer = pointer + 1
i = i + 1
mark string end at pointer
The function seen could either take O(n) time and O(1) space or O(1) time and O(|alphabet|) space if we use a hash map.
Based on your description, I'm assuming the input is a string (which is immutable in javascript) and I'm not sure what exactly does "one or two additional variables" mean so based on your implementation, I'm going to assume it's ok to use O(N) space. To improve time complexity, I think implementations differ according to different requirements for the outputted string.
Assumption1: the order of the outputted string is in the order that it appears the first time. eg. "bcabcc" -> "bca"
Suppose the length of s is N, the following implementation uses O(N) space and O(N) time.
function removeDuplicates(s) {
const set = new Set(); // use set so that insertion and lookup time is o(1)
let res = "";
for (let i = 0; i < s.length; i++) {
if (!set.has(s[i])) {
set.add(s[i]);
res += s[i];
}
}
return res;
}
Assumption2: the outputted string has to be of ascending order.
You may use quick-sort to do in-place sorting and then loop through the sorted array to add the last-seen element to result. Note that you may need to split the string into an array first. So the implementation would use O(N) space and the average time complexity would be O(NlogN)
Assumption3: the result is the smallest in lexicographical order among all possible results. eg. "bcabcc" -> "abc"
The following implementation uses O(N) space and O(N) time.
const removeDuplicates = function(s) {
const stack = []; // stack and set are in sync
const set = new Set(); // use set to make lookup faster
const lastPos = getLastPos(s);
let curVal;
let lastOnStack;
for (let i = 0; i < s.length; i++) {
curVal = s[i];
if (!set.has(curVal)) {
while(stack.length > 0 && stack[stack.length - 1] > curVal && lastPos[stack[stack.length - 1]] > i) {
set.delete(stack[stack.length - 1]);
stack.pop();
}
set.add(curVal);
stack.push(curVal);
}
}
return stack.join('');
};
const getLastPos = (s) => {
// get the last index of each unique character
const lastPosMap = {};
for (let i = 0; i < s.length; i++) {
lastPosMap[s[i]] = i;
}
return lastPosMap;
}
I was unsure what was mean't by:
...without using any additional buffer.
So I thought I would have a go at doing this in one loop, and let you tell me if it's wrong.
I have worked on the basis that the function you have provided gives the correct output, you were just looking for it to run faster. The function below gives the correct output and run's a lot faster with any large string with lots of duplication that I throw at it.
function removeDuplicates(originalString) {
let outputString = '';
let lastChar = '';
let lastCharOccurences = 1;
for (let char = 0; char < originalString.length; char++) {
outputString += originalString[char];
if (lastChar === originalString[char]) {
lastCharOccurences++;
continue;
}
if (lastCharOccurences > 1) {
outputString = outputString.slice(0, outputString.length - (lastCharOccurences + 1)) + originalString[char];
lastCharOccurences = 1;
}
lastChar = originalString[char];
}
console.log("The string with removed duplicates is:", outputString)
}
removeDuplicates("Hippotamuus")
Again, sorry if I have misunderstood the post...

Get current sentence by cursor/caret position

I have a string containing multiple sentences. I also have the current cursor/caret position.
I need to be able to extract the current sentence at the given cursor position.
For example, take this string:
This is the first sentence. And this is the second! Finally, this is the third sentence
If the current cursor position is 33 then the cursor is in the second sentence.
In which case, the result returned should be:
And this is the second!
I only need to use the standard sentence definers of .?!
Any help with this would be greatly appreciated.
Although I am expecting regex to be required, if there is a faster alternative using native methods I would be interested in that also.
Here is a way to achieve what you need: use String#split with /[?!.]/g to get an array of sentences and then iterate over the array to sum up the lengths of the sentences found, and if the index is smaller than the count, return the sentence.
function getSentenceByPos(idx, str) {
pos = 0;
array = str.split(/[?!.]/g);
for (var i=0; i<array.length; i++) {
pos += array[i].length + 1;
if (pos >= idx) {
return array[i];
}
}
}// 26 still 1 then `.`. 51 then `!` - 53 is 3rd sentence!
document.write(getSentenceByPos(53, "This is the first sentence. And this is the second! Finally, this is the third sentence"));
I wanted to add an answer that doesn't use regular expression to split up the
string because doing so is quite inefficient and would likely be very slow on
larger chunks of text.
The most efficient way to do it would probably be to use a couple of loops to search, requiring only 2 passes to find the ends of the sentence.
var sentenceFromPos = function (s, pos) {
var len = s.length,
start,
end,
char;
start = pos;
end = pos;
while (start >= 0) {
char = s.charAt(start);
if (char === '.' || char === '?' || char === '!') {
break;
}
start -= 1;
}
while (end < len) {
char = s.charAt(end);
if (char === '.' || char === '?' || char === '!') {
break;
}
end += 1;
}
return s.substring(start + 1, end + 1).trim();
};
var phrase = 'This is the first sentence. And this is the second! Finally, this is the third sentence';
console.log(sentenceFromPos(phrase, 10));
console.log(sentenceFromPos(phrase, 33));
console.log(sentenceFromPos(phrase, 53));
This function will respect cursors over the limits of the phrases (like ! or .)
function getPhrase(string, cursor) {
phrases = string.match(/.*?(!|\.|$)/g)
basecursor = 0;
phrase = phrases[0]
for(ii=0; ii<phrases.length-1; ii++) {
if (basecursor+phrases[ii].length<cursor) {
phrase = phrases[ii+1]
basecursor += phrases[ii].length
}
}
return(phrase)
}
string = "This is the first sentence. And this is the second! Finally, this is the third sentence"
cursor = 0
phrase = getPhrase(string, cursor)
document.write(phrase)

Categories

Resources