2

Writing a recursion algorithm to get anagrams of a string. Expected output for string abc is ['abc', 'acb', 'bac', 'bca', 'cab', 'cba']

However, what I get is [ 'abc', 'acb', 'ba', 'cab', 'cba' ].

I know where the issue is: When the recursion goes back to level 0 (meaning str==='abc' and i==1), substring works (exracts 'a'), but slice does not(does not extract c).

function anagramPermutationMemo(str, memo, resultsArr, level) {
    if (!memo) {
        memo = '';
        resultsArr = [];
        level = 0;
    }
    console.log('str -', str);
    console.log('memo -', memo, '\n');

    if (str.length === 0) {
        resultsArr.push(memo);
        return;
    }
    for (var i in str) {
        console.log('level', level);
        console.log('str', str);
        console.log('i -', i, str[i]);
        console.log('substring', str.substring(0, i));
        console.log('slice', str.slice(i + 1));
        var newStr = str.substring(0, i).concat(str.slice(i + 1));
        console.log('newStr - ', newStr, '\n');
        anagramPermutationMemo(newStr, memo + str[i], resultsArr, level + 1);
    }
    level -= 1;
    console.log('backtrack', level,'\n');

    return resultsArr;
};
Monasha
  • 711
  • 2
  • 16
  • 27
barakbd
  • 988
  • 10
  • 16
  • See [Permutations without recursive function call](http://stackoverflow.com/questions/34013675/permutations-without-recursive-function-call) – guest271314 Nov 09 '16 at 04:54
  • Also see http://stackoverflow.com/questions/24387939/generating-all-possible-combinations-of-strings – Derek Nov 09 '16 at 05:32
  • 1
    [Don't use `for…in` enumerations on strings!](https://stackoverflow.com/q/500504/1048572) – Bergi Nov 09 '16 at 23:34
  • @Bergi The extra properties logged at `console` at SO was first indication, here, that `for..in` loop was logging `enumerable` properties. – guest271314 Nov 10 '16 at 00:06

2 Answers2

1

It would help if you'd also log the value of i+1 (which you're passing to slice). The problem with your for…in enumeration is that it enumerates properties, which are strings. Your i will be "0", "1" and "2" (and possibly other things if String.prototype was extended with enumerable methods).

When you add 1 to that, it's doing string concatenation, and you'll end up with "01", "11" and "21" instead of the desired numbers. Given that slice casts its arguments to numbers, "01" actually works as desired, but the others will lead to indices after the end of the string, so you end up with omitting those parts entirely:

anagramPermutationMemo("abc".substring(0, "1")+"abc".slice("11"), ""+"abc"["1"], […], 0+1);
//                                                         ^^^^ should be 2
anagramPermutationMemo("a",                                       "b",           […], 1);
//                      ^ should be "ac"

To fix the issue, just use a standard for (var i=0; i<str.length; i++) loop.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

I know where the issue is

The issue is for..in loop.

Explanation:

variable of for..in loop is a String, not a Number. Within bracket notation

str[i]

expected result is returned, as property of object is a string, e.g., str["0"]; Number can also be used to get property of object within bracket notation str[0].

String.prototype.slice() calls ToInteger on parameters, though does not return expected result without casting string to number using sign

ToInteger

The abstract operation ToInteger converts its argument to an integral numeric value. This abstract operation functions as follows:

  1. Let number be the result of calling ToNumber on the input argument.
  2. If number is NaN, return +0.
  3. If number is +0, −0, +∞, or −∞, return number.
  4. Return the result of computing sign(number) × floor(abs(number)).

String.prototype.substring() expects Number as parameter.

If either argument is NaN or negative, it is replaced with zero; if either argument is larger than the length of the String, it is replaced with the length of the String.


Solution, substitute for loop for for..in loop

function anagramPermutationMemo(str, memo, resultsArr, level) {
  if (!memo) {
    memo = '';
    resultsArr = [];
    level = 0;
  }
  console.log('str -', str);
  console.log('memo -', memo, '\n');

  if (str.length === 0) {
    resultsArr.push(memo);
    return;
  }
  for (var i = 0; i < str.length; i++) {
    console.log('level', level);
    console.log('str', str);
    console.log('i -', i, str[i]);
    console.log('substring', str.substring(0, i));
    console.log('slice', str.slice(i + 1));
    var newStr = str.substring(0, i).concat(str.slice(i + 1));
    console.log('newStr - ', newStr, '\n');
    anagramPermutationMemo(newStr, memo + str[i], resultsArr, level + 1);
  }
  level -= 1;
  console.log('backtrack', level, '\n');

  return resultsArr;
};

var p = anagramPermutationMemo("abc");

console.log(p);

Solution:

Cast i parameter to Number using + operator at calls to .substring(), .slice() when using for..in loop, where parameters of methods are expecting Number.

function anagramPermutationMemo(str, memo, resultsArr, level) {
  if (!memo) {
    memo = '';
    resultsArr = [];
    level = 0;
  }

  if (str.length === 0) {

    resultsArr.push(memo);
    return;
  }
  for (var i in str) {
    
    var newStr = str.substring(0, +i).concat(str.slice(+i + 1));
    var _newStr = str.substring(0, i).concat(str.slice(i + 1));
    
    console.log(`newStr with string as parameter:${_newStr}`);
    console.log(`newStr with number as parameter:${newStr}`);
    console.log(`i:${str.substring(0, i)}, ${str.slice(i + 1)}`);
    console.log(`+i:${str.substring(0, +i)}, ${str.slice(+i + 1)}`)

    anagramPermutationMemo(newStr, memo + str[i], resultsArr, level + 1);

  }
  level -= 1;

  return resultsArr;
};

Object.defineProperty(String, "abc", {
  value:123,
  enumerable:true
})

var p = anagramPermutationMemo("abc");

console.log(p);
guest271314
  • 1
  • 15
  • 104
  • 177
  • Thanks! Though I still don't understand why it was causing an issue. for ... in still returns a i as an index ( I console.log it), then what is the problem? – barakbd Nov 09 '16 at 20:12
  • @user3250799 See [`for..in`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in) at MDN _"The `for...in` statement iterates over the enumerable properties of an object, in arbitrary order. For each distinct property, statements can be executed."_ , _"The loop will iterate over all enumerable properties of the object itself and those the object inherits from its constructor's prototype (properties closer to the object in the prototype chain override prototypes' properties)."_ – guest271314 Nov 09 '16 at 20:30
  • Casting `i` back to a number is not a solution. Avoiding the `for in` loop is the only solution, not just an "alternative". – Bergi Nov 09 '16 at 23:44
  • @Bergi Ok. Took a while for this novice to find the difference, here. Should `for..in` approach be removed from Answer? Why is `a`, `a,c` apparently the only issue? Or, am not seeing everything clearly? Tried at `console` here at SO and properties of `String.prototype` were logged? What is really occurring? Can you share illumination on the issue? – guest271314 Nov 09 '16 at 23:48
  • 1
    @guest271314 Yes, I think you should remove the for…in approach. I'll answer the rest in my own answer :-) – Bergi Nov 09 '16 at 23:53