Search code examples
pythoncaesar-cipher

How to make shift('w','f') return 'b'?


Using this program to take out spaces, punctuation, and make letters lower case...

def pre_process(s): #Enter: "Jim's secret password."

    s= s.replace("'","")
    s= s.replace('.','')
    s= s.lower()
    s= s.replace(" ","")
    return s

How can I encrypt a message so that the letters each shift by an amount equal to the corresponding letter in the alphabet? For example m shifted 5 times becomes r, but w shifted 5 times becomes b. This is my current code:

def shift(ch,k):    
    return chr(ord('a')+(ord(ch)-ord('a')+k) % 26)

Solution

  • def shift(ch, k):
        return chr(ord('a') + ((ord(ch) - ord('a')) + 
                               (ord(k) - ord('a'))) % 26)
    

    Sort of an explanation:

    def shift(ch, k):
        #
        # k_delta
        # ────>
        #
        # ch_delta                 k_delta
        # ────────────────────────>────>
        # a....f.........m....r....w..zab
        # ──────────────>────>         ┊
        # ch_delta       k_delta       ┊
        #                              ┊
        #                             %26
    
        ch_delta = ord(ch) - ord('a')
        k_delta = ord(k) - ord('a')
        return chr(ord('a') + (ch_delta + k_delta) % 26)
    

    Unless k varies, you can use str.translate to speed up encryption:

    import string
    message = 'mw'
    key = 'f'
    enc_table = string.maketrans(
        string.ascii_lowercase,
        ''.join(shift(c, key) for c in string.ascii_lowercase)
    )
    message.translate(enc_table) # -> 'rb'
    

    I'd also suggest replacing the magic number 26 with len(string.ascii_lowercase) for example.

    Decryption can be done using the same function, but with a different key. The relation between them is that enc_delta + dec_delta = 0 modulo 26. From this results that dec_delta = -enc_delta % 26. Therefore:

    dec_k = chr(ord('a') + ((ord(enc_k) - ord('a'))) % 26)