I am attempting to solve a generic Palindrome problem recursively. However, it seems that my algorithm is only evaluating the first recursive call, not the second, which should check all characters in the string. There is apparently a logic error in my algorithm, but I can't spot it. Can anyone advise? See the code below.
function isPalindrome(totalChars: number, lastIdx: number, str: string): boolean | undefined {
console.log(`lastIdx: ${lastIdx}; char: ${str[lastIdx]}`);
// const curIdx = lastIdx;
let highIdx = lastIdx;
const lowIdx = totalChars-1 - highIdx;
// Base Case:
if(totalChars === 0) return true;
if (lowIdx === highIdx) return true;
if (lowIdx > highIdx) {
console.log(`Endpoint reached; STR: ${str}; LOW: ${str[lowIdx]}; high: ${str[highIdx]}`);
return;
}
if(str[lowIdx] === str[highIdx]) {
console.log(`Loop through idx; STR: ${str}; LOW: ${str[lowIdx]}; high: ${str[highIdx]}`);
return true;
}
else if(str[lowIdx] !== str[highIdx]) return false;
// Recursive Case:
return isPalindrome(totalChars, highIdx, str) && isPalindrome(totalChars, highIdx-1, str);
}
// console.log("a is Palindrome: " + isPalindrome("a".length, "a".length-1, "a"));
// console.log("motor is Palindrome: " + isPalindrome("motor".length, "motor".length-1,"motor"));
console.log("rotor is Palindrome: " + isPalindrome("rotor".length, "rotor".length-1,"rotor"));
There are a few problems:
your if...else will always result in a return, and so the statement with the recursive call will never be executed.
Note that the condition after else if will always be true when it gets evaluated, since it is the negation of the condition that is evaluated in the earlier if statement.
More importantly, when that earlier if condition is true, you don't want to return, as it has not been verified yet that the remaining (inner) characters match. This still has to be verified via the recursive call, so this is not a place to perform a return. Just remove that if block, and only return when the characters differ.
So replace this:
if(str[lowIdx] === str[highIdx])
{
return true;
}
else if(str[lowIdx] !== str[highIdx]) return false;
With just:
if(str[lowIdx] !== str[highIdx]) return false;
The first recursive call passes the same arguments as the current execution of the function got -- this will lead to infinite recursion. A recursive call must always make the problem smaller. In this case, there is actually no need to make two recursive calls, and you should remove that first one.
So replace this:
return isPalindrome(totalChars, highIdx, str) && isPalindrome(totalChars, highIdx-1, str);
with:
return isPalindrome(totalChars, highIdx-1, str);
The base case has a condition where return is executed without boolean return value. The function should always return a boolean value. In this case it should be true, because it means that all character-pairs were compared, and there is no single-middle character remaining (the size of the string is even). So you can combine this case with the previous base case. In fact, that base case condition will also work when totalChars is zero, so you can omit that first if.
So change this:
if (totalChars === 0) return true;
if (lowIdx === highIdx) return true;
if (lowIdx > highIdx) {
return;
}
with:
if (lowIdx >= highIdx) return true;
Related
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)
I understand the 3rd Conditional but not 2nd one
On the 2nd Conditional - if the length of "str" is 2 (meaning it has 2 characters) then return "str[0] === str[1]" but what if those last two characters are different "c" "g" maybe?
how comparison is being executed in return str[0] === str[1] ? does the comparison have to be inside if() statement because if() statement returns true ?
However, this line return str[0] === str[1] being outside the scope of if() statement return true or false
function isPalindrome(str) {
// 1st Conditional
if (str.length === 1) return true
// 2nd Conditional
else if (str.length===2) return str[0]===str[1]
// 3rd Conditional
else if (str[0] === str.slice(-1)) {
return isPalindrome(str.slice(1,-1))
}
return false
}
return "str[0] === str[1]", but what if those last two characters are different "c" "g" maybe?
This statement will return a boolean value. That boolean value is determined by str[0] === str[1]. This is a comparison that is either false or true. In the example "cg", that comparison will evaluate to false, and so the return statement will make the function return with the value false.
how comparison is being executed in return str[0] === str[1]?
It is executed like in any other context. The expression is str[0] === str[1], and the result of that evaluation is returned by the return statement.
does the comparison have to be inside if() statement because if() statement returns true ?
The return should only be executed when the preceding if statement is true, i.e. that return should only be executed when the length of the string is true.
However, this line return str[0] === str[1] being outside the scope of if() statement return true or false
That statement is not outside the scope of the if statement. It is a statement that is only executed when the if condition is true.
If it helps, we could rewrite that if (str.length===2) return str[0]===str[1] as follows:
if (str.length===2) {
let isPalindromeOfTwo = str[0]===str[1];
return isPalindromeOfTwo;
}
Or even (but this is an antipattern):
if (str.length===2) {
if (str[0]===str[1]) {
return true; // It is a palindrome of 2
} else {
return false; // Not a palindrome
}
}
Just realise that a comparison represents a value: a boolean value. This value can be used in any context that requires a value. You can for instance do any of these:
console.log(str[0]===str[1]); // output will be "false" or "true"
let x = str[0]===str[1]; // x will be a boolean value: false or true
let y = Number(str[0]===str[1]); // y will be a number, 0 or 1
So I am suppose to create a function that reverses a values boolean parameter. [Here][1] is what I came up with:
I really just want to understand what is my flaw in thinking. How can I fix my approach?
Please have a look to your code:
function not(x) {
if (1 == true) {
return "true";
} else if (0 == false) {
return "false";
}
}
Whats right:
function not(x) {
}
Whats goes wrong:
if (1 == true) {
// ^ ^^ ^^^^
// | | boolean
// | equal (not type safe)
// number
You compare constant values. In this case a number 1 with a boolean true. The comparison operator is equality == and this "converts the operands if they are not of the same type". That means that one is equal true and the next part is evaluated and
return "true";
ever, because there are no variables involved. The rest of the else part is never reached, as well as the next comparison, which is never reached.
As well as 'true' is always returned, it is not the type you want, because you need a boolean and return a string.
What should change:
if (x == true) {
// ^
// the parameter of the function
return true;
// ^
// boolean
} else {
// ^
// no other if
return false;
// ^
// boolean
}
or a short version of all with the logical NOT operator !:
function not(x) {
return !x;
}
I assume you want to write the function for learning purpose, because you do not need to write it in practice. Javascript already has ! (not) operator.
For your function and implementation would be:
function not(x) {
if(x === true) return false;
else return true;
}
But as I said you can just use ! operator.
I want to coerce strings to primitives whenever possible, in a way that is safe to pass any value. I'm looking for a more "native" way of doing it, instead of trying to cover all possible cases.
value("0") //0
value("1") //1
value("-1") //-1
value("3.14") //3.14
value("0x2") //2
value("1e+99") //1e+99
value("true") //true
value("false") //false
value("null") //null
value("NaN") //NaN
value("undefined") //undefined
value("Infinity") //Infinity
value("-Infinity") //-Infinity
value("") //""
value(" ") //" "
value("foo") //"foo"
value("1 pizza") //"1 pizza"
value([]) //[]
value({}) //{}
value(0) //0
value(1) //1
value(-1) //-1
value(3.14) //3.14
value(0x2) //2
value(1e+99) //1e+99
you get the idea
function value(x){
if(typeof x==="string"){
if(x=="") return x;
if(!isNaN(x)) return Number(x);
if(x=="true") return true;
if(x=="false") return false;
if(x=="null") return null;
if(x=="undefined") return undefined;
}
return x;
}
The major problem is that because isNaN() return "is a Number" for things like
"" empty strings
" " blank strings
[] arrays
etc
Edit
Based on the accepted answer:
function value(x) {
if (typeof x === "string") {
switch (x) {
case "true": return true;
case "false": return false;
case "null": return null;
case "undefined": return void 0;
}
if (!isNaN(x) && !isNaN(parseFloat(x))) return +x;
}
return x;
}
The problem with your code is that isNaN alone can't be used to detect numeric strings properly.
For example, since +" " === 0, then isNaN(" ") === false.
Instead, I suggest using the 2nd isNumeric function of this list of testcases, taken from Validate decimal numbers in JavaScript.
function isNumeric(n) {
return !isNaN(n) && !isNaN(parseFloat(n));
}
function value(x){
if(typeof x !== "string") return x;
switch(x) {
case "true": return true;
case "false": return false;
case "null": return null;
case "undefined": return void 0;
}
if(isNumeric(x)) return +x;
return x;
}
I'll just go ahead and say what I think we're all saying in comments: There's no native way to do this that does exactly what you've outlined.
You've talked about covering all the bases, but there really aren't that many bases to cover, they're listed in the specification, and you seem to have done most of the work. Just be sure that whatever utility function you have that does this has an adequate test suite, and if you find cases you haven't covered (or new primitives are added to the spec, which is really unlikely), update the function and its tests accordingly.
I think that it is obvious what my code does.
Why does my code return a whole string if I use the !== operator? I know that arrays in Javascript start from at index 0, and here I'm entering the whole filename as the argument, so indexOf(".") will always be greater then 0. No, I'm not passing an .htaccess file here.
function getFileExtension(i) {
// return the file extension (with no period) if it has one, otherwise false
if(i.indexOf(".") !== 0) { //
return i.slice(i.indexOf(".") + 1, i.length);
} else {
return false;
}
}
// here we go! Given a filename in a string (like 'test.jpg'),
getFileExtension('pictureofmepdf'); return given string
// both operand are same type and value
But if I change comparasion to
(i.indexOf(".") > 0) // logs false
P.S. I case that you are asking, this is form usvsth3m.
indexOf() returns the index of the substring, so it can return 0, which would mean that the substring appears at position 0. If the substring is not found, it returns -1 instead, so change your if statement to reflect this logic:
if(i.indexOf(".") >= 0)
Additionally, you should use substring() to extract a substring from a string - slice() is for arrays.
return i.substring(i.indexOf(".") + 1, i.length);
Still, I think a better way to do this is with split():
var fileNameArray = i.split("."); // "foo.txt" --> ["foo" "txt"]
if(fileNameArray.length >= 2) {
return fileNameArray[1];
} else {
return false; //maybe you want to return "" instead?
}
The String method indexOf returns (if it is founded) the first index of the string you search, and remember, index can be zero, that why you have to do a strict comparision to check if indexOf it is not returning a boolean false.
I will suggest you to use lastIndexOf for this case because a file named as something.min.js will return min.js as an valid extension, and nope.
Well, to simplify, I omit that indexOf returns index which is typeof number, or -1, not returning boolean value FALSE in case when given value not found. So in case of comparing -1 to 0, result is true, and that's why I actually geting outputed given string, instead false. Well, MDN is in my bookmark bar now
var z = -1;
console.log(z >= 0); // evaluates false because -1 < 0
console.log(z !== 0); // evaluates true because -1 !== 0
// also if z > 0 it is !== 0, but could be < 0
So next code works like a charm.
function getFileExtension(i) {
// i will be a string, but it may not have a file extension.
// return the file extension (with no period) if it has one, otherwise false
if(i.indexOf(".") >= 0) {
return i.substring(i.indexOf(".") + 1, i.length);
} else {
return false;
}
}
getFileExtension('pictureofmepdf');