I recently had an interview where you had to recursively go over a string, and if it contained an AB || BA || CD || DC, it had to be deleted from the array. You would recursively go over this as deleting the CD from ACDBB would give you an AB which you would then have to delete to return a B as a string.
This is what I have, and when I test it out, I see it comes up with the right answer deep in the loops, but it never populates back to the top.
What am I missing?
const LETTERS = [/AB/g, /BA/g, /CD/g, /DC/g];
const stringGame = (string) => {
let newString = '';
if(string.length <= 1) return string;
LETTERS.forEach(regExToCheck => {
if(string.match(regExToCheck)) {
newString = string.replace(regExToCheck, '')
}
stringGame(newString);
})
return newString
}
// Expect answer: CAACC
console.log(stringGame('ABDCABCABAAABCCCD'))
Move the recursion,
return the recursion, and
Add an essential condition to end recursion:
const LETTERS = [/AB/g, /BA/g, /CD/g, /DC/g];
const stringGame = (string) => {
let newString = '';
if (string.length <= 1) return string;
LETTERS.forEach(regExToCheck => {
if (string.match(regExToCheck)) {
newString = string.replace(regExToCheck, '')
}
})
// Moved, returned, and ended recursion
return ('' === newString) ? string : stringGame(newString);
}
// Expect answer: CAACC
console.log(stringGame('ABDCABCABAAABCCCD'))
When recursing the function, i.e., when executing the function within itself, the return'ed value of that execution needs to be used, typically return'ed:
return stringGame(newString);
...in order for it to bubble back up the levels of recursion.
Also, since the replace() function is using a regex global replacement flag (g) all, for example, AB's, are being replaced in a single execution, so there's no need to recurse within the forEach() loop:
LETTERS.forEach(regExToCheck => {
if(string.match(regExToCheck)) {
newString = string.replace(regExToCheck, '')
}
stringGame(newString); // <-- double oops
})
Rather, recurse after the loop, AND return the value of the execution:
LETTERS.forEach(regExToCheck => {
if(string.match(regExToCheck)) {
newString = string.replace(regExToCheck, '')
}
})
return stringGame(newString);
One more principle of recursive functions is providing exit strategies—define what conditions to end the recursion, bubble back up, and return a final result.
The only exit condition provided is:
if (string.length <= 1) return string;
Surely if there's only one character left no match will be found—a proper time to end recursion. However, and more importantly, there also needs to be a path to exit when there are more than one characters but no matches can be found.
There are multiple ways to determine this but in the case provided in the question the most expedient is when after looping through the regex/replace routine no changes were made to the string. Since with every recursion the stringGame() function instantiates an empty string (newString) if, after looping through the regex/replace routine, newString is still an empty string, this indicates there were no matches found and it's time to end recursion:
if ( '' === newString ) {
return string;
}
else {
return stringGame(newString);
}
...otherwise, recurse.
The stringGame function doesn't have side effects, so the line in the loop here:
stringGame(newString);
doesn't do anything - you need to communicate the result of the recursive call back to the outer level.
A nicer way to approach this would be to combine the LETTERS into a single regular expression, and then to replace all matches with the empty string until the replacement produces no changes.
const LETTERS = [/AB/g, /BA/g, /CD/g, /DC/g];
const pattern = new RegExp(
LETTERS
.map(re => {
const str = String(re);
return str.slice(1, str.length - 2);
})
.join('|'),
);
const stringGame = (string) => {
const newString = string.replace(pattern, '');
return newString === string
? string
: stringGame(newString);
}
// Expect answer: CAACC
console.log(stringGame('ABDCABCABAAABCCCD'))
(If you want to use the global flag in the constructed pattern, you'll have to account for the .lastIndex)
Related
This is more of a 'why does this happen' question than a trouble shooting question.
I am trying to create a function that tests whether a word is a palindrome. I thought initially that I can use an if statement it compare the word and when it is reversed. However the if statement gave me undefined. When I compared it regularly (word === reverseWord), it came back with a truth or false. My question is what is going on in the if statement for it do wonk out? Thanks!
const palindromes = function(word) {
*//Turn word into an array*
var arrWord = Array.from(word);
*//Reverse the array*
var reversedWord = arrWord.reverse();
*//This is true*
return arrWord === reversedWord;
//But this is undefined?
if (arrWord === reversedWord) {true} else {false};
}
Its returning undefined because you are not using return keyword anywhere.
Another mistake you are making is that you are comparing two arrays which reference same object in memory it arrWord === reversedWord will always return true.
Convert word to array using split('') and reverse() it. And then join(''). And finally compare reversed string and word
const palindromes = function(word) {
var reversedWord = word.split('').reverse().join('');
console.log(word === reversedWord);
if (word === reversedWord) {
return true
} else {
return false
};
}
console.log(palindromes("aba"))
console.log(palindromes("ba"))
The code can be reduced to a single line.
const palindromes = (word) => word.split('').reverse().join('') === word
console.log(palindromes("aba"))
console.log(palindromes("ba"))
I have sorted out two string and than used json.stringify to compare it to get Anagram. please refer below code, is this a right way to code.
function same(ar, ar1) {
//sorting the string
var o = ar.split("").sort();
var o1 = ar1.split("").sort();
//comparing two string
if (JSON.stringify(o) == JSON.stringify(o1)) {
return true;
} else {
return false;
}
}
same("ria", "air"); //true
same("", ""); //true
same("aaz", zza); //false
It'll work in most cases, but it's unnecessarily computationally expensive.
It may not work when certain non-ASCII characters are used, because .split('') will result in splitting up the character code points, eg:
console.log('𝟘'.split(''));
That's not an accurate representation of each character as an element of the array. Use Array.from instead:
console.log(Array.from('𝟘'));
After that, you can also make the algorithm less expensive by counting up the number of occurrences of each character (O(n)) rather than sorting (O(n log n)). For example:
const getCount = str => Array.from(str).reduce((counts, char) => {
counts[char] = (counts[char] || 0) + 1;
return counts;
}, {});
function same(ar,ar1){
const counts1 = getCount(ar);
const counts2 = getCount(ar1);
const keys1 = Object.keys(counts1);
const keys2 = Object.keys(counts2);
if (keys1.length !== keys2.length) {
return false;
}
return keys1.every(char => counts1[char] === counts2[char]);
}
console.log(same('abc', 'cba'));
console.log(same('abc', 'aba'));
I am new to JavaScript. I have created a indexof function in but it is not giving the correct output:
Question is:
/*
Implement a function called indexOf that accepts two parameters: a string and a character, and returns the first index of character in the string.
*/
This is my code:
function indexOf(string, character) {
let result = string;
let i = 0;
let output = 1;
while (i < result.length) {
if (result[i] === character) {
output = output + indexOf[i];
}
}
return output;
}
I want to know what i am doing wrong. Please Help.
You are making things a little harder than you need to. If you want to do this without calling the built-in indexOf(), which I assume is the point of the exercise, you just need to return from the function as soon as your condition matches. The instructions say "return the first index" — that's the i in your loop.
If you make it through the loop without finding something it's traditional to return -1:
function indexOf(string, character) {
let i=0;
while(i < string.length){
if(string[i] == character){ // yes? just return the index i
return i
}
i++ // no? increase i and move on to next loop iteration
}
return -1; // made it through the loop and without returning. This means no match was found.
}
console.log(indexOf("Mark Was Here", "M"))
console.log(indexOf("Mark Was Here", "W"))
console.log(indexOf("Mark Was Here", "X"))
Assuming from your question that the exercise is to only match the first occurrence of a character and not a substring (multiple characters in a row), then the most direct way to do it is the following:
const indexOf = (word, character) => {
for (let i = 0; i < word.length; i++) {
if (word[i] === character) {
return i;
}
}
return -1;
}
If you also need to match substrings, leave a comment on this answer if you can't figure it out and I'll help you along.
indexOf() is a built in method for strings that tells you the index of a particular character in a word. Note that this will always return the index of the FIRST matching character.-
You can write something like:
function indexOf(string, character){
return string.indexOf(character)
}
So if I were to use my function and pass in the two required arguments:
indexOf("woof", "o") //this would return 1
I try to check if a word (wordToCheck) only consists of letters from an array (letters) and also contains every letter in the array only as often (or rather not more times than they are in the array) as it actually is inside of the array.
Here are examples of what the desired function should return:
checkIfWordContainsLetters("google", ["a","o","o","g","g","l","e","x"]) === true
checkIfWordContainsLetters("google", ["a","o","g","g","l","e","x"]) === false
How can I make this code work?
function checkIfWordContainsLetters(wordToCheck, letters) {
var lettersToString = letters.toString();
var lettersTrimmed = lettersToString.replace(/,/gi, "?");
var regEx = new RegExp(lettersTrimmed, "gi");
if (wordToCheck.match(regEx)!== null) {
return true;
}
else return false;
}
You could use this ES6 function:
function checkIfWordContainsLetters(wordToCheck, letters){
return !letters.reduce((a, b) => a.replace(b,''), wordToCheck.toLowerCase()).length;
}
console.log(checkIfWordContainsLetters("google", ["a","o","o","g","g","l","e","x"]));
console.log(checkIfWordContainsLetters("google", ["a","o","g","g","l","e","x"]));
The idea is to go through each letter in the letters array, and remove one (not more!) occurrence of it in the given wordToCheck argument (well, not exactly in it, but taking a copy that lacks that one character). If after making these removals there are still characters left over, the return value is false -- true otherwise.
Of course, if you use Internet Explorer, you won't have the necessary ES6 support. This is the ES5-compatible code:
function checkIfWordContainsLetters(wordToCheck, letters){
return !letters.reduce(function (a, b) {
return a.replace(b, '');
}, wordToCheck.toLowerCase()).length;
}
console.log(checkIfWordContainsLetters("google", ["a","o","o","g","g","l","e","x"]));
console.log(checkIfWordContainsLetters("google", ["a","o","g","g","l","e","x"]));
As long as it is not the best solution for long strings for which using some clever regex is definitely better, it works for short ones without whitespaces.
function checkIfWordContainsLetters(word, letters){
word = word.toLowerCase().split('');
for(var i = 0; i < letters.length; i++) {
var index = word.indexOf( letters[i].toLowerCase() );
if( index !== -1 ) {
// if word contains that letter, remove it
word.splice( index , 1 );
// if words length is 0, return true
if( !word.length ) return true;
}
}
return false;
}
checkIfWordContainsLetters("google", ["a","o","o","g","g","l","e","x"]); // returns true
checkIfWordContainsLetters("google", ["a","o","g","g","l","e","x"]); // returns false
I have a function that returns true if a character is a form of punctuation and I'm trying to write another function that accepts a string and removes the spaces and punctuation marks while calling the first function. I got most of it I think. But now I'm stuck. Any help is appreciated.
var isPunct = function(ch) {
if (ch = ch.match(/[,.!?;:'-]/g))
return true
else
return false
}
//B
var compress = function(s) {
var result = "";
//loop to traverse s
for (var i = 0; i < s.length; i++) {
if (!(isPunct(ch));
//(isPunct(s.charAt(i) || s.charAt(i) == " "));
//do nothing
else
result = result + !compress(i)
}
return result
}
Some issues:
The inner condition should in fact be the opposite: you want to do nothing when it is a punctuation character, i.e. you don't want to add it to the result. Only in the other case you want to do that.
The call !compress(i) is wrong: first of all that function expects a string, not an index, and it returns a string, not a boolean (so to perform ! on it). It seems like you want to call your function recursively, and although that is an option, you are also iterating over the string. You should do one of the two: recursion or iteration.
You reference a variable ch in the compress function which you have not defined there.
So, if you want to write compress the iteration way, change your code as follows:
var compress = function(s) {
var result = "", ch; // define ch.
//loop to traverse s
for (var i = 0; i < s.length; i++) {
ch = s[i]; // initialise ch.
if (!isPunct(ch)) result = result + ch; // only add when not punctuation
}
return result;
}
If on the other hand you want to keep your recursive call to compress, then you should do away with your for loop:
var compress = function(s) {
var result = "", ch, rest;
if (s.length == 0) return '';
result = compress(s.substr(1)); // recursive call
ch = s[0];
if (!isPunct(ch)) result = ch + result;
return result;
}
The function isPunct also has a strange thing happening: it assigns a boolean value to ch in the if expression. This does not make your function malfunction, but that assignment serves no purpose: the match method already returns the boolean you need for your if condition.
It is also not really nice-looking to first evaluate a boolean expression in an if to then return that same value in the form of false and true. This you can do by just returning the evaluated expression itself:
var isPunct = function(ch) {
return ch.match(/[,.!?;:'-]/g);
}
On a final note, you don't really need the isPunct function if you only use it in compress. The whole logic can be performed in one function only, like this:
let compress = s => s.replace(/[,.!?;:'-]/g,'');
// Demo:
console.log(compress('a,b,c')); // abc
If you prefer to keep isPunct and don't want to repeat the regular expression elsewhere, then you can do the replace like this:
let isPunct = ch => ch.match(/[,.!?;:'-]/g);
let compress = s => Array.from(s).filter(ch => !isPunct(ch)).join('');
// Demo:
console.log(compress('a,b,c')); // abc
Note how the use of ES6 arrow functions and ES5 Array methods makes the code quite lean.