1

I have an assignment to create a program that converts each character in a string by a set number entered at the command line. Example: if the user enters 1 at the command line then enters abc, def then the program should convert the string to bcd, efg.

I've written the program but I can't figure out how to get the program to not convert the punctuation characters.

The program currently converts abc, def and prints bcdefg. It needs to print bcd, efg and include the punctuation characters without converting them.

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

int main(int argc, string argv[]) //user enter number at cmd prompt

{
    string key = argv[1]; //store user entered number
    int k = atoi(argv[1]); //only accept consecutive digits
    if (argc != 2 || k == 0) 
    {    
        printf("Usage: ./caesar key\n"); /*show error if user enters non-consecutive digits*/
        return 1;
    }
    string original = get_string("Plaintext: "); /* prompt user for message to spin*/

    for (int i = 0, n = strlen(original); i < n; i++)  /* get string length and loop char change*/            
        if (isalnum(original[i])) /* only convert alphanumeric character*/                    
            printf("%c", original[i] + k); /* print and convert character by number entered at prompt*/

    printf("\n");
    return 0;
}
Clifford
  • 88,407
  • 13
  • 85
  • 165
  • what if `!isalnum(original[i])`? what if `original[i] == 'z' && k == 1` for example? and why `isalnum`, do you need to convert numbers too? also `original[i] + k` isn't portable https://stackoverflow.com/questions/58003555/string-repetition-replaced-by-hyphen-c?noredirect=1#comment102412380_58003555 – fas Sep 20 '19 at 19:32
  • Whilst single statements in conditional blocks don't need to be enclosed in `{`...`}`, I would strongly advise it for clarity and simpler code-maintenance If you choose not to, you should at least use conventional indentation to show structure - I fixed that for you. – Clifford Sep 20 '19 at 23:00
  • The cipher algorithm described is impractical and non-reversible, for example for an ASCII character set, if the plain-text were "`9:`", and k were 1, the result would be "`::`", and it is then ambiguous what the original text may have been. Traditionally a Caesar cipher wraps around such that for k = 1, Z becomes A. If you include digits, then you will need to define the character sequence, because they are not contiguous, and `'A'` is distinct from `'a'` also. Have you really presented the assignment accurately? – Clifford Sep 20 '19 at 23:27
  • The points I have raised regarding case sensitivity, wrap-around, character set dependency are dealt with in the answer (to a different question) at https://stackoverflow.com/questions/57655966/wanted-to-do-caesars-cipher-but-couldnt-change-the-last-two-characters/57658088#57658088 – Clifford Sep 20 '19 at 23:31
  • If it turns out that your description of the assignment is inaccurate, fix the question to get better answers, if the assignment itself is flawed, raise it with your tutor perhaps? – Clifford Sep 20 '19 at 23:42
  • `strlen()` has to iterate the string to find the nul terminator, but you are iterating the string in any case, so you can instead simply loop while `original[i] != '\0'`. – Clifford Sep 20 '19 at 23:58

2 Answers2

1

You are only outputting the characters that your are transforming (those that are in the isalnum set). You need to also output the characters not transformed. For example:

    char cipher = original[i] ;
    if( isalnum( original[i] )
    {
        cipher += k ;
    }

    printf( "%c", cipher ) ;

However the algorithm as described remains deeply flawed in several ways, but it is not clear whether it is the assignment that is flawed (in which case that is not your problem) or if your description of the assignment is inaccurate.

A more practical solution might look like:

#include <ctype.h>

char caesar( char x, int key )
{
    const char alphabet[] = {'a','b','c','d','e','f','g','h',
                             'i','j','k','l','m','n','o','p',
                             'q','r','s','t','u','v','w','x',
                             'y','z',
                             '0','1','2','3','4','5','6','7','8','9'};

    char cipher = x  ;

    for( int i = 0;
         cipher == x && i < sizeof( alphabet );
         i++ )
    {
        if( alphabet[i] == tolower( x ) )
        {
            cipher = alphabet[(i + key) % sizeof( alphabet )] ;
            if( isupper( x ) )
            {
                cipher = toupper( cipher ) ;
            }
        }
    }

    return cipher ;
}

Then your output loop would be:

for( int i = 0; original[i] != '\0' ); i++)
{
    printf("%c", ceasar( original[i], k ) ) ;
}
Clifford
  • 88,407
  • 13
  • 85
  • 165
  • The first example is in the range of knowledge of where we're expected to be in the class so it's perfect. The second example wasn't covered yet. Thanks for your help. One day I hope to be as good as you. – Sam Shields Sep 21 '19 at 13:11
  • @SamShields : Very pragmatic; do what is asked of you to get the grade; worry about whether it actually makes any sense when you have a real task to solve. – Clifford Sep 21 '19 at 13:47
-1

What you're currently doing only prints the character + k if it's alpha-numeric, but not printing it otherwise. You you really want to do is only add k if it is alpha-numeric. With a little boolean logic and math, we can avoid the if-statement altogether.

printf("%c", original[i] + (k * !!isalnum(original[i])));

If isalnum returns true, !! will make the resulting value 1 (it can return non-zero value if true; double-negating logically will make it either 1 if true or 0 if false). So now we add k if it's alpha-numeric, or add nothing otherwise, and print the character either way.

Christian Gibbons
  • 4,272
  • 1
  • 16
  • 29