Less Code to Achieve this Effect? - c

I am working on some string manipulation functions, just out of my own interest. However, I may want to use these functions sometime in future code. I wrote the following to check if a substring exists within a string. I ran into a problem though. My program compares each char in both strings, however it had problems with out of order chars. It would take a long time to explain so I will just give an example:
if checking if "oobar" exists in the string "fooobar" my program would have trouble finding the location of the substrings because it would trip up on the first instances of the char 'o'
I developed a work around to this, but that's what it is a work around and not really a solid solution. So I was wondering if anybody could tell me how they would improve the following code (keep in mind I do NOT want to use any additional libraries):
int chksbstr(char *str, char *sbstr)
{
int i, sbstrlen, strlen, p = 0;
for(i = 0; sbstr[i] != '\0'; i++);
sbstrlen = i;
for(i = 0; str[i] != '\0'; i++);
strlen = i;
if(sbstrlen > strlen)
{
printf("\n**Error substring is larger than base string!");
return 2;
}
if(sbstrlen == strlen)
{
if(str == sbstr) return 0;
else return 1;
}
for(i = 0; i <= strlen; i++)
{
if(str[i] == sbstr[p]) p++;
else if(str[i] != str[i - 1]) p = 0;
if(p == sbstrlen) return 0;
}
return 1;
}

23 lines fewer:
if (strstr(baseString, subString) != NULL)
{
/* contains */
}

Recursion can do this better. Oh, and by the way, descriptive identifiers are way better. There's no reason to randomly remove all the vowels, and don't forget const.
int check_substring(const char* str, const char* to_go, const char* substr) {
if (*to_go == '\0') return 1; // Hit all
if (*str == '\0') return 0; // Ran out of string to check
if (*str == *to_go) return check_substring(str + 1, to_go + 1, substr);
else {
if (*str == *substr)
return check_substring(str + 1, substr + 1, substr);
else
return check_substring(str + 1, substr, substr);
}
}
int does_contain_substring(const char* str, const char* sbstr) {
return check_substring(str, sbstr, sbstr);
}

Related

Can only write to first element of char** buffer

I'm quite new to C and am trying to write a function, which will split a string into an array of strings at a specific delimiter. But strangely I can only write at the first index of my char** array of strings, which will be my result. For example if I want to split the following string "Hello;;world;;!" at ;; I get [ "Hello" ] instead of [ "Hello", "world", "!" ]. I can't find my mistake.
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "strings.h"
int split(char **dest, const char *src, const char *splitStr) {
char buffer[16384];
int counter = 0;
int len = strlen(splitStr);
int flag = 0;
int start = 0;
for (int i = 0; i < strlen(src); i++) {
flag = 0;
if (src[i] == splitStr[0]) {
for (int j = 1; j < len; j++) {
//check if all elements in delimiter are in string
if (src[i + j] == splitStr[j] && j != (len - 1)) {
continue;
}
else if(src[i + j] == splitStr[j] && j == (len - 1)) {
buffer[i] = '\0';
dest[counter] = malloc(sizeof(char) * (i - start + 1));
strncpy(dest[counter], buffer + start, (i - start));
start = i + (len-1)
flag = 1;
i += (len - 1);
counter++;
}
//if not break
else {
break;
}
}
}
if (i == (strlen(src) - 1)) {
buffer[i] = src[i];
buffer[i + 1] = '\0';
counter++;
break;
}
if (flag == 0) {
buffer[i] = src[i];
}
}
return counter;
}
A proper function call would look like this:
auto src = "Hello;;world;;!";
auto buffer = (char **)malloc(32);
int count = split(buffer, src, ";;");
The buffer should contain, all the splitted strings, more or less like this: [ "Hello", "world", "!" ].
Currently my result buffer looks like this in the debugger. It appears as only the first element is written into it.
There are multiple problems in your code:
you compute string lengths repeatedly, which may be very inefficient. Instead of testing i < strlen(src) you should write src[i] != '\0'.
your test for check a matching delimiter is too complicated. You should use strstr to locate the delimiter string in the remaining portion of the string.
strncpy does not do what you think: strncpy(dest[counter], buffer + start, (i - start)); should be replaced with memcpy(dest[counter], buffer + start, i - start); and you must set the null terminator explicitly: dest[counter][i - start] = '\0'; You should read why you should never use strncpy().
it is unclear why you use buffer at all.
Here is a modified version:
#include <stdlib.h>
#include <string.h>
/* if POSIX function strndup() is not defined on your system, use this */
char *strndup(const char *str, size_t n) {
size_t len;
for (len = 0; len < n && str[len] != '\0'; len++)
continue;
char *s = malloc(len + 1);
if (s != NULL) {
memcpy(s, str, len);
s[len] = '\0';
}
return s;
}
int split(char **dest, const char *src, const char *splitStr) {
const char *p = str;
const char *end;
int counter = 0;
size_t len = strlen(splitStr);
if (len == 0) {
/* special case */
while (*p != '\0') {
dest[counter++] = strndup(p++, 1);
}
} else {
while ((end = strstr(p, splitStr)) != NULL) {
dest[counter++] = strndup(p, end - p);
p = end + len;
}
dest[counter++] = strdup(p);
}
return counter;
}
First of all you are not updating the start variable after you have copied the first string.
For simple debugging I would recommend adding some printf statements to see what is going on.
Proper formatting is not to be underestimated to make the code easy to read and easier to debug.
Also it is not clear what the buffer is for, and I think you can do without it.
The tips in the comments are also good. Split the function into smaller pieces and structure your code so it is simple to read.
A suggestion is to write a function to find the index of the next split string and the end of the string. Then you can use that to get the index and length you need to copy.

Check whether a given substring is present in the given string

I should write a program in C to check whether a given substring is present in the given string. The code I wrote is below but it doesn't work. Can anyone tell me where the problem is?
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[30]="the test string";
char sbstr[30]="test";
char strcp[30];
int len = strlen(str);
int i=0;
int p=0;
while(i<len)
{
while (str[i] != '\0' && str[i] != ' ')
{
strcp[i] = str[i];
++i;
}
strcp[i] = '\0';
p = strcmp(sbstr, strcp);
if (p==0)
{
printf("exist");
break;
}
++i;
}
}
For the array strcp
char strcp[30];
you need to support a separate index.
Something like
int j = 0;
while (str[i] != '\0' && str[i] != ' ')
{
strcp[j++] = str[i++];
}
strcp[j] = '\0';
Pay attention to that there is standard C function strstr that can be used to perform the task.
I know you've already accepted an answer, but here's a slightly more efficient way to do a substring comparison that does not involve making a copy of the candidate substring to begin with in each iteration.
char str[30]="the test string";
char sbstr[30]="test";
int len = strlen(str);
int sublen = strlen(sbstr);
int found = 0;
int i = 0; // starting index in str to start comparing on
while (!found && sublen <= len) {
found = 1;
// found = !strncmp(str+i, sbstr, sublen);
for (int j = 0; j < sublen; j++) {
if (str[i+j] != sbstr[j]) {
found = 0;
break;
}
}
if (!found) {
i++;
len--;
}
}
if (found) {
printf("Exists starting at index %d\n", i);
}
And if you really want to get hardcore, there are well known algorithms such as the Boyer–Moore string-search algorithm which can search faster by using a table-lookup scheme IIRC.

Sorting an array of strings in an alphabetical order using pointers

I have a project where I have to create a program that would let users input names in any order. Then the program displays the names alphabetical order. Also all of this has to be done using pointers. Now my attempt at the program prompts the user to put in the names and it displays them, but I can't get it to sort them for some reason. Can someone please help me?
Here's my attempt at the program:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main() {
int list;
char *names[20];
char str[20];
printf("Enter the number of names: ");
scanf("%d", &list);
fflush(stdin);
for (int i = 0; i < list; i++) {
printf("Enter name %d: ", i + 1);
// gets(str);
scanf("%[^\t\n]s", str);
fflush(stdin);
names[i] = (char *)malloc(strlen(str) + 1);
strcpy(names[i], str);
}
void sortNames();
for (int i = 0; i < 5; i++)
printf("%s\n", names[i]);
return 0;
}
void sortNames(char **name, int *n) {
int i, j;
for (j = 0; j < *n - 1; j++) {
for (i = 0; i < *n - 1; i++) {
if (compareStr(name[i], name[i + 1]) > 0) {
char *t = name[i];
name[i] = name[i + 1];
name[i + 1] = t;
}
}
}
}
int compareStr(char *str1, char *str2) {
while (*str1 == *str2) {
if (*str1 == '\0' || *str2 == '\0')
break;
str1++;
str2++;
}
if (*str1 == '\0' && *str2 == '\0')
return 0;
else
return -1;
}
Focusing only on the issues with sorting, the main one is that you never call the sorting function you later define. The line
void sortNames();
serves only to declare a function with the identifier sortNames which takes any number of arguments of any types (probably not quite what you want to do). I would recommend revising this line to instead be
sortNames(names, list); // Not &list because I'm about to suggest not taking it as a pointer
Then for the sortNames function itself, I'm not totally clear on why you take the length of the array to be sorted as a pointer instead of just passing the int itself. I'd recommend revising this function to instead be
void sortNames(char **name, int n) {
int i, j;
for (j = 0; j < n - 1; j++) {
for (i = 0; i < n - 1; i++) {
if (compareStr(name[i], name[i + 1]) > 0) {
char *t = name[i];
name[i] = name[i + 1];
name[i + 1] = t;
}
}
}
}
One issue with this as it stands though is that the expression compareStr(name[i], name[i + 1]) > 0 is always false. This is due to the fact that compareStr only ever returns 0 or -1. You could fix this by re-writing compareStr to correctly handle the case where the *str1 > *str2. One possible way to do this could be
int compareStr(char *str1, char *str2) {
if (*str1 == '\0' && *str2 == '\0') {
return 0;
} else if (*str1 > *str2) {
return 1;
} else if (*str1 < *str2) {
return -1;
}
return compareStr(str1 + 1, str2 + 1);
}
Although if you're writing this to learn I'd suggest trying to revise your current iterative solution instead of just copying & pasting this version.
Finally, because you want to use these functions before you've defined them you should either move their definitions prior to when they're used (i.e., have compareStr, then sortNames and then main) or provide a forward declaration at the start of your file for these functions, i.e., add
void sortNames(char **name, int n);
int compareStr(char *str1, char *str2);
above your main.
As others have noted you probably want to avoid fflush(stdin) as its undefined behaviour and I would recommend against casting the result of malloc.
The problem here is that the function compareStr will never return a value greater than 0. It just tells you if the 2 strings are similar or not.
For sorting, you need to add an extra logic as follows:
int compareStr(char *str1, char *str2) {
while (*str1 == *str2) {
if (*str1 == '\0' || *str2 == '\0')
break;
str1++;
str2++;
}
if (*str1 == '\0' && *str2 == '\0'){
return 0;
}
else if(*str1 > *str2){
return 1;
}else{
return -1;
}
}
Besides this, you have to make a call to function sortNames as sortNames(names, &list) and make sure that function definitions are written in proper order or make use of function declarations.

The program returns some weird and incomplete string

I am trying to write a function that delete whitespaces from a string but the output is not reasonable at all. I need help fam!
Code:
char* deleteSpace(char *String, int n) {
int i;
char* withoutSpaces;
withoutSpaces = calloc(n, sizeof (char));
for (i = 0; i < n - 1; i++) {
if (String[i] == ' ')
withoutSpaces[i] = String[++i];
else
withoutSpaces[i] = String[i];
}
return withoutSpaces;
}
You need to have to indices a "read" index for the source string and a "write" index for the destination.
Also, for better debugability and readability, put the index increment, ++i on a separate line. Oh - it looks like you are incrementing i twice. Once implicitly by the loop and again with the ++i.
Also unclear if n represents the length of the string with or without the null terminator. So let's just let the function deal with figuring that out via strlen.
Don't forget to null terminate the output string.
Several other bugs as well. Here's a version that's improved:
char* deleteSpace(const char *String) {
int j = 0;
const char* ptr = String;
size_t n = 0;
size_t spaces = 0;
char* withoutSpaces = NULL;
// count how many characters we expect to copy over
while (*ptr) {
n += (*ptr == ' ') ? 0 : 1;
ptr++;
}
withoutSpaces = (char*)malloc(n+1); // +1 for null char
for (i = 0; i < n; i++) {
if (String[i] != ' ') {
withoutSpaces[j] = String[i];
j++;
}
}
withoutSpaces[j] = '\0';
return withoutSpaces;
}
Also, if you just want to compact the string in place without allocating a new string.
void deleteSpace(char *String) {
char* ptrWrite = String;
while (*String) {
if (*String != ' ') {
*ptrWrite = *String;
ptrWrite++;
}
String++;
}
*ptrWrite = '\0';
}

Removing consecutive repeated characters from string using C

I'm trying to remove consecutive repeated characters from a given string.
Example:
bssdffFdcrrrtttii ***#
output is supposed to be:
bsdfFdcrti *#
This code doesn't work and only prints the first char (b), I want to learn about my mistake.
when I'm doing a printf test, it works but not for spaces.
I think the problem might be with the new char array.
void Ex6() {
char* string[80];
scanf("%s", &string);
puts(removeDup(string));
}
char* removeDup(char *string) {
int i, c = 0;
char* newString[80];
for (i = 0; i < strlen(string); i++) {
if (string[i] != string[i + 1]) {
newString[c++] = string[i];
}
}
return newString;
}
There are several problems with your program:
The declaration of newString should be char newString[80], i.e., an array of characters and not an array of pointers-to-characters, and likewise for the declaration in Ex6.
The call to scanf should then be scanf("%s", string), since string is already the address of an array of characters, but...
Use fgets to read a string from the user to ensure that you read whitespace, if it's important, and that the buffer is not exceeded.
newString is allocated on the stack and so should not be returned to the caller. It is better to do a char *newString = strdup(string), or, slightly less sloppy, char *newString = malloc(strlen(string)+1), which will call malloc for a block of memory sufficient to hold the original string, and thus the version without duplicates -- the comments rightly point out that this could be optimized. In principle, the caller, i.e., Ex6, must free the returned pointer to avoid a memory leak but it hardly matters in such a short program.
The result needs a null terminator: newString[c] = '\0'.
Otherwise, the removeDup function seems to work correctly.
So, putting all of that together:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* removeDup(const char *string)
{
size_t i, c = 0;
size_t string_len = strlen(string);
char *newString = malloc(string_len + 1);
for (i = 0; i < string_len; i++) {
if (string[i] != string[i + 1]) {
newString[c++] = string[i];
}
}
newString[c] = '\0';
return newString;
}
#define MAX_STRING_LEN 80
void Ex6() {
char string[MAX_STRING_LEN];
char* result;
if (fgets(string, MAX_STRING_LEN, stdin) != NULL) {
result = removeDup(string);
printf("%s", result);
free(result);
}
}
Finally, I agree with #tadman's comment. Since the input string must anyway be traversed to calculate the length, we may as well optimize the size of the result string:
char* removeDup(const char *string)
{
size_t i, c = 0;
char *newString;
for (i = 0; string[i] != '\0'; i++)
c += (string[i] != string[i + 1]);
newString = malloc(c + 1);
for (i = c = 0; string[i] != '\0'; i++) {
if (string[i] != string[i + 1]) {
newString[c++] = string[i];
}
}
newString[c] = '\0';
return newString;
}
There are quite a few issues in your program. It wouldn't even compile let alone run. Also, the most problematic issue is that you are returning a pointer to a local variable from a function that ceases its scope upon completion. A simplified version of your program is as follows:
void Ex6()
{
char string[80];
scanf("%s", string);
int i, c = 0;
char newString[80];
for (i = 0; i < strlen(string); i++) {
if (string[i] != string[i + 1]) {
newString[c++] = string[i];
}
}
newString[c] = '\0';
puts(newString);
}
You can do it with O(n) time and O(1) space, by modifying existing string:
#include <stdio.h>
char* removeDup(char* input) {
char* newTail = input, *oldTail = input;
while (*oldTail) {
if (*newTail == *oldTail) {
++oldTail;
} else {
*++newTail = *oldTail++;
}
}
return newTail;
}
int main() {
char string[] = "bssdffFdcrrrtttii ***#";
char* newEnd = removeDup(string);
char* tmp = string;
while (tmp != newEnd) {
printf("%c", *tmp++);
}
//Print the last char if string had any duplicates
if(*tmp) {
printf("%c", *tmp++);
}
return 0;
}

Resources