nested for loop repeets my code is there a way to not do this?

Question:

i’ve recently started to learn programming and I’m following a course and have some code that I don’t know how to fix if anyone can help that would be great 🙂 its meant to be a decoder for the vigenere cipher

def vigenere(message,keyword):
    alphabet = "abcdefghijklmnopqrstuvwxyz"
    new_message = ""
    punctuation = ".!?' "
    for i in message:
        for x in keyword:
            if not i in punctuation:
                num = alphabet.find(i)
                num2 = alphabet.find(x)
                new_message += alphabet[(num+num2) % 26]
            else:
                new_message += i
    return new_message
            

vigenere("dfc aruw fsti gr vjtwhr wznj? vmph otis! cbx swv jipreneo uhllj kpi rahjib eg fjdkwkedhmp!", "friends")
Asked By: Luca Roscan

||

Answers:

I’m pleasantly surprised to know that I remember how Vigenere ciphers work from that one time I got into cryptography 15 years ago. Here’s a working version of your code, where I did my best to make minimal changes.

def vigenere(message,keyword):
    alphabet = "abcdefghijklmnopqrstuvwxyz"
    new_message = ""
    punctuation = ".!?' "
    idx = 0
    for c in message:
        x = keyword[idx%len(keyword)]
        if not c in punctuation:
            num = alphabet.find(c)
            num2 = alphabet.find(x)
            new_message += alphabet[(num-num2) % 26]
            idx+=1
        else:
            new_message += c
    return new_message

There are 3 big changes that I think you should try to understand.

1: You are decrypting here, not encrypting, hence the change from num + num2 to num - num2.

2: You have misunderstood what a nested loop does. With the structure

    for i in message:
        for x in keyword:

You end up going through every combination of i and x. That is, the pairs i,x end up being

('d','f'), ('f','f'), ('c','f'), ..., ('p','f'),
('d','r'), ('f','r'), ('c','r'), ..., ('p','r'),

and so forth. What you’re actually trying to do here is consider the pairs ('d','f'), ('f','r'), ('c','i'), and so forth. In other words, you want to simultaneously iterate through both strings. In order to do that, you need a loop with the structure

for i,x in zip(message,keyword):

or alternatively, you can just iterate through an index (say j) and access the characters as message[j] and keyword[j].

3: The above is actually not quite what you want. First of all, we reach the end of our keyword before the end of the message. When that happens, we need to loop back to the beginning of the keyword. Or, we can simply take the index of where we are in the keyword modulo the keyword’s length, which is what I’ve done. Second, spaces and punctuation need to be ignored, which is to say that our index within the keyword shouldn’t advance in those cases.

The decrypted message, for any interested.

you were able to decode this? nice work! you are becoming quite the expert at crytography!


Here’s a slightly more efficient version. Note that appending an element to a list is faster than the string replacement new_message += <character>. Also, the decryption process (handled by the decrypt helper function) is made more efficient with the help of the ord and chr functions.

def decrypt(letter,key_letter):
    return chr(ord('a') + ((ord(letter)-ord(key_letter))%26))

def vigenere(message,keyword):
    a = ord('a')
    new_message = []
    punctuation = ".!?' "
    idx = 0
    for c in message:
        x = keyword[idx]
        if not c in punctuation:
            new_message.append(decrypt(c,x))
            idx = idx+1 if idx+1<len(keyword) else 0
        else:
            new_message.append(c)
    return ''.join(new_message)
Answered By: Ben Grossmann
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.