JavaScript算法练习:Caesars Cipher

发布于 大漠

这篇文章将要练习的是回转13位密码(ROT13)维基百科是这样描述ROT13的:

套用ROT13到一段文字上仅仅只需要检查字符字母顺序并替换它在13位之后的对应字母,有需要超过时则重新绕回26英文字母开头即可。 A换成N、B换成O、依此类推到M换成Z,然后序列反转:N换成A、O换成B、最后Z换成M。只有这些出现在英文字母里头的字符受影响;数字、符号、空白字符以及所有其他字符都不变。因为只有在英文字母表里头只有26个,并且26 = 2 × 13,ROT13函数是它自己的逆反。如下图所示:

ROT13

比如将DOG向后移13位:

D -> E,F,G,H,I,J,K,L,M,N,O,P,Q
O -> P,Q,R,S,T,U,V,W,X,Y,Z,A,B
G -> H,I,J,K,L,M,N,O,P,Q,R,S,T

DOG转换为QBT。对照上面的图,也可以查出对应的转换值。

ASCII字符

在JavaScript中可以通过String.prototype.charCodeAt()方法返回0至65535这间的整数,代表索引处理字符的UTF-16编码单元。可以通过这个方法查出每个字符对应的ASCII编码:

'a'.charCodeAt();    //97
'A'.charCodeAt();    //65
'abc'.charCodeAt(0); //97
'ABC'.charCodeAt(0); //65

传给String.prototype.charCodeAt()的值如果是:

  • 一个大于等于0,小于字符串长度的整数,如果不是一个数值,则默认为0
  • 如果值小于0或大于字符串的长度,则返回NaN

那么,对应的ROT13中的字符对应的编码如下:

-----------------------------------------------------------------
| 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
-----------------------------------------------------------------
| A  | B  | C  | D  | E  | F  | G  | H  | I  | J  | K  | L  | M  |
-----------------------------------------------------------------
| N  | O  | P  | Q  | R  | S  | T  | U  | V  | W  | X  | Y  | Z  |
-----------------------------------------------------------------
| 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
-----------------------------------------------------------------

如此一来,A-Z对应的就是65-90。而ROT13是将大写字符串将向后移13位,然后转换成对应的字符。那么:

  • 小于65和大于90对应的就是小写字符a-z
  • 大于等于65和小于等于77对应的就是大写字符A-M
  • 大于等于78和小于等于90对应的就是大写字符N-Z

在实现ROT13功能,在整个函数中还需要使用到String.prototype.charAt()方法和String.fromCharCode()方法:

  • String.prototype.charAt(): 返回字符串中指定位置的字符。字符串中的字符从左向右索引,第一个字符的索引值为0,最后一个字符(假设该字符位于字符串 stringName 中)的索引值为 stringName.length - 1。 如果指定的 index 值超出了该范围,则返回一个空字符串。
  • String.fromCharCode(): 根据指定的 Unicode 编码中的序号值来返回一个字符串。该方法返回一个字符串,而不是一个String对象。

ROT13实现思路

创建一个rot13()函数,并且给其传递一个str字符串(也就是需要实现字符串向后移13位的字符)。实现整个功能,将按下面的几个步骤来:

  • 声明一个变量newString,用来存一个空的字符串
  • 使用String.charCodeAt()查询对应字符的ASCII序号
  • 如果字符是非大写英文字母(序号小于65或大于91),将该字符直接放到newString中,并使用continue进入下一个循环
  • 如果序号小于78(A-M字母),使用String.fromCharCode()转换成该序号加13的字符,并且放入newString
  • 如果序号大于78(N-Z字母),使用String.fromCharCode()转找成该序号减13的字符,并且放入newString
  • 返回newString

测试用例

为了检测rot13(str)函数功能是否正常,可以通过下面的示例来进行检测:

  • rot13("SERR PBQR PNZC");返回FREE CODE CAMP
  • rot13("SERR CVMMN!");返回FREE PIZZA!
  • rot13("SERR YBIR?");返回FREE LOVE?
  • rot13("GUR DHVPX OEBJA QBT WHZCRQ BIRE GUR YNML SBK.");返回THE QUICK BROWN DOG JUMPED OVER THE LAZY FOX.

实现方案

下面罗列了一些实现rot13(str)方案。虽然各种方案细节有所不同,但整个实现思路是按照上述介绍的思路进行的。

方法1

function rot13(str) {
    var newString = [];
    // charCodeAt()返回0-65535之间的整数
    for (var i = 0; i < str.length; i++) {
    	var temp = str.charCodeAt(i);
    	// 如果字符是非大写英文字母(序号小于65或大于91),将该字符直接放到newString中,并使用continue进入下一个循环
        if (temp < 65 || temp > 91) {
        	// 返回字符串指定位置的字符
            newString.push(str.charAt(i));
            continue;

        // 如果序号大于77(N-Z字母),使用String.fromCharCode()转找成该序号减13的字符,并且放入newString
        } else if (temp > 77) {
        	// String.fromCharCode()根据序号(指定的Unicode编码中的序号)值来返回一个字符串
            newString.push(String.fromCharCode(temp - 13));
        
        // 如果序号小于78(N-Z字母),使用String.fromCharCode()转找成该序号减13的字符,并且放入newString
        } else {
            newString.push(String.fromCharCode(temp + 13));
        }
    }
    return newString.join("");
}

方法2

function rot13(str) {
    var newString = "";
    for (var i in str) {
    	var temp = str.charCodeAt(i);
        if (temp < 65 || temp > 91) {
            newString += str[i];
            continue;
        }

        if (temp > 77) {
            newString += String.fromCharCode(temp - 13);
        }
        
        else {
            newString += String.fromCharCode(temp + 13);
        }
    }
    return newString;
}

方法3

function rot13(str) {
    var tempArr = str.split("");
    var newString = [];

    for (var i = 0; i < tempArr.length; i++) {
    	var temp = tempArr[i].charCodeAt(0);
        var unicode = temp - 13;
        if (unicode < 65) {
            unicode += 26;
        }
        if (temp < 65 || temp > 90) {
            newString.push(tempArr[i]);
        } else {
            newString.push(String.fromCharCode(unicode));
        }
    }
    return newString.join("");
}

方法4

function rot13(str) {
    var input = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
    var output = ["n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"];

    str = str.toLowerCase().split("");

    for (var i = 0, l = str.length; i < l; i++) {
        if (input.indexOf(str[i]) !== -1) {
            var index = input.indexOf(str[i]);
            str[i] = output[index];
        }
    }

    str = str.join("").toUpperCase();

    return str;
}

方法5

function rot13(str) {
    var tempArr = str.split("");
    var newString = [];
    var alphaBets = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split("");
    for (i = 0; i < tempArr.length; i++) {
    	var temp = tempArr[i];
        var minified = alphaBets.indexOf(temp) !== -1 ? (alphaBets.indexOf(temp) < 13 ? newString.push(alphaBets[alphaBets.indexOf(temp) + 13]) : newString.push(alphaBets[alphaBets.indexOf(tempArr[i]) - 13])) : newString.push(tempArr[i]);
    }
    return newString.join(""); // Array to String
}

方法6

function isLetter(letter){
   if(letter >= 65 && letter <= 90)
       return true;
   else
       return false;
}
function rot13(str) {
 var newStr = "";
 for(var i=0;i<str.length;i++){
   var letter = str.charCodeAt(i);
   if(isLetter(letter)){
       if(isLetter(letter + 13))
           newStr += String.fromCharCode(letter + 13);
       else{
           var addTo64 = (letter + 13) - 90;
           newStr += String.fromCharCode(64 + addTo64);
       }
   }
   else
       newStr += String.fromCharCode(letter);
 }
 return newStr;
}

方法7

function rot13(str){
    return (str ? str : 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('');
}

方法8

function rot13(str) {
  return str.replace( /[A-Za-z]/g , function(c) {
    return String.fromCharCode( c.charCodeAt(0) + ( c.toUpperCase() <= "M" ? 13 : -13 ) );
  } );

  // 也可以将上面的代码换成下面这个
  // str.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});
}

总结

上面通过搜集和整理了实现rot13(str)的八种方法,虽然每一种方法略有不同,但整个实现思路是一致的。如果你有更好的实现方案欢迎在下面的评论中与我们一起分享。

扩展阅读

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.fedev.cn/javascript/bonfire-caesars-cipher-solution.htmlNike Zoom Kobe IV