Just for fun I have written a function to check if string given is palindrome. When I run the prog it throws segmentation fault. Could anyone please throw light on it.
int palindrome( const char *input )
{
char * reverse;
int len = 0 ;
int i = 0;
bool result = false;
len = strlen(input);
if( len <= 1)
return -1;
reverse = (char *)malloc( sizeof ( char)* len);
printf( " the len of character is %d", len);
while( input[i++] != '/0')
{
reverse[ --len] = input[i];
}
reverse[len] = '/0';
printf(" the reverse string is %s", reverse);
if( !strcmp( input, reverse) )
return 1;
else
return 0;
}
Thanks
Sam
Changing the code as per suggestions to:
int palindrome( const char *input )
{
char * reverse;
int len = 0 ;
int i = 0;
bool result = false;
len = strlen(input)+1;
if( len <= 2)
return -1;
reverse = (char *)malloc(len);
printf( " the len of character is %d", len);
reverse[len] = '\0' ;
while( input[i++] != '\0')
{
reverse[ --len] = input[i];
}
printf(" the reverse string is %s", reverse);
if( !strcmp( input, reverse) )
return 1;
else
return 0;
}
I still have a problem. The segmentation fault has disappeared but the reversed string is empty.
A few points:
the null terminator for strings is '\0', not '/0'.
sizeof(char) is always 1, you don't need to multiply by it.
you should always allocate enough space for a string and its null terminator such as with strlen(s) + 1.
you should not cast the return value from malloc in C, it hides certain errors that you would be better off knowing about.
since you're gradually reducing len to populate the reverse string, it will end up as 0, not usable for placing the null terminator at the end of that string. This is probably the immediate cause of your core dump since calling strcmp on a non-null-terminated string is a bad idea.
it's not really necessary to create a reversed string at all, you can just check the first and last characters, then the second and second-to-last and so on (until they meet in the middle, or cross over).
On that last point, what I mean is something like (pseudo-code):
def isPalindrome (str):
left = 0
right = strlen(str) - 1
while left < right:
if str[left] <> str[right]:
return false
left = left + 1
right = right - 1
return true
And, with the update, you have the right idea, setting the null terminator fist before reducing len.
But since, len is now the string length plus one, you're populating indexes 1 thru LEN rather than 0 thru LEN-1.
Change:
reverse[len] = '\0';
to:
reverse[--len] = '\0';
Or, better yet, change it so that len is still the length of the string (for printf):
len = strlen (input) + 1;
:
reverse = malloc (len);
to:
len = strlen (input);
:
reverse = malloc (len + 1);
A string with len characters in it needs a buffer of len+1 characters, because of the null character at the end.
Try this. I just have tested it and it works.
int palindrome( const char *input )
{
char * reverse;
int len = strlen(input);
int i = 0;
if( len <= 1)
return -1;
reverse = (char *)malloc( sizeof(char) * len + 1);
printf( " the len of character is %d", len);
reverse[len--] = '\0';
while( input[i] != '\0')
{
reverse[len--] = input[i++];
}
printf(" the reverse string is %s", reverse);
if( !strcmp( input, reverse) )
return 1;
else
return 0;
}
Related
I'm learning C and I've created some small "challenges" for myself to solve. I have to create a program that reads an input string which consists of words separated by underscore and returns the last letter of each odd word followed by the number of chars of that word.
The input won't be empty. The words are separated by exactly 1 underscore. The first and last chars won't be underscores (so no _this_is_a_sentence or this_is_a_sentence_ or _this_is_a_sentence_
Example:
input: we_had_a_lot_of_rain_in_today
output: e2a1f2n2
Explanation:
We only consider words in an odd position, so we just need to consider: we, a, of and in. Now, for each of those words, we get the last char and append the total number of chars of the word: we has 2 chars, so it becomes e2. a has 1 char, so it becomes a1, of has 2 chars so it becomes f2 and in has 2 chars so it becomes n2.
This is my code so far
#include <stdio.h>
void str_dummy_encrypt(char *sentence)
{
int currentWord = 1;
int totalChars = 0;
for (int i = 0; sentence[i] != '\0'; i++)
{
if (sentence[i] == '_')
{
if (currentWord % 2 != 0)
{
// I know the last char of the word is on sentence[i-1]
// and the total chars for this word is totalChars
// but how to return it in order to be printed?
}
currentWord++;
totalChars = 0;
} else {
totalChars++;
}
}
}
int main()
{
char sentence[100];
while (scanf("%s", sentence) != EOF)
{
str_dummy_encrypt(sentence);
}
return 0;
}
I think I'm on the right path, but I don't have any clue on how to return the result to the main function so it can be printed.
Thanks in advance
... how to return the result (?)
You have a couple choices:
Pass in the destination
Caller provides an ample destination.
void str_dummy_encrypt(size_t dsize, char *destination, const char *sentence)
Allocate and return the destination
Caller should free the returned pointer when done.
char *str_dummy_encrypt(const char *sentence) {
...
char *destination = malloc()
...
return destination;
}
Over-write the source
This one is tricky as code needs to insure the destination does not get ahead of the source, but I think you are OK given the task requirements, as long as string length > 1.
void str_dummy_encrypt(char *sentence) {
char *destination = sentence;
...
}
Others
Let us go deeper with pass in the destination and return a flag indicating success/error.
Use snprintf() to form the letter-count.
// Return error flag
int str_dummy_encrypt(size_t dsize, char *destination, const char *sentence) {
...
if (currentWord % 2 != 0) {
int len = snprintf(destination, dsize, "%c%d", sentence[i-1], totalChars);
if (len < 0 || (unsigned) len >= dsize) {
// We ran out of room
return -1; // failure
}
// Adjust to append the next encoding.
dsize -= len;
destination += len;
}
...
return 0;
}
Usage
char sentence[100];
char destination[sizeof sentence + 1]; // I think worse case is 1 more than source.
...
if (str_dummy_encrypt(sizeof destination, destination, sentence)) {
puts("Error");
} else {
puts(destination);
}
Code has other issues:
Does not handle an odd number of words correctly like "abc".
Attempts sentence[i-1] with leading _ like "_abc".
Poor input:
No width limit, weak test.
char sentence[100];
// while(scanf("%s", sentence) != EOF)
while(scanf("%99s", sentence) == 1)
Perhaps other issues.
Consider a test like if(sentence[i+1] == '_' || sentence[i+1] == '\0') to detect end of word and avoid 2 issues mentioned above. (Count and other code will need adjusting too.)
As it follows from the description of the task the function should return a new string that is built based on the fornat of the passed source string.
It means that you need to allocated dynamically a character array within the function where the result string will be stored.
As the source string is not changed within the function then the function parameter should have qualifier const.
And you should always write more general functions. This restriction
The words are separated by exactly 1 underscore. The first and last
chars won't be underscores (so no this_is_a_sentence or
this_is_a_sentence or this_is_a_sentence
for the function does not make it general. The function should be able also to process strings like "_this_is_a_sentence_".
Here is a demonstration program that shows how the function can be implemented.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char * str_dummy_encrypt( const char *s )
{
size_t n = 0;
for (const char *p = s; *p; )
{
size_t length = 0;
while (length == 0 && *p)
{
length = strcspn( p, "_" );
if (length == 0) ++p;
}
if (length != 0)
{
p += length;
n += 1 + snprintf( NULL, 0, "%zu", length );
}
length = 0;
while (length == 0 && *p)
{
length = strcspn( p, "_" );
p += length == 0 ? 1 : length;
}
}
char *result = malloc( n + 1 );
if (result != NULL)
{
result[n] = '\0';
if (n != 0)
{
char *current = result;
for (const char *p = s; *p; )
{
size_t length = 0;
while (length == 0 && *p)
{
length = strcspn( p, "_" );
if (length == 0) ++p;
}
if (length != 0)
{
p += length;
*current++ = p[-1];
current += sprintf( current, "%zu", length );
}
length = 0;
while (length == 0 && *p)
{
length = strcspn( p, "_" );
p += length == 0 ? 1 : length;
}
}
}
}
return result;
}
int main( void )
{
const char *s = "_we__had___a_lot_of_rain_in_today___";
char *result = str_dummy_encrypt( s );
if (result != NULL) puts( result );
free( result );
}
The program output is
e2a1f2n2
The same output will be if to use the string showed in your question that is "we_had_a_lot_of_rain_in_today".
The function would be more general if to add one more parameter that will specify the delimiter as
char * str_dummy_encrypt( const char *s, char c );
Or as the shown function implementation uses the standard C string function strcspn then the function could accept a set of delimiters like
char * str_dummy_encrypt( const char *s, const char *delimiters );
You do not actually need to read the word into a buffer, you can just read one character at a time and keep track of the last char, the word number and its length:
#include <stdio.h>
int main() {
int c, lastc = ' ', n = 1, len = 0;
for (;;) {
c = getchar();
if (c == '_' || c == '\n' || c == EOF) {
if (n & 1) {
printf("%c%d", lastc, len);
}
n++;
len = 0;
if (c != '_')
break;
} else {
lastc = c;
len++;
}
}
printf("\n");
return 0;
}
When I run this function I don't get a return value of 1 or 0. Im not sure why, I'm new to pointers and any type of help/tips would be greatly appreciated.
int isPalindrome (char * str)
{
char def[SIZE];
int length = strlen(str);
for(int count; count <= length; count++ ){
def[count] = str[count];
}
int c;
char *begin, *end, temp;
begin = str;
end = str;
for (c = 0; c < length - 1; c++)
end++;
for (c = 0; c < length/2; c++)
{
temp = *end;
*end = *begin;
*begin = temp;
begin++;
end--;
}
for(int count2; count2 <= length; count2++){
if(str[count2] != def[count2]){
return 0;
}
return 1;
}
}
The function is called with..
if(isPalindrome(arr) == 1)
printf ("\nIs a palindrome.\n\n");
To check if a string is a palindrome you create a reversed string, after first allocating enough room for all chars, by looping through the original one and changing the order. you can then use a strcmp to check if the reversed and original string are the same.
int isPalindrome (char * str)
{
int length = strlen(str);
char* reversed = malloc(sizeof(char)*length); //we allocate enough space for length chars
for(int i = 0; i < length; i++) {
reversed[i] = str[length-1-i]; //populate reversed with the chars in str but in the reversed order
}
if(strcmp(str,reversed) == 0) //strcmo(a,b) return 0 if they are equal
{
free(reversed); //deallocate the space for reversed
return 1;
}
free(reversed); //deallocate the space for reversed
return 0;
}
In these loops
for(int count; count <= length; count++ ){
def[count] = str[count];
}
and
for(int count2; count2 <= length; count2++){
if(str[count2] != def[count2]){
return 0;
}
return 1;
}
there are used uninitialized variables count and count2. So the function has undefined behavior.
Pay attention the the function is declared and defined incorrectly. It is too complicated and uses a magic number SIZE.
Also you should use the type size_t instead of the type int because the return type of the function strlen is size_t and in general an object of the type int can not accommodate an object of the type size_t.
There is no need to create an auxiliary array and change the original string to check whether the given string is a palindrome. Moreover the parameter shall be defined with the qualifier const. Otherwise you will be unable to check whether a string literal is a palindrome because modifying a string literal invokes undefined behavior.
Also if you are trying to use pointers then there is no need to use also indices in loops.
The function can be defined much simpler.
Here you are.
#include <stdio.h>
#include <string.h>
_Bool isPalindrome( const char *s )
{
const char *first = s, *last = s + strlen( s );
if ( first != last )
{
while ( first < --last && *first == *last ) ++first;
}
return !( first < last );
}
int main(void)
{
char *s1 = "121";
printf ( "\"%s\" is %s%s\n", s1, isPalindrome( s1 ) ? "a " : "not ", "palindrome." );
char *s2 = "1221";
printf ( "\"%s\" is %s%s\n", s2, isPalindrome( s2 ) ? "a " : "not ", "palindrome." );
return 0;
}
The program output is
"121" is a palindrome.
"1221" is a palindrome.
As you can see the function uses only pointers and neither index.
Your program has multiple problems. Lets see them one by one:
for(int count; count <= length; count++ ){
^^^^
def[count] = str[count];
}
You are not initialising count, you are just declaring it. Without any initial value count will just be some garbage. To fix this:
for(int count = 0; count <= length; count++ ){
def[count] = str[count];
}
Please note that older C versions require you to declare all variables in the beginning of a function block as far as I remember. So this is anyways wrong.
Next, your palindrome logic is incorrect. You are just swapping characters between begin and end.
All you have to do is check while iterating HALF THE STRING is whether the characters from the beginning equal to the end.
for (c = 0; c < length/2; c++)
{
// Check if "begin" equals to the current "end"
if (*begin != *end)
{
return 0;
}
// Move "begin" forward and "end" backwards
begin++;
end--;
}
Here is your complete function in working condition:
int isPalindrome(char* str)
{
int length = strlen(str);
int c;
char *begin, *end;
begin = str;
// Why use the below for loop when you can directly move end to the end?
end = str + length - 1;
// for (c = 0; c < length - 1; c++)
// end++;
for (c = 0; c < length / 2; c++) {
if (*begin != *end) {
return 0;
}
begin++;
end--;
}
return 1;
}
the function is called here
printf("copy %s\n", string_dupe(s1));
and the function is
char* string_dupe(char *s){
char* new_s;
new_s = malloc(8 * sizeof(s));
int i;
for (i = 0; i < size_of(s); i++){
new_s[i] = s[i];
}
s[i] = '\0';
return new_s;
}
So i've created the NULL byte or i at least think i have. But whenever i run this is keeps getting random values on the end of the input string. i put statements around it checking the ith value and it appears to be a null byte. i also check the next i + 1 element and it is never the same as the value that appears, can anyone help me out?
example input
s[] = "beetles"
ouput - beetles?
sizeof(s) will return the size of the pointer which is 4 or 8 byte. And sizeof(*s) will return size of char (because s is char*) which is 1 byte. To get the length of the string you have to iterate over it until you reach \0 or use strlen.
char* string_dupe(char *s){
unsigned int len = 0;
while (s[len] != '\0') ++len; // conpute length of string
char* new_s = (char*) malloc(len + 1); // one extra byte for '\0'
unsigned int i;
for (i = 0; i < len; ++i){
new_s[i] = s[i];
}
new_s[i] = '\0'; // Append '\0' to new_s not s
return new_s;
}
use strlen(s) to compute the length of the string.
you are suppose to terminate string pointed by new_s with '\0' not s.
char* string_dupe(char *s)
{
char* new_s;
size_t len = strlen(s);
new_s = malloc(len+1);
if (new_s != NULL)
strcpy(new_s,s);
return new_s;
}
I am trying to capture a user input string, then display that string in reverse order next to the initial string. My code is as follows:
char str[300], revstring[300];
int i, strlen;
int main(void) {
printf("Enter a string: "); //Prompt user for input string
gets(str);
for (i = 0; str[i] != NULL; i++) { //Get length of string
strlen += 1;
}
for (i = 0; i <= strlen; i++) {
revstring[i] = str[strlen - i];
}
printf("\n\nThe palindrome of your input is %s%s\n\n\n", str, revstring);
return 0;
}
When I run the program however, I see nothing after the initial string. I come from a python background so maybe I am thinking about this in too much of a python mindset, but I feel like this should work.
After this loop
for (i = 0; str[i] != NULL; i++) { //Get length of string
strlen += 1;
}
str[strlen] is equal to the terminating zero '\0'. And the next loop starts from writing this zero in the first element of the array revstring when i is equal to 0.
for (i = 0; i <= strlen; i++) {
revstring[i] = str[strlen - i];
}
As result nothing is displayed.
Also you should not forget to append the result string with the terminating zero.
Take into account that the function gets is unsafe and is not supported any more by the C Standard. It is better to use the standard function fgets. But using it you should remove the appended new line character.
The program can be written the
#include <stdio.h>
#define N 300
int main( void )
{
char str[N], revstring[N];
printf( "Enter a string: " );
fgets( str, N, stdin );
size_t length = 0;
while ( str[length] != '\0' && str[length] != '\n' ) ++length;
if ( str[length] == '\n' ) str[length] = '\0';
size_t i = 0;
for ( ; i != length; i++ ) revstring[i] = str[length - i - 1];
revstring[i] = '\0';
printf("\n\nThe palindrome of your input is %s%s\n\n\n", str, revstring);
return 0;
}
Its output might look like
Enter a string: Hello, Froobyflake
The palindrome of your input is Hello, FroobyflakeekalfyboorF ,olleH
The string is a null-terminated string. You are copying the null character to the beginning of the reversed string. This tells the system that they string is terminated at the first character.
You could use this code instead.
for (i = 0; i < strlen; i++)
{
revstring[i] = str[(strlen - 1) - i];
}
revstring[strlen] = 0;
Here only the characters before the null character are copied and then the null character is added at the end.
I tried to make a function which replace every word in a text with the word shifted to right by 'k' times.
the code look like this:
void operation_3(char *string, int k){
int len = 0, i;
int string_len = strlen(string);
char *word;
char s[12] = " .,?!\"'";
char *dup;
dup = strdup(string);
word = strtok(dup, s);
while (word != NULL) {
len = strlen(word);
char *new_word = (char *)malloc(len * sizeof(char));
for (i = 0; i < k; i++) {
new_word = shift_to_right(word);
}
string = replace_word(string, word, new_word);
word = strtok(NULL, s);
}
}
shift_to_right is:
char *shift_to_right(char *string){
char temp;
int len = strlen(string) - 1;
int i;
for (i = len - 1; i >= 0; i--) {
temp = string[i+1];
string[i+1] = string[i];
string[i] = temp;
}
return string;
}
replace_word is:
char *replace_word(char *string, char *word, char *new_word) {
int len = strlen(string) + 1;
char *temp = malloc(len * sizeof(char));
int temp_len = 0;
char *found;
while (found = strstr(string, word)) {
if (strlen(found) != strlen(word) || isDelimitator(*(found - 1)) == 1) {
break;
}
memcpy(temp + temp_len, string, found - string);
temp_len = temp_len + found - string;
string = found + strlen(word)
len = len - strlen(word) + strlen(new_word);
temp = realloc(temp, len * sizeof(char));
memcpy(temp + temp_len, new_word, strlen(new_word));
temp_len = temp_len + strlen(new_word);
}
strcpy(temp + temp_len, string);
return temp;
}
and isDelimitator is:
int isDelimitator(char c) {
if(c == ' ' || c == '.' || c == ',' || c == '?' || c == '!' ||
c == '"' || c == '\0' || c == '\'') {
return 0;
}
else return 1;
}
I tested shift_to_right, replace_word and isDelimitator and work fine. But the final function, operation_3 doesn't work as expected. For example, for input: "Hi I am John" and for k = 1 the output is : "Hi I am John". Basically operation_3 doesn't modify the string. Any advice, corrections please?
There are a few things which I see are possibly the reason for error.
1) In operation_3 you do this : new_word = shift_to_right(word); And, in the definition of char *shift_to_right(char *string) you modify the string itself and return a pointer to it. So, if you called shift_to_right(word) and word = "Hi" then after the execution of shift_to_right both word and new_word are now pointing to the same string "iH", so in replace_word when you pass both the words and check for the substring word you will always get NULL, because, there is no substring "iH".
A possible solution, in shift_to_right add a statement,
char *new_string = strdup(string);
and instead of swapping the characters in string, swap the characters now in new_string and return the new_string from the function.
Your code shall look like this ::
char *shift_to_right(char *string){
char temp;
int len = strlen(string) - 1;
char *new_string = strdup(string);
int i;
for (i = len - 1; i >= 0; i--) {
temp = new_string[i+1];
new_string[i+1] = new_string[i];
new_string[i] = temp;
}
return new_string;
}
2) In the function replace_word, for a moment let us consider that the above mentioned error does not occur and replace_word get called with the parameters :: replace_word(string, "Hi", "iH");.
So, when you perform found = strstr(string, word), it gives you a pointer to the first letter where Hi started. So, in this case, if your string was "Hi I am John", then you get a pointer to the first H, and when you perform strlen(found) you will get 12(length of string left starting from the pointer) as the output, and strlen(word) will always be less (unless found points to the last word in the string), so in most cases your if condition becomes true and you break from the loop, without any swapping.
Moreover, as you yourself pointed out in the comments that strstr will return Johns as well if you want a substring John the only solution for this would be to run a loop and check that in string after John if there is delimiter character or not, if there is no delimiter character, then this is not the substring that you needed.
replace_word shall look something like this ::
void replace_word(char *string, char *word, char *new_word) {
char *found = strstr(string, word);
int len = strlen(word);
while(found) {
char temp = *(found + len);
if(isDelimeter(temp) == 0) {
break;
} else {
found = strstr(found + len + 1);
}
}
if(found != NULL) {
for(int i = 0; i < len; i++) {
*(found + i) = new_word[i]; // *(found + i) is accessing the i^th, character in string from the pointer found
}
}
}
I think this replace_word shall work, you can directly modify the string, and there is no need to actually make a temp string and return it. This reduces the need of allocating new memory and saving that pointer.
I hope this could help!
EDIT :: Since we have been using strdup in the code, which dynamically allocates memory of the size of the string with an extra block for the \0 character, we shall take care of freeing it explicitly, so it will be a good idea according to me free the allocated memory in replace_word just before we exit the function since the new_word is useless after it.
Moreover, I saw a statement in your code::
1) char *new_word = (char *)malloc(len * sizeof(char));
Just before you start the shifting the words, I hope you understand that you do not need to do it. new_word is just a pointer, and since we now allocated memory to it in strdup we do not need to do it. Even before, considering the code that you had written there was no reason to allocate memory to new_word since you were returning the address of the array, which was already in the stack, and would stay in the stack till the end of the execution of the program.
This code is simpler than what you have, and it prints all the word delimiters that were in the input string. And rather than looking for specific punctuation characters, it checks alphanumeric instead.
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main(void)
{
char instr[] = "Hi! I am 'John' ;)";
int lennin = strlen(instr);
int shifts, i, len, index, start, next;
printf("Working with %s\n", instr);
for(shifts=0; shifts<5; shifts++) { // various examples
printf("Shifts = %d ", shifts);
start = 0;
while(start < lennin) {
while (start < lennin && !isalnum(instr[start])) { // find next alphanum
printf("%c", instr[start]); // output non-alphanum
start++;
}
next = start + 1;
while (isalnum(instr[next])) // find next non-alphanum
next++;
len = next - start;
for(i=0; i<len; i++) { // shift the substring
index = i - shifts;
while(index < 0) index += len; // get index in range
printf("%c", instr[start + (index % len)]); // ditto
}
start = next; // next substring
}
printf("\n");
}
return 0;
}
Program output:
Working with Hi! I am 'John' ;)
Shifts = 0 Hi! I am 'John' ;)
Shifts = 1 iH! I ma 'nJoh' ;)
Shifts = 2 Hi! I am 'hnJo' ;)
Shifts = 3 iH! I ma 'ohnJ' ;)
Shifts = 4 Hi! I am 'John' ;)