2

So I'm making a function to decipher rot13 or a variant of caesar's cipher. But for some reason it only deciphers every second letter... I have no clue what to change or do. Thanks

function rot13(str) {
  let regexp = /\w/gi;
  let obj = {
    "A": "N",
    "B": "O",
    "C": "P",
    "D": "Q",
    "E": "R",
    "F": "S",
    "G": "T",
    "H": "U",
    "I": "V",
    "J": "W",
    "K": "X",
    "L": "Y",
    "M": "Z",
    "N": "A",
    "O": "B",
    "P": "C",
    "Q": "D",
    "R": "E",
    "S": "F",
    "T": "G",
    "U": "H",
    "V": "I",
    "W": "J",
    "X": "K",
    "Y": "L",
    "Y": "M",
  };
  for (let i = 0; i < str.length; i++) {
    if (regexp.test(str[i])) {
      str = str.replace(str[i], obj[str[i]]);  
    }
  };
  return str;
}
console.log(rot13("SERR"));

//output: FEER

//wanted output: FREE
Api.
  • 25
  • 4
  • It's not every second letter - try `console.log(rot13("AA"));`, `console.log(rot13("AAA"));`, `console.log(rot13("AAAA"));`, etc. I suspect something's going wrong because you're modifying the string while looping. – IMSoP Apr 05 '22 at 16:01

2 Answers2

1

You have two issues:

  • When using the g flag on a regex, the regex becomes stateful, and the .test method keeps track of where in the string it is searching. From MDN:

As with exec() (or in combination with it), test() called multiple times on the same global regular expression instance will advance past the previous match.

This means that when you use the global flag, once the regex has matched one character, it will not match another until it has been reset:

const rgx = /\w/g;

console.log("Different results!:", rgx.test("a"), rgx.test("a"))

This is what is responsible for the "every other character" replacement that you see, since the if condition in your loop will only execute on every other pass.

To fix that, just avoid using the g flag on the regex unless you need stateful behavior.

Second issue:

  • When using the .replace method with a string as the first argument, only the first instance of that string will be replaced.

This means if the character you are replacing appears earlier, it will be that earlier character that is swapped, rather than the one at your current index.

console.log("Only the first is replaced!:", "aaaa".replace("a", "b"))

One way to adjust for that is instead of using .replace to try and swap the character, rebuild the string using .slice while changing out the encoded character for its counterpart. This method ensures the character that is changed is the one at your current index i:

str = str.slice(0, i) + obj[str[i]] + str.slice(i+1)

Putting that all together, you could modify your snippet like so:

function rot13(str) {
  let regexp = /\w/i; // No global match!
  let obj = {
    "A": "N",
    "B": "O",
    "C": "P",
    "D": "Q",
    "E": "R",
    "F": "S",
    "G": "T",
    "H": "U",
    "I": "V",
    "J": "W",
    "K": "X",
    "L": "Y",
    "M": "Z",
    "N": "A",
    "O": "B",
    "P": "C",
    "Q": "D",
    "R": "E",
    "S": "F",
    "T": "G",
    "U": "H",
    "V": "I",
    "W": "J",
    "X": "K",
    "Y": "L",
    "Z": "M",
  };
  for (let i = 0; i < str.length; i++) {
    if (regexp.test(str[i])) {
      str = str.slice(0, i) + obj[str[i]] + str.slice(i+1)
    }
  };
  return str;
}

console.log(rot13("SERR"));
//output: FREE
CRice
  • 29,968
  • 4
  • 57
  • 70
-1

This seems to work fine

const rot13 = (str) => {
  const base = 'A'.charCodeAt()
  
  return str
  .split('')
  .map(c => String.fromCharCode(((c.charCodeAt() - base + 13) % 26) + base)).join('')


}

console.log(rot13('SERR'))
R4ncid
  • 6,944
  • 1
  • 4
  • 18