tags:

views:

292

answers:

5

I'm trying to slowly knock out all of the intricacies of python. Basically, I'm looking for some way, in python, to take a string of characters and push them all over by 'x' characters.

For example, inputing abcdefg will give me cdefghi (if x is 2).

Thanks!

+2  A: 

This solution works for both lowercase and uppercase:

from string import lowercase, uppercase

def caesar(text, key):
    result = []
    for c in text:
        if c in lowercase:
            idx = lowercase.index(c)
            idx = (idx + key) % 26
            result.append(lowercase[idx])
        elif c in uppercase:
            idx = uppercase.index(c)
            idx = (idx + key) % 26
            result.append(uppercase[idx])
        else:
            result.append(c)
    return "".join(result)

Here is a test:

>>> caesar("abcdefg", 2)
'cdefghi'
>>> caesar("z", 1)
'a'
Evan Fosmark
caesar('z', 1) breaks this.
Steve Losh
To fix what I mentioned in my previous comment, you need to use parenthesis because `%` has a higher precedence than `+`: `idx = (idx + key) % 26`
Steve Losh
Yeah I just figured that out, heh. I forgot that modulus is higher.
Evan Fosmark
It's fixed now.
Evan Fosmark
+4  A: 

I think your best bet is to look at string.translate. You may have to use make_trans to make the mapping you like.

David Raznick
+6  A: 

My first version:

>>> key = 2
>>> msg = "abcdefg"
>>> ''.join( map(lambda c: chr(ord('a') +  (ord(c) - ord('a') + key)%26), msg) )
'cdefghi'
>>> msg = "uvwxyz"
>>> ''.join( map(lambda c: chr(ord('a') +  (ord(c) - ord('a') + key)%26), msg) )
'wxyzab'

(Of course it works as expected only if msg is lowercase...)

edit: I definitely second David Raznick's answer:

>>> import string
>>> alphabet = "abcdefghijklmnopqrstuvwxyz"
>>> key = 2
>>> tr = string.maketrans(alphabet, alphabet[key:] + alphabet[:key])
>>> "abcdefg".translate(tr)
'cdefghi'
Federico Ramponi
+1 for using lambda, map, and fold (join) all in one line. :)
Jeremy Powell
What's good about using lambda, map, and join all in one line? Isn't that something one is supposed to try to avoid in Python?
Evan Fosmark
You can even use alphabet = string.ascii_lowercase, if you don't fancy writing it out.
David Raznick
@Evan yep, it's not the most pythonic way to do things... list comprehensions are preferred over map and filter and loops with accumulators over reduce (I'm not very happy with that, but it's Guido's Word xD)
fortran
@Evan Agreed. I guess I'm just happy to see others thinking in terms of functional programming.
Jeremy Powell
+3  A: 

I would do it this way (for conceptual simplicity):

def encode(s):
    l = [ord(i) for i in s]
    return ''.join([chr(i + 2) for i in l])

Point being that you convert the letter to ASCII, add 2 to that code, convert it back, and "cast" it into a string (create a new string object). This also makes no conversions based on "case" (upper vs. lower).

Potential optimizations/research areas:

  • Use of StringIO module for large strings
  • Apply this to Unicode (not sure how)
Sean Woods
It looks like all you have to do for the Unicode version is to replace `chr()` with `unichr()`.
Bastien Léonard
warning: what happens to y and z? Mod 26 required somewhere...
Federico Ramponi
+1  A: 

Another version. Allows for definition of your own alphabet, and doesn't translate any other characters (such as punctuation). The ugly part here is the loop, which might cause performance problems. I'm not sure about python but appending strings like this is a big no in other languages like Java and C#.

def rotate(data, n):
    alphabet = list("abcdefghijklmopqrstuvwxyz")

    n = n % len(alphabet)
    target = alphabet[n:] + alphabet[:n]

    translation = dict(zip(alphabet, target))
    result = ""
    for c in data:
        if translation.has_key(c):
            result += translation[c]
        else:
            result += c

    return result

print rotate("foobar", 1)    
print rotate("foobar", 2)    
print rotate("foobar", -1)
print rotate("foobar", -2)

Result:

gppcbs
hqqdct
emmazq
dllzyp

The make_trans() solution suggested by others is the way to go here.

Emil H