Search code examples
encryptioncs50vigenere

Change and wrap keyword integers without loop in C


I'm writing a program that accepts a string at the command prompt then converts each character of the string to corresponding 0-25 digit of the alphabet. Each digit is then used to encipher each character of another string the user enters after being prompted by the program. Each alphabetic character of the second string should match the order of the string of integers and the string of integers will wrap if the second string is longer. The goal of the program is the use the first string as a key to shift each character of a message (the second string).

Example (desired output): User runs program and enters keyword: bad

User is prompted to enter string of alphabetical characters and punctuation only: Dr. Oz

Program converts keyword 'bad' into 1,0,3

Program enciphers message into Er. Ra

What I actually get is:

… T.B.S. …

I've tried many things but unfortunately I can't seem to figure out how to loop and wrap the key without looping the second message. If you run the program you will see my problem.

#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

int shift(char key1);

int main(int argc, string argv[]) // user enter number at cmd prompt
{
    if (argv[1] == '\0')
    {
        printf("Usage: ./vigenere keyword\n");
        return 1;
    }
    string key = argv[1]; // declare second arg as string
    for (int i = 0, n = strlen(key); i < n; i++)
        if (isdigit(key[i]) != 0 || argc != 2)
        {
            printf("Usage: ./vigenere keyword\n");
            return 1;
        }
    string text = get_string("plaintext: ");
    printf("ciphertext: ");
    int k;
    char t;

    for (int j = 0, o = strlen(text); j < o; j++)
    {
        t = text[j];
        for (int i = 0, n = strlen(key); i < n; i++)
        {
            k = shift(key[i]);
            if (isupper(t))
            {
                t += k;
                if (t > 'Z')
                {
                    t -= 26;
                }
            }
            if (islower(t))
            {
                t += k;
                if (t > 'z')
                {
                    t -= 26;
                }
            }
            printf("%c", t);
        }
    }

    printf("\n");
}

int shift(char key1)
{
    int k1 = key1;
    if (islower(key1))
    {
        k1 %= 97;
    }
    if (isupper(key1))
    {
        k1 %= 65;
    }
    return k1;
}

I appreciate any help and suggestions but please keep in mind the solution should match the level of coding my program suggests. There may be many advanced ways to write this program but unfortunately we are still in the beginning of this course so showing new methods (which I will definitely try to understand) may go over my head.


Solution

  • Here's a modified version of your code, with changes based on my comments:

    #include <cs50.h>
    #include <stdio.h>
    #include <string.h>
    #include <ctype.h>
    
    int shift(char key1);
    
    int main(int argc, string argv[]) // user enter number at cmd prompt
    {
        if (argc != 2 || argv[1][0] == '\0')
        {
            fprintf(stderr, "Usage: ./vigenere keyword\n");
            return 1;
        }
        string key = argv[1]; // declare second arg as string
        for (int i = 0, n = strlen(key); i < n; i++)
        {
            if (!isalpha(key[i]))
            {
                fprintf(stderr, "Usage: ./vigenere keyword\n");
                return 1;
            }
        }
        string text = get_string("plain text: ");
        printf("ciphertext: ");
    
        int keylen = strlen(key);
        int keyidx = 0;
        for (int j = 0, o = strlen(text); j < o; j++)
        {
            int t = text[j];
            if (isupper(t))
            {
                int k = shift(key[keyidx++ % keylen]);
                t += k;
                if (t > 'Z')
                    t -= 26;
            }
            else if (islower(t))
            {
                int k = shift(key[keyidx++ % keylen]);
                t += k;
                if (t > 'z')
                    t -= 26;
            }
            printf("%c", t);
        }
    
        printf("\n");
    }
    
    int shift(char key1)
    {
        if (islower(key1))
            key1 -= 'a';
        if (isupper(key1))
            key1 -= 'A';
        return key1;
    }
    

    The test for exactly two arguments and for a non-empty key are moved to the top. This is slightly different from what was suggested in the comments. The error messages are printed to standard error, not standard output. I'd probably replace the second 'usage' message with a more specific error — the key may only contain alphabetic characters or thereabouts. And the errors should include argv[0] as the program name rather than hard-coding the name. The key validation loop checks that the key is all alphabetic, rather than checking that they are not digits — there are more character classes than digits and letters. The code uses keyidx and keylen to track the length of the key and the position in the key. I use single-letter variable names, but usually only for loop indexes or simple pointers (usually pointers into strings); otherwise I use short semi-mnemonic names. There are two calls to shift() so that keyidx is only incremented when the input character is a letter. There are other ways that this could be coded.

    One very important change not foretold in the comments is the change of type for t — from char to int. When it is a char, if you encrypt letter z with a letter late in the alphabet (e.g. y), the value 'z' + 24 overflows the (signed) char type prevalent on Intel machines, giving a negative value (most typically; formally, the behaviour is undefined). That leads to bogus outputs. Changing to int fixes that problem. Since the value of t is promoted to int anyway when passed to printf(), there is no harm done in the printing. I used the prompt plain text: with a space so that the input and output align on the page.

    I decided not to use the extra local variable k1 in shift(). I also used subtraction instead of modulus as noted in the comments.

    Given the program cc59 created from cc59.c, a sample run is:

    $ cc59 bad
    plain text: Dr. Oz
    ciphertext: Er. Ra
    $ cc59 zax
    plain text: Er. Ra
    ciphertext: Dr. Oz
    $ cc59 ablewasiereisawelba
    plain text: The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs. The five boxing wizards jump quickly. How vexingly quick daft zebras jump. Bright vixens jump; dozy fowl quack. 
    ciphertext: Tip uqius fisef fkb uvmpt zzar lpi cehq dkk. Abck nj fkx oqxy jqne zskfn ljbykr bckj. Xpw fezp coxjyk sirivuw rmml ufjckmj. Lkw nmbzrody mytdk dbqx vetzej ncep. Xvthht wtbank rydt; lgzu jzxl qvlgg.
    $ cc59 azpweaiswjwsiaewpza
    plain text: Tip uqius fisef fkb uvmpt zzar lpi cehq dkk. Abck nj fkx oqxy jqne zskfn ljbykr bckj. Xpw fezp coxjyk sirivuw rmml ufjckmj. Lkw nmbzrody mytdk dbqx vetzej ncep. Xvthht wtbank rydt; lgzu jzxl qvlgg.
    ciphertext: The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs. The five boxing wizards jump quickly. How vexingly quick daft zebras jump. Bright vixens jump; dozy fowl quack.
    $
    

    The decrypting keys were derived by matching the 'encrypting' letters in row 1 with the decrypting letters in row 2 of the data:

    abcdefghijklmnopqrstuvwxyz
    azyxwvutsrqponmlkjihgfedcb
    

    With encryption and decryption, the most basic acid test for the code is that the program can decrypt its own encrypted output given the correct decrypting key and the cipher text.