Rotate list every for every letter in string

I am attempting to make a Caesar cipher that changes the key each letter, I currently have a working cipher that scrambles the entire string once, running 1-25 however I would like it to do it for each letter, as in the string "ABC" would shift A by 1, B by 2 and C by 3, resulting in BDF

I already have a working cipher, and am just not sure how to have it change each letter.

    upper = collections.deque(string.ascii_uppercase)
    lower = collections.deque(string.ascii_lowercase)

    upper.rotate(number_to_rotate_by)
    lower.rotate(number_to_rotate_by)

    upper = ''.join(list(upper))
    lower = ''.join(list(lower))

    return rotate_string.translate(str.maketrans(string.ascii_uppercase, upper)).translate(str.maketrans(string.ascii_lowercase, lower))
#print (caesar("This is simple", 2))
our_string = "ABC"


for i in range(len(string.ascii_uppercase)):
    print (i, "|", caesar(our_string, i))

Outcome is this:

0 | ABC
1 | ZAB
2 | YZA
3 | XYZ
4 | WXY
5 | VWX
6 | UVW
7 | TUV
8 | STU
9 | RST
10 | QRS
11 | PQR
12 | OPQ
13 | NOP
14 | MNO
15 | LMN
16 | KLM
17 | JKL
18 | IJK
19 | HIJ
20 | GHI
21 | FGH
22 | EFG
23 | DEF
24 | CDE
25 | BCD

What I would like is to have it a shift of 1 or 0 for the first letter, then 2 for the second, and so on.

1 answer

  • answered 2019-05-15 06:53 miloszlakomy

    Good effort! Note that the mapping doesn't only rearrange letters in the alphabet, so it's never achieved by rotating the alphabet. In your example, upper would become the following mapping:

    ABCDEFGHIJKLMNOPQRSTUVWXYZ
    BDFHJLNPRTVXZBDFHJLNPRTVXZ
    

    Also note this cipher is not easily reversible, i.e. it's not clear whether to reverse 'B'->'A' or 'B'->'N'.


    When you feel confused about a piece of code, often it's a good sign it's time to refactor a part of it into a separate function, e.g. like this:

    (Playground: https://ideone.com/wNSADR)

    import string
    
    
    def letter_index(letter):
        """Determines the position of the given letter in the English alphabet
    
        'a' -> 0
        'A' -> 0
        'z' -> 25
        """
        if letter not in string.ascii_letters:
            raise ValueError("The argument must be an English letter")
    
        if letter in string.ascii_lowercase:
            return ord(letter) - ord('a')
        return ord(letter) - ord('A')
    
    
    def caesar(s):
        """Ciphers the string s by shifting 'A'->'B', 'B'->'D', 'C'->'E', etc
    
        The shift is cyclic, i.e. 'A' comes after 'Z'.
        """
        ret = ""
        for letter in s:
            index = letter_index(letter)
            new_index = 2*index + 1
            if new_index >= len(string.ascii_lowercase):
                # The letter is shifted farther than 'Z'
                new_index %= len(string.ascii_lowercase)
            new_letter = chr(ord(letter) - index + new_index)
            ret += new_letter
    
        return ret
    
    
    print('caesar("ABC"):', caesar("ABC"))
    print('caesar("abc"):', caesar("abc"))
    print('caesar("XYZ"):', caesar("XYZ"))
    

    Output:

    caesar("ABC"): BDF
    caesar("abc"): bdf
    caesar("XYZ"): VXZ
    

    Resources: