Algorithm practice: The ROT13 translator

23 Sep 2019 - John


For today's excercise, we're going to build a ROT13 translator. The name stands for ROTate 13, which means every character of a string will be shifted 13 characters ahead. For example, a becomes n, b becomes o, c becomes p and so on.

Image credit: duolingo.com

First things first: divide and conquer

As always, we need to be able to articulate what we need to do in plain English before attempting to write any code:

  • We need to go through every character regardless of its casing.
  • Then we need to grab its position from the UTF-16 code table and add 13 to it.
  • Then we replace the letter.

The naive approach

We're going to initialize an empty function and a few variables:

const testString = 'The quikck brown fox jumps over the lazy dog.';
function charTrans(char) {
  let character = '';
  let charPosition = char.charCodeAt(0) + 13;
}

Our first function is going to be a helper function that we will use later on. It takes a single character and initializes two variables: the first one, character, we will use to store the character position in the UTF-16 table. The second one is the one we will use to shift the character up by 13 positions.
Now we're going to add a few conditions:

if (char <= 'Z') {
  character = 90;
} else {
  character = 122;
}
if (char == ' ') {
  characher = 32;
}

The first if/else block determines the position of the character Z depending on its casing. You can check this in your dev tools console by typing String.fromCharCode(90) or String.fromCharCode(122). Then if the character is a space, we assign it its corresponding value of 32.
Then we add another check:

if (character >= charPosition) {
  return String.fromCharCode(charPosition);
} else {
  return String.fromCharCode(charPosition - 26);
}

If the character is greater than or equals to charposition, we return the string equivalent of the character's position. If not, it means we exceeded the allowed position, so we need to go back 26 steps.
The final function so far:

const testString = 'The quikck brown fox jumps over the lazy dog.';
function charTrans(char) {
  let character = '';
  let charPosition = char.charCodeAt(0) + 13;
  if (char <= 'Z') {
    character = 90;
  } else {
    character = 122;
  }
  if (char == ' ') {
    characher = 32;
  }
  if (character >= charPosition) {
    return String.fromCharCode(charPosition);
  } else {
    return String.fromCharCode(charPosition - 26);
  }
}

That's it for our helper function. Now let's build the one that will make the call. In my previous post, we explored regex, so let's use our newly acquired powers to build our function:

const rot13Translator = (str) => {
  return str.replace(/[a-z]/gi, (c) => { return charTrans(c) })
}

And that's it. We're running String.replace() on our test string with the regex rule "match all letters regardless of its case", and then we're running our helper function as the second argument. Every time String.replace() looks into a character to replace, it will run our helper function and use the result as the value to replace the original character.

A more elegant approach

Our function works perfectly and we have no errors, but is there a way to make it a bit more elegant? Yes, there is!

const rot13Translator = (string) => {
return string.replace(/[a-z]/gi, (c) => { return String.fromCharCode((c <= 'Z' ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26) });
}

While we lose a bit of readability due to our font settings, this one-liner is a perfect translation of our original code. We're taking full advantage of the ternary operator to shorten our if statements in order to be able to accomplish the same with less code.

So that's all I have for today, thanks for reading and be sure to share if you liked!