Search code examples
pythoncs50vigenere

Vigenere cipher in Python not working for uppercase/lowercase letter conversion


As part of the CS50 Harvard programming course, which I'm currently attending, I'm doing an exercise called "Vigenere".

The Cipher of Vigenere is supposed to have a key as input, for example "abcd". This will encode any plaintext, according to each character in the key, where a = 0 and b = 1 etc. So a key "abcd" with plaintext "aa!aa" would give "ab!cd". if the plaintext is longer than the key, the key is supposed to loop back to [0] and start over until plaintext has all been encoded. Non alphabetic letters are supposed to printed out normally however.

My program is doing everything right (it's going line by line and the expected behavior is met) except when I receive input that starts with an uppercase letter followed by a lowercase letter my program prints a different letter with the lowercase key then it should give me. EX: key: "Baz". Plaintext: "aaa". Result: "bgz" where it should return "baz".

Have been googling, debugging but just can't figure it out. Have tried doing it in a lot of other different ways too but I just can't get it to work. (sorry for kind of copy paste, as you might notice I've already posted a similar problem, however that was in C (this is python) and it was a different kind of bug)

Code:

import sys

if len(sys.argv) != 2 or not sys.argv[1].isalpha():
    print("usage: python vigenere.py keyword")
    sys.exit()

cipher = sys.argv[1]
plaintext = input("plaintext: ")

j = 0

def code(j):
    for key in cipher:
        if key.islower():
            return ord(cipher[j]) - 97
        if key.isupper():
            return ord(cipher[j]) - 65

print("ciphertext: ", end="")

for letters in plaintext:
    if letters.islower():
        print(chr(((ord(letters) - 97 + code(j)) % 26) + 97), end="")
        j += 1
    if letters.isupper():
        print(chr(((ord(letters) - 65 + code(j)) % 26) + 65), end="")
        j += 1
    if j == len(cipher):
        j = 0
    if not letters.isalpha():
        print(letters, end="")

print("")

Solution

  • The problem in your code is caused by your code function.

    In it, you use the line for key in cipher:, before checking it with if key.islower(): or if key.isupper():.

    The problem is that every time we enter the code function, due to the for loop we only check if the first letter in the cipher is upper or lower case.

    eg. for cipher 'Baz', j = 0, we check if B is upper/lower, and get upper. We immediately return uppercase B

    For cipher 'Baz', j = 1, we check if B is upper/lower, and get upper. We immediately return upper A. (When we should be checking a for upper/lower, and returning lower a!)


    This problem is fixed by checking the right letter of the cipher for upper or lower case, and can be fixed by replacing for key in cipher: with key = cipher[j], like in the block below:

    def code(j):
        key = cipher[j]
        if key.islower():
            return ord(key) - 97
        if key.isupper():
            return ord(key) - 65
    

    After making the change, for the following inputs:

    cipher = "BazBgh" plaintext = "aaaaAa"

    We get

    ciphertext: bazbGh