-2

Problem: take a string and move every character in the alphabet 13 times forward; for example 'hello' would be 'uryyb', but the trick here is that if there is a vowel in the element before then its 14 spaces so it would be 'urzyb'. I got the 14 space but then nothing else happens to the other letters so I keep getting 'hezlo', but if remove the // and use this line of code

message[i] = message[i] + key;`

then it doesn't do the 14 and only does 13 times. Can someone explain what I'm doing wrong and how to fix it?

#include<stdio.h>

int main()
{
char message[100], ch;
int i, key;

printf("Enter a message to encrypt: ");
gets(message);
printf("Enter key: ");
scanf("%d", &key);

for(i = 0; message[i] != '\0'; ++i){

    if(message[i] >= 'a' && message[i-1] <= 'z'){
        if(message[i-1] == 'e'){
            message[i]=message[i] + 14;
        }

        //message[i] = message[i] + key;

        if(message[i] > 'z'){
            message[i] = message[i] - 'z' + 'a'-1 ;
        }

        message[i] = message[i];
    }
}

printf("Encrypted message: %s", message);

return 0;
} 

Output is hezlo should be urzyb

  • Will you please use some punctuation in your question, and split that paragraph into sentences? I have no idea how to even read it :( – virolino Nov 01 '19 at 05:05
  • 1
    `message[i-1] <= 'z'`. For `i=0` this can crash your code. Also, where are you adding 13 to the current character? Is it the commented line? Then inside `if(message[i-1] == 'e')` you might want to add `continue`. Otherwise the `l` after `e` in `hello` will be increment `14 + key` times. You don't want that. Also last line of your loop is useless – kuro Nov 01 '19 at 06:06
  • Make sure you read [Why gets() is so dangerous it should never be used!](https://stackoverflow.com/questions/1694036/why-is-the-gets-function-dangerous-why-should-it-not-be-used) – David C. Rankin Nov 01 '19 at 06:27
  • I wonder if you've tried my answer. – Taha Paksu Nov 01 '19 at 19:53

4 Answers4

1

I have three advises for you.

  1. Don't use gets, it is deprecated and for good reason, use fgets instead.
  2. Since you are modifying the message character by character. You cannot look back at the previous character using message[i-1] to see if that was a wovel, because it was already shifted in the previous iteration of the loop. store the previous character in a separate variable instead.
  3. Since you are wrapping back to 'a' when you reach 'z', consider using the modulus arithmetic, which is used to cycle the numbers in a given range.

see the code below with these ideas applied to your code.

int main()
{
    // ...
    printf("Enter a message to encrypt: ");
    fgets(message,100,stdin);
    printf("Enter key: ");
    scanf("%d", &key);

    char p = 1; // some initial value that is not a vowel.
    for(i = 0; message[i] != '\0'; ++i){
        if(message[i] >= 'a' && message[i] <= 'z'){
            char ch = (message[i]-'a' + key) % 26 + 'a'; // using modular math
            if (strchr("aeiou",p)){
                ch++; // increment by 1 if previous character was a vowel
            }   
            p = message[i]; // save previous character
            message[i]=ch;  // update message.
        }   
    }   
    printf("Encrypted message: %s", message);
    return 0;
}
AliA
  • 690
  • 6
  • 8
  • thank you very much! so far yours is the one that works the best – david ponce Nov 02 '19 at 23:22
  • thanks a lot so just one quick follow up question how would I be able to maybe make and array and have each word of the array get pass to this algorthim like for exm "how many words you want to encrypt" inpout would be the size of the array and then a for loop to scanf every word into the array so it could be encrypted? any suggestions? – david ponce Nov 29 '19 at 10:02
0

First of all, pay attention to things like this one:

if(message[i-1] == ...

because on the first iteration the index i is 0: so, i-1 is negative, and does not address a valid character of the string. This is an error. The way to solve this is that if the index (i) is 0, then there is no previous character, so you know it can not be a vowel.

Second, you state that you want to "slide" the characters: then you must include a statement to do that, like this:

message[i] = message[i] + key;

Try to describe the algorithm in plain english, then translate it in C code. Something like this:

Scan all characters, and add 13 to each; but if the preceding char is a vowel, than add 14 instead of 13.

The direct outcome of the previous sentence goes like this:

for(i = 0; message[i] != '\0'; i++) {
  if (i > 0) {
    // we are past the first letter
    if (message[i-1]=='a' || message[i-1]=='e' || ...)
      message[i] += 14;
      else message[i] += 13;
  } else {
    // it's the first letter, there can not be a preceding vowel
    message[i] += 13;
  }
}

The above algorithm has some problem - mainly, it does tests for vowels on the already modified message. And it is slightly too verbose. Better to do something like this (warning, untested):

int wasvowel = 0;
char previous;

for(i = 0; message[i] != '\0'; i++) {

  previous = message[i];  // save for later

  if (wasvowel)
    message[i] += key+1;
    else message[i] += key;

  wasvowel = previous=='a' || previous=='e' ...
}

The code above misses some checks; it is not clear what to do if the translated char becomes a not-letter, for example. But the general idea is there.

A note about the variable "previous" (perhaps the name is not very correct). The algorithm must consider each char in order to determine whether it is a vowel or not. But this check must be made before changing the string. Imagine what happens with words having two vowels in a row, like "aerial". The letter 'e' must be slided 14 times, ok, but the letter 'r' too. So we must remember that the letter before the 'r' was a vowel, and to do that we must preserve the 'e'. There are other methods to do that, but this one is simple.

Another note about the variable wasvowel. Prior to the cycle its value is set to 0: of course there is no vowel before the start of the message. Then in the cycle wasvowel is set, ready for the next iteration. In the last iteration, wasvowel is set again, but the value will be never used. This is, I think, acceptable in this context - it is possible that, in another context, it would not.

  • the only issue is that after a certain character it stars printing funky character like if you do world the output is "ä|yq" – david ponce Nov 01 '19 at 13:29
  • @davidponce yes, I stated this in my answer ("it is not clear what to do if the translated char becomes a not-letter"). There was some code in your post, but not explained, so I preferred to not rely on it. – linuxfan says Reinstate Monica Nov 02 '19 at 05:36
  • thanks a lot so just one quick follow up question how would I be able to maybe make and array and have each word of the array get pass to this algorthim like for exm "how many words you want to encrypt" inpout would be the size of the array and then a for loop to scanf every word into the array so it could be encrypted? any suggestions? – david ponce Nov 29 '19 at 10:01
  • @davidponce I think you can use the algorithm above, passing it the array (char*) and, if not null-terminated, its size. A declaration like "int crypt(char* message, char key) {...} would be compatible with the above code fragment. – linuxfan says Reinstate Monica Nov 29 '19 at 10:44
0

Because you are overwriting the main string, the e becomes r and then you can't read e to compare to anymore. That's why you are getting uryyb instead of urzyb. This is a modified code with an alternative vowel check method and another buffer for the modified string, keeping the original intact:

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

int main()
{
    unsigned char message[100], original[100], ch;
    int i, key, len;

    memset(message, 0, 100);
    memset(original, 0, 100);

    printf("Enter a message to encrypt: ");
    scanf("%s", original);

    printf("Enter key: ");
    scanf("%d", &key);

    for(i = 0, len = strlen(original); i < len; i++){
        if(original[i] >= 'a' && original[i] <= 'z'){
            if(i > 0 && memchr("aeiou", original[i-1], 5)){
                message[i] = original[i] + 14;
            }else{
                message[i] = original[i] + 13;
            }            
        }
    }

    for(i = 0, len = strlen(message); i < len; i++){
        if(message[i] > 'z'){
            message[i] = message[i] - 'z' + 'a' - 1;
        }
    }

    printf("Encrypted message: %s", message);

    return 0;   
}

Edit: moved the overflow fix outside another loop.

Edit: forgot an crucial part, chars need to be unsigned.

https://onlinegdb.com/ryo2dKR5H

Taha Paksu
  • 15,371
  • 2
  • 44
  • 78
  • hey man! yours also works quite well the only problem im having is that it's outputting – david ponce Nov 01 '19 at 21:43
  • so i'm also having the same issue as I did with the other one on this one for example on characters closer to "z" get funky character for example if the word I want to encrypt is "world" the output should be "jbfyq" but i get "äbÇyq" – david ponce Nov 01 '19 at 21:49
  • nope the output is still äbÇyq, when the input is world – david ponce Nov 02 '19 at 20:10
  • Sorry for the late reply, the chars need to be unsigned. Forgot to change that. – Taha Paksu Nov 05 '19 at 05:29
  • thanks a lot so just one quick follow up question how would I be able to maybe make and array and have each word of the array get pass to this algorthim like for exm "how many words you want to encrypt" inpout would be the size of the array and then a for loop to scanf every word into the array so it could be encrypted? any suggestions? – david ponce Nov 29 '19 at 10:01
0

First, don't use gets() it is so vulnerable to exploit by buffer overrun is has been completely removed from the current C standard library, see Why gets() is so dangerous it should never be used!

While there is nothing wrong with reading and buffering your input (using fgets() or POSIX getline()), there is no real need to do so if you simply want to output the encrypted input. You can use getchar() to read each character of input and simply convert each character as they are read.

Regardless whether you buffer the input of just convert it on the fly, organizing your +13 (or if previous char was a vowel +14) logic into a simple function that takes the current character c and the previous character prev can help keep your logic straight and code clean, e.g.

/* simple function to encrypt lowercase chars,
 * prev == vowel, c + 14, else c + 13
 */
char encrypt (const char c, const char prev)
{
    char enc;

    if (!islower (c))   /* validate c is lowercase */
        return c;

    if (prev == 'a' || prev == 'e' || prev == 'i' ||    /* if prev is vowel */ 
        prev == 'o' || prev == 'u')
        enc = 'a' + (c - 'a' + 14) % 26;    /* add 14 to c */
    else
        enc = 'a' + (c - 'a' + 13) % 26;    /* add 13 to c */

    return enc;     /* return encrypted char */
}

(note: the validation of the input with islower() from the ctype.h header ensures your conversion is applied to only lowercase characters. You can expand it to handle both cases -- that is left to you)

Also note the logic of the addition wraps back to the beginning in case adding +13 (or +14) would result in a character beyond 'z'. (for example 'z' + 13 == 'm'). You can adjust as required.

Then your code becomes simply:

int main (void) {

    int c, prev = 0;

    fputs ("Enter a message to encrypt: ", stdout);     /* prompt */
    while ((c = getchar()) != '\n' && c != EOF) {       /* read each char */
        putchar (encrypt (c, prev));                    /* output encrypted */
        prev = c;   /* save current as prev */
    }
    putchar ('\n'); /* tidy up with newline */
}

Putting it altogether in a short example and adding the two required header files, you could do:

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

/* simple function to encrypt lowercase chars,
 * prev == vowel, c + 14, else c + 13
 */
char encrypt (const char c, const char prev)
{
    char enc;

    if (!islower (c))   /* validate c is lowercase */
        return c;

    if (prev == 'a' || prev == 'e' || prev == 'i' ||    /* if prev is vowel */ 
        prev == 'o' || prev == 'u')
        enc = 'a' + (c - 'a' + 14) % 26;    /* add 14 to c */
    else
        enc = 'a' + (c - 'a' + 13) % 26;    /* add 13 to c */

    return enc;     /* return encrypted char */
}

int main (void) {

    int c, prev = 0;

    fputs ("Enter a message to encrypt: ", stdout);     /* prompt */
    while ((c = getchar()) != '\n' && c != EOF) {       /* read each char */
        putchar (encrypt (c, prev));                    /* output encrypted */
        prev = c;   /* save current as prev */
    }
    putchar ('\n'); /* tidy up with newline */
}

Example Use/Output

$ ./bin/charencrypt
Enter a message to encrypt: hello
urzyb

Look things over and let me know if you have further questions.

Edit - Converting an Array of Chars

To read into a buffer (array) of char and then convert each character in the buffer based on the same logic, requires little change. The only change required is instead of converting each character on-the-fly, you read your input into a buffer, then loop over each character in the buffer making the conversions. (only caveat, you must save the prev/last char before making the conversion so you know how to correctly apply the next conversion)

The changes needed to the above are minimal. The follow uses the exact same encrypt function and a single character array to store what is read from the user and then the conversions are made in-place updating the characters in the same array, e.g.

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

#define MAXC 2048   /* don't skimp on buffer size (except for embedded dev.) */

/* simple function to encrypt lowercase chars,
 * prev == vowel, c + 14, else c + 13
 */
char encrypt (const char c, const char prev)
{
    char enc;

    if (!islower (c))   /* validate c is lowercase */
        return c;

    if (prev == 'a' || prev == 'e' || prev == 'i' ||    /* if prev is vowel */ 
        prev == 'o' || prev == 'u')
        enc = 'a' + (c - 'a' + 14) % 26;    /* add 14 to c */
    else
        enc = 'a' + (c - 'a' + 13) % 26;    /* add 13 to c */

    return enc;     /* return encrypted char */
}

int main (void) {

    char buf[MAXC], *p = buf;   /* buffer and pointer to buffer */
    int current, prev = 0;

    fputs ("Enter a message to encrypt: ", stdout);     /* prompt */
    if (!fgets (buf, MAXC, stdin)) {                    /* read into buffer */
        fputs ("(user canceled input)\n", stdout);
        return 1;
    }
    while (*p && *p != '\n') {              /* read each char */
        current = *p;                       /* save current */
        *p = (encrypt (*p, prev));          /* encrypt char */
        prev = current;                     /* set prev to current */
        p++;                                /* advance to next char */
    }
    fputs (buf, stdout);                    /* output converted buffer */
}

How you loop over the characters in the array is up to you. You can use a for loop with array indexes, or simply use a pointer and advance the pointer to the next character on each iteration until you reach the nul-terminating character or '\n' character, each of which would signify the end of the characters you are converting.

If you want to use a second array so that you preserve both the original and have the new encrypted array, just declare another array and fill it in the same manner, except instead of writing the converted values back to the original array, write it to your second array (don't forget to nul-terminate the second array)

Example Use/Output

The out is exactly the same:

$ ./bin/charencryptbuf
Enter a message to encrypt: hello
urzyb

Let me know if you have further questions.


Edit Based On Comment To Encrypt Multiple-Words

If you simply want to prompt the user for the number of words to encrypt, that as in my comment, you just need to wrap what you are doing in a loop. Prompt the user for the number of words to encrypt, then loop that number of times encrypting each word. The changes to the code above are minimal, e.g.

int main (void) {

    char buf[MAXC];                 /* buffer to hold input */
    int nwords;                     /* no. of words to encrypt */

    fputs ("How may words to encrypt?: ", stdout);  /* prompt no. of words */
    if (!fgets (buf, MAXC, stdin) || sscanf (buf, "%d", &nwords) != 1 ||
            nwords < 1) {
        fputs ("error: invalid integer input or nothing to do.\n", stderr);
        return 1;
    }
    for (int i = 0; i < nwords; i++) {
        char *p = buf;                          /* pointer to buf */
        int current, prev = 0;                  /* current and prev chars */
        printf ("\nenter word[%d]: ", i + 1);   /* prompt */
        if (!fgets (buf, MAXC, stdin)) {        /* read into buffer */
            fputs ("(user canceled input)\n", stdout);
            return 1;
        }
        while (*p && *p != '\n') {              /* read each char */
            current = *p;                       /* save current */
            *p = (encrypt (*p, prev));          /* encrypt char */
            prev = current;                     /* set prev to current */
            p++;                                /* advance to next char */
        }
        fputs (buf, stdout);                    /* output converted buffer */
    }
}

Example Use/Output

$ ./bin/charencryptbufmulti
How may words to encrypt?: 6

enter word[1]: hello
urzyb

enter word[2]: there
gurfr

enter word[3]: how
ubk

enter word[4]: are
nfr

enter word[5]: you
lbi

enter word[6]: hello
urzyb

Separating And Encrypting Any Number of Words Entered by the User Individually

To encrypt multiple words separately, you just need to do what you are doing for the whole string but separating the input into tokens (individual words). C provides the strtok() function to do just that, but you will want to make a copy of the entire input string if you need to preserve it as strtok() modifies the original. You can also simply make a copy of each token to preserve the original word and encrypted word separately.

An easy way to implement the addition is just to write a small wrapper-function that takes whole words as an input parameter and then passes the word to the existing encrypt() function. So for the encrypt-word (or encrypt-wrapper), you could do:

/** Simple wrapper function that takes a word and passes it to encrypt */
char *encryptw (char *buf)
{
    char *p = buf;          /* pointer to buffer holding word */
    int current, prev = 0;  /* current and previous characters */

    while (*p) {                            /* read each char */
        current = *p;                       /* save current */
        *p = (encrypt (*p, prev));          /* encrypt char */
        prev = current;                     /* set prev to current */
        p++;                                /* advance to next char */
    }

    return buf;
}

(note: the char* type and return buf; is just a convenience to allow you make immediate use of the encrypted word, e.g. char word[] = "hello"; puts (encryptw (word)); Also note, encryptw() modifies the input, so you could not pass a string-literal, e.g. encryptw("hello");)

Having moved the code that encrypts a word into the encryptw() function, all you need to do in main() is separate the words into tokens and pass each token to encryptw() to encrypt and then output the results. You must include string.h for strtok() as well as for strcpy(), e.g.

#include <string.h>
...
#define MAXW  128       /* max individual word size to encrypt */
#define DELIM " \t\n"   /* strtok delimiters */
...
int main (void) {
    ...
    /* loop separating buf into individual words to encrypt */
    p = strtok (buf, DELIM);    /* 1st call - pass buf */
    while (p) {                 /* validate return not NULL */
        strcpy (word, p);       /* make copy, to preserve original */
        encryptw (word);        /* pass word to encryptw to encrypt word */
        /* output word, original and encrypted */
        printf ("word[%2zu]  :  %-12s  :  (%s)\n", ++n, p, word);
        p = strtok (NULL, DELIM);   /* all subsequent calls - pass NULL */
    }
}

(note: above the output is now the word number encrypted, e.g. word[1].. followed by the original word and then the encrypted word in parenthesis)

The full code containing all changes would be:

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

#define MAXC 2048   /* don't skimp on buffer size (except for embedded dev.) */
#define MAXW  128       /* max individual word size to encrypt */
#define DELIM " \t\n"   /* strtok delimiters */

/* simple function to encrypt lowercase chars,
 * prev == vowel, c + 14, else c + 13
 */
char encrypt (const char c, const char prev)
{
    char enc;

    if (!islower (c))   /* validate c is lowercase */
        return c;

    if (prev == 'a' || prev == 'e' || prev == 'i' ||    /* if prev is vowel */ 
        prev == 'o' || prev == 'u')
        enc = 'a' + (c - 'a' + 14) % 26;    /* add 14 to c */
    else
        enc = 'a' + (c - 'a' + 13) % 26;    /* add 13 to c */

    return enc;     /* return encrypted char */
}

/** Simple wrapper function that takes a word and passes it to encrypt */
char *encryptw (char *buf)
{
    char *p = buf;          /* pointer to buffer holding word */
    int current, prev = 0;  /* current and previous characters */

    while (*p) {                            /* read each char */
        current = *p;                       /* save current */
        *p = (encrypt (*p, prev));          /* encrypt char */
        prev = current;                     /* set prev to current */
        p++;                                /* advance to next char */
    }

    return buf;
}

int main (void) {

    char buf[MAXC], *p = buf,   /* buffer and pointer to buffer */
        word[MAXW];             /* array for word to encrypt */
    size_t n = 0;

    fputs ("Enter a message to encrypt: ", stdout);     /* prompt */
    if (!fgets (buf, MAXC, stdin)) {                    /* read into buffer */
        fputs ("(user canceled input)\n", stdout);
        return 1;
    }
    putchar ('\n'); 

    /* loop separating buf into individual words to encrypt */
    p = strtok (buf, DELIM);    /* 1st call - pass buf */
    while (p) {                 /* validate return not NULL */
        strcpy (word, p);       /* make copy, to preserve original */
        encryptw (word);        /* pass word to encryptw to encrypt word */
        /* output word, original and encrypted */
        printf ("word[%2zu]  :  %-12s  :  (%s)\n", ++n, p, word);
        p = strtok (NULL, DELIM);   /* all subsequent calls - pass NULL */
    }
}

Example Use/Output

Combining your original string from your question "hello" with the phrase from your last comment "how many words you want to encrypt", running the program and passing the combined string would result in:

$ ./bin/charencryptbufmulti
Enter a message to encrypt: hello how many words you want to encrypt

word[ 1]  :  hello         :  (urzyb)
word[ 2]  :  how           :  (ubk)
word[ 3]  :  many          :  (znbl)
word[ 4]  :  words         :  (jbfqf)
word[ 5]  :  you           :  (lbi)
word[ 6]  :  want          :  (jnbg)
word[ 7]  :  to            :  (gb)
word[ 8]  :  encrypt       :  (rbpelcg)

Let me know if that is what you described in your last comment and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • so far yours is working the best! thank you but what if I wanted to make like an arrays of words, and wanted each of those words to do the encryption? how would I do that with the stdout function? – david ponce Nov 01 '19 at 13:25
  • @davidponce I dropped an update showing how you would use a character array to store your input and then encrypt that buffer in-place. – David C. Rankin Nov 01 '19 at 14:58
  • thanks a lot so just one quick follow up question how would I be able to maybe make and array and have each word of the array get pass to this algorthim like for exm "how many words you want to encrypt" inpout would be the size of the array and then a for loop to scanf every word into the array so it could be encrypted? any suggestions? – david ponce Nov 29 '19 at 10:01
  • Fairly easy. You will just add a counter (e.g. `int nwords = 0;`), prompt to have to user enter the value, and then wrap everything from `fputs ("Enter a message to encrypt: ", stdout);` to `fputs (buf, stdout); }` in a `for (int i = 0; i < nwords; i++) { ... }` loop. That way the user inputs the number of words (`nwords`) and you loop that many times doing the same thing you did before. Let me know if you have problems. – David C. Rankin Nov 29 '19 at 17:27
  • oh man that's hard to follow, ive just started using c recently and I'm not very familiar how stdout and stdin and how arrays work with this commands, any pointers? – david ponce Nov 30 '19 at 14:32
  • @davidponce I added an update that separates all words entered as input into individual tokens (words) and then show how to encrypt each individually. Let me know if that is what you were describing. If you want to take the words individually as input (e.g. the user says 5-words, then prompt for each of the words), let me know -- that was easier to do as described in my last comment, but it looked like you want to take all the words at once and use them individually. – David C. Rankin Nov 30 '19 at 23:20
  • @davidponce I added examples for both prompting for the number of words to encrypt and then looping taking and encrypting each word separately, and also an example that takes the entire input provided by the user and separates each word and provides the individual encryption results for each word. That should cover each interpretation. – David C. Rankin Nov 30 '19 at 23:45
  • so I tested the code but now on the second encryption if it ended with a vowel than it does +14 rather than restart encrypting for example input 2, hello, world the output is "urzyb", "kbfyq" while the first one is right the second one isn't it should be "jbfyq", any idea why its doing that? – david ponce Dec 02 '19 at 14:46
  • I'll take a look in a bit. I suspect adding the loop there is a variable that needs to be reset that isn't. All words should encrypt the same... – David C. Rankin Dec 02 '19 at 19:12
  • @davidponce - yep, you were right, and I knew what it was... `current` and `prev` just needed to be reinitialized for each word. You do that simply by moving them into the scope where they are needed rather than leaving them declared in outer scope in `main()`. Works now. – David C. Rankin Dec 02 '19 at 20:27