CS50 Substitution - c

This problem set requires us to code a programme that takes a key from the user from the CLI and then an input (plaintext) and return a ciphertext version that is scrambled based on the key provided.
My code returns the correct ciphertext given any key and plaintext, however, the apparent output when using the in-built check50 module from cs50 is "", an empty string.
#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
string encrypt(string keys, string inputs);
char newkey;
string ciphertext;
int main(int argc, string argv[])
{
if (argc == 1)
{
printf("Please enter a key!\n");
return 1;
}
if (argc > 2)
{
printf("You can only have one key. The key must not have any spaces!\n");
return 1;
}
if (0 < strlen(argv[1]) && strlen(argv[1]) < 26)
{
printf("Key must contain 26 characters!\n");
return 1;
}
for (int j = 0; j < strlen(argv[1]); j++)
{
if (!((argv[1][j] >= 'a' && argv[1][j] <= 'z') || (argv[1][j] >= 'A' && argv[1][j] <= 'Z')))
{
printf("Key must contain alphabets only!");
return 1;
}
}
for (int k = 0; k < strlen(argv[1]); k++)
{
for (int l = (k + 1); l < strlen(argv[1]); l++)
{
if (argv[k] == argv[l])
{
printf("There can be no duplicate alphabets in the key!");
return 1;
}
}
}
string key = argv[1];
string input = get_string("plaintext: ");
encrypt(key, input);
printf("ciphertext: %s\n", ciphertext);
}
string encrypt(string keys, string inputs)
{
char ciphertexts[strlen(inputs)];
for (int i = 0; i < strlen(inputs); i++)
{
if (islower(inputs[i]))
{
int index = inputs[i] - 97;
newkey = keys[index];
ciphertexts[i] = tolower(newkey);
}
else if (isupper(inputs[i]))
{
int index = inputs[i] -65;
newkey = keys[index];
ciphertexts[i] = toupper(newkey);
}
else
{
ciphertexts[i] = inputs[i];
}
}
ciphertext = ciphertexts;
printf("%s\n",ciphertexts);
printf("%s\n",ciphertext);
return ciphertext;
}
The errors are as follows:
:( encrypts "A" as "Z" using ZYXWVUTSRQPONMLKJIHGFEDCBA as key
expected "ciphertext: Z\...", not ""
This means that using a key of ZYXWVUTSRQPONMLKJIHGFEDCBA, and a plaintext of "A", "Z" is expected, but my programme outputs "".
I have a printf statement printing the cipher text, but it is somehow not captured

Memory management
Rather than return a pointer to a local variable (which is no longer valid after function ends #kaylum), pass into encrypt() a pointer to where to form the encrypted string.
Don't forget about '\0'
In forming the encrypted string account for the null character. #kaylum
Avoid repeated calls to strlen(inputs)
Advanced: Avoid negative ch with is...(ch)
Along with some other ideas, perhaps:
#include <ctype.h>
void encrypt(char *ciphertexts, const char *keys, char *inputs) {
// Let's work with unsigned char to avoid trouble negative char
const unsigned char *ukeys = (const unsigned char*) keys;
const unsigned char *uinputs = (const unsigned char*) inputs;
unsigned char *uciphertexts = (unsigned char*) ciphertexts;
size_t i = 0;
// Code uses a do loop to catch the null character.
do {
if (islower(uinputs[i])) {
int index = uinputs[i] - 'a';
unsigned char newkey = ukeys[index];
uciphertexts[i] = tolower(newkey);
} else if (isupper(uinputs[i])) {
int index = uinputs[i] - 'A';
unsigned char newkey = ukeys[index];
uciphertexts[i] = toupper(newkey);
} else {
uciphertexts[i] = uinputs[i];
}
} while (uinputs[i++]);
}
Code still has trouble when the locale is not "C" or with non-ASCII, but we can leave those as advanced issues.
Advanced
Since strings to encrypt can be lengthy, consider pre-forming a key valid for all char. I.e.
unsigned char keys256[UCHAR_MAX + 1] = { 0 };
for (i = 0; i <= UCHAR_MAX; i++) {
keys256[i] = i;
}
for (i = 0; key[i]; i++) {
keys256['a' + i] = tolower(key[i]);
keys256['A' + i] = toupper(key[i]);
}
and then in encrypt(..., keys256, ...), use a greatly simplified loop.
...
do {
uciphertexts[i] = keys256(uinputs[i]);
} while (uinputs[i++]);
...

Related

What is happening to the printf function?

I am attempting to write the substitution program here https://cs50.harvard.edu/x/2022/psets/2/substitution/
here is my code:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
int main(int argc, string argv[1])
{
if (argc > 2 || argc < 2){
printf("Plz 1 word in command \n");
return 1;
}
int sum = 0;
string arg1 = argv[1];
//test
for (int i = 0; i < strlen(arg1); i++){
if (isalpha(arg1[i]) == 0){
printf("plz only alphabet character \n");
return 1;
}}
// convert all key to upper
for (int i = 0; i < strlen(arg1); i++){
arg1[i] = toupper(arg1[i]);
}
for (int i = 0; i < strlen(arg1); i++){
sum = sum + (int)(arg1[i]);
}
if (strlen(arg1) != 26){
printf("Plz input 26 char \n");
return 1;
} else if (sum != 2015){printf("no oveerlapping letter plz \n");
return 1; }
//test finish
string al = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
string pt = get_string("plaintext: ");
char pt1[strlen(pt)];
char cp[strlen(pt)];
// all plain text to upper
for (int i = 0; i < strlen(pt); i++){
pt1[i]=toupper(pt[i]);
}
//scan
for (int a = 0; a < strlen(pt); a++){
char b = pt1[a];
for (int i = 0; i < strlen(al); i++){
if ( al[i] == b){
cp[a] = arg1[i];
break;
} else {
cp[a] = b;
}
}
//case preserve
if (islower(pt[a])){
cp[a] = tolower(cp[a]);
}}
printf("ciphertext: %s \n", cp);
return 0;
}
when i type in the key YTNSHKVEFXRBAUQZCLWDMIPGJO and then type "hello!1 lmao" as plaintext, here is what i receive
substitution/ $ ./substitution YTNSHKVEFXRBAUQZCLWDMIPGJO
plaintext: hello!1 lmao
ciphertext: ehbbq!1 bayq�
it should only show ehbbq!1 bayq but it is showing more letter than i intended,
there might be other letter or simbol after "bayq", can someone explain to me what is going on and why there are additional text in my output?
You need a null terminatig character (usually it is character having integer value 0) to terminate the string
size_t a;
for (a = 0; a < strlen(pt); a++)
{
char b = pt1[a];
for (int i = 0; i < strlen(al); i++)
{
if ( al[i] == b)
{
cp[a] = arg1[i];
break;
} else {
cp[a] = b;
}
}
//case preserve
if (islower((unsigned char)pt[a]))
{
cp[a] = tolower((unsigned char)cp[a]);
}
}
cp[a] = 0;
you need to pass unsigned char to functions like tolower. I did not analyze the logic of your code as it is your home work.
Also cp is too short, it has to be char cp[strlen(pt) + 1];
Your char arrays as declared are too short to handle copies of the data, due to the need for a null-terminator att the end.
char pt1[strlen(pt)];
char cp[strlen(pt)];
Rather you need to do:
char pt1[strlen(pt) + 1];
char cp[strlen(pt) + 1];
However, the other approach would be to simply use strdup to dynamically allocate sufficient storage and copy the data.
char pt1 = strdup(pt);
char cp = strdup(pt);
Of course, any function that returns dynamically allocated memory (likely including cs50's get_string) means you should remember to free that memory. And ensure it actually succeeded.

Check50 "not a valid ASCII Ouput" on pset2 caesar

I completed the caesar assignment on cs50 and tested it on my terminal and it worked perfectly, but on check50 is kept failing some tests.
#include <ctype.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
int getkey(string k);
string cipher(string s, int key);
int key;
int p;
int q;
int main(int argc, string argv[])
{
// Allow 2 command line inputs
if (argc == 2)
{
// Assign a local string to allow char scan
string s = argv[1];
// Check if all inputs are numbers
for (int i = 0; s[i] != 0; i++)
{
if (s[i] < 48 || s[i] > 57)
{
printf("Usage: ./caesar key\n");
return 1;
}
}
// Get key from string
int cipherkey = getkey(s);
// Get user text
string text = get_string("plaintext: ");
// Calculate ciphertext and print
string ciphertext = cipher(text, cipherkey);
printf("ciphertext: %s\n", ciphertext);
}
else
{
printf("Usage: ./caesar key\n");
return 1;
}
}
// Change string to int. Turns out theres already a function for this called atoi()
int getkey(string k)
{
key = 0;
for(int i = 0, conv = 0, n = strlen(k); k[i] != 0; i++, n--)
{
// Calcute the placevalue
p = pow(10, n-1);
conv = k[i] - 48; // Convert to int
key = key + (conv * p); // Sum up
}
return key % 26;
}
// Cipher text
string cipher (string s, int key)
{
for(int i = 0; s[i] != 0; i++)
{
if(islower(s[i]))
{
s[i] = s[i] + key;
while(s[i] > 122)
{
s[i] = (s[i] - 123) + 97;
}
}
else if(isupper(s[i]))
{
s[i] = s[i] + key;
while(s[i] > 90)
{
s[i] = (s[i] - 91) + 65;
}
}
}
return s;
}
with error message
:) caesar.c compiles.
:) encrypts "a" as "b" using 1 as key
:( encrypts "barfoo" as "yxocll" using 23 as key
output not valid ASCII text
:) 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]
:) handles non-numeric key
:) handles too many arguments
I wrote the code without knowing the "atoi" function so i implemented a function called getkey() to return key. when i returned key normally, it failed.
:( encrypts "barfoo" as "onesbb" using 65 as key
Output not a valid ASCII text
Until i returned key % 26;
I dont know why check50 isnt working although the program works well on my terminal. Please help.
Updated code:
#include <cs50.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
string cipher(string s, int key);
int main(int argc, string argv[])
{
// Allow 2 command line inputs
if (argc == 2)
{
// Assign a local string to allow char scan
string s = argv[1];
// Check if all inputs are numbers
for (int i = 0; s[i] != 0; i++)
{
if (s[i] < 48 || s[i] > 57)
{
printf("Usage: ./caesar key\n");
return 1;
}
}
// Get key from string
int cipherkey = atoi(s);
// Get user text
string text = get_string("plaintext: ");
// Calculate ciphertext and print
string ciphertext = cipher(text, cipherkey);
printf("ciphertext: %s\n", ciphertext);
}
else
{
printf("Usage: ./caesar key\n");
return 1;
}
}
// Cipher text
string cipher (string s, int key)
{
for(int i = 0; s[i] != 0; i++)
{
if(islower(s[i]))
{
s[i] = (int) s[i] + key;
while(s[i] > 'z')
{
s[i] = (s[i] - 123) + 97;
}
}
else if(isupper(s[i]))
{
s[i] = (int) s[i] + key;
while(s[i] > 'Z')
{
s[i] = (s[i] - 91) + 65;
}
}
}
return s;
}
I've rewritten the function trying to avoid going to far making corrections and improvements. Now, the copy is actually unnecessary, but I hope it helps you understand that a "signed (8 bit) char" is not 'wide' enough to use in calculations that lead to overflow...
Please read the following and try to follow along.
string cipher( string s, int key ) {
for( int i = 0; s[i] != '\0'; i++ ) {
if( !isalpha( s[i] ) )
continue;
int copy = (int)s[i]; // unnecessary casting, but...
// transform ASCII value into 0-25 range
if( islower( s[i] ) )
copy = copy - 'a';
else // must be uppercase
copy = copy - 'A';
copy = (copy + key) % 26;
// transform enciphered MODULO back into ASCII char
if( islower( s[i] ) )
copy = copy + 'a';
else // must be uppercase
copy = copy + 'A';
s[i] = copy;
}
return s;
}
Quiet night, so I thought I'd rewrite the rewrite just to see what it looked like...
string cipher( string s, int key ) {
for( char *cp = s; *cp; cp++ )
if( isalpha( *cp ) ) {
char c = "Aa"[ !!islower(*cp) ];
*cp = (char)(((*cp - c + key) % 26) + c);
}
return s;
}

Why I keep getting a random character at the end of my string in C?

I am trying to write a program that encrypts whatever the user types according to an encryption alphabet. However, when printing out the result, I keep getting one extra random character at the end of my result string. I have tried to end my result string with an '\0' but it doesn't work. Please send some help!
#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int find_index(char a[], int num_elements, char value);
int main (void)
{
string ori = "abcdefghijklmnopqrstuvwxyz"; // original alphabet
string key = "ZYXWVUTSRQPONMLKJIHGFEDCBA"; // encryption alphabet
string plain_text = get_string("plaintext: ");
// count the number of characters in plain_text
int count = 0;
for (int i = 0; plain_text[i] != '\0'; i++)
{
count++;
}
char answer [count + 1];
answer[count+1] = '\0';
for (int i = 0; i < count ; i++)
{
// if the character is not in alphabet, just add it to answer
if (isalpha(plain_text[i]) == false)
{
answer[i] = plain_text[i];
}
else
{
// take the original character in plain text
char ori_char = plain_text[i];
// find it index in the orginal alphabet
int index_ori = find_index(ori, 26, tolower(ori_char));
// find the corresponding character in encryption alphabet
char res_char = key[index_ori];
if islower(ori_char)
{
res_char = tolower(res_char);
}
else
{
res_char = toupper(res_char);
}
// update the char list
answer[i] = res_char;
}
}
printf ("ciphertext: %s\n", answer);
}
int find_index(string a, int num_elements, char value) // find index of a character in a string
{
int x = -1;
for (int i=0; i < num_elements; i++)
{
if (a[i] == value)
{
x = i;
return x;
}
}
return(x); /* if it was not found */
}
char answer [count + 1]; answer[count+1] = '\0'; is UB as it attempts to access outside the array.
Perhaps other problems too.

CS50 Substitution - Does not output ciphertext - logic error

The function of the program is to be run with a command-line argument, for example, might be the string NQXPOMAFTRHLZGECYJIUWSKDVB. This 26-character key means that A (the first letter of the alphabet) should be converted into N (the first character of the key), B (the second letter of the alphabet) should be converted into Q (the second character of the key), and so forth. A message like HELLO, then, would be encrypted as FOLLE, replacing each of the letters according to the mapping determined by the key.
e.g.
./substitution JTREKYAVOGDXPSNCUIZLFBMWHQ
plaintext: HELLO
ciphertext: VKXXN*
After starting the program with a valid command-line argument, the program doesn't output anything. It should output the ciphertext which has been encrypted using the key. I can't seem to find where the logic error is.
#include <stdio.h>
#include <cs50.h>
#include <string.h>
#include <ctype.h>
bool is_valid_key(string plaintext);
int main(int argc, string argv[])
{
//Error message if user inputs incorret comand-line argument
if (argc != 2)
{
printf("Usage: ./substitution key\n");
return 1;
}
if (!is_valid_key(argv[1]))
{
printf("Key must contain 26 characters.\n");
return 1;
}
//Prompts user for plaintext
string plaintext = get_string("Plaintext: ");
string difference = argv[1];
for (int i = 'A'; i < 'Z'; i++)
{
difference[i - 'A'] = toupper(difference[i - 'A']) - i;
}
printf("Ciphertext: ");
for (int i = 0, len = strlen(plaintext); i < len; i++)
{
if(isalpha(plaintext[i]))
{
plaintext[i] = plaintext[i] + difference[plaintext[i] - (isupper(plaintext[i]) ? 'A' : 'a')];
}
printf("%c", plaintext[i]);
}
printf("\n");
}
//Checks vailidity of the key
bool is_valid_key(string plaintext)
{
int len = strlen(plaintext);
if (len != 26)
{
return false;
}
int freq[26] = { 0 };
for (int i = 0; i < len; i++)
{
if (!isalpha(plaintext[i]))
{
return false;
}
int index = toupper(plaintext[i]) - 'A';
if (freq[index] > 0)
{
return false;
}
freq[index]++;
}
return true;
}
Line 25 should have been for (int i = 'A'; i <= 'Z'; i++).
Line 29 should have been printf("ciphertext: ");

Vigenere Cipher

I am trying to make Vigenere Cipher in C. https://www.youtube.com/watch?v=9zASwVoshiM this is info about Vigenere Cipher. My code works doesnt work for certain cases like encrypts "world, say hello!" as "xoqmd, rby gflkp!" using "baz" as keyword instead it encrypts it as xomd, szz fl. Another example is:
encrypts "BaRFoo" as "CaQGon" using "BaZ" as keyword but instead it encrypts it as CakGo. My code is given below please help me out:
#include<stdio.h>
#include<cs50.h>
#include<ctype.h>
#include<string.h>
#include<stdlib.h>
int main(int argc, string argv[]) {
//string plaintext;
string key;
if (argc != 2) {
printf("Please run the programme again this time using a command line argument!\n");
return 1;
}
key = argv[1];
int keys[strlen(key)];
for (int m = 0; m< strlen(key); m++) {
if (isalpha(key[m]) == false) {
printf("Re-Run The programme without any symbols.\n");
return 1;
}
}
for (int b = 0; b < strlen(key); b++) {
if (isupper(key[b]) == false) {
keys[b] = key[b] - 'a';
}
else {
keys[b] = key[b] - 'A';
}
}
//printf("Enter a string which should be encrypted: \n");
string plaintext = GetString();
int plength = strlen(plaintext);
int klength = strlen(key);
string ciphertext = key;
for (int u = 0; u<plength; u++) {
if (isalpha(plaintext[u]) == false) {
printf("%c", plaintext[u]);
continue;
}
int value = u % klength;
ciphertext[u] = (keys[value] + plaintext[u]);
if ((islower(plaintext[u])) && (ciphertext[u])>'z') {
ciphertext[u] = ciphertext[u] - 'z' + 'a' - 1;
}
if ((isupper(plaintext[u])) && (ciphertext[u])>'z') {
ciphertext[u] = ciphertext[u] - 'Z' + 'A' - 1;
}
printf("%c", ciphertext[u]);
}
printf("\n");
return 0;
}
string ciphertext = key;
ciphertext should start out as blank, it should not be set to key.
ciphertext[u] = ciphertext[u] - 'z' + 'a' - 1;
This is not correct because ciphertext[u] can go out of range. Character should be between 'a' to 'z'. Use the mod % operator to make sure character is within range. For example:
int j = 0;
for (int u = 0; u<plength; u++)
{
int c = plaintext[u];
if (isalpha(plaintext[u]))
{
int k = keys[j % klength];
if (islower(c))
{
c = 'a' + (c - 'a' + k) % 26;
}
else
{
c = 'A' + (c - 'A' + k) % 26;
}
j++;
}
ciphertext[u] = c;
}
printf("%s\n", ciphertext);
Lots of little issues (ciphertext needs to be allocated dynamically and start out as a copy of plaintext, not key; need to mod calculations to the length of the alphabet; incorrect calculations; print errors to stderr) and lots of little optimizations that can be made (combine loops; combine if clauses; save key length to a variable earlier). A rework of your code:
#include <stdio.h>
#include <cs50.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, string argv[]) {
if (argc != 2) {
fprintf(stderr, "Please run the program again with a command line argument!\n");
return 1;
}
string key = argv[1];
int key_length = strlen(key);
int keys[key_length];
for (int i = 0; i < key_length; i++) {
if (!isalpha(key[i])) {
fprintf(stderr, "Re-run The program without any symbols.\n");
return 1;
}
keys[i] = toupper(key[i]) - 'A';
}
// printf("Enter a string which should be encrypted: \n");
string plaintext = GetString();
int text_length = strlen(plaintext);
string ciphertext = strcpy(malloc(text_length + 1), plaintext);
for (int i = 0, j = 0; i < text_length; i++) {
if (!isalpha(plaintext[i])) {
continue;
}
int index = j++ % key_length;
ciphertext[i] = (toupper(plaintext[i]) - 'A' + keys[index]) % (1 + 'Z' - 'A');
ciphertext[i] += isupper(plaintext[i]) ? 'A' : 'a';
}
printf("%s\n", ciphertext);
free(ciphertext);
return 0;
}
Encrypts "world, say hello!" as "xoqmd, rby gflkp!" with key "baz"
Encrypts "BaRFoo" as "CaQGon" with key "BaZ"

Resources