3

I want to replace each character of a string by a different one, shifted over in the alphabet. I'm shifting by 2 in the example below, so a -> c, b -> d, etc.

I'm trying to use a regular expression and the sub function to accomplish this, but I'm getting an error.

This is the code that I have:

p = re.compile(r'(\w)')
test = p.sub(chr(ord('\\1') + 2), text)
print test

where the variable text is an input string.

And I'm getting this error:

TypeError: ord() expected a character, but string of length 2 found

I think the problem is that I the ord function is being called on the literal string "\1" and not on the \w character matched by the regular expression. What is the right way to do this?

5
  • 1
    You have no reason to use regular expressions; regular expressions are for detecting substrings (often with special structure), but you can already extract one-character substrings without regular expressions: it's just [c for c in string]. I've provided a solution anyway (which doesn't use regexes). Commented Jan 27, 2012 at 3:21
  • The reason I went with a regex is because I only wanted to shift "word" characters, and wasn't sure how to do that with a comprehension. (I'm just learning python, if you couldn't tell) Commented Jan 27, 2012 at 3:32
  • The string may contain whitespace and punctuation. These characters should not be shifted, but any numbers or letters should be shifted. Sorry for the ambiguity. There were several good answers to the original question. I'd upvote if I could. Commented Jan 27, 2012 at 4:08
  • Joe: Do note that \w means [A-Za-z0-9_] in most regex parsers. Commented Jan 27, 2012 at 19:08
  • @ninjagecko [c for c in string] was my first idea, but I didn't know how to pattern match so that only values in [A-Za-z0-9] would be shifted. I also didn't realize that \w included '_' Thanks for your help. Commented Jan 27, 2012 at 19:36

4 Answers 4

4

It won't work like this. Python first runs chr(ord('\\') + 2 and then passes that result to p.sub.

You need to put it in a separate function or use an anonymous function (lambda):

p = re.compile(r'(\w)')
test = p.sub(lambda m: chr(ord(m.group(1)) + 2), text)
print test

Or better yet use maketrans instead of regular expressions:

import string

shift = 2

t = string.maketrans(string.ascii_lowercase, string.ascii_lowercase[shift:] +
                                             string.ascii_lowercase[:shift])
string.translate(text, t)
Sign up to request clarification or add additional context in comments.

2 Comments

This will fail to shift around, e.g. 'z' -> 'a'. No downvote because the OP also made this mistake.
Good point @ninjagecko, I'll leave it in to demonstrate a callback with sub. He should use maketrans either way.
2

Full version

def shouldShift(char):
    return char in string.lowercase

def caesarShift(string, n):
    def letterToNum(char):
        return ord(char)-ord('a')
    def numToLetter(num):
        return chr(num+ord('a'))

    def shiftByN(char):
        return numToLetter((letterToNum(char)+n) % 26)

    return ''.join((shiftByN(c) if shouldShift(c) else c) for c in string.lower())

One-liner

If you really want a one-liner, it would be this, but I felt it was uglier:

''.join(chr((ord(c)-ord('a')+n)%26 + ord('a')) for c in string)

Demo

>>> caesarShift(string.lowercase, 3)
'defghijklmnopqrstuvwxyzabc'

Comments

1

Try this, using list comprehensions:

input = 'ABC'
''.join(chr(ord(c)+2) for c in input)
> 'CDE'

It's simpler than using regular expressions.

1 Comment

Or this ''.join(chr(ord(c)+1)for c in 'HAL'), with apologies to HAL 9000
0
def CaesarCipher(s1,num):
new_str = ''
for i in s1:
    asc_V = ord(i)

    if asc_V in range(65, 91):
        if asc_V + num > 90:
            asc_val = 65 + (num - 1 - (90 - asc_V))
        else:
            asc_val = asc_V + num

        new_str = new_str + chr(asc_val)


    elif (asc_V in range(97, 123)):
        if asc_V + num > 122:
            asc_val = 97 + (num - 1 - (122 - asc_V))
        else:
            asc_val = asc_V + num

        new_str = new_str + chr(asc_val)

    else:
        new_str = new_str + i

return new_str        

print (CaesarCipher("HEllo", 4))

print (CaesarCipher("xyzderBYTE", 2))

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.