28

Code in question with syntax highlighting here: via Friendpaste

rot13.js:

ERRONEOUS

<script>
String.prototype.rot13 = rot13 = function(s)
 {
    return (s = (s) ? s : this).split('').map(function(_)
     {
        if (!_.match(/[A-Za-z]/)) return _;
        c = Math.floor(_.charCodeAt(0) / 97);
        k = (_.toLowerCase().charCodeAt(0) - 96) % 26 + 13;
        return String.fromCharCode(k + ((c == 0) ? 64 : 96));
     }).join('');
 };
</script>

As you can see, using quite literally a single line to attach a method to the String object a la prototyping, I'm having a map() method that I previously set up (I know for sure that that code works perfectly; it's simply iterating over each element in the array and applying the function specified in the parameter) go over each character in a string and do what I thought were the proper calculations to transform the string into it's rot13'd counterpart. I was sadly mistaken. Can anybody spot where I went wrong?

georgeawg
  • 48,608
  • 13
  • 72
  • 95
Hexagon Theory
  • 43,627
  • 5
  • 26
  • 30

17 Answers17

77

You could use the super-short:

s.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});
Sophie Alpert
  • 139,698
  • 36
  • 220
  • 238
  • 9
    Just spotted that your code is being used on rot13.com now – Howard Feb 29 '16 at 16:53
  • 3
    Since this seems to be a golfed answer: You can make it two characters shorter by replacing `/[A-Za-z]/g` with `/[A-Z]/gi` or `/[a-z]/gi`. Did case insensitive RegExp not exist in JavaScript in 2009? – trlkly Mar 31 '16 at 05:51
  • @Howard [rot13.com](https://rot13.com) guys forget to check their "improvement", this formula works for `rot13`, but they decided to implement `rot1` and up to `rot25` without thinking too much, and as result they lost primary gist of `rot13` - reversibility :) So, keep in mind, - their solutions works only for `rot13` – Alex Dec 03 '21 at 13:46
23

Here's a solution using replace and indexOf functions:

function rot13(s) {
  return s.replace(/[A-Z]/gi, c =>
    "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm"[
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".indexOf(c) ] )
}

This is made up of:

  • /[A-Z]/gi regular expression for matching only characters
  • replace is used to substitute those characters
  • an a replacer function written as an arrow function
  • indexOf is to convert the character into a numeric lookup index
  • we lookup the index in the substitution array and we're done
Stephen Quan
  • 21,481
  • 4
  • 88
  • 75
16

This gives correct results.

function rot13(s)
 {
    return (s ? s : this).split('').map(function(_)
     {
        if (!_.match(/[A-Za-z]/)) return _;
        c = Math.floor(_.charCodeAt(0) / 97);
        k = (_.toLowerCase().charCodeAt(0) - 83) % 26 || 26;
        return String.fromCharCode(k + ((c == 0) ? 64 : 96));
     }).join('');
 }
 
 alert(rot13(rot13("Mark this as accepted answer :)")));
Zibri
  • 9,096
  • 3
  • 52
  • 44
12

Just because it's even shorter and also more understandable/logical:

function rot13(s) {
  return s.replace( /[A-Za-z]/g , function(c) {
    return String.fromCharCode( c.charCodeAt(0) + ( c.toUpperCase() <= "M" ? 13 : -13 ) );
  } );
}
Herman
  • 361
  • 3
  • 13
  • Ouch! Feel ashamed, I should have done this simple test. Only small correction needed: '<=' instead of '<'. Now it works fine! – Herman Sep 17 '13 at 18:46
  • `/[A-z]/` also looks fishy to me. –  Apr 26 '14 at 22:08
  • @Rhymoid If you meant **"`[A-z]` is wrong"**, then I (again shamefully) agree... I got `[A-z]` from the (very useful) site www.w3schools.com, but when I test it, I see that this range also contains some special characters, like backslash and underscore... So I changed it to `[A-Za-z]`. – Herman Apr 27 '14 at 10:06
  • Worth noting that the original function stops if the input isn't strictly alphabetically, which this doesn't do. (in my case, this one is actually more preferable) – Jezzamon Aug 04 '16 at 13:18
10

Kevin M's solution is compact and elegant. It's got one tiny error, though: the regular expression used with the replace function doesn't limit substitution to alphabetic characters. The [A-z] character range includes punctuation characters ([\] ^ _ `), which will be swapped for letters when they should be left alone.

The fixed version looks like this:

function r(a,b){return++b?String.fromCharCode((a<"["?91:123)>(a=a.charCodeAt()+13)?a:a-26):a.replace(/[a-zA-Z]/g,r)}

It's still only 116 bytes. Remarkably small and quite clever.

(Sorry for the full answer posting; I'm still lacking the 50 rep required to post this as a comment to Kevin's excellent answer.)

Lonnon Foster
  • 319
  • 3
  • 9
5

Here's a version that's 80-columns, doesn't update string.prototype, well indented and reasonably short.

function rot13(str) {
  return str.replace(/[a-zA-Z]/g, function(chr) {
    var start = chr <= 'Z' ? 65 : 97;
    return String.fromCharCode(start + (chr.charCodeAt(0) - start + 13) % 26);
  });
}

And an example showing it is working:

rot13('[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]')
"[nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM]"
rot13(rot13('[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]'))
"[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]"
Vjeux
  • 5,756
  • 4
  • 31
  • 24
5
var rot13 = String.prototype.rot13 = function(s)
{
  return (s = (s) ? s : this).split('').map(function(_)
  {
    if (!_.match(/[A-Za-z]/)) return _;
    c = _.charCodeAt(0)>=96;
    k = (_.toLowerCase().charCodeAt(0) - 96 + 12) % 26 + 1;
    return String.fromCharCode(k + (c ? 96 : 64));
  }
  ).join('');
};

alert('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.rot13());
yields nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM

Mixing zero-based and one-based indices for the lose. I blame Netscape.

Paul
  • 139,544
  • 27
  • 275
  • 264
Sparr
  • 7,489
  • 31
  • 48
4

One-liner that weighs in at 116 bytes:

function r(a,b){return++b?String.fromCharCode((a<"["?91:123)>(a=a.charCodeAt()+13)?a:a-26):a.replace(/[a-zA-Z]/g,r)}

Usage:

r('The Quick Brown Fox Jumps Over The Lazy Dog.');

Kevin M
  • 1,524
  • 17
  • 38
2

There is still room for enhancement, checking (c<="Z") is actually a check against the codepoint (that we need later on), following that idea gives us a win !

//versus Kevin M's style : 115 chars (vs 116)
//102 chars with nodejs Buffer (see below)

function r(a,b){return++b?String.fromCharCode(((a=a.charCodeAt())<91?78:110)>a?a+13:a-13):a.replace(/[a-zA-Z]/g,r)}
//nodejs style
function r(a,b){return++b?Buffer([((a=Buffer(a)[0])<91?78:110)>a?a+13:a-13]):a.replace(/[a-zA-Z]/g,r)}

//versus Ben Alpert style : 107 chars (vs 112)
//93 chars with nodejs Buffer (see below)

s.replace(/[a-zA-Z]/g,function(a){return String.fromCharCode(((a=a.charCodeAt())<91?78:110)>a?a+13:a-13)});
//nodejs style
s.replace(/[a-zA-Z]/g,function(a){return Buffer([((a=Buffer(a)[0])<91?78:110)>a?a+13:a-13])})

// Same code, formated for production

String.prototype.rot13 = function() {
  return this.replace(/[a-zA-Z]/g, function(a){
    return String.fromCharCode(((a=a.charCodeAt())<91?78:110)>a?a+13:a-13);
  });
}

In nodejs, you can use Buffer to cast/serialize codepoints, e.g. :

var a=65;
""+Buffer([a])           == "A" // note that the cast is done automatically if needed
String.fromCharCode(a)   == "A"

var b="A";
Buffer(a)[0]             == 65
a.charCodeAt()           == 65
131
  • 3,071
  • 31
  • 32
  • Using nodejs Buffer is a total WIN `function r(a,b){return++b?Buffer([((a=Buffer(a)[0])<91?78:110)>a?a+13:a-13]):a.replace(/[a-zA-Z]/g,r)}` – 131 Jan 31 '15 at 21:42
  • Great idea to use Buffer! – Kevin M Feb 09 '15 at 17:20
  • +1 on the Ben Alpert style for Nodejs. I did some heavy performance testing in nodejs and Ben's is very fast and was slightly better than the Kevin M style. Also, no recursion with the Ben's algorithm. – Timothy C. Quinn Aug 20 '15 at 21:49
  • @131 - Typo in your Buffer cast/serialize comment. You don't use variable b after initializing. – Timothy C. Quinn Aug 20 '15 at 21:55
  • Using a framework library doesn't make code shorter, it add more weight to the browser by way of having to load the library first before the code can be used, if possible, use native (javascript) based code, if you have already loaded the library for use on many other elements, then thats one option but to load a library just to do a ROT13 is just a waste of time and bandwidth and data allowance. – Mark Giblin Mar 25 '17 at 22:24
2

My golfed version is 82 bytes long (vs. Ben Albert, which is 35% heavier, but inspired mine):

S.replace(/[a-z]/gi,c=>String.fromCharCode((c=c.charCodeAt())+((c&95)>77?-13:13)))

Differences:

  • case-insensitive to catch only English alphabet.
  • arrow functions without return and braces.
  • delete parameters from charCodeAt.
  • test against code insted of string.
  • doing +13-26=-13.
  • test uppercased (&95) against 77 (78+13=91, overflow).

Extra: If you want to perform ROT5 on digits, add: .replace(/\d/gi,c=>(c>4?-5:5)+c*1)

ESL
  • 986
  • 11
  • 18
2

Combining various techniques here, I came up with this 78 character JavaScript ES6 function, which works on Node:

rot13=s=>s.replace(/[a-z]/ig,c=>Buffer([((d=Buffer(c)[0])&95)<78?d+13:d-13]));
jrbedard
  • 3,662
  • 5
  • 30
  • 34
DSchnellDavis
  • 136
  • 1
  • 7
1

Here’s a JavaScript library that performs ROT-n letter substitution: https://github.com/mathiasbynens/rot

rot is a JavaScript library that performs rotational letter substitution. It can be used to shift any ASCII letters in the input string by a given number of positions in the alphabet. To ROT-13 the string 'abc', for example:

// ROT-13 is the default
rot('abc');
// → 'nop'

// Or, specify `13` explicitly:
rot('abc', 13);
// → 'nop'
Mathias Bynens
  • 144,855
  • 52
  • 216
  • 248
1

This is in no way trying to compete with the excellent stuff here, as you see I can't comment but I have my own novice attempt to write this in JS and getting it to work before I read more elegant solutions here - I'm going to share it here.

I tried to write it with indexOf, a switch, by adding 13, by String.fromCharCode() and CharCodeAt(). They were getting too long - the helper function in this one is unnecessary but this was my shortest : )

function rot13(string) { 
  var result = '', 
      store,
      str = string.toLowerCase();  

  //helper function
  function strgBreak(a){
    var result = [];
    return result = a.split('');
  }

  //rot13 arrays
  var alphArr = strgBreak('abcdefghijklmnopqrstuvwxyz');
  var inverseArr = strgBreak('nopqrstuvwxyzabcdefghijklm');

 for ( var i = 0; i < str.length; i++ ) {
     if (alphArr.indexOf( str[i] ) !== -1) {
        result += inverseArr[ alphArr.indexOf( str[i] ) ];
    } else result += str[i];
  }
 return result.toUpperCase(); 
}
isosceles
  • 57
  • 2
  • 13
1

Here's a modern approach to the ROT13 substitution cipher:

const ROT13 = s =>
  s.replace(/[a-z]/gi, c =>
    String.fromCharCode(c.charCodeAt() + 13 - 26 * /[n-z]/i.test(c)));

console.log(ROT13('The quick brown fox jumps over 13 lazy dogs.'));

The result of the test case above is:

Gur dhvpx oebja sbk whzcf bire 13 ynml qbtf.

Grant Miller
  • 27,532
  • 16
  • 147
  • 165
0

CoffeeScript version of @ben-alpert 's answer:

string.replace /[a-zA-Z]/g, (c) -> String.fromCharCode if (if c <= 'Z' then 90 else 122) >= (c = c.charCodeAt(0) + 13) then c else c - 26

Or as function:

ROT13 = (string) -> string.replace /[a-zA-Z]/g, (c) -> String.fromCharCode if (if c <= 'Z' then 90 else 122) >= (c = c.charCodeAt(0) + 13) then c else c - 26
ROT13('asd') # Returns: 'nfq'
Community
  • 1
  • 1
dr.dimitru
  • 2,645
  • 1
  • 27
  • 36
0

My favourite and simple to understand version of decision ROT13

function rot13(message) {
  var a = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  var b = "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM"
  return message.replace(/[a-z]/gi, c => b[a.indexOf(c)])
}

a - classic alphabet, b - prepered dictionary with 13th replacement, return result with simple RegEx replacement

krolovolk
  • 464
  • 6
  • 18
0

While I really like the RegEx solution, I primarily undertook the project to see if I could get it done. Glad to report that I finally did manage to do so:

String.prototype.rot13 = rot13 = function(s)
 {
    return (s ? s : this).split('').map(function(_)
     {
        if (!_.match(/[A-za-z]/)) return _;
        c = Math.floor(_.charCodeAt(0) / 97);
        k = (_.toLowerCase().charCodeAt(0) - 83) % 26 || 26;
        return String.fromCharCode(k + ((c == 0) ? 64 : 96));
     }).join('');
 }
Hexagon Theory
  • 43,627
  • 5
  • 26
  • 30