Related
Im trying to write a C program that removes all occurrences of repeating chars in a string except the last occurrence.For example if I had the string
char word[]="Hihxiivaeiavigru";
output should be:
printf("%s",word);
hxeavigru
What I have so far:
#include <stdio.h>
#include <string.h>
int main()
{
char word[]="Hihxiiveiaigru";
for (int i=0;i<strlen(word);i++){
if (word[i+1]==word[i]);
memmove(&word[i], &word[i + 1], strlen(word) - i);
}
printf("%s",word);
return 0;
}
I am not sure what I am doing wrong.
With short strings, any algorithm will do. OP's attempt is O(n*n) (as well as other working answers and #David C. Rankin that identified OP's short-comings.)
But what if the string was thousands, millions in length?
Consider the following algorithm: #paulsm4
Form a `bool` array used[CHAR_MAX - CHAR_MIN + 1] and set each false.
i,unique = n - 1;
From the end of the string (n-1 to 0) to the front:
if (character never seen yet) { // used[] look-up
array[unique] = array[i];
unique--;
}
Mark used[array[i]] as true (index from CHAR_MIN)
i--;
Shift the string "to the left" (unique - i) places
Solution is O(n)
Coding goal is too fun to just post a fully coded answer.
I would first write a function to determine if a char ch at a given position i is the last occurence of ch given a char *. Like,
bool isLast(char *word, char ch, int p) {
p++;
ch = tolower(ch);
while (word[p] != '\0') {
if (tolower(word[p]) == ch) {
return false;
}
p++;
}
return true;
}
Then you can use that to iteratively emit your desired characters like
int main() {
char *word = "Hihxiivaeiavigru";
for (int i = 0; word[i] != '\0'; i++) {
if (isLast(word, word[i], i)) {
putchar(word[i]);
}
}
putchar('\n');
}
And (for completeness) I used
#include <stdio.h>
#include <ctype.h>
#include <stdbool.h>
Outputs (as requested)
hxeavigru
Additional areas where you are currently hurting yourself.
Your for loop must NOT increment the index, e.g. for (int i=0; word[i];). This is because when you memmove() by 1, you have just incremented the indexes. That also means the value to save for last is now i - 1.
there should only be one call to strlen() in the program. You can simply subtract one from length each time memmove() is called.
only increment your loop counter variable when memmove() is not called.
Additionally, avoid hardcoding strings. You shouldn't have to recompile your code just to test the results of "Hihxiivaeiaigrui" instead of "Hihxiivaeiaigru". You shouldn't have to recompile just to remove all but the last 'a' instead of the 'i'. Either pass the string and character to find as arguments to your program (that's what int argc, char **argv are for), or prompt the user for input.
Putting it altogether you could do (presuming word is 1023 characters or less):
#include <stdio.h>
#include <string.h>
#define MAXC 1024
int main (int argc, char **argv) {
char word[MAXC]; /* storage for word */
strcpy (word, argc > 1 ? argv[1] : "Hihxiivaeiaigru"); /* copy to word */
int find = argc > 2 ? *argv[2] : 'i', /* character to find */
last = -1; /* last index where find found */
size_t len = strlen (word); /* only compute strlen once */
printf ("%s (removing all but last %c)\n", word, find);
for (int i=0; word[i];) { /* loop over each char -- do NOT increment */
if (word[i] == find) { /* is this my character to find? */
if (last != -1) { /* if last is set */
/* overwrite last with rest of word */
memmove (&word[last], &word[last + 1], (int)len - last);
last = i - 1; /* last now i - 1 (we just moved it) */
len = len - 1;
}
else { /* last not set */
last = i; /* set it */
i++; /* increment loop counter */
}
}
else /* all other chars */
i++; /* just increment loop counter */
}
puts (word); /* output result -- no need for printf (no coversions) */
}
Example Use/Output
$ ./bin/rm_all_but_last_occurrence
Hihxiivaeiaigru (removing all but last i)
Hhxvaeaigru
What if you want to use "Hihxiivaeiaigrui"? Just pass it as the 1st argument:
$ ./bin/rm_all_but_last_occurrence Hihxiivaeiaigrui
Hihxiivaeiaigrui (removing all but last i)
Hhxvaeagrui
What if you want to use "Hihxiivaeiaigrui" and remove duplicate 'a' characters? Just pass the string to search as the 1st argument and the character to find as the second:
$ ./bin/rm_all_but_last_occurrence Hihxiivaeiaigrui a
Hihxiivaeiaigrui (removing all but last a)
Hihxiiveiaigrui
Nothing removed if only one of the characters:
$ ./bin/rm_all_but_last_occurrence Hihxiivaeiaigrui H
Hihxiivaeiaigrui (removing all but last H)
Hihxiivaeiaigrui
Let me know if you have further questions.
Im trying to write a C program that removes all occurrences of repeating chars in a string except the last occurrence.
Process the string (or word) from last character and move towards the first character of string (or word). Now, think of it as a problem where you have to remove all occurrence of a character from string and except the first occurrence. Since, we are processing the string from last character to first character, so, we have to move the characters, which are remain after removing duplicates, to the start of string once you have processed whole string and, if, there were duplicate characters found in the string. The complexity of this algorithm is O(n).
Implementation:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define INDX(x) (tolower(x) - 'a')
void remove_dups_except_last (char str[]) {
int map[26] = {0}; /* to keep track of a character processed */
size_t len = strlen (str);
char *p = str + len; /* pointer pointing to null character of input string */
size_t i = 0;
for (i = len; i != 0; --i) {
if (map[INDX(str[i - 1])] == 0) {
map[INDX(str[i - 1])] = 1;
*--p = str[i - 1];
}
}
/* if there were duplicates characters then only copy
*/
if (p != str) {
for (i = 0; *p; ++i) {
str[i] = *p++;
}
str[i] = '\0';
}
}
int main(int argc, char* argv[])
{
if (argc != 2) {
printf ("Invalid number of arguments\n");
return -1;
}
char str[1024] = {0};
/* Assumption: the input string/word will contain characters A-Z and a-z
* only and size of input will not be more than 1023.
*
* Leaving it up to you to check the valid characters in input string/word
*/
strcpy (str, argv[1]);
printf ("Original string : %s\n", str);
remove_dups_except_last (str);
printf ("Removed duplicated characters except the last one, modified string : %s\n", str);
return 0;
}
Testcases output:
# ./a.out Hihxiivaeiavigru
Original string : Hihxiivaeiavigru
Removed duplicated characters except the last one, modified string : hxeavigru
# ./a.out aa
Original string : aa
Removed duplicated characters except the last one, modified string : a
# ./a.out a
Original string : a
Removed duplicated characters except the last one, modified string : a
# ./a.out TtYyuU
Original string : TtYyuU
Removed duplicated characters except the last one, modified string : tyU
You can re-iterate to get each characters of your string, then if it is not "i" and not the last occurrence of the i, copy to a new string.
#include <stdio.h>
#include <string.h>
int main() {
char word[]="Hihxiiveiaigru";
char newword[10000];
char* ptr = strrchr(word, 'i');
int index=0;
int index2=0;
while (index < strlen(word)) {
if (word[index]!='i' || index ==(ptr - word)) {
newword[index2]=word[index];
index2++;
}
index++;
}
printf("%s",newword);
return 0;
}
Swap 2 first character to second last character and so on like 3 first character to third last character
untill character to middle of the word.
Input:
I love programming
Output:
i lvoe pnimmargorg
I tried to do
#include <stdio.h>
#include <string.h>
int main(){
int t;
scanf("%d",&t);
char s[105+t];
getchar();
for(int i=0;i<t;i++){
scanf("%[^\n]",s);
int len = strlen(s);
char temp[105+t];
getchar();
for(int i=0;i<len/2;i++){
strcpy(temp,s);
s[i] = s[len-i-1];
s[len-i-1] = temp;
}
for(int i=0;i<len/2;i++){
printf("%c",s[i]);
}
}
return 0;
}
This should do for swapping
for(int i=0;i<len/2;i++)
{
temp = s[i];
s[i] = s[len-i-1];
s[len-i-1] = temp;
}
There are a number of ways to approach this. The key is you want to be able to locate the beginning and end of each word in a line. (actually the 2nd and next to last character in each word). You cannot use strtok() or strsep() as both write nul-terminating characters in place of delimiters in your string. What you need is some way to manually advance though your line to locate the beginning and ending of each word.
One way is to use a pair of pointers and nested loops, with the outer loop advancing the start-pointer until the beginning of the next word is found. Then setting the end-pointer equal to the start-pointer and looping with a nested loop moving the end pointer until the next space or end-of-line is found. You know what lies between the two pointers will be your word.
Advance the start-pointer by one to the 2nd character and decrement the end-pointer by two to locate the next to last character (you subtract 2, one because you currently point to the next space after the word -- to find the last char, and then subtract again for the next-to-last char)
Another option is to use strspn() and strcspn() to take the place of the nested loops. strspn() returns the number of left-most characters in the string made up of only characters in the accept parameter (use " \t\n" to cause it to skip over spaces to the start of the next word) and strcspn() which returns the left-most number of character not including the characters in the reject parameter. (use the same " \t\n" to cause it to skip over all characters in the work to the next space returning the length of the word).
Writing a simple function to apply the logic to each word in the given string and swapping from the 2nd to next-to-last character in each word could be done as:
char *outsidein (char *s)
{
char *p = s; /* pointer to position in s to work toward end */
while (1) {
size_t offset = strspn (p, " \t\n"), /* find 1st non-space */
length = strcspn (p + offset, " \t\n"); /* find next space */
if (!length) /* at end, break */
break;
char *sp = p + offset + 1, /* start pointer to 2nd char in word */
*ep = p + offset + length - 2; /* end pointer to next to last char */
while (sp < ep) { /* loop toward middle */
int tmp = *ep; /* swap chars at sp & ep */
*ep-- = *sp;
*sp++ = tmp;
}
p += offset + length; /* update pointer to space following word */
}
return s; /* for convenience, return a pointer to s for immediate use */
}
(note: the string passed as the parameter s must be a mutable string)
Above the pointer p is advanced down the string saving the beginning location of each word, and then the number of spaces (offset) and length is added to it to advance to the next space after the word you processed.
A short example program that puts it altogether could be:
#include <stdio.h>
#include <string.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
char *outsidein (char *s)
{
char *p = s; /* pointer to position in s to work toward end */
while (1) {
size_t offset = strspn (p, " \t\n"), /* find 1st non-space */
length = strcspn (p + offset, " \t\n"); /* find next space */
if (!length) /* at end, break */
break;
char *sp = p + offset + 1, /* start pointer to 2nd char in word */
*ep = p + offset + length - 2; /* end pointer to next to last char */
while (sp < ep) { /* loop toward middle */
int tmp = *ep; /* swap chars at sp & ep */
*ep-- = *sp;
*sp++ = tmp;
}
p += offset + length; /* update pointer to space following word */
}
return s; /* for convenience, return a pointer to s for immediate use */
}
int main (void) {
char line[MAXC];
while (fgets (line, MAXC, stdin)) { /* read each line */
line[strcspn (line, "\n")] = 0; /* trim \n character from end */
printf ("%s\n", outsidein (line)); /* output modified line */
}
}
Example Use/Output
$ echo "I love programming" | ./bin/str_outsidein
I lvoe pnimmargorg
There are a number of ways to do it. Look things over and let me know if you have questions.
That's a nice little exercise, that seems tricky at frst, but keeping it simple keeps the bugs away.
Things to look out for (aka traps for young players)
multiple spaces between words.
input strings that start with spaces.
input strings that end with a space.
See comments in code below.
#include <stdio.h>
#include <string.h>
void scramble(char* str)
{
char *p, *q, *word_start;
char c;
if (str == NULL)
return;
while (*str != 0)
{
// skip spaces, you could also use while (isspace(*str & 0xFF))
// which would suddenly make this function much more
// versatile and interesting.
while (*str == ' ')
++str;
// keep track of the start of the word
word_start = str;
// find end of word, you could also use isspace() here,
// if you decide to go that route.
while (*str != ' ' && *str != 0)
++str;
// str now points to one character past the last char of word
// swap the letters we want, from second to second to last,
// leave the others untouched. Note that the p < q test
// automatically skips processing of words of 3 bytes or less.
// so this takes also care of strings ending with a space
// which ould give us an empty word (when str == word_start)
for (p = word_start + 1, q = str - 2; p < q; ++p, --q)
{
c = *p;
*p = *q;
*q = c;
}
}
}
const char input_string[] = "I love programming";
int main()
{
char output_string[sizeof(input_string)];
strcpy(output_string, input_string);
printf("input: %s\n", input_string);
scramble(output_string);
printf("output: %s\n", output_string);
return 0;
}
Try it, and step through it: https://onlinegdb.com/SkxKerFYP
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
I have three advises for you.
Don't use gets, it is deprecated and for good reason, use fgets instead.
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.
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;
}
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.
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
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.
beginner here. So I'm trying to write some code that take a sentence and returns the longest word. When I debugg the program everything looks correct as I'd expect including the char array. However when I come to print the output I invariably get a NULL...
I've put in the entire code because I think one of the loops must be effecting the array string pointer in some way?
#include <stdio.h>
#include <string.h>
void LongestWord(char sen1[500]) {
/*
steps:
1. char pointer. Each Byte holds array position of each space or return value Note space = 32 & return = 10.
2. Once got above asses biggest word. Biggest word stored in short int (starting position)
3. Once got biggest word start - move to sen using strncpy
*/
char sen[500];
char *ptr = sen;
int i = 0;
int space_position[500];
int j = 0;
int k = 0;
int word_size_prior_to_each_position[500];
int l = 0;
int largest = 0;
int largest_end_position = 0;
int largest_start_position =0;
memset(&sen[0], 0, 500);
memset(&space_position[0], 0, 2000);
memset(&word_size_prior_to_each_position[0], 0, 2000);
while (i < 500) { //mark out where the spaces or final return is
if ((sen1[i] == 0b00100000) ||
(sen1[i] == 0b00001010))
{
space_position[j] = i;
j = j+1;
}
i = i+1;
}
while (k < 500) {
if (k == 0) {
word_size_prior_to_each_position[k] = (space_position[k]);
}
//calculate word size at each position
if ((k > 0) && (space_position[k] != 0x00)) {
word_size_prior_to_each_position[k] = (space_position[k] - space_position[k-1]) -1;
}
k = k+1;
}
while (l < 500) { //find largest start position
if (word_size_prior_to_each_position[l] > largest) {
largest = word_size_prior_to_each_position[l];
largest_end_position = space_position[l];
largest_start_position = space_position[l-1];
}
l = l+1;
}
strncpy(ptr, sen1+largest_start_position+1, largest);
printf("%s", *ptr);
return 0;
}
int main(void) {
char stringcapture[500];
fgets(stringcapture, 499, stdin);
LongestWord(stringcapture); //this grabs input and posts into the longestword function
return 0;
}
In the function LongestWord replace
printf("%s", *ptr);
with
printf("%s\n", ptr);
*ptr denotes a single character, but you want to print a string (see %s specification), so you must use ptr instead. It makes sense to also add a line break (\n).
Also remove the
return 0;
there, because it's a void function.
Returning the longest word
To return the longest word from the function as pointer to char, you can change the function signature to
char *LongestWord(char sen1[500])
Since your pointer ptr points to a local array in LongestWord it will result in a dangling reference as soon as the function returns.
Therefore you need to do sth like:
return strdup(ptr);
Then in main you can change your code to:
char *longest_word = LongestWord(stringcapture);
printf("%s\n", longest_word);
free(longest_word);
Some more Hints
You have a declaration
int space_position[500];
There you are calling:
memset(&space_position[0], 0, 2000);
Here you are assuming that an int is 4 bytes. That assumption leads to not-portable code.
You should rather use:
memset(&space_position[0], 0, sizeof(space_position));
You can even write:
memset(space_position, 0, sizeof(space_position));
since space_position is the address of the array anyway.
Applied to your memsets, it would look like this:
memset(sen, 0, sizeof(sen));
memset(space_position, 0, sizeof(space_position));
memset(word_size_prior_to_each_position, 0, sizeof(word_size_prior_to_each_position));
Instead of using some binary numbers for space and return, you can alternatively use the probably more readable notation of ' ' and '\n', so that you could e.g. write:
if ((sen1[i] == ' ') ||
(sen1[i] == '\n'))
instead of
if ((sen1[i] == 0b00100000) ||
(sen1[i] == 0b00001010))
The variable largest_end_position is assigned but never used somewhere. So it can be removed.
The following line
strncpy(ptr, sen1 + largest_start_position + 1, largest);
would omit the first letter of the word if the first word were also the longest. It seems largest_start_position is the position of the space, but in case of the first word (largest_start_position == 0) you start to copy from index 1. This special case needs to be handled.
You have a local array in main that is not initialized.
So instead of
char stringcapture[500];
you must write
char stringcapture[500];
memset(stringcapture, 0, sizeof(stringcapture));
alternatively you could use:
char stringcapture[500] = {0};
Finally in this line:
largest_start_position = space_position[l - 1];
You access the array outside the boundaries if l==0 (space_position[-1]). So you have to write:
if (l > 0) {
largest_start_position = space_position[l - 1];
}
else {
largest_start_position = 0;
}
While Stephan has provided you with a good answer addressing the problems you were having with your implementation of your LongestWord function, you may be over-complicating what your are doing to find the longest word.
To be useful, think about what you need to know when getting the longest word from a sentence. You want to know (1) what the longest word is; and (2) how many characters does it contain? You can always call strlen again when the function returns, but why? You will have already handled that information in the function, so you might as well make that information available back in the caller.
You can write your function in a number of ways to either return the length of the longest word, or a pointer to the longest word itself, etc. If you want to return a pointer to the longest word, you can either pass an array of sufficient size as a parameter to the function for filling within the function, or you can dynamically allocate storage within the function so that the storage survives the function return (allocated storage duration verses automatic storage duration). You can also declare an array static and preserve storage that way, but that will limit you to one use of the function in any one expression. If returning a pointer to the longest word, to also make the length available back in the caller, you can pass a pointer as a parameter and update the value at that address within your function making the length available back in the calling function.
So long as you are simply looking for the longest word, the longest word in the unabridged dictionary (non-medical) is 29-characters (taking 30-characters storage total), or for medical terms the longest word is 45-character (taking 46-characters total). So it may make more sense to simply pass an array to fill with the longest word as a parameter since you already know what the max-length needed will be (an array of 64-chars will suffice -- or double that to not skimp on buffer size, your call).
Rather than using multiple arrays, a simple loop and a pair of pointers is all you need to walk down your sentence buffer bracketing the beginning and end of each word to pick out the longest one. (and the benefit there, as opposed to using a strtok, etc. is the original sentence is left unchanged allowing it to be passed as const char * allowing the compiler to further optimize the code)
A longest_word function that passes the sentence and word to fill as parameters returning the length of the longest string is fairly straight forward to do in a single loop. Loosely referred to as a State Loop, where you use a simple flag to keep track of your read state, i.e. whether you are in a word within the sentence or whether you are in whitespace before, between or after the words in the sentence. A simple In/Out state flag.
Then you simply use a pointer p to locate the beginning of each word, and an end-pointer ep to advance down the sentence to locate the end of each word, checking for the word with the max-length as you go. You can use the isspace() macro provided in ctype.h to locate the spaces between each word.
The loop itself does nothing more than loop continually while you keep track of each pointer and then check which word is the longest by the simple pointer difference ep - p when the end of each word is found. If a word is longer than the previous max, then copy that to your longest word array and update max with the new max-length.
A short implementation could be similar to:
size_t longest_word (const char *sentence, char *word)
{
const char *p = sentence, *ep = p; /* pointer & end-pointer */
size_t in = 0, max = 0; /* in-word flag & max len */
if (!sentence || !*sentence) /* if NULL or empty, set word empty */
return (*word = 0);
for (;;) { /* loop continually */
if (isspace (*ep) || !*ep) { /* check whitespace & end of string */
if (in) { /* if in-word */
size_t len = ep - p; /* get length */
if (len > max) { /* if greater than max */
memcpy (word, p, len); /* copy to word */
word[len] = 0; /* nul-terminate word */
max = len; /* update max */
}
p = ep; /* update pointer to end-pointer */
in = 0; /* zero in-word flag */
}
if (!*ep) /* if end of word, bail */
break;
}
else { /* non-space character */
if (!in) { /* if not in-word */
p = ep; /* update pointer to end-pointer */
in = 1; /* set in-word flag */
}
}
ep++; /* advance end-pointer */
}
return max; /* return max length */
}
A complete example taking the sentence to be read as user-input could be similar to:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAXWRD 64 /* longest word size */
#define MAXC 2048 /* max characters in sentence */
size_t longest_word (const char *sentence, char *word)
{
const char *p = sentence, *ep = p; /* pointer & end-pointer */
size_t in = 0, max = 0; /* in-word flag & max len */
if (!sentence || !*sentence) /* if NULL or empty, set word empty */
return (*word = 0);
for (;;) { /* loop continually */
if (isspace (*ep) || !*ep) { /* check whitespace & end of string */
if (in) { /* if in-word */
size_t len = ep - p; /* get length */
if (len > max) { /* if greater than max */
memcpy (word, p, len); /* copy to word */
word[len] = 0; /* nul-terminate word */
max = len; /* update max */
}
p = ep; /* update pointer to end-pointer */
in = 0; /* zero in-word flag */
}
if (!*ep) /* if end of word, bail */
break;
}
else { /* non-space character */
if (!in) { /* if not in-word */
p = ep; /* update pointer to end-pointer */
in = 1; /* set in-word flag */
}
}
ep++; /* advance end-pointer */
}
return max; /* return max length */
}
int main (void) {
char buf[MAXC], word[MAXWRD];
size_t len;
if (!fgets (buf, MAXC, stdin)) {
fputs ("error: user canceled input.\n", stderr);
return 1;
}
len = longest_word (buf, word);
printf ("longest: %s (%zu-chars)\n", word, len);
return 0;
}
Example Use/Output
Entered string has 2-character leading whitespace as well as 2-characters trailing whitespace:
$ ./bin/longest_word
1234 123 12 123456 1234 123456789 12345678 1 1234
longest: 123456789 (9-chars)
This isn't intended to be a substitute for Stephan's answer helping with the immediate issues in your implementation, rather this is an example providing you with an alternative way to think about approaching the problem. Generally the simpler you can keep any coding task, the less error prone it will be. Look it over and let me know if you have any further questions about the approach.
Program compiles, but when I try to use it, I get an endless loop. Where did i do wrong. I put comments into what I am trying to accomplish.
I have tried changing it to a for loop, but I still get issues. I think I am suppose to stick with a while loop to accomplish what I am trying to accomplish.
//Declare the arrays to hold the strings
char str[21], vowels[21], consonants[21];
int i=0;
//Declare the pointers
char *strPointer, *vowelPointer, *consonantPointer;
//Print out the prompt to the user
printf("Enter a string (20 characters maximum): ");
//Scan the user input into str
//Only allow 20 characters
scanf("%s", str);
//Set strPointer to the beginning of the user's string
strPointer = str;
//Set vowelPointer to the beginning of the vowels string
vowelPointer = vowels;
//Set consonantPointer to the beginning of tht consonant string
consonantPointer = consonants;
//Loop through the user's string until the end of the string
while(*strPointer !='\0')
{
//Check if what strPointer is pointing to is a vowel
if(strPointer[i]=='A'||strPointer[i]=='a'||strPointer[i]=='E'||strPointer[i]=='e'||strPointer[i]=='I'||strPointer[i]=='i'||strPointer[i]=='O'||strPointer[i]=='o'||strPointer[i]=='U'||strPointer[i]=='u')
{
//Move the letter from strPointer to vowelPointer
strPointer=vowelPointer
;
//Move the vowelPointer
vowelPointer=vowels
;
}
else
{
//Move the letter from strPointer to consonantPointer
strPointer=consonantPointer
;
//Move the consonantPointer
consonantPointer=consonants
;
}
//Move the strPointer
strPointer=str;
}
//Add null terminators where appropriate
strPointer[21]='\0';
str[21]='\0';
//Set the vowel and consonant pointers back to the beginning of their strings
vowelPointer[0];
consonantPointer[0];
//Print the original string and the resulting vowel and consonant strings
printf("Original string: %s\n", str);
printf("%s\n", vowelPointer);
printf("%s\n", consonantPointer);
The output is explained in my printf statements at the end. Input string, reprinted with the vowels and consonants separated and listed.
I would like to make some suggestions to help you out.
First, when you are scanning from user input, it is wise to use a buffer. Then, strcpy the contents of the buffer into the char array. This will help to prevent overflow. Please see the below code for more details about this topic.
Second, you can use a for loop to iterate through each of the letters. I hope my answer will help you understand what I mean.
#include <stdio.h>
#include <ctype.h>
#include <string.h>
int main(void)
{
//Declare the arrays to hold the strings
char c, str[21], vowels[21], consonants[21], buffer[21];
int i = 0, j = 0, h = 0;
//Declare the pointers
char *strPointer, *vowelPointer, *consonantPointer;
//Print out the prompt to the user
printf("Enter a string (20 characters maximum): ");
//Scan the user input into str
scanf("%s", buffer);
// Copy the buffer into the str
strcpy(str, buffer);
// go letter by letter checking if it is a vowel or consonant
for (int i = 0; str[i]; i++)
{
// make the character uppercase
c = toupper(str[i]);
// if the letter is a vowel add the letter to the vowel array,
// then increase the position
if (c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U')
vowels[j++] = str[i];
else
// add the letter to the consonants array, then increase the position
consonants[h++] = str[i];
}
// properly terminate the strings
vowels[j] = '\0';
consonants[h] = '\0';
//Print the original string and the resulting vowel and consonant strings
printf("Original string: %s\n", str);
printf("%s\n", vowels);
printf("%s\n", consonants);
return 0;
}
You invoke an infinite loop by setting:
strPointer=str;
at the end of your while (*strPointer !='\0') loop. This resets the address held by strPointer to the beginning of str. strPointer is never incremented, so your loop loops with strPointer pointing to the first character in str over-and-over-and-over again...
Further, you initialize vowelPointer=vowels; which sets the address of vowelPointer to point to the beginning element of the uninitialized vowels array. The same occurs with consonantPointer=consonants;
Since both vowelPointer and consonantPointer point to arrays declared with automatic storage duration, you invoke Undefined Behavior when you attempt to access the uninitialized values with:
printf("%s\n", vowelPointer);
printf("%s\n", consonantPointer);
(but luckily you never get there because of your endlesss loop over while (*strPointer !='\0'))
Additionally, validate EVERY input and affirmatively protect your array bounds by using the field-width modifier with scanf (or better, use fgets()). For example:
//Only allow 20 characters
if (scanf( "%20s", str) != 1) {
fputs ("error: (user canceled input)\n", stderr);
return 1;
}
(note: when reading with "%s" the trailing '\n' is not consumed and will remain in your input buffer)
To fix your pointer problems, you can do something similar to the following:
//Declare the arrays to hold the strings
char str[21], vowels[21], consonants[21];
size_t vidx = 0, cidx = 0; /* indexes for vowels/consonants */
...
//Loop through the user's string until the end of the string
while (*strPointer)
{
//Check if what strPointer is pointing to is a vowel
if (strPointer[i]=='A'|| strPointer[i]=='a'||
strPointer[i]=='E'|| strPointer[i]=='e'||
strPointer[i]=='I'|| strPointer[i]=='i'||
strPointer[i]=='O'|| strPointer[i]=='o'||
strPointer[i]=='U'|| strPointer[i]=='u')
{
//Copy the letter from strPointer to vowelPointer
if (vidx < 20) {
vowelPointer[vidx] = *strPointer;
vidx++;
}
/* or using pointer arithmetic */
// if (vowelPointer - vowels < 20) {
// *vowelPointer = *strPointer;
// vowelPointer++;
// }
}
else {
//Copy the letter from strPointer to consonantPointer
if (cidx < 20) {
consonantPointer[cidx] = *strPointer;
cidx++;
}
/* same alternative available for consonantPointer */
}
//Move the strPointer
strPointer++;
}
//Add null terminators where appropriate
vowelPointer[vidx] = 0;
consonantPointer[cidx] = 0;
//Reset the ponters
vowelPointer = vowels;
consonantPointer = consonants;
//Print the original string and the resulting vowel and consonant strings
printf("Original string: %s\n", str);
printf("%s\n", vowelPointer);
printf("%s\n", consonantPointer);
If you are still stuck, you can put it altogether in a short, slightly more concise example as:
#include <stdio.h>
#include <ctype.h>
#define MAXC 1024 /* don't skimp on buffer size */
int main (void) {
char str[MAXC], cons[MAXC], vowels[MAXC], /* arrays */
*strptr = str, *consptr = cons, *vowelptr = vowels; /* pointers */
fputs ("Enter a string (1022 characters maximum): ", stdout);
if (!fgets (str, MAXC, stdin)) { /* validate EVERY read */
fputs ("error: (user canceled input)\n", stderr);
return 1;
}
while (*strptr) { /* loop over each character */
char lc = tolower (*strptr); /* convert to lowercase to compare */
if (lc == 'a' || lc == 'e' || lc == 'i' || lc == 'o' || lc == 'u')
*vowelptr++ = *strptr; /* copy vowel to array */
else if (!isspace (*strptr)) /* otherwise if not whitespace */
*consptr++ = *strptr; /* copy to consonant array */
strptr++; /* advance string pointer */
}
*vowelptr = *consptr = 0; /* nul-terminate arrays */
printf ("str : %scons : %s\nvowels: %s\n", str, cons, vowels);
}
(question: do you know why there is no '\n' needed after "str : %s" in the printf above?)
Note above that the character is converted to lowercase before comparison for a vowel to cut in-half the number of conditionals needed to check for a vowel. Don't skimp on buffer size. Generally you have 1M of stack space (4M on Linux). Use a buffer of at least 256-chars, or a simple 1K buffer as used above. Also note if you wanted to print using the pointer, you would simply reset them to point to the original arrays immediately after exiting the loop, e.g.
strptr = str;
consptr = cons;
vowelptr = vowels;
Regardless, you can print using the arrays or the pointers after resetting them as both will both point to the same address. See C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)
Example Use/Output
$ ./bin/sepvowels
Enter a string (1022 char max): A quick brown fox jumps over the lazy dog
str : A quick brown fox jumps over the lazy dog
cons : qckbrwnfxjmpsvrthlzydg
vowels: Auioouoeeao