[python] Short rot13 function - Python

I am searching for a short and cool rot13 function in Python ;-) I've written this function:

def rot13(s):
    chars = "abcdefghijklmnopqrstuvwxyz"
    trans = chars[13:]+chars[:13]
    rot_char = lambda c: trans[chars.find(c)] if chars.find(c)>-1 else c
    return ''.join( rot_char(c) for c in s ) 

Can anyone make it better? E.g supporting uppercase characters.

This question is related to python string encoding

The answer is


For arbitrary values, something like this works for 2.x

from string import ascii_uppercase as uc, ascii_lowercase as lc, maketrans                                                                                                            

rotate = 13 # ROT13                                                                    
rot = "".join([(x[:rotate][::-1] + x[rotate:][::-1])[::-1] for x in (uc,lc)])   

def rot_func(text, encode=True):                                                
    ascii = uc + lc                                                             
    src, trg = (ascii, rot) if encode else (rot, ascii)                         
    trans = maketrans(src, trg)                                                 
    return text.translate(trans)                                                

text = "Text to ROT{}".format(rotate)                                           
encode = rot_func(text)                                                         
decode = rot_func(encode, False)

This works for uppercase and lowercase. I don't know how elegant you deem it to be.

def rot13(s):
    rot=lambda x:chr(ord(x)+13) if chr(ord(x.lower())+13).isalpha()==True else chr(ord(x)-13)
    s=[rot(i) for i in filter(lambda x:x!=',',map(str,s))]
    return ''.join(s)

You can also use this also

def n3bu1A(n):
    o=""
    key = {
       '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', '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 x in n:
        v = x in key.keys()
        if v == True:
            o += (key[x])   
        else:
            o += x
    return o

Yes = n3bu1A("N zhpu fvzcyre jnl gb fnl Guvf vf zl Zragbe!!")
print(Yes)

The maketrans and translate methods of str are handy for this type of thing.

Here's a general solution:

import string

def make_rot_n(n):
    lc = string.ascii_lowercase
    uc = string.ascii_uppercase
    trans = str.maketrans(lc + uc,
                          lc[n:] + lc[:n] + uc[n:] + uc[:n])
    return lambda s: str.translate(s, trans)

rot13 = make_rot_n(13)

rot13('foobar')
# 'sbbone'

This works on Python 2 (but not Python 3):

>>> 'foobar'.encode('rot13')
'sbbone'

You can support uppercase letters on the original code posted by Mr. Walter by alternating the upper case and lower case letters.

chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"

If you notice the index of the uppercase letters are all even numbers while the index of the lower case letters are odd.

  • A = 0 a = 1,
  • B = 2, b = 3,
  • C = 4, c = 4,
  • ...

This odd-even pattern allows us to safely add the amount needed without having to worry about the case.

trans = chars[26:] + chars[:26]

The reason you add 26 is because the string has doubled in letters due to the upper case letters. However, the shift is still 13 spaces on the alphabet.

The full code:

def rot13(s):
    chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"
    trans = chars[26:]+chars[:26]
    rot_char = lambda c: trans[chars.find(c)] if chars.find(c) > -1 else c
    return ''.join(rot_char(c) for c in s)

OUTPUT (Tested with python 2.7):

print rot13("Hello World!") --> Uryyb Jbeyq!

A one-liner to rot13 a string S:

S.translate({a : a + (lambda x: 1 if x>=0 else -1)(77 - a) * 13 for a in range(65, 91)})

In python-3 the str-codec that @amber mentioned has moved to codecs standard-library:

> import codecs
> codecs.encode('foo', 'rot13')
sbb

Short solution:

def rot13(text):
    return "".join([x if ord(x) not in range(65, 91)+range(97, 123) else
            chr(((ord(x)-97+13)%26)+97) if x.islower() else
            chr(((ord(x)-65+13)%26)+65) for x in text])

The following function rot(s, n) encodes a string s with ROT-n encoding for any integer n, with n defaulting to 13. Both upper- and lowercase letters are supported. Values of n over 26 or negative values are handled appropriately, e.g., shifting by 27 positions is equal to shifting by one position. Decoding is done with invrot(s, n).

import string

def rot(s, n=13):
    '''Encode string s with ROT-n, i.e., by shifting all letters n positions.
    When n is not supplied, ROT-13 encoding is assumed.
    '''
    upper = string.ascii_uppercase
    lower = string.ascii_lowercase
    upper_start = ord(upper[0])
    lower_start = ord(lower[0])
    out = ''
    for letter in s:
        if letter in upper:
            out += chr(upper_start + (ord(letter) - upper_start + n) % 26)
        elif letter in lower:
            out += chr(lower_start + (ord(letter) - lower_start + n) % 26)
        else:
            out += letter
    return(out)

def invrot(s, n=13):
    '''Decode a string s encoded with ROT-n-encoding
    When n is not supplied, ROT-13 is assumed.
    '''
    return(rot(s, -n))

From the builtin module this.py (import this):

s = "foobar"

d = {}
for c in (65, 97):
    for i in range(26):
        d[chr(i+c)] = chr((i+13) % 26 + c)

print("".join([d.get(c, c) for c in s]))  # sbbone

def rot13(s):
    lower_chars = ''.join(chr(c) for c in range (97,123)) #ASCII a-z
    upper_chars = ''.join(chr(c) for c in range (65,91)) #ASCII A-Z
    lower_encode = lower_chars[13:] + lower_chars[:13] #shift 13 bytes
    upper_encode = upper_chars[13:] + upper_chars[:13] #shift 13 bytes
    output = "" #outputstring
    for c in s:
        if c in lower_chars:
                output = output + lower_encode[lower_chars.find(c)]
        elif c in upper_chars:
            output = output + upper_encode[upper_chars.find(c)]
        else:
            output = output + c
    return output

Another solution with shifting. Maybe this code helps other people to understand rot13 better. Haven't tested it completely.


from string import maketrans, lowercase, uppercase

def rot13(message):
   lower = maketrans(lowercase, lowercase[13:] + lowercase[:13])
   upper = maketrans(uppercase, uppercase[13:] + uppercase[:13])
   return message.translate(lower).translate(upper)

As of Python 3.1, string.translate and string.maketrans no longer exist. However, these methods can be used with bytes instead.

Thus, an up-to-date solution directly inspired from Paul Rubel's one, is:

rot13 = bytes.maketrans(
    b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
    b"nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM")
b'Hello world!'.translate(rot13)

Conversion from string to bytes and vice-versa can be done with the encode and decode built-in functions.


I found this post when I started wondering about the easiest way to implement rot13 into Python myself. My goals were:

  • Works in both Python 2.7.6 and 3.3.
  • Handle both upper and lower case.
  • Not use any external libraries.

This meets all three of those requirements. That being said, I'm sure it's not winning any code golf competitions.

def rot13(string):
    CLEAR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    ROT13 = 'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm'
    TABLE = {x: y for x, y in zip(CLEAR, ROT13)}

    return ''.join(map(lambda x: TABLE.get(x, x), string))



if __name__ == '__main__':
    CLEAR = 'Hello, World!'
    R13 = 'Uryyb, Jbeyq!'

    r13 = rot13(CLEAR)
    assert r13 == R13

    clear = rot13(r13)
    assert clear == CLEAR

This works by creating a lookup table and simply returning the original character for any character not found in the lookup table.

Update

I got to worrying about someone wanting to use this to encrypt an arbitrarily-large file (say, a few gigabytes of text). I don't know why they'd want to do this, but what if they did? So I rewrote it as a generator. Again, this has been tested in both Python 2.7.6 and 3.3.

def rot13(clear):
    CLEAR = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    ROT13 = 'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm'
    TABLE = {x: y for x, y in zip(CLEAR, ROT13)}

    for c in clear:
        yield TABLE.get(c, c)



if __name__ == '__main__':
    CLEAR = 'Hello, World!'
    R13 = 'Uryyb, Jbeyq!'

    r13 = ''.join(rot13(CLEAR))
    assert r13 == R13

    clear = ''.join(rot13(r13))
    assert clear == CLEAR

Interesting exercise ;-) i think i have the best solution because:

  1. no modules needed, uses only built-in functions --> no deprecation
  2. it can be used as a one liner
  3. based on ascii, no mapping dicts/strings etc.

Python 2 & 3 (probably Python 1):

def rot13(s):
    return ''.join([chr(ord(n) + (13 if 'Z' < n < 'n' or n < 'N' else -13)) if n.isalpha() else n for n in s])

def rot13_verbose(s):
    x = []
    for n in s:
        if n.isalpha():
            # 'n' is the 14th character in the alphabet so if a character is bigger we can subtract 13 to get rot13
            ort = 13 if 'Z' < n < 'n' or n < 'N' else -13
            x.append(chr(ord(n) + ort))
        else:
            x.append(n)
    return ''.join(x)



# crazy .min version (99 characters) disclaimer: not pep8 compatible^

def r(s):return''.join([chr(ord(n)+(13if'Z'<n<'n'or'N'>n else-13))if n.isalpha()else n for n in s])

It's very simple:

>>> import codecs
>>> codecs.encode('foobar', 'rot_13')
'sbbone'

I couldn't leave this question here with out a single statement using the modulo operator.

def rot13(s):
    return ''.join([chr(x.islower() and ((ord(x) - 84) % 26) + 97
                        or x.isupper() and ((ord(x) - 52) % 26) + 65
                        or ord(x))
                    for x in s])

This is not pythonic nor good practice, but it works!

>> rot13("Hello World!")
Uryyb Jbeyq!

Try this:

import codecs
codecs.encode("text to be rot13()'ed", "rot_13")

Examples related to python

programming a servo thru a barometer Is there a way to view two blocks of code from the same file simultaneously in Sublime Text? python variable NameError Why my regexp for hyphenated words doesn't work? Comparing a variable with a string python not working when redirecting from bash script is it possible to add colors to python output? Get Public URL for File - Google Cloud Storage - App Engine (Python) Real time face detection OpenCV, Python xlrd.biffh.XLRDError: Excel xlsx file; not supported Could not load dynamic library 'cudart64_101.dll' on tensorflow CPU-only installation

Examples related to string

How to split a string in two and store it in a field String method cannot be found in a main class method Kotlin - How to correctly concatenate a String Replacing a character from a certain index Remove quotes from String in Python Detect whether a Python string is a number or a letter How does String substring work in Swift How does String.Index work in Swift swift 3.0 Data to String? How to parse JSON string in Typescript

Examples related to encoding

How to check encoding of a CSV file UnicodeEncodeError: 'ascii' codec can't encode character at special name Using Javascript's atob to decode base64 doesn't properly decode utf-8 strings What is the difference between utf8mb4 and utf8 charsets in MySQL? The character encoding of the plain text document was not declared - mootool script UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 23: ordinal not in range(128) How to encode text to base64 in python UTF-8 output from PowerShell Set Encoding of File to UTF8 With BOM in Sublime Text 3 Replace non-ASCII characters with a single space