79

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.

dreftymac
  • 31,404
  • 26
  • 119
  • 182
svenwltr
  • 17,002
  • 12
  • 56
  • 68

20 Answers20

168

It's very simple:

>>> import codecs
>>> codecs.encode('foobar', 'rot_13')
'sbbone'
ForceBru
  • 43,482
  • 10
  • 63
  • 98
Nazmul Hasan
  • 6,840
  • 13
  • 36
  • 37
  • 6
    This appears to be the new _proper_ way to do it. They moved it out of the string object for modularity. Now the string object `str.encode()` raises errors about not returning a byte array or bytes object. – Youarefunny Apr 22 '11 at 01:27
  • Note also that this causes problems if you're trying to make Unicode text into rot13 and it contains any character larger than one byte (e.g., a non-breaking space). – Milo P Mar 31 '14 at 21:00
  • 1
    @Ensemble [the documentation](https://docs.python.org/3/library/codecs.html#text-transforms) says "New in version 3.2: Restoration of the `rot_13` text transform." and "Changed in version 3.4: Restoration of the `rot13` alias." So neither work on 3.0 and 3.1, then `rot_13` works on 3.2 and 3.3 and then both `rot_13` and `rot13` work on 3.4+ (as well as Python 2). – Boris Verkhovskiy Feb 10 '21 at 08:40
87

maketrans()/translate() solutions…

Python 2.x

import string
rot13 = string.maketrans( 
    "ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz", 
    "NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm")
string.translate("Hello World!", rot13)
# 'Uryyb Jbeyq!'

Python 3.x

rot13 = str.maketrans(
    'ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz',
    'NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm')
'Hello World!'.translate(rot13)
# 'Uryyb Jbeyq!'
Mr. Lance E Sloan
  • 3,297
  • 5
  • 35
  • 50
Paul Rubel
  • 26,632
  • 7
  • 60
  • 80
  • 4
    This is the shortest and its available under 3.0 :-) – svenwltr Jul 17 '10 at 01:53
  • 16
    This no longer works on Python3.2+; `maketrans` was removed from the `string` module. Use Nazmul Hasan's `codecs` answer instead. – Wooble Sep 09 '13 at 18:30
  • 9
    for Python3.x: change to ```rot13 = str.maketrans(...)``` and then ```"Hello World!".translate(rot13)``` – Muposat May 18 '16 at 15:53
  • for `Python3.x` `import string rot13 = str.maketrans( "ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz", "NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm") print(str.translate("Hello World!", rot13))` – Brent Knox Nov 16 '16 at 16:47
  • Same idea with basic python: `def rot13(s): m = {a:b for a,b in zip("ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz", "NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm")} return ''.join(m.setdefault(c, c) for c in s)` – pat May 24 '21 at 06:01
  • I liked it, very interesting way to do. – MadMad666 Jan 25 '22 at 20:13
67

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

>>> 'foobar'.encode('rot13')
'sbbone'
meshy
  • 8,470
  • 9
  • 51
  • 73
Amber
  • 507,862
  • 82
  • 626
  • 550
25

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'
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
ars
  • 120,335
  • 23
  • 147
  • 134
16

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
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
Artur Gaspar
  • 4,407
  • 1
  • 26
  • 28
  • 2
    You can also directly import this. `import this;rot13 = lambda s:''.join(this.d.get(c, c) for c in s)`. Just saying. – 김민준 Jul 22 '17 at 09:06
9

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.

bbc
  • 240
  • 2
  • 8
6

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

> import codecs
> codecs.encode('foo', 'rot13')
sbb
ankostis
  • 8,579
  • 3
  • 47
  • 61
  • @steven-rumbalski Strange! In py3.5 I get this error: ``LookupError: 'rot13' is not a text encoding; use codecs.encode() to handle arbitrary codecs`` – ankostis Sep 23 '16 at 16:20
  • 2
    @StevenRumbalski: if `str.encode('rot13')` ever worked in older 3.x releases then that was a mistake, as `str.encode()` is strict about producing `bytes` results *only*, while ROT13 is a text-to-text codec. – Martijn Pieters Jan 17 '18 at 16:31
6

Try this:

import codecs
codecs.encode("text to be rot13()'ed", "rot_13")
Barrest
  • 121
  • 2
  • 5
  • 1
    The `.encode` method is included as part of string objects and doesn't need to be specifically imported. – Amber Jul 17 '10 at 00:42
  • 1
    But in Python 3, you need to spell it "rot_13" or "rot-13", not "rot13". I edited the answer. –  Jun 19 '13 at 12:15
  • 1
    @Amber: `str.encode()` can only produce bytes in Python 3. The rot13 codec is a str-to-str codec and is only available via `codecs.encode()` and `codecs.decode()`. – Martijn Pieters Jan 17 '18 at 16:29
  • @user1220978: `rot13` is available in 3.6 at the very least. – Martijn Pieters Jan 17 '18 at 16:30
4

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))
jeroen
  • 41
  • 3
4

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)})
wjv
  • 2,288
  • 1
  • 18
  • 21
2

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)
2

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)
Eratosthenes
  • 2,280
  • 1
  • 14
  • 11
2

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!
Diaz
  • 241
  • 1
  • 3
2

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])
yamm
  • 1,523
  • 1
  • 15
  • 25
  • Your solution is elegant, but won't handle scenarios with accents (ie éêèö and alike) because str.isalpha() returns True for those characters. Here's a lightly modified version that does not use str.isalpha(): `def rot13(s): return ''.join([chr(ord(n) + (13 if 'Z' < n < 'n' or n < 'N' else -13)) if ('a' <= n <= 'z' or 'A' <= n <= 'Z') else n for n in string])` – Orsiris de Jong Dec 03 '19 at 18:19
1
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.

DeaD_EyE
  • 433
  • 5
  • 10
0
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)
user847988
  • 984
  • 1
  • 16
  • 30
0

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
Deacon
  • 3,615
  • 2
  • 31
  • 52
0

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)
wjandrea
  • 28,235
  • 9
  • 60
  • 81
0

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!
Zv_oDD
  • 1,838
  • 1
  • 18
  • 26
-2

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])
SyntaxError
  • 330
  • 3
  • 16