cs50x 2020 - pset2 - substitution - duplicate characters in key - cs50

I keep getting an error around handling duplicate characters in key when checking my code for the substitution problem within pset2 of the cs50 course 2020. My code and further details are below - can anyone please help with this? Thanks
The error message it gives me is
:( handles duplicate characters in key
timed out while waiting for program to exit
When I check my code for duplicate characters it seems to work fine (printing Usage: ./substitution key and ending the program)
Code below
# include <stdio.h>
# include <cs50.h>
# include <string.h>
# include <stdlib.h>
# include <ctype.h>
int main(int argc, string argv[])
{
// Check that only one argument submitted
if (argc == 2)
{
// Check that key contains 26 characters
int keylen = strlen(argv[1]);
if (keylen == 26)
{
// Check that all characters are letters
for (int i = 0; i < keylen; i++)
{
bool lettercheck = isalpha(argv[1][i]);
if (lettercheck == true)
{
// THIS IS CAUSING ERROR - Check that no letters have been repeated - put all in lowercase to do so
for (int n = 0; n < i; n++)
{
char currentletter = argv[1][i];
char previousletter = argv[1][i - 1];
if (tolower(currentletter) == tolower(previousletter))
{
printf("Usage: ./substitution key\n");
return 1;
}
}
}
else
{
printf("Usage: ./substitution key\n");
return 1;
}
}
}
else
{
printf("Key must contain 26 characters.\n");
return 1;
}
}
else
{
printf("Usage: ./substitution key\n");
return 1;
}
// Get user input
string input = get_string("plaintext: ");
//Transform input using key
for(int i = 0; i < strlen(input); i++)
{
char currentletter = input[i];
int testlower = islower(currentletter);
int testupper = isupper(currentletter);
if (testupper > 0)
{
int j = input[i] - 65;
input[i] = toupper(argv[1][j]);
}
else if (testlower > 0)
{
int j = input[i] - 97;
input[i] = tolower(argv[1][j]);
}
}
printf("ciphertext: %s\n", input);
}
Edit:
Figured out solution - problem was with the second for loop was iterating against i - 1 times instead of n times
Code should have been
charpreviouslletter = argv[1][n]
instead of
charpreviousletter = argv[1][i - 1]
for (int n = 0; n < i; n++)
{
char currentletter = argv[1][i];
char previousletter = argv[1]**[i - 1]**

In this loop-
// THIS IS CAUSING ERROR - Check that no letters have been repeated - put all in lowercase to do so
for (int n = 0; n < i; n++)
{
char currentletter = argv[1][i];
char previousletter = argv[1][i - 1];
if (tolower(currentletter) == tolower(previousletter))
{
printf("Usage: ./substitution key\n");
return 1;
}
}
you're comparing only the current character to the previous character. This doesn't work for strings like abcdefca
Notice how, c and a have duplicates - but they're not right next to their originals and hence your logic won't find these duplicates. Your logic will only work for duplicates that are next to each other such as aabcddef.
Instead, you need to take a note of which characters you've encountered whilst looping through. If you encounter a character that you have already encountered, you know there's a duplicate.
Thankfully, the key is only expected to contain all 26 characters of the alphabet without any duplicates. This means you can simply have an int array of 26 slots - each slot counts the number of appearances of the letter at that index. 0th index stands for 'a', 1st for 'b' and so on.
This way, you can very easily get the index of an alphabetic character using letter - 'a', where letter is the alphabetic character. So if the letter was a, you'd get 0, which is indeed the index of 'a'
Also, you have a nested loop while traversing the key, this nested loop also traverses through the key. Except it does it only up until a certain index, the index being the current index of the outer loop. This seems wasteful and weird. Why not simply loop through once, check if current character is an alphabetic letter and also check if this letter has been encountered before. That's all you have to do!
int letter_presence[26];
char upperletter;
string key = argv[1];
if (strlen(key) == KEY_LEN)
{
for (int index = 0; index < KEY_LEN; index++)
{
if (!isalpha(key[index]))
{
// Wrong key - invalid character
printf("Usage: ./substitution key\n");
return 1;
}
if (letter_presence[tolower(key[index]) - 'a'] == 0)
{
// This letter has not been encountered before
letter_presence[upperletter - 'A'] = 1;
}
else
{
// Wrong key - Duplicate letters
return 1;
}
}
// All good
}

Related

Why am I getting same characters with any key I type?

Why am I getting this return 4 repeated characters for Pset2 Substition CS50
Everything seems to work fine except for the checking for repeated characters in the command line argument. Rest of the code works fine when I remove this return for the checking repeated characters. Everything I type gives me this error
#include <cs50.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
int main(int argc, string argv[])
{
if (argc == 2)
{
//When there are 26 characters in string
if (strlen(argv[1]) == 26)
{
string check = argv[1];
//Checking each characters in string(array) where [i] is the array number
for (int i = 0, n = strlen(argv[1]); i < n; i++)
{
//If the characters in the string is alphabet or not
if (!isalpha(argv[1][i]))
{
printf("Key must only contain Alphabetic Characters\n");
return 3;
}
//Check for repeated alphabet
for (int l = 0; l < n; l++)
{
if (argv[1][i] == check[l])
{
printf("Key must not contain repeated Characters\n");
return 4;
}
}
}
//Get Plaintext
string plaintext = get_string("Plaintext: ");
//Going through each characters in string
for (int j = 0, n = strlen(plaintext); j < n; j++)
{
//For LOWERCASE CHARACGERS
if (plaintext[j] >= 'a' && plaintext[j] <= 'z')
{
plaintext[j] = argv[1][plaintext[j] - 97];
plaintext[j] = tolower(plaintext[j]);
}
//For UPPERCASE CHARACTERS
else if (plaintext[j] >= 'A' && plaintext[j] <= 'Z')
{
plaintext[j] = argv[1][plaintext[j] - 65];
plaintext[j] = toupper(plaintext[j]);
}
}
printf("Ciphertext: %s\n",plaintext);
}
//If Characters are not 26 in string
else
{
printf("Key must contain 26 Characters\n");
return 2;
}
}
//When Location is other than argv[1]
else
{
printf("./substitution KEY\n");
return 1;
}
}
When i is 0 and l is 0 what is being compared here if (argv[1][i] == check[l])? Actually whenever i and l are equal it is comparing a character to itself. Program does not need to compare any characters it has already processed. The compare should start at the current char + 1.

Complexity Analysis: Remove consecutive(3+) duplicates from string

I have written a solution for removing 3(let's call this k) or more consecutive same characters from string.
eg.
in: daabbcccbbaad out: dd
in: aaabbb out:
in: aaabbbc out: c
code:
void removeDup(string& s, int start) {
if (s.length() < 3) {
return;
}
if (start > s.length() - 2) return;
int count = 1;
char c = s[start];
int i = start + 1;
while (i < s.length() && s[i] == c) {
++count, ++i;
}
if (count >= 3) {
start = i - count - 2;
if (start < 0) start = 0;
s.erase(i - count, count);
} else {
start = i;
}
return removeDup(s, start);
}
For each match start pointer will move 2 step behind. Also problem size is reducing by no of matched characters.
I want to understand how to do complexity analysis for this. Also for any k(let's say k=n/2) how will the complexity change.
ps: if you have a better solution with constant space, kindly post.

Minimum number of swaps to convert a string to palindrome

We are given a string and we have to find out the minimum number of swaps to convert it into a palindrome.
Ex-
Given string: ntiin
Palindrome: nitin
Minimum number of swaps: 1
If it is not possible to convert it into a palindrome, return -1.
I am unable to think of any approach except brute force. We can check on the first and last characters, if they are equal, we check for the smaller substring, and then apply brute force on it. But this will be of a very high complexity, and I feel this question can be solved in another way. Maybe dynamic programming. How to approach it?
First you could check if the string can be converted to a palindrome.
Just have an array of letters (26 chars if all letters are latin lowercase), and count the number of each letter in the input string.
If string length is even, all letters counts should be even.
If string length is odd, all letters counts should be even except one.
This first pass in O(n) will already treat all -1 cases.
If the string length is odd, start by moving the element with odd count to the middle.
Then you can apply following procedure:
Build a weighted graph with the following logic for an input string S of length N:
For every element from index 0 to N/2-1:
- If symmetric element S[N-index-1] is same continue
- If different, create edge between the 2 characters (alphabetic order), or increment weight of an existing one
The idea is that when a weight is even you can do a 'good swap' by forming two pairs in one swap.
When weight is odd, you cannot place two pairs in one swap, your swaps need to form a cycle
1. For instance "a b a b"
One edge between a,b of weight 2:
a - b (2)
Return 1
2. For instance: "a b c b a c"
a - c (1)
b - a (1)
c - b (1)
See the cycle: a - b, b - c, c - a
After a swap of a,c you get:
a - a (1)
b - c (1)
c - b (1)
Which is after ignoring first one and merge 2 & 3:
c - b (2)
Which is even, you get to the result in one swap
Return 2
3. For instance: "a b c a b c"
a - c (2)
One swap and you are good
So basically after your graph is generated, add to the result the weight/2 (integer division e.g. 7/3 = 3) of each edge
Plus find the cycles and add to the result length-1 of each cycle
there is the same question as asked!
https://www.codechef.com/problems/ENCD12
I got ac for this solution
https://www.ideone.com/8wF9DT
//minimum adjacent swaps to make a string to its palindrome
#include<bits/stdc++.h>
using namespace std;
bool check(string s)
{
int n=s.length();
map<char,int> m;
for(auto i:s)
{
m[i]++;
}
int cnt=0;
for(auto i=m.begin();i!=m.end();i++)
{
if(i->second%2)
{
cnt++;
}
}
if(n%2&&cnt==1){return true;}
if(!(n%2)&&cnt==0){return true;}
return false;
}
int main()
{
string a;
while(cin>>a)
{
if(a[0]=='0')
{
break;
}
string s;s=a;
int n=s.length();
//first check if
int cnt=0;
bool ini=false;
if(n%2){ini=true;}
if(check(s))
{
for(int i=0;i<n/2;i++)
{
bool fl=false;
int j=0;
for(j=n-1-i;j>i;j--)
{
if(s[j]==s[i])
{
fl=true;
for(int k=j;k<n-1-i;k++)
{
swap(s[k],s[k+1]);
cnt++;
// cout<<cnt<<endl<<flush;
}
// cout<<" "<<i<<" "<<cnt<<endl<<flush;
break;
}
}
if(!fl&&ini)
{
for(int k=i;k<n/2;k++)
{
swap(s[k],s[k+1]);
cnt++;
}
// cout<<cnt<<" "<<i<<" "<<endl<<flush;
}
}
cout<<cnt<<endl;
}
else{
cout<<"Impossible"<<endl;
}
}
}
Hope it helps!
Technique behind my code is Greedy
first check if palindrome string can exist for the the string and if it can
there would be two cases one is when the string length would be odd then only count of one char has be odd
and if even then no count should be odd
then
from index 0 to n/2-1 do the following
fix this character and search for this char from n-i-1 to i+1
if found then swap from that position (lets say j) to its new position n-i-1
if the string length is odd then every time you encounter a char with no other occurence shift it to n/2th position..
My solution revolves around the palindrome property that first element and last element should match and if their adjacent elements also do not match then its not a palindrome. Keep comparing and swapping till both reach the same element or adjacent elements.
Written solution in java as below:
public static void main(String args[]){
String input = "natinat";
char[] arr = input.toCharArray();
int swap = 0;
int i = 0;
int j = arr.length-1;
char temp;
while(i<j){
if(arr[i] != arr[j]){
if(arr[i+1] == arr[j]){
//swap i and i+1 and increment i, decrement j, swap++
temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
i++;j--;
swap++;
} else if(arr[i] == arr[j-1]){
//swap j and j-1 and increment i, decrement j, swap++
temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
i++;j--;
swap++;
} else if(arr[i+1] == arr[j-1] && i+1 != j-1){
//swap i and i+1, swap j and j-1 and increment i, decrement j, swap+2
temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
temp = arr[i];
arr[i] = arr[i+1];
arr[i+1] = temp;
i++;j--;
swap = swap+2;
}else{
swap = -1;break;
}
} else{
//increment i, decrement j
i++;j--;
}
}
System.out.println("No Of Swaps: "+swap);
}
My solution in java for any type of string i.e Binary String, Numbers
public int countSwapInPalindrome(String s){
int length = s.length();
if (length == 0 || length == 1) return -1;
char[] str = s.toCharArray();
int start = 0, end = length - 1;
int count = 0;
while (start < end) {
if (str[start] != str[end]){
boolean isSwapped = false;
for (int i = start + 1; i < end; i++){
if (str[start] == str[i]){
char temp = str[i];
str[i] = str[end];
str[end] = temp;
count++;
isSwapped = true;
break;
}else if (str[end] == str[i]){
char temp = str[i];
str[i] = str[start];
str[start] = temp;
count++;
isSwapped = true;
break;
}
}
if (!isSwapped) return -1;
}
start++;
end--;
}
return (s.equals(String.valueOf(str))) ? -1 : count;
}
I hope it helps
string s;
cin>>s;
int n = s.size(),odd=0;
vi cnt(26,0);
unordered_map<int,set<int>>mp;
for(int i=0;i<n;i++){
cnt[s[i]-'a']++;
mp[s[i]-'a'].insert(i);
}
for(int i=0;i<26;i++){
if(cnt[i]&1) odd++;
}
int ans=0;
if((n&1 && odd == 1)|| ((n&1) == 0 && odd == 0)){
int left=0,right=n-1;
while(left < right){
if(s[left] == s[right]){
cnt[left]--;
cnt[right]--;
mp[s[left]-'a'].erase(left);
mp[s[right]-'a'].erase(right);
left++;
right--;
}else{
if(cnt[left]&1 == 0){
ans++;
int index = *mp[s[left]-'a'].rbegin();
mp[s[left]-'a'].erase(index);
mp[s[right]-'a'].erase(right);
mp[s[right]-'a'].insert(index);
swap(s[right],s[index]);
cnt[left]-=2;
}else{
ans++;
int index = *mp[s[right]-'a'].begin();
mp[s[right]-'a'].erase(index);
mp[s[left]-'a'].erase(left);
mp[s[left]-'a'].insert(index);
swap(s[left],s[index]);
cnt[right]-=2;
}
left++;
right--;
}
}
}else{
// cout<<odd<<" ";
cout<<"-1\n";
return;
}
cout<<ans<<"\n";

Vigenere.c CS50 Floating Point Exception (Core Dumped)

I am working on the Vigenere exercise from Harvard's CS50 (in case you noticed I'm using string and not str).
My program gives me a Floating Point Exception error when I use "a" in the keyword.
It actually gives me that error
when I use "a" by itself, and
when I use "a" within a bigger word it just gives me wrong output.
For any other kind of keyword, the program works perfectly fine.
I've run a million tests. Why is it doing this? I can't see where I'm dividing or % by 0. The length of the keyword is always at least 1. It is probably going to be some super simple mistake, but I've been at this for about 10 hours and I can barely remember my name.
#include <stdio.h>
#include <cs50.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
int main (int argc, string argv[])
{
//Error message if argc is not 2 and argv[1] is not alphabetical
if (argc != 2)
{
printf("Insert './vigenere' followed by an all alphabetical key\n");
return 1;
}
else if (argv[1])
{
for (int i = 0, n = strlen(argv[1]); i < n; i++)
{
if (isalpha((argv[1])[i]) == false)
{
printf("Insert './vigenere' followed by an all alphabetical key\n");
return 1;
}
}
//Store keyword in variable
string keyword = argv[1];
//Convert all capital chars in keyword to lowercase values, then converts them to alphabetical corresponding number
for (int i = 0, n = strlen(keyword); i < n; i++)
{
if (isupper(keyword[i])) {
keyword[i] += 32;
}
keyword[i] -= 97;
}
//Ask for users message
string message = GetString();
int counter = 0;
int keywordLength = strlen(keyword);
//Iterate through each of the message's chars
for (int i = 0, n = strlen(message); i < n; i++)
{
//Check if ith char is a letter
if (isalpha(message[i])) {
int index = counter % keywordLength;
if (isupper(message[i])) {
char letter = (((message[i] - 65) + (keyword[index])) % 26) + 65;
printf("%c", letter);
counter++;
} else if (islower(message[i])) {
char letter = (((message[i] - 97) + (keyword[index])) % 26) + 97;
printf("%c", letter);
counter++;
}
} else {
//Prints non alphabetic characters
printf("%c", message[i]);
}
}
printf("\n");
return 0;
}
}
This behavior is caused by the line keyword[i] -= 97;, there you make every 'a' in the key stream a zero. Later you use strlen() on the transformed key. So when the key starts with an 'a', keywordLength therefor is set to zero, and the modulo keywordLength operation get into a division by zero. You can fix this by calculating the keyword length before the key transformation.

Alphabet string code, looping

So I need to finish this program that asks user to type in a word and then he needs to write it back "encrypted", only in number. So a is 1, b is 2... For example if I give the word "bad", it should come back as "2 1 4". The program I made seems to do this always only for the 1st letter of the word. My question that I would need help with is, why does this program stop looping after the 1st letter? Am I even doin it right or is it completely off? Any help would be much appreciated.
Console.Write("Please, type in a word: ");
string start = Console.ReadLine();
string alphabet = "abcdefghijklmnopqrstuvwxyz";
for (int c = 0; c < alphabet.Length; c++)
{
int a = 0;
if (start[a] == alphabet[c])
{
Console.Write(c + 1);
a++;
continue;
}
if (start[a] != alphabet[c])
{
a++;
continue;
}
}
I accomplished it with a nested loop:
Console.Write("Please, type in a word: ");
string start = Console.ReadLine();
string alphabet = "abcdefghijklmnopqrstuvwxyz";
for (int a = 0; a < start.Length; a++)
{
for (int c = 0; c < alphabet.Length; c++)
{
if (start[a] == alphabet[c])
{
Console.Write(c + 1);
}
}
}
While comparing the strings, it makes sense, at least to me, to loop through both of them.
Your program was stopping after the first letter because your were resetting "a" to 0 at the beginning of every loop.

Resources