Wrapping around array causing unexpected output in c - c

I would expect that the values in the array would repeat in every 3rd character but I do not understand why to out put are different if they are referencing the same index.
Input:
Command line argument: baz
User input: barfoo
Output: caqmut
Expected output: caqgon
Values of key_number[] after first for loop:
key_number[0] = 1
key_number[1] = 0
key_number[2] = 25
key_number[3] = 32767
key_number[4] = 32
key_number[5] = 57
Code used:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main(int argc, string argv[])
{
if(argc<=1)
{
printf("Usage: ./vigenere k");
exit(1);
}
// convert command line input from string to int varible keyword
string keyword = argv[1];
//prompt user input and initialized
printf("plaintext: ");
string plaintext = get_string();
printf("ciphertext: ");
//create lower_case_array
char lower_case[26] = "abcdefghijklmnopqrstuvwxyz";
//create upper_case_array
char upper_case[26] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
//convert key from string to int[]
//int array_length =strlen(plaintext);
int key_number[6]; // [array_length]
int key_length = strlen(keyword);
//loop though key_number for the length of plaintext
for (int i = 0; i < (strlen(plaintext)); i++)
{
//if char is uppercase
if (keyword[i] >= 65 && keyword[i] < 91 )
{
key_number[i] = (keyword[i % key_length]) - 65;
}
// if char is lowercase
else if (keyword[i] >= 97 && keyword[i] < 123)
{
key_number[i] = (keyword[i % key_length]) - 97;
}
}
//print out letters accoring to ceasar cryptography
for (int i = 0; i < strlen(plaintext); i++)
{
//branch if letter is upper case
if (plaintext[i] >= 65 && plaintext[i] < 91 )
{
printf("%c", upper_case[((plaintext[i] - 65 + key_number[i]) % 26)]);
}
//branch if letter is lower case
else if (plaintext[i] >= 97 && plaintext[i] < 123)
{
printf("%c", lower_case[((plaintext[i] - 97 + key_number[i]) % 26)]);
}
//branch if any other character
else
{
printf("%c", plaintext[i]);
}
}
printf("\n");
}

what puzzled me when I read your code is that you're using keyword[i % key_length] sometimes and sometimes keyword[i].
So when you're using i, you can go past the keyword table: undefined behaviour because plaintext is probably longer than keyword
You could have avoided that by rewriting your code more cleanly:
for (int i = 0; i < (strlen(plaintext)); i++)
{
char kc = keyword[i % key_length];
//if char is uppercase
if (kc >= 'A' && kc <= 'Z' ) // aka isupper(kc)
{
key_number[i] = kc - 'A';
}
// if char is lowercase
else if (kc >= 'a' && kc <= 'z') // aka islower(kc)
{
key_number[i] = kc - 'a';
}
}

Related

Why my ciphered text string is longer than expected?

I'm facing the CS50's substitution problem.
It works, even though the code is not very optimized.
The problem is that i had to add this line of code at the end to make it work:
`
cipher_text[strlen(plain_text)] = '\0';
`
otherwise the lenght of the cipher_text is longer than expected.
Can you tell me why?
this is the code i've written.
`
#include <cs50.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
int main(int argc, string argv[])
{
// check if there's 1 argument, if there are > 1 or < 0 print error message and return 1
if (argc != 2)
{
printf("Error, type 1 command\n");
return 1;
}
string key = argv[1];
long lenght = strlen(key);
// check if the key is valid (26 characters) or not, if not return 1 and print error
if (lenght != 26)
{
printf("Key must contain 26 characters.\n");
return 1;
}
// iterate throughout the key, element after element whith the 1st for loop
//check if contains letters or something else in the 1st if
//make the key all lower in order to compare letter repetition in the else if
//check for double letters (compare every letter (key[i]) with all the other letters) in the second for loop
for (int i = 0 ; i < lenght ; i++)
{
if (isalpha(key[i]) == 0) //(key[i] < 65 || key[i] > 90) && (key[i] < 97 || key[i] > 122))
{
printf("Key must contain only letters\n");
return 1;
}
else if (isupper(key[i]))
{
key[i] = tolower(key[i]);
}
for (int j = 0 ; j < lenght ; j++)
{
if (j != i && key[j] == key[i])
{
printf("You can't repeat letters in key (you've repeated the letter %c)\n", key[j]);
return 1;
}
}
}
//ask user for the text to cipher
string plain_text = get_string("plaintext: ");
char alphabet[] = "abcdefghijklmnopqrstuvwxyz";
char ALPHABET[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char cipher_text[strlen(plain_text)];
// iterate through every index in plain_text
//if it is not alphabetical, add it to cipher text
//otherwise, check if it is lower or upper, and add to cipher_text
//
for (int j = 0; j < strlen(plain_text) ; j++)
{
if (isalpha(plain_text[j]) == 0)
{
cipher_text[j] = plain_text[j];
}
for (int x = 0 ; x < lenght ; x++)
{
if(islower(plain_text[j]))
{
if (plain_text[j] == alphabet[x])
{
cipher_text[j] = key[x];
}
}
else if (isupper(plain_text[j]))
{
if (plain_text[j] == ALPHABET[x])
{
cipher_text[j] = toupper(key[x]);
}
}
}
}
cipher_text[strlen(plain_text)] = '\0';
printf("ciphertext: %s\n", cipher_text);
return 0;
}
`

Mistake in CS50 Readability project with only 1 input, all others work

I seem to have made a mistake in my code but I can't find it.
All reading grades give me the correct grade, except for grade 7 which results in grade 8.
I assume it is a rounding error of some sort?
I tested the following piece of code with and without the round() in the last function.
Without it most of the grade levels are off, with the round() in there I only get an mistake a the grade7 level.
Where is my mistake?
#include <cs50.h>
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <string.h>
//Prototypes
int count_letters(string text);
int count_words(string text);
int count_sentences(string text);
int get_score (string text);
//Main function
int main(void)
{
//Get user input
string text = get_string("Text: ");
//Grade user text
int i = get_score(text);
if(i < 1)
{
printf("Before Grade 1\n");
}
else if (i > 1 && i < 16)
{
printf("Grade %i\n", i);
}
else
{
printf("Grade 16+\n");
}
}
// Extra functions
int count_letters(string text)
{
// variables
int letters = strlen(text);
int total_letters = 0;
int characters = 0;
// Loop through text and count all non-letters
for(int i = 0; i < letters; i++)
{
if((text[i] < 65 || text[i] > 95) && (text[i] < 97 || text[i] > 122))
{
characters++;
}
}
// substract all non-letters from total chars and return.
total_letters = letters - characters;
return total_letters;
}
int count_words(string text)
{
// variables
int letters = strlen(text);
int spaces = 1;
// Loop through text and count all spaces
for(int i = 0; i < letters; i++)
{
if(text[i] == ' ')
{
spaces++;
}
}
return spaces;
}
int count_sentences(string text)
{
// variables
int letters = strlen(text);
int sentence = 0;
// Loop through text and count all sentences
for(int i = 0; i < letters; i++)
{
if(text[i] == 46 || text[i] == 33 || text[i] == 63)
{
sentence++;
}
}
return sentence;
}
int get_score (string text)
{
//variables
int letters = count_letters(text);
int words = count_words(text);
int sentence = count_sentences(text);
float index = 0;
// letters divided by words * 100
float L = 100 * letters / words;
// sentences divided by words *100
float S = 100 * sentence / words;
index = round(0.0588 * L - 0.296 * S - 15.8);
return index;
}
if((text[i] < 65 || text[i] > 95) && (text[i] < 97 || text[i] > 122)) is almost certainly a bug. You probably meant to be implementing isalpha, but you did it incorrectly. You meant to write:
if((text[i] < 'A' || text[i] > 'Z') && (text[i] < 'a' || text[i] > 'z')), which would have avoided the typo in which 95 was used instead of 90. Instead of this, though, you should just use the standard library and write:
if( ! isalpha(text[i]) ) ...
Using literals like 'A' instead of the magic number 65 makes the code more readable and helps avoid trivial mistakes like this.
There are quite a few issues with your code:
As #IrAM has mentioned in a comment, your if does not handle a score of 1. Moreover, you can simplify your if checks if you start from the other end, i.e. first check for greater than 16:
int main(void)
{
//Get user input
string text = get_string("Text: ");
//Grade user text
int i = get_score(text);
if(i > 16)
{
printf("Grade 16+\n");
}
else if (i > 0)
{
printf("Grade %i\n", i);
}
else
{
printf("Before Grade 1\n");
}
}
As #Gerhardh mentions, you are dividing two integers which forces the result to be an integer too. If at least one of the operands is casted to a float, the result is type-promoted to a float:
// letters divided by words * 100
float L = 100 * (float) letters / words;
// sentences divided by words *100
float S = 100 * (float) sentence / words;
Optimizations
You have three different functions for counting words, sentences and letters. Why three loops when you can do it in one loop? Plus an additional iteration for strlen() in each function. Write a Count struct like this:
struct Count
{
int letters;
int words;
int sentences;
int length;
};
Then have one function that returns this struct. Like #WilliamPursell mentions, using character literals instead of ASCII values makes code much more readable:
Count get_count(string text)
{
Count result = {0, 1, 0, 0};
result.length = strlen(text);
int characters = 0;
// Loop through text and count all non-letters
for(int i = 0; i < count.length; i++)
{
if((text[i] < 'A' || text[i] > 'Z') && (text[i] < 'a' || text[i] > 'z'))
{
characters++;
}
if(text[i] == ' ')
{
result.words++;
}
if(text[i] == '.' || text[i] == '!' || text[i] == '?')
{
result.sentences++;
}
}
// subtract all non-letters from total chars and return.
count.letters = count.length - characters;
return result;
}
This is what get_score() will change to:
int get_score (string text)
{
//variables
Count result = get_count(text);
float index = 0;
// letters divided by words * 100
float L = 100 * (float) result.letters / result.words;
// sentences divided by words *100
float S = 100 * (float) result.sentences / result.words;
index = round(0.0588 * L - 0.296 * S - 15.8);
return index;
}
Side Note: A '.' may always not necessarily mean the end of a sentence. It has other meanings like in an acronym or as ellipsis.

CS50 Caesar Prints Random Characters

I'm a little confused with this problem, because I got it to work and submitted and got full credit, but the code only words when I print the initial variables before the loop. This code works:
int main(int argc, string argv[]) {
// c = (p + k) % 26, where c is result text, p is input and k
// is key
//considers if arg count is two
if (argc == 2) {
int n = strlen(argv[1]);
int check = 0;
if (isdigit(argv[1][0])) {
for (int i = 1; i < n; i++) {
if (isdigit(argv[1][i]) || argv[1][i] == '0') {
check++;
} else {
check--;
}
}
}
// verifies all characters are numeric
if (check != n - 1) {
printf("Usage: ./caesar key\n");
return 1;
}
} else {
printf("Usage: ./caesar key\n");
// returning 1 identifies an error and exits the program
return 1;
}
int key = atoi(argv[1]);
string plaintext = get_string("plaintext: ");
printf("%i\n", key);
printf("%s\n", plaintext);
int m = strlen(plaintext);
printf("%i\n", m);
char ciphertext[m];
int usekey = (key % 26);
printf("%i\n", key);
// NEED to figure out how to handle wrap around
// need to understand ASCII
for (int i = 0; i < m; i++) {
int c = plaintext[i];
//encrypts upper case letters
if (c >= 65 && c <= 90) {
//incorporates wrap around for uppercase
if (c + usekey >= 90) {
int val = 90 - c;
int key2 = usekey - val;
char cipher = 64 + key2;
ciphertext[i] = cipher;
}
//considers if key works fine
else {
char cipher = c + usekey;
ciphertext[i] = cipher;
}
}
//encrypts lower case letters
else if (c >= 97 && c <= 122) {
//incorporates wrap around for lowercase
if (c + usekey >= 122) {
int val = 122 - c;
int key2 = usekey - val;
char cipher = 96 + key2;
ciphertext[i] = cipher;
} else {
char cipher = c + usekey;
ciphertext[i] = cipher;
}
} else {
//encrypts punctuation
ciphertext[i] = c;
}
printf("*\n");
}
printf("ciphertext: %s\n", ciphertext);
}
However, this code, does not work (for encrypts a as b using 1 as key, and for world, say hello! as iadxp, emk tqxxa! using 12 as key). It randomly prints different characters after the correct answer, and I cannot figure out why.
int main(int argc, string argv[]) {
// c = (p + k) % 26, where c is result text, p is input and k
// is key
//considers if arg count is two
if (argc == 2) {
int n = strlen(argv[1]);
int check = 0;
if (isdigit(argv[1][0])) {
for (int i = 1; i < n; i++) {
if (isdigit(argv[1][i]) || argv[1][i] == '0') {
check++;
} else {
check--;
}
}
}
// verifies all characters are numeric
if (check != n - 1) {
printf("Usage: ./caesar key\n");
return 1;
}
} else {
printf("Usage: ./caesar key\n");
// returning 1 identifies an error and exits the program
return 1;
}
int key = atoi(argv[1]);
string plaintext = get_string("plaintext: ");
int m = strlen(plaintext);
char ciphertext[m];
int usekey = (key % 26);
// NEED to figure out how to handle wrap around
// need to understand ASCII
for (int i = 0; i < m; i++) {
int c = plaintext[i];
//encrypts upper case letters
if (c >= 65 && c <= 90) {
//incorporates wrap around for uppercase
if (c + usekey >= 90) {
int val = 90 - c;
int key2 = usekey - val;
char cipher = 64 + key2;
ciphertext[i] = cipher;
}
//considers if key works fine
else {
char cipher = c + usekey;
ciphertext[i] = cipher;
}
}
//encrypts lower case letters
else if (c >= 97 && c <= 122) {
//incorporates wrap around for lowercase
if (c + usekey >= 122) {
int val = 122 - c;
int key2 = usekey - val;
char cipher = 96 + key2;
ciphertext[i] = cipher;
} else {
char cipher = c + usekey;
ciphertext[i] = cipher;
}
}
//encrypts punctuation
else {
ciphertext[i] = c;
}
}
printf("ciphertext: %s\n", ciphertext);
}
I think your if conditions is not works as it should be. you can print 'argv[1][i]' and see the problem. here is my code may help you.
bool isNumber(char number[])
{
int i = 0;
for (; number[i] != 0; i++)
{
if (!isdigit(number[i])) //check if there is something that is not digit
{
return false;
}
}
return true;
}
int main(int argc, string argv[])
{
if (argc == 2 && isNumber(argv[1]) == 1)
{
int k = atoi(argv[1]);
string plainText, chipherText;
plainText = get_string("plaintext: ");
printf("ciphertext: ");
for (int i = 0, n = strlen(plainText) ; i < n; i++)
{
// checking if it is lowercase 97 = a to 112 = z and if it + 13 characters along.
if (plainText[i] >= 'a' && plainText[i] <= 'z')
{
printf("%c", (((plainText[i] - 'a') + k) % 26) + 'a'); // print out lowercase with key
} // if it it between uppercase A and Z
else if (plainText[i] >= 'A' && plainText[i] <= 'Z')
{
printf("%c", (((plainText[i] - 'A') + k) % 26) + 'A'); // print out uppercase with key
}
else
{
printf("%c", plainText[i]);
}
}
printf("\n");
return 0;
}
else if (argc != 2 || isNumber(argv[1]) == 0)
{
printf("Error\n");
return 1;
}
}
You allocate m bytes for cyphertext, which is not enough for the null terminator, which you do not set either, causing random characters to appear after the encrypted output. This is actually undefined behavior, so anything can happen including a program crash.
You do not need to store the encrypted text, just output it one byte at a time.
Also do not use ASCII values such as 65 and 90, use character constants 'A' and 'Z' that are much more readable.
Here is a simplified version:
#include <stdio.h>
#include <stdlib.h>
#include <cs50.h>
int main(int argc, string argv[]) {
if (argc != 2) {
printf("Usage: ./caesar key\n");
// returning 1 identifies an error and exits the program
return 1;
}
char *p;
int key = strtol(argv[1], &p, 10);
if (*p || p == argv[1]) {
printf("caesar: invalid argument: %s\n", argv[1]);
return 1;
}
string plaintext = get_string("plaintext: ");
// assuming ASCII
for (size_t i = 0; plaintext[i]; i++) {
int c = plaintext[i];
if (c >= 'A' && c <= 'Z') {
c = 'A' + (c - 'A' + key) % 26;
} else
if (c >= 'a' && c <= 'z') {
c = 'a' + (c - 'a' + key) % 26;
}
putchar(c);
}
putchar('\n');
free(plaintext);
return 0;
}

Cs50 Caesar Check50 fail

I am doing Caesar exercise from CS50 course, but it fail.
Here is the code:
# include <stdio.h>
# include <cs50.h>
# include <string.h>
# include <stdlib.h>
# include <ctype.h>
int main(int argc, string argv[])
{
if (argc == 2)
{
string key = argv[1];
int l = strlen(key);
for (int i = 0; i < l; i++)
{
// if key[i] is an alphabet character
if (isalpha(key[i]) != 0)
{
printf("Usage: ./caesar key\n");
return 1;
}
}
//change charater to number
int k = atoi(argv[1]);
//print Plaintext
string plaintext = get_string("Plaintext: ");
int n = strlen(plaintext);
char ciphertext[n];
//declare plaintext in ASCII
int nplaintext[n];
//Change plaintext to ASCII
for (int i = 0; i < n; i++)
{
nplaintext[i] = (int)plaintext[i];
}
//Declare ASCII for ciphertext which we name "plusplaintext"
int plusnplaintext[n];
for (int i = 0; i < n; i++)
{
//if Capital
if ((nplaintext[i] < 91) && (nplaintext[i] > 64))
{
plusnplaintext[i] = 65 + ((nplaintext[i] + k) - 65) % 26 ;
}
//if Lowercase
else if ((nplaintext[i] < 123) && (nplaintext[i] > 96))
{
plusnplaintext[i] = 97 + ((nplaintext[i] + k) - 97) % 26 ;
}
//if not character a -> z and A -> Z
else
{
plusnplaintext[i] = nplaintext[i];
}
}
for (int i = 0; i < n; i++)
{
ciphertext[i] = (char)plusnplaintext[i];
}
printf("ciphertext: %s\n", ciphertext);
}
else
{
printf("Usage: ./caesar key\n");
return 1;
}
}
Here is the output from Check50:
:) caesar.c exists.
:) caesar.c compiles.
**:( encrypts "a" as "b" using 1 as key, output not valid ASCII text**
:) encrypts "barfoo" as "yxocll" using 23 as key
:) encrypts "BARFOO" as "EDUIRR" using 3 as key
:) encrypts "BaRFoo" as "FeVJss" using 4 as key
:) encrypts "barfoo" as "onesbb" using 65 as key
**:( encrypts "world, say hello!" as "iadxp, emk tqxxa!" using 12 as key, output not valid ASCII text**
:) handles lack of argv[1]
When I test the code, sometimes it gives right result, sometimes it give the wrong result with some more characters at the end...
How can I correct my code?
The problem is with the logic here
//if Capital
plusnplaintext[i] = 65 + ((nplaintext[i] + k) - 65) % 26 ;
and
//if Lowercase
plusnplaintext[i] = 97 + ((nplaintext[i] + k) - 97) % 26 ;
use this logic instead
//if Capital
((((nplaintext[i] - 90) + 25) + key) % 26) + 65
//if Lowercase
((((nplaintext[i] - 122) + 25) + key) % 26) + 97
and it is also not necessary to assign the letters to a string, instead you can directly print it
#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
int main(int argc, string argv[])
{
// Error Checking on the command line argument
if (argc != 2)
{
printf("Usage: ./caesar key\n");
return 1;
}
else if (argc == 2)
{
for (int i = 0; i < strlen(argv[1]); i++)
{
if (isdigit(argv[1][i]) == 0)
{
printf("Usage: ./caesar key\n");
return 1;
}
}
}
int key = atoi(argv[1]);
string plain_text = get_string("plaintext: ");
printf("ciphertext: ");
for (int i = 0; i < strlen(plain_text); i++)
{
if (isalpha(plain_text[i]) != 0)
{
// printf("%c", plain_text[i] + key);
if (isupper(plain_text[i]))
{
printf("%c", ((((plain_text[i] - 90) + 25) + key) % 26) + 65);
}
else
{
printf("%c", ((((plain_text[i] - 122) + 25) + key) % 26) + 97);
}
}
else
{
printf("%c", plain_text[i]);
}
}
printf("\n");
}
Instead of using the ascii value of a letter in your code, it could be helpful to keep them as characters for the sake of clarity. I took a snippet of your code and changed the values to demonstrate.
//if Capital
if ((nplaintext[i] <= 'Z') && (nplaintext[i] >= 'A'))
{
plusnplaintext[i] = 'A' + ((nplaintext[i] + k) - 'A') % 26 ;
}
This makes it clear what those values represent, and you don't have to reference an ascii chart. More comments would also help a user, or yourself later on, understand what the purpose is for each part of your code.
You could also create some functions outside of your main function. For example, to check for all digits in command line input, or change the letters by the amount given in the key. If you start working on long programs of code, it could be helpful to have functions defined that you can use as many times as you need. It also cleans up your main for clarity.
This function below takes the character of each letter in the original text (char p), then "moves" the character by k spaces (int k, given by the user as the key in the command line). It works when called in a for loop in main that iterates over each letter in the original given string (text[i]), with i being increased for each time the loop executes.
char rotate(char p, int k)
{
// declare variable for the rotated letter to be stored
char c;
// check if it is a letter
if (isalpha(p))
{
// check for lowercase letter
if (islower(p))
{
// subtract ascii value from p to initialize to 0 - 25 for computations
p -= 'a'; // if p is a, it is now initialized to 0, if b to 1, if c to 3, etc
c = (p + k) % 26; // use Caesar's algorithm to rotate letters, 'wrapping' by using % 26
c += 'a'; // add ascii value back to result to get the rotated letter
}
// the only other option is uppercase since we checked for only letters, do the same for uppercase letters as for lowercase
else
{
p -= 'A';
c = (p + k) % 26;
c += 'A';
}
}
// if it is the nul character '\0' return 0, do not print
else if (p == '\0')
{
return 0;
}
// if not a letter or nul character, return the character as is
else
{
c = p;
}
// print the rotated letter which is now stored in c
printf("%c", c);
// return the value of c
return c;
}

Why is my vigenere.c not working?

I keep making changes to the looping part of this code and my check50 always fails. I don't know what's going on. Below is my code:
#include <stdio.h>
#include <ctype.h>
#include <cs50.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, string argv[])
{
// declare variables
int cipherText;
if (argc != 2)
{
printf("Usage: ./vigenere keyword");
printf("\n");
return 1;
}
// keyword is the second command line argument
string key = argv[1];
int keylen = strlen(argv[1]);
// iterate through keyword to check if alphabetical
for (int i = 0, n = strlen(argv[1]); i < n; i++)
{
if ((key[i] >= '0') && (key[i] <= '9'))
{
printf("Keyword must consist only of letters.");
return 1;
}
}
// get the plaintext
string plainText = GetString();
// encypher - iterate over the characters in string, print each one encrypted
for (int i = 0, j = 0, n = strlen(plainText); i < n; i++, j++)
{
// start the key again if key shorter than plainText
if (j >= strlen(key))
{
j = 0;
}
// skip key[j] if plainText[i] is not an alpha character
if (!isalpha(plainText[i]))
{
j = (j-1);
}
// makes Aa = 0, Zz = 25 for the uppercase letters
if (isupper(key[j]))
{
key[j] = (key[j] - 'A');
}
// makes Aa = 0, Zz = 25 for lowercase letters
else if (islower(key[j]))
{
key[j] = (key[j] - 'a');
}
if (isupper(plainText[i]))
{
cipherText = (plainText[i] - 'A');
cipherText = ((cipherText + key[j%keylen])%26) + 'A';
printf("%c", cipherText);
}
else if (islower(plainText[i]))
{
cipherText = (plainText[i] - 'a');
cipherText = ((cipherText + key[j%keylen])%26 + 'a');
printf("%c", cipherText);
}
else
{
printf("%c", plainText[i]);
}
}
printf("\n");
return 0;
}
Some answered this: "The first for loop has a problem. The condition is checking for i > keylen when it should be checking for i < keylen".
Also when computing the next output value, the steps should be
(p[i]-65) results in a number between 0 and 25
adding (key[i % keylen]) results in a number between 0 and 50
apply modulo 26 so the number is between 0 and 25 (this is the missing step)
then add 65 to get the output"
and it's what I tried to do.
Given this code:
int keylen = strlen(argv[1]);
// iterate through keyword to check if alphabetical
for (int i = 0, n = strlen(argv[1]); i < n; i++)
{
if ((key[i] >= '0') && (key[i] <= '9'))
{
printf("Keyword must consist only of letters.");
return 1;
}
}
Your test inside the loop identifies digits as 'not a letter' (which is valid), but ignores punctuation, spaces and so on. You should probably be using if (!isalpha(key[i])) for the test (and it is courteous to print the erroneous character in the error message, which should be printed on standard error, not standard output, and should end with a newline:
fprintf(stderr, "Keyword must consist only of letters (%c found at %d)\n",
key[i], i+1);
You could refine that so it doesn't try printing non-printable characters with %c, but this is a huge step in the right direction.
You really don't need to set n in the loop; you just set keylen before the loop, so you could have written:
for (int i = 0; i < keylen; i++)
However, that is mostly cosmetic. Your real problem lies here:
// start the key again if key shorter than plainText
if (j >= strlen(key))
{
j = 0;
}
// makes Aa = 0, Zz = 25 for the uppercase letters
if (isupper(key[j]))
{
key[j] = (key[j] - 'A');
}
// makes Aa = 0, Zz = 25 for lowercase letters
else if (islower(key[j]))
{
key[j] = (key[j] - 'a');
}
You modify the key string on each iteration through the key. Unfortunately, though, if any of the letters in the key is a or A, you've converted that to '\0', which means that strlen(key) returns a different answer from before. So, you should use keylen in place of strlen(). AFAICS, if there isn't an a or A, that part of the code is OK.
Later, you have:
if (isupper(plainText[i]))
{
cipherText = (plainText[i] - 'A');
cipherText = ((cipherText + key[j%keylen])%26) + 'A';
printf("%c", cipherText);
}
The j % keylen is superfluous; j is already limited to 0 .. keylen-1. Similarly with the code for lower-case text.
Putting these changes together, and dummying up a GetString() function using fgets(), I get:
#include <stdio.h>
#include <ctype.h>
// #include <cs50.h>
#include <stdlib.h>
#include <string.h>
typedef char *string;
static char *GetString(void)
{
static char buffer[4096];
if (fgets(buffer, sizeof(buffer), stdin) == 0)
{
fprintf(stderr, "EOF detected in GetString()\n");
exit(EXIT_SUCCESS);
}
buffer[strlen(buffer) - 1] = '\0';
return buffer;
}
int main(int argc, string argv[])
{
// declare variables
int cipherText;
if (argc != 2)
{
printf("Usage: ./vigenere keyword");
printf("\n");
return 1;
}
// keyword is the second command line argument
string key = argv[1];
int keylen = strlen(argv[1]);
// iterate through keyword to check if alphabetical
for (int i = 0; i < keylen; i++)
{
if (!isalpha(key[i]))
{
printf("Keyword must consist only of letters (%c at %d)\n",
key[i], i+1);
return 1;
}
}
// get the plaintext
string plainText = GetString();
// encypher - iterate over the characters in string, print each one encrypted
for (int i = 0, j = 0, n = strlen(plainText); i < n; i++, j++)
{
// start the key again if key shorter than plainText
if (j >= keylen)
{
j = 0;
}
// skip key[j] if plainText[i] is not an alpha character
if (!isalpha(plainText[i]))
{
j = (j - 1);
}
// makes Aa = 0, Zz = 25 for the uppercase letters
if (isupper(key[j]))
{
key[j] = (key[j] - 'A');
}
// makes Aa = 0, Zz = 25 for lowercase letters
else if (islower(key[j]))
{
key[j] = (key[j] - 'a');
}
if (isupper(plainText[i]))
{
cipherText = (plainText[i] - 'A');
cipherText = ((cipherText + key[j]) % 26) + 'A';
printf("%c", cipherText);
}
else if (islower(plainText[i]))
{
cipherText = (plainText[i] - 'a');
cipherText = ((cipherText + key[j]) % 26 + 'a');
printf("%c", cipherText);
}
else
{
printf("%c", plainText[i]);
}
}
printf("\n");
return 0;
}
Sample run:
$ ./vigenere bakedalaska
What a wonderful world! The news is good, and the Vigenere cipher is solved.
Xhkx d wznvorguv arrwd! Lre oegw ls rogn, aod dlh Vtgwxese mmshpr ac splfig.
$

Resources