Bonfire: Caesar’s Cipher – Best Solution

Caesar's Cipher Challenge - Free Code Camp

The Caesar’s Cipher was a fun little challenge. It’s cool to code some code 🙂

The goal of this challenge: Write a function which takes a ROT13 encoded string as input and returns a decoded string.

The suggested methods are String.charCodeAt() and String.fromCharCode().

Best Solution

function rot13(str) {
    var decoded="";
    for (var i in str) {
        if (str.charCodeAt(i) < 65 || str.charCodeAt(i) > 91) { // check if char is outside A-Z range
            decoded += str[i];
            continue;
        }
        // chars A-M will loop back over Z, so you need to add 13(26-13)
        if (str.charCodeAt(i) < 78) {
            decoded += String.fromCharCode(str.charCodeAt(i) + 13);
        }
        // all remaining chars - N-Z
        else {
            decoded += String.fromCharCode(str.charCodeAt(i) - 13);
        }
    }
    return decoded;
}

// Change the inputs below to test
rot13("GUR DHVPX OEBJA QBT WHZCRQ BIRE GUR YNML SBK.");

CodePen Example

See the Pen Bonfire: Caesar’s Cipher – Best Solution by Mike Doubintchik (@allurewebsolutions) on CodePen.

Code Breakdown

From Mozilla: The charCodeAt() method returns the numeric Unicode value of the character at the given index. The static String.fromCharCode() method returns a string created by using the specified sequence of Unicode values.

First we create an empty variable called decoded. This will allow us later to populate one decoded character at a time until we reach the solution.

Next we create a for loop. In this case, I used the shorthand notation of for(var i in str) which is the equivalent of for(i = 0; i < str.length; i++). This will iterate through the whole encrypted input string.

Our first check is to see whether the character in the string is non-“A to Z,” such as a period or space. A had a charCode of 65 and Z of 90, so we check if our current character is outside the range of 65 – 90. If it is, we add it directly to our decoded string without any manipulation and continue running the loop.

Then we check if a character is A – M. The reason we have to do this is because we can’t just shift left 13 (subtract 13) for characters with values 65 – 77. For example let’s take the letter E. It has a charCode value of 69. If we subtract 13, that will be 56, which is the number 8. Obviously that doesn’t work. The solution is to loop back around from the end of the alphabet.

We could do this by trying to find the remainder once we shift left as much as possible. The math goes like this 90 – (13 – (69 – 65)). However, it’s easier just to loop the other way in the alphabet, by shifting right. So instead we would do 69 + 26 (letters of the alphabet) – 13 (left shift) or 69 + 13 (26-13).

Finally, we can shift left all the characters greater than charCode 77 by 13.

Each new character gets added to the decoded string with decoded+=. In the end, we return our decoded string.

Alternative Solution (as suggested by Aman in the comments)

This solution uses the array.push() method instead of concatenating strings. It’s approximately 30% slower when running benchmarks.

function rot13(str) {
  var answer = [];
  for (var i in str) {
    if (str.charCodeAt(i) < 65 || str.charCodeAt(i) > 91) {
      answer.push(str[i]);
      continue;
    } else {
      if (str.charCodeAt(i) < 78) {
        answer.push(String.fromCharCode(str.charCodeAt(i) + 13));
        continue;
      } else {
        answer.push(String.fromCharCode(str.charCodeAt(i) - 13));
        continue;
      }
    }
  }
  return answer.join("");
}

Alternative Solution (as suggested by Jonnie in the comments)

This solution uses character mapping and it’s completely different from all the other solutions. To see an explanation of how this solution check out Jonnie’s comment below.

rot13 = m => m.split('')
    .map(b => {
    x = b.charCodeAt();
    return x > 96 && x < 123 ? String.fromCharcode((x - 84) % 26 + 97) : x > 64 && x < 91 ? String.fromCharCode((x - 52) % 26 + 65) : b
    })
    .join('');

Discussion

I would love to hear others’ solutions and discover better and cooler ways to solve these challenges. Please comment with your questions, suggestions, or anything you would like.

If you found my solution useful or learned something new from this blog post, please feel free add kudos inside the main chat of Free Code Camp: Thanks @allurewebsolutions

Mike Doubintchik

Author Mike Doubintchik

More posts by Mike Doubintchik

Join the discussion 24 Comments

  • Aman Agarwal says:

    Really cool solution. However, you might want to make a slight change. In Js, strings are immutable – so any function you use to concatenate or replace a string, usually returns a NEW string altogether. That’s why I think using the “+=” operator to append characters to decoded could be suboptimal. Instead, if you add all these characters to an array (as you did in your previous solution) and simply join them in the end, it could improve the efficiency by a few increments. Here’s my code – I did incorporate some of your things though – like adding a continue statement at every execution of ‘push’ where it’s pointless to leave the loop running.

    function rot13(str) { // LBH QVQ VG!
      var answer = [];
      for(i=0, len=str.length; i 64) && (str.charCodeAt(i) < 91)) {
          if(str.charCodeAt(i) < 78) {
            answer.push(String.fromCharCode(str.charCodeAt(i) + 13));
            continue;
          } else {
            answer.push(String.fromCharCode(str.charCodeAt(i) - 13));
            continue;
          }
        } else {
          answer.push(str[i]);
          continue;
        }
      }
      var s = answer.join("");
      return s;
    }
    
    // Change the inputs below to test
    rot13("SERR PBQR PNZC");
    • I’m getting an error on this line: for(i=0, len=str.length; i 64) && (str.charCodeAt(i) < 91)) {

      Can you please check your code?

      I'd like to run a comparison of speed script: http://codepen.io/allurewebsolutions/pen/qbpZLp?editors=001

      FYI, you can use the tags pre and code to format the code in your comments.

      • Aman Agarwal says:
        function rot13(str) { // LBH QVQ VG!
            var answer = [];
            for(i=0, len=str.length; i64) && (str.charCodeAt(i) < 91)) {
                if(str.charCodeAt(i) < 78) {
                    answer.push(String.fromCharCode(str.charCodeAt(i) + 13));
                    continue;
                } else {
                    answer.push(String.fromCharCode(str.charCodeAt(i) - 13));
                    continue;
                }
              } else {
              answer.push(str[i]);
              continue;
              }
            }
            var s = answer.join("");
            return s;
        }
        

  • Jay says:

    Hi, just wanted to share my solution, where I push everything into an array, check and convert, and then use join to turn it all into a string:

    function rot13(str) { 
    
      // convert string to Unicode. Use a loop, string is array. Pass on on-alphabetic characters as well. Push into new array convertedString
    
      var convertedString = [];
      for (i = 0; i  26. Not for non-alphabetic characters
      for (j = 0; j = 65 && convertedString[j] = 78) {
            convertedString[j] = (convertedString[j] + 13) - 26;
          } else {
            convertedString[j] = convertedString[j] + 13;
          }
        }
        //convert every element back to string, inside loop
    
        convertedString[j] = String.fromCharCode(convertedString[j]);
      }
      // Convert array with shifted uniodes, back to string
    
      convertedString = convertedString.join("");
    
      return convertedString;
    }
    
    // Change the inputs below to test
    rot13("GUR DHVPX OEBJA QBT WHZCRQ BIRE GUR YNML SBK.");
  • Christian says:

    Here is my functional solution, somewhat faster (8%) according to jsperf.

    function rot13(str) {
      return str.split("").map(function(e){
        var code = e.charCodeAt(),
        if(realCode < 65)
            realCode = 90 - 65 % realCode + 1;
        if(realCode = 65 && code <=90) ? String.fromCharCode(realCode) : e;
      }).join("");
    }
    
  • Christian says:

    It looks like percent symbol is not accepted, actually a whole statement was skipped

    It should be:

    realCode = code – 13;
    if(realCode < 65)
           realCode = 90 - 65 [percent] realCode + 1; 
    • Thanks for your solution and thanks for noticing the issue with the “%” symbol. I wrapped your solution in “pre” tags and tried to insert the missing code. Did I do it correctly?

      Can you give a little description of your solution and thought process on how you reached it? I would like to add it to the blog post.

  • Andythedandyone says:
    function rot13(str) { // LBH QVQ VG!
        var newArr = [];
        var final = [];
    
        for (var i = 0; i = 65 && newArr[i] =78 && newArr[i] <= 90) {
                newArr[i] -= 13;
            }
        final[i] = String.fromCharCode(newArr[i]);
        }
        return final.join('');
    }
    
    console.log(rot13("LBH QVQ VG!"));
    
  • Andythedandyone says:

    Hey Mike, Im not sure what happened with the code above I provided, but its syntax is completely wrong. I guess during the copying/paste and edit, something may have happened, This here is the right code I wanted to provide before. It was late night, probably running out of coffee then :).

     
    function rot13(str) { // LBH QVQ VG!
    var newArr = [];
    var final = [];
    
    for (var i = 0; i < str.length; i++) {
        newArr[i] = (str.charCodeAt(i));
    
        if (newArr[i] >= 65 && newArr[i] < = 77) {
            newArr[i] += 13;
        }
        else if (newArr[i] >=78 && newArr[i] < = 90) {
            newArr[i] -= 13;
        }
    final[i] = String.fromCharCode(newArr[i]);
    }
    return final.join('');
    }
    
    console.log(rot13("LBH QVQ VG!"));
    
  • Andythedandyone says:

    Mike, indeed WP is chewing part of the code off. And I thought I did it..lol
    Here it goes …
    https://gist.github.com/andythedandyone/9b6a7e1650bf5e232bcf76064790a1b9

    • Really not sure why WordPress is stripping code from comments, but I fixed it manually.

      Your code is interesting…using two arrays. How does the performance compare?

      • Andythedandyone says:

        Im not sure, jsperf is not working for the moment, if someone can benchmark it, feel free. I don’t expect it to be the fastest, I thought it just to be a clean code and easy enough for beginners to understand. The option of going with two arrays is simply to strip the string into single numbers inside the array, from there adding or subtracting depending on which half the numbers falls at. And for the last, pour the {un cipher’d} numbers back to alpha into a new array to display.

  • JBrown194 says:

    You can actually make this code a ton simpler by making a map:

    rot13=m=>m.split("").map(b=>{x=b.charCodeAt();return x>96&&x64&&x<91?String.fromCharCode((x-52)%26+65):b}).join("")
    
    • JBrown194 says:

      That didnt copy well:

      Lets try again?

      rot13=m=>m.split("")
      .map(b=>{
      x=b.charCodeAt();
      return x>96&&x64&&x<91?String.fromCharCode((x-52)%26+65):b
      }).join("")
      
      • Cool, that is much shorter. Can you walk us through the code?

        • JBrown194 says:

          yeah,

          so the rot13 = m =>… is arrow function notation… you should look it up, its newer and not supported on older browsers..

          but the code basically does this, it splits the string into an array and calls the map function. when calling map, you pass a function that will execute on all elements in the array. This is bit advanced, but it means you don’t need a loop.

          The function that map runs basically converts the character to a character code, and then will change the code by 13 characters, for wrap around, i use a modulus to do this.

          for example for lower lower case character a, the character code is 97 you add 13 to this for 110, then subtract 97, for 13 take the modulus 26 in case its over 26, and then adds back 97 to get to 110… this seems like a ton of extra steps that are not needed.. but when you get to a character in the 2nd half of the alphabet it fixes the wrap round issue.

          example, lower case ‘z’ is 122, add 13 for 135, then subtract 97 for 38, this is over 26… so its not in the lower case alphabet, you mod this by 26 to get to 12, then add 97 to get to 109, which is the correct character ‘m’

          this function got really messed up when i submitted my comment, it should really look like the following:
          http://screencast.com/t/rnwiBPTI1n0

          the function checks to see if the character code is lower, upper, or defualt (any other character)

          finally, the join function just joins the new array that map returns.

      • JBrown194 says:

        For some odd reason, its cutting out a bit of code in the middle…

        Maybe adding spaces to my code will fix this?

        rot13 = m => m.split("").map(b => {x=b.charCodeAt(); return x > 96 && x64 && x < 91 ? String.fromCharCode((x-52)%26+65) : b }).join("")

        if not then reply to this and i can send you an email

Leave a Reply