-1

This is my Vigenere Cipher code. I'm trying to use the do while loop to repeat the iteration over the key entered by the user. The key is applied to the plaintext word entered by the user, one letter at a time. I need to be able to iterate over the key until the end of the plaintext word, in case the key is shorter than the word. I've tried to add the repeat using a do-while loop with the first for loop being the key iteration.

The last line of the 'do while' loop is throwing the error of undeclared identifier i. This is the only error I'm getting. The inside 'for' loop is from the Caesar Cipher and passed all checks. I think my do while loop is wrong. If I add a definition of char* word[i] or word[i] I get an overshadowing error regardless of where I put it. I would like to use this code rather than completely change it so I understand if it's possible to do it this way. Any suggestions, however, would be welcome.

int main(int argc, char* argv[])
{
    if (argc<2) //key
    {
        printf("Please enter your word key"); //prompts if no key is entered
        return 1;
    }

    char* key = (argv[1]);

    if(argc>=2)
    {
        printf("plaintext:");
        char* word = GetString();

        printf("ciphertext:");

        do
        {  //starts loop to repeat following for loop

            for(int l=0; l<strlen(key); l++) //iterate over letters in key
            {
                int num=l;
                for(int i=0; i<strlen(word); i++)  //iterates through word entered by user as plaintext
                {
                    if(isupper(word[i]))  //if original characters are uppercase
                    {
                        int cipher = (word[i] + num -65) % 26 + 65;
                        printf("%c", cipher);
                    }
                    else if(islower(word[i]))  //if original characters are lowercase
                    {
                        int cipher = (word[i] + num - 97) % 26 + 97;
                        printf("%c", (cipher));
                    }
                    else    //all other types of characters
                    {
                        printf("%c", word[i]);
                    }
                }
            }
            printf("\n");
        }while((word[i])<strlen(word));  // loop to recommence iterating over letters in the key   (i throwing undeclared identifier error)
    }
}
Hearner
  • 2,711
  • 3
  • 17
  • 34
Android
  • 11
  • 8
  • 1
    That `i` is declared in the `for` loop's initialisation expression, so it's only valid inside the `for` loop... that's why you can't test it in the `do...while()` condition. Just declare it before the `do...while()` instead of in the `for`'s initialisation. Also remember that after the `for`, `i` will be one larger than it was on the last pass through the `for` loop. – Dmitri Mar 15 '17 at 18:12
  • The variable `i` is limited in scope to the `for` loop that defines it. It is not accessible outside that loop. If you want to access `i` in the `while` condition of the `do … while` loop, you'll need to define it outside the scope of the `do … while` loop (if you defined it inside, it would not be accessible in the condition; it would go out of existence at the `}` before the condition. – Jonathan Leffler Mar 15 '17 at 18:14
  • I understand that is why it's happening but I can't find a way to fix it. Every time I re-declare i anywhere else in the script I get an over-shadowing error. Can you tell me exactly how to express word [i] again and where to do so to avoid the over-shadowing error in this script as I've tried everything I can think of? Also what is your opinion of my while line of script at the bottom. Is it otherwise suitable? Is there another way I can express this loop to iterate repeatedly over the key as long as there are letters left in word? – Android Mar 15 '17 at 18:24
  • You need to change `for(int i=0` to `for(i=0` to avoid creating a second `i` variable, and declare `int i=0;` before the `do ... while` loop. – Ian Abbott Mar 15 '17 at 18:32
  • The iteration test in the `do ... while` loop looks a little strange. Do you _really_ want to compare the character value at `word[i]` to the length of the `word` string? – Ian Abbott Mar 15 '17 at 18:37
  • Ian thanks for fixing the i issue. The do while loop is probably an issue. The code now compiles but I'm getting the error of \ expected output, not standard error of "/opt/sandbox50/bin/run.sh: line 31: 102..." In the while loop I was trying to iterate over the key repeatedly until the whole word was enciphered. This would be necessary in the case of shorter key words where the plaintext word to be enciphered was longer. How would you express that while loop? – Android Mar 15 '17 at 20:03
  • I think I might have an endless loop which is causing the sandbox error. Line 31 is above the first 'for' loop for some reason. – Android Mar 15 '17 at 22:37

1 Answers1

1

I think you have too many loops [levels].

If the key length runs out before the word is finished, you restart encrypting the word from the beginning of the word (i.e. a bug).

The main thrust is to loop through all word chars. A single loop that increments i works, providing it also increments l [modulo key length].

Here's a cleaned up version [please pardon the gratuitous style cleanup]:

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

// NOTE: I don't have GetString on my system
char *FakeGetString(void);

int
main(int argc, char *argv[])
{

    // key
    // prompts if no key is entered
    if (argc < 2) {
        printf("Please enter your word key");
        return 1;
    }

    char *key = (argv[1]);
    int klen = strlen(key);

    if (argc >= 2) {
        printf("plaintext:");

#if 0
        char *word = GetString();
#else
        char *word = FakeGetString();
#endif

        int wlen = strlen(word);

        printf("ciphertext:");

        // current key index
        int l = 0;

        // starts loop to repeat following for loop
        // iterates through word entered by user as plaintext
        // advance to next key char [with wrap to beginning if we're short]
        for (int i = 0;  i < wlen; ++i, l = (l + 1) % klen) {
            int num = key[l];
            int cipher;

            // if original characters are uppercase
            if (isupper(word[i])) {
                cipher = (word[i] + num - 65) % 26 + 65;
            }

            // if original characters are lowercase
            else if (islower(word[i])) {
                cipher = (word[i] + num - 97) % 26 + 97;
            }

            // all other types of characters
            else {
                cipher = word[i];
            }

            printf("%c", cipher);
        }

        printf("\n");
    }

    return 0;
}

// NOTE: I don't have GetString on my system
char *
FakeGetString(void)
{
    static char buf[1000];
    char *cp;

    fgets(buf,sizeof(buf),stdin);
    cp = strchr(buf,'\n');
    if (cp != NULL)
        *cp = 0;

    return buf;
}

UPDATE:

The above code I wrote was pretty close but wasn't Vigenere because your original equation was off. The key value has to be an offset/row number, so it needs to have 'A' subtracted from it (i.e. keywords can only be uppercase).

So, here is the corrected version [with some additional cleanups]:

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

// NOTE: I don't have GetString on my system
char *FakeGetString(void);

int
baseof(int chr)
{
    int base;

    // if original character is uppercase
    if (isupper(chr)) {
        base = 'A';
    }

    // if original character is lowercase
    else if (islower(chr)) {
        base = 'a';
    }

    // anything else
    else
        base = 0;

    return base;
}

int
main(int argc, char *argv[])
{
    int kval;
    int base;
    int i;

    // key
    // prompts if no key is entered
    if (argc < 2) {
        printf("Please enter your word key\n");
        return 1;
    }

    char *key = argv[1];
    int klen = strlen(key);

    // key must be uppercase and we only want row numbers
    for (i = 0;  i < klen;  ++i) {
        kval = key[i];
        base = baseof(kval);

        if (base) {
            key[i] = kval - base;
            continue;
        }

        printf("Key value must be only A-Z\n");
        return 1;
    }

    if (argc >= 2) {
        printf("plaintext:");

#if 0
        char *word = GetString();
#else
        char *word = FakeGetString();
#endif
        int wlen = strlen(word);

        printf("ciphertext:");

        // starts loop to repeat following for loop
        // iterates through word entered by user as plaintext
        // advance to next key char [with wrap to beginning if we're short]
        for (i = 0;  i < wlen;  ++i) {
            int wval = word[i];
            int cipher;

            base = baseof(wval);

            // uppercase or lowercase
            if (base) {
                kval = key[i % klen];
                cipher = ((wval - base) + kval) % 26 + base;
            }

            // all other types of characters
            else {
                cipher = wval;
            }

            printf("%c",cipher);
        }

        printf("\n");
    }

    return 0;
}

// NOTE: I don't have GetString on my system
char *
FakeGetString(void)
{
    static char buf[1000];
    char *cp;

    fgets(buf,sizeof(buf),stdin);
    cp = strchr(buf,'\n');
    if (cp != NULL)
        *cp = 0;

    return buf;
}

UPDATE #2:

Your code passed all check50 checks except this one: :( encrypts "world, say hello!" as "xoqmd, rby gflkp!" using "baz" as keyword \ expected output, but not "ciphertext:xoqmd, szz gflkp!\n". It didn't encipher just the word 'say' properly, which is weird.

I tested it using the test data/example from the Wikipedia page on Vigenere, but it only had one ready test example [without spaces or punctuation].

This was the only check which contained a space (before the word 'say'). The spaces have to be directly copied. Perhaps that's why.

The spaces were directly copied, so that was okay. But ...

The correct way is that when a non-alpha char is copied, the key index must not be incremented.

My version used i to index the phrase and i % klen to index the key, so the key index would always be incremented [effectively]. That is the bug.

Ironically, I had wondered about this, but didn't have the expanded test data at the time.

So, the solution is to separate the index variables [again :-)].

Here's the corrected version. When I was fixing it I renamed the i variable to something more descriptive (e.g. widx) and (re)created the index variable for the key (e.g. kidx).

Notice that, now, kidx is only incremented when actually encrypting a character. The "pass through" case does not

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

// NOTE: I don't have GetString on my system
char *FakeGetString(void);

int
baseof(int chr)
{
    int base;

    // if original character is uppercase
    if (isupper(chr)) {
        base = 'A';
    }

    // if original character is lowercase
    else if (islower(chr)) {
        base = 'a';
    }

    // anything else
    else
        base = 0;

    return base;
}

int
main(int argc, char *argv[])
{
    int kval;
    int base;
    int widx;
    int kidx;

    // key
    // prompts if no key is entered
    if (argc < 2) {
        printf("Please enter your word key\n");
        return 1;
    }

    char *key = argv[1];
    int klen = strlen(key);

    // key must be uppercase and we only want row numbers
    for (kidx = 0;  kidx < klen;  ++kidx) {
        kval = key[kidx];
        base = baseof(kval);

        if (base) {
            key[kidx] = kval - base;
            continue;
        }

        printf("Key value must be only A-Z\n");
        return 1;
    }

    if (argc < 2)
        return 1;

    printf("plaintext:");

#if 0
    char *word = GetString();
#else
    char *word = FakeGetString();
#endif
    int wlen = strlen(word);

    printf("ciphertext:");

    kidx = 0;

    // starts loop to repeat following for loop
    // iterates through word entered by user as plaintext
    // advance to next key char [with wrap to beginning if we're short]
    for (widx = 0;  widx < wlen;  ++widx) {
        int wval = word[widx];
        int cipher;

        base = baseof(wval);

        // uppercase or lowercase
        if (base) {
            kval = key[kidx];
            cipher = ((wval - base) + kval) % 26 + base;
            kidx = (kidx + 1) % klen;
        }

        // all other types of characters
        else {
            cipher = wval;
        }

        printf("%c",cipher);
    }

    printf("\n");

    return 0;
}

// NOTE: I don't have GetString on my system
char *
FakeGetString(void)
{
    static char buf[1000];
    char *cp;

    fgets(buf,sizeof(buf),stdin);
    cp = strchr(buf,'\n');
    if (cp != NULL)
        *cp = 0;

    return buf;
}
Craig Estey
  • 30,627
  • 4
  • 24
  • 48
  • Your code passed all check50 checks except this one: :( encrypts "world, say hello!" as "xoqmd, rby gflkp!" using "baz" as keyword \ expected output, but not "ciphertext:xoqmd, szz gflkp!\n". It didn't encipher just the word 'say' properly, which is weird. This was the only check which contained a space (before the word 'say'). The spaces have to be directly copied. Perhaps that's why. – Android Mar 18 '17 at 01:50
  • That one passed all of the check50 checks as expected. I will study it, though I don't understand it, then start my code again. Yours seems very different to what I was trying to do. Thanks for your assistance. I gather it's a bit counter-productive that we are not taught to just use the libraries we would find in the real world. – Android Mar 18 '17 at 10:53
  • I've given you 3 versions. Start with the 1st one [closest to your original]. Look at the differences to your own. Then, the 2nd and 3rd. Each one improves the algorithm and the modularity/simplicity [this was deliberate as a teaching tool]. Ask yourself: What is different, why did he do _that_? Your original had 3 loops and mine had one. Think: why? Then, why was `baseof` created (Hint: because, otherwise there would be replicated code). For something as simple as this, there aren't too many alternate ways to do this that don't add needless complexity or code replication – Craig Estey Mar 19 '17 at 01:05
  • Thanks. I will take your advice. I've only been coding in C for a few weeks so I've got a long way to go. ;-} – Android Mar 19 '17 at 15:27
  • I sensed that based on your code, which is why I took the time to incrementally clean up the programming style--to help you learn. I've been doing C for 35+ yrs [you should have seen my code then ;-)]. Not to worry. When learning a new lang, it is normal to absorb material but not quite really understand it [and feel like you're hanging by a thread]. The "Aha!" moment usually takes a few [more] months--and, suddenly, it all floods in and everything you've been studying becomes all so clear. So, just persevere and be patient with yourself. – Craig Estey Mar 19 '17 at 18:15