C - Print the most frequent strings - c

in these days I have been posting some code because I am doing an exercise, finally it seems that I have ended it, but I noticed it doesn't work.
The exercise asks in input:
- N an integer, representing the number of strings to read
- K an integer
- N strings
The strings can be duplicates. In the output there is a print of the K strings most frequent, ordered according to their frequency (decreasing order).
Example test set:
Input:
6
2
mickey
mouse
mickey
hello
mouse
mickey
Output:
mickey // Has freq 3
mouse // Has freq 2
I hope I explained the exercise in a good way, as this is my attempt.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct _stringa {
char* string;
int freq;
} stringa;
int compare(const void *elem1, const void *elem2) {
stringa *first = (stringa *)elem1;
stringa *second = (stringa *)elem2;
if (first->freq < second->freq) {
return -1;
} else if (first->freq > second->freq) {
return 1;
} else {
return 0;
}
}
int BinarySearch(stringa** array, char* string, int left, int right) {
int middle;
if (left==right) {
if (strcmp(string,array[left]->string)==0) {
return left;
} else {
return -1;
}
}
middle = (left+right)/2;
if ((strcmp(string,array[middle]->string)<0) || (strcmp(string,array[middle]->string)==0) ) {
return BinarySearch(array, string, left, middle);
} else {
return BinarySearch(array, string, middle+1, right);
}
}
int main (void)
{
char value[101];
int n = 0;
int stop;
scanf("%d", &n); // Number of strings
scanf("%d", &stop); // number of the most frequent strings to print
stringa **array = NULL;
array = malloc ( n * sizeof (struct _stringa *) );
int i = 0;
for (i=0; i<n; i++) {
array[i] = malloc (sizeof (struct _stringa));
array[i]->string = malloc (sizeof (value));
scanf("%s", value);
int already;
already = BinarySearch(array, value, 0, i); // With a binary search, I see if the string is present in the previous positions of the array I am occupying. If it is not present, I copy the string into the array, otherwise, I use the value of binary search (which is the position of the element in the array) and I update the frequency field
if (already==-1) {
strcpy(array[i]->string,value);
array[i]->freq = 1;
} else {
array[already]->freq += 1;
}
}
stringa **newarray = NULL; // New struct array of strings
newarray = malloc ( n * sizeof (struct _stringa *) );
int k = 0;
for (i=0; i<n; i++) { // I use this loop to copy the element that don't have a frequency == 0
if (array[i]->freq != 0) {
newarray[k] = malloc(sizeof(struct _stringa));
newarray[k] = malloc(sizeof(value));
newarray[k]->string = array[i]->string;
newarray[k]->freq = array[i]->freq;
k++;
}
}
qsort(newarray, n, sizeof(stringa*), compare);
i=0;
while ((newarray[i]!= NULL) && (i<k)) {
printf("%s ", newarray[i]->string);
printf("%d\n", newarray[i]->freq);
i++;
}
// Freeing operations
while (--n >= 0) {
if (array[n]->string) free (array[n]->string);
if (array[n]) free (array[n]);
}
if (array) free (array);
if (newarray) free (newarray);
return 0;
}
Thank you in advance to anyone who will have the time and patience to read this code.
EDIT:
I forgot to add what it's not working right.
If I don't use the qsort for debugging reasons, and I use this input for example:
5
2 // random number, I still have to do the 'print the k strings' part,
hello
hello
hello
hello
hello
It prints:
hello 3 (freq)
hello 2 (freq)
So it doesn't work properly. As you suggested in the comments, the binary search is flawed as it works only on an ordered list. What I could do is order the array each time, but I think this would be counter-productive. What could be the idea to get rid of the problem of locating only the strings that are not present in the array?

If you want an efficient method without sorting, use a hash table.
Otherwise, simply put the each unique string in an array and scan it linearly, simple and reliable.
On modern hardware, this kind of scan is actually fast due to caches and minimising indirection. For small numbers of items an insertion sort is actually more efficient than qsort's in practice. Looking at the "Tim sort" algorithm for instance, which is stable and avoids qsort's poor performance with nearly sorted data, it mixes merge and insertion sorts to achieve n Log n, without extreme cases on real data.

Related

How can I correctly allocate memory for this MergeSort implementation in C (with the DS I am using)?

My goal here is to perform MergeSort on a dynamic array-like data structure I called a dictionary used to store strings and their relative weights. Sorry if the implementation is dumb, I'm a student and still learning.
Anyway, based on the segfaults I'm getting, I'm incorrectly allocating memory for my structs of type item to be copied over into the temporary lists I'm making. Not sure how to fix this. Code for mergesort and data structure setup is below, any help is appreciated.
/////// DICTIONARY METHODS ////////
typedef struct {
char *item;
int weight;
} item;
typedef struct {
item **wordlist;
//track size of dictionary
int size;
} dict;
//dict constructor
dict* Dict(int count){
//allocate space for dictionary
dict* D = malloc(sizeof(dict));
//allocate space for words
D->wordlist = malloc(sizeof(item*) * count);
//initial size
D->size = 0;
return D;
}
//word constructor
item* Item(char str[]){
//allocate memory for struct
item* W = malloc(sizeof(item));
//allocate memory for string
W->item = malloc(sizeof(char) * strlen(str));
W->weight = 0;
return W;
}
void merge(dict* D, int start, int middle, int stop){
//create ints to track lengths of left and right of array
int leftlen = middle - start + 1;
int rightlen = stop - middle;
//create new temporary dicts to store the two sides of the array
dict* L = Dict(leftlen);
dict* R = Dict(rightlen);
int i, j, k;
//copy elements start through middle into left dict- this gives a segfault
for (int i = 0; i < leftlen; i++){
L->wordlist[i] = malloc(sizeof(item*));
L->wordlist[i] = D->wordlist[start + i];
}
//copy elements middle through end into right dict- this gives a segfault
for (int j = 0; j < rightlen; j++){
R->wordlist[j] = malloc(sizeof(item*));
R->wordlist[j]= D->wordlist[middle + 1 + k];
}
i = 0;
j = 0;
k = leftlen;
while ((i < leftlen) && (j < rightlen)){
if (strcmp(L->wordlist[i]->item, R->wordlist[j]->item) <= 0) {
D->wordlist[k] = L->wordlist[i];
i++;
k++;
}
else{
D->wordlist[k] = R->wordlist[j];
j++;
k++;
}
}
while (i < leftlen){
D->wordlist[k] = L->wordlist[i];
i++;
k++;
}
while (j < rightlen){
D->wordlist[k] = L->wordlist[j];
j++;
k++;
}
}
void mergeSort(dict* D, int start, int stop){
if (start < stop) {
int middle = start + (stop - start) / 2;
mergeSort(D, start, middle);
mergeSort(D, middle + 1, stop);
merge(D, start, middle, stop);
}
I put print statements everywhere and narrowed it down to the mallocs in the section where I copy the dictionary to be sorted into 2 separate dictionaries. Also tried writing that malloc as malloc(sizeof(D->wordlist[start + i])). Is there something else I need to do to be able to copy the item struct into the wordlist of the new struct?
Again, I'm new to this, so cut me some slack :)
There are numerous errors in the code:
In merge() when copying elements to the R list, the wrong (and uninitialized) index variable k is being used instead of j. R->wordlist[j]= D->wordlist[middle + 1 + k]; should be R->wordlist[j]= D->wordlist[middle + 1 + j];.
In merge() before merging the L and R lists back to D, the index variable k for the D list is being initialized to the wrong value. k = leftLen; should be k = start;.
In merge() in the loop that should copy the remaining elements of the "right" list to D, the elements are being copied from the "left" list instead of the "right" list. D->wordlist[k] = L->wordlist[j]; should be D->wordlist[k] = R->wordlist[j];.
In Item(), the malloc() call is not reserving space for the null terminator at the end of the string. W->item = malloc(sizeof(char) * strlen(str)); should be W->item = malloc(sizeof(char) * (strlen(str) + 1)); (and since sizeof(char) is 1 by definition it can be simplified to W->item = malloc(strlen(str) + 1);).
Item() is not copying the string to the allocated memory. Add strcpy(W->item, str);.
There are memory leaks in merge():
L->wordlist[i] = malloc(sizeof(item*)); is not required and can be removed since L->wordlist[i] is changed on the very next line: L->wordlist[i] = D->wordlist[start + i];.
Similarly, R->wordlist[j] = malloc(sizeof(item*)); is not required and can be removed since R->wordlist[j] is changed on the very next line.
L and R memory is created but never destroyed. Add these lines to the end of merge() to free them:
free(L->wordlist);
free(L);
free(R->wordlist);
free(R);
None of the malloc() calls are checked for success.
Allocate it all at once, before the merge sort even starts.
#include <stdlib.h>
#include <string.h>
// Weighted Word --------------------------------------------------------------
//
typedef struct {
char *word;
int weight;
} weighted_word;
// Create a weighted word
//
weighted_word* CreateWeightedWord(const char *str, int weight){
weighted_word* W = malloc(sizeof(weighted_word));
if (W){
W->word = malloc(strlen(str) + 1); // string length + nul terminator
if (W->word)
strcpy( W->word, str);
W->weight = weight;
}
return W;
}
// Free a weighted word
//
weighted_word *FreeWeightedWord(weighted_word *W){
if (W){
if (W->word)
free(W->word);
free(W);
}
return NULL;
}
// Dictionary (of Weighted Words) ---------------------------------------------
//
typedef struct {
weighted_word **wordlist; // this is a pointer to an array of (weighted_word *)s
int size; // current number of elements in use
int capacity; // maximum number of elements available to use
} dict;
// Create a dictionary with a fixed capacity
//
dict* CreateDict(int capacity){
dict* D = malloc(sizeof(dict));
if (D){
D->wordlist = malloc(sizeof(weighted_word*) * capacity);
D->size = 0;
D->capacity = capacity;
}
return D;
}
// Free a dictionary (and all weighted words)
//
dict *FreeDict(dict *D){
if (D){
for (int n = 0; n < D->size; n++)
FreeWeightedWord(D->wordlist[n]);
free(D->wordlist);
free(D);
}
return NULL;
}
// Add a new weighted word to the end of our dictionary
//
void DictAddWord(dict *D, const char *str, int weight){
if (!D) return;
if (D->size == D->capacity) return;
D->wordlist[D->size] = CreateWeightedWord(str, weight);
if (D->wordlist[D->size])
D->size += 1;
}
// Merge Sort the Dictionary --------------------------------------------------
// Merge two partitions of sorted words
// words • the partitioned weighted word list
// start • beginning of left partition
// middle • end of left partition, beginning of right partition
// stop • end of right partition
// buffer • temporary work buffer, at least as big as (middle-start)
//
void MergeWeightedWords(weighted_word **words, int start, int middle, int stop, weighted_word **buffer){
int Lstart = start; int Rstart = middle; // Left partition
int Lstop = middle; int Rstop = stop; // Right partition
int Bindex = 0; // temporary work buffer output index
// while (left partition has elements) AND (right partition has elements)
while ((Lstart < Lstop) && (Rstart < Rstop)){
if (strcmp( words[Rstart]->word, words[Lstart]->word ) < 0)
buffer[Bindex++] = words[Rstart++];
else
buffer[Bindex++] = words[Lstart++];
}
// if (left partition has any remaining elements)
while (Lstart < Lstop)
buffer[Bindex++] = words[Lstart++];
// We don't actually need this. Think about it. Why not?
// // if (right partition has any remaining elements)
// while (Rstart < Rstop)
// buffer[Bindex++] = words[Rstart++];
// Copy merged data from temporary buffer back into source word list
for (int n = 0; n < Bindex; n++)
words[start++] = buffer[n];
}
// Merge Sort an array of weighted words
// words • the array of (weighted_word*)s to sort
// start • index of first element to sort
// stop • index ONE PAST the last element to sort
// buffer • the temporary merge buffer, at least as big as (stop-start+1)/2
//
void MergeSortWeightedWords(weighted_word **words, int start, int stop, weighted_word **buffer){
if (start < stop-1){ // -1 because a singleton array is by definition sorted
int middle = start + (stop - start) / 2;
MergeSortWeightedWords(words, start, middle, buffer);
MergeSortWeightedWords(words, middle, stop, buffer);
MergeWeightedWords(words, start, middle, stop, buffer);
}
}
// Merge Sort a Dictionary
//
void MergeSortDict(dict *D){
if (D){
// We only need to allocate a single temporary work buffer, just once, right here.
dict * Temp = CreateDict(D->size);
if (Temp){
MergeSortWeightedWords(D->wordlist, 0, D->size, Temp->wordlist);
}
FreeDict(Temp);
}
}
// Main program ---------------------------------------------------------------
#include <stdio.h>
int main(int argc, char **argv){
// Command-line arguments --> dictionary
dict *a_dict = CreateDict(argc-1);
for (int n = 1; n < argc; n++)
DictAddWord(a_dict, argv[n], 0);
// Sort the dictionary
MergeSortDict(a_dict);
// Print the weighted words
for (int n = 0; n < a_dict->size; n++)
printf( "%d %s\n", a_dict->wordlist[n]->weight, a_dict->wordlist[n]->word );
// Clean up
FreeDict(a_dict);
}
Notes for you:
Be consistent. You were inconsistent with capitalization and * placement and, oddly, vertical spacing. (You are waaay better than most beginners, though.) I personally hate the Egyptian brace style, but to each his own.
I personally think there are far too many levels of malloc()s in this code too, but I will leave it at this one comment. It works as is.
Strings must be nul-terminated — that is, each string takes strlen() characters plus one for a '\0' character. There is a convenient library function that can copy a string for you too, called strdup(), which AFAIK exists on every system.
Always check that malloc() and friends succeed.
Don’t forget to free everything you allocate. Functions help.
“Item” was a terribly non-descript name, and it overlapped with the meaning of two different things in your code. I renamed them to separate things.
Your dictionary object should be expected to keep track of how many elements it can support. The above code simply refuses to add words after the capacity is filled, but you could easily make it realloc() a larger capacity if the need arises. The point is to prevent invalid array accesses by adding too many elements to a fixed-size array.
Printing the array could probably go in a function.
Notice how I set start as inclusive and stop as exclusive. This is a very C (and C++) way of looking at things, and it is a good one. It will help you with all kinds of algorithms.
Notice also how I split the Merge Sort up into two functions: one that takes a dictionary as argument, and a lower-level one that takes an array of the weighted words as argument that does all the work.
The higher-level merge sort a dictionary allocates all the temporary buffer the merge algorithm needs, just once.
The lower-level merge sort an array of (weighted_word*)s expects that temporary buffer to exist and doesn’t care (or know anything) about the dictionary object.
The merge algorithm likewise doesn't know much. It is simply given all the information it needs.
Right now the merge condition simply compares the weighted-word’s string value. But it doesn’t have to be so simple. For example, you could sort equal elements by weight. Create a function:
int CompareWeightedWords(const weighted_word *a, const weighted_word *b){
int rel = strcmp( a->word, b->word );
if (rel < 0) return -1;
if (rel > 0) return 1;
return a->weight < b->weight ? -1 : a->weight > b->weight;
}
And put it to use in the merge function:
if (CompareWeightedWords( words[Rstart], words[Lstart] ) < 0)
buffer[Bindex++] = words[Rstart++];
else
buffer[Bindex++] = words[Lstart++];
I don’t think I forgot anything.

last number in a function array

I want to write a function where I have a given array and number N. The last occurrence of this number I want to return address. If said number cannot be found I want to use a NULL-pointer
Start of the code I've made:
int main(void) {
int n = 3;
int ary[6] = { 1,3,7,8,3,9 };
for (int i = 0; i <= 6; i++) {
if (ary[i] == 3) {
printf("%u\n", ary[i]);
}
}
return 0;
}
result in command prompt:
3
3
The biggest trouble I'm having is:
it prints all occurrences, but not the last occurrence as I want
I haven't used pointers much, so I don't understand how to use the NULL-pointer
I see many minor problems in your program:
If you want to make a function, make a function so your parameters and return types are explicit, instead of coding directly in the main.
C arrays, like in most languages, start the indexing at 0 so if there are N element the first has index 0, then the second has 1, etc... So the very last element (the Nth) has index N-1, so in your for loops, always have condition "i < size", not "i <= size" or ( "i <= size-1" if y'r a weirdo)
If you want to act only on the last occurence of something, don't act on every. Just save every new occurence to the same variable and then, when you're sure it was the last, act on it.
A final version of the function you describe would be:
int* lastOccurence(int n, int* arr, int size){
int* pos = NULL;
for(int i = 0; i < size; i++){
if(arr[i] == n){
pos = &arr[i]; //Should be equal to arr + i*sizeof(int)
}
}
return pos;
}
int main(void){
int n = 3;
int ary[6] = { 1,3,7,8,3,9 };
printf("%p\n", lastOccurence(3, ary, 6);
return 0;
}
Then I'll add that the NULL pointer is just 0, I mean there is literally the line "#define NULL 0" inside the runtime headers. It is just a convention that the memory address 0 doesn't exist and we use NULL instead of 0 for clarity, but it's exactly the same.
Bugs:
i <= 6 accesses the array out of bounds, change to i < 6.
printf("%u\n", ary[i]); prints the value, not the index.
You don't actually compare the value against n but against a hard-coded 3.
I think that you are looking for something like this:
#include <stdio.h>
int main(void)
{
int n = 3;
int ary[6] = { 1,3,7,8,3,9 };
int* last_index = NULL;
for (int i = 0; i < 6; i++) {
if (ary[i] == n) {
last_index = &ary[i];
}
}
if(last_index == NULL) {
printf("Number not found\n");
}
else {
printf("Last index: %d\n", (int)(last_index - ary));
}
return 0;
}
The pointer last_index points at the last found item, if any. By subtracting the array's base address last_index - ary we do pointer arithmetic and get the array item.
The cast to int is necessary to avoid a quirk where subtracting pointers in C actually gives the result in a large integer type called ptrdiff_t - beginners need not worry about that one, so just cast.
First of all, you will read from out of array range, since your array last element is 5, and you read up to 6, which can lead in segmentation faults. #Ludin is right saying that you should change
for (int i = 0; i <= 6; i++) // reads from 0 to 6 range! It is roughly equal to for (int i = 0; i == 6; i++)
to:
for (int i = 0; i < 6; i++) // reads from 0 to 5
The last occurrence of this number I want to return as address.
You are printing only value of 3, not address. To do so, you need to use & operator.
If said number cannot be found I want to use a NULL-pointer
I don't understand, where do you want to return nullpointer? Main function can't return nullpointer, it is contradictory to its definition. To do so, you need to place it in separate function, and then return NULL.
If you want to return last occurence, then I would iterate from the end of this array:
for (int i = 5; i > -1; i--) {
if (ary[i] == 3) {
printf("place in array: %u\n", i); // to print iterator
printf("place in memory: %p\n", &(ary[i])); // to print pointer
break; // if you want to print only last occurence in array and don't read ruther
}
else if (i == 0) {
printf("None occurences found");
}
}
If you want to return an address you need yo use a function instead of writing code in main
As you want to return the address of the last occurence, you should iterate the array from last element towards the first element instead of iterating from first towards last elements.
Below are 2 different implementations of such a function.
#include <stdio.h>
#include <assert.h>
int* f(int n, size_t sz, int a[])
{
assert(sz > 0 && a != NULL);
// Iterate the array from last element towards first element
int* p = a + sz;
do
{
--p;
if (*p == n) return p;
} while(p != a);
return NULL;
}
int* g(int n, size_t sz, int a[])
{
assert(sz > 0 && a != NULL);
// Iterate the array from last element towards first element
size_t i = sz;
do
{
--i;
if (a[i] == n) return &a[i];
} while (i > 0);
return NULL;
}
int main(void)
{
int n = 3;
int ary[] = { 1,3,7,8,3,9 };
size_t elements = sizeof ary / sizeof ary[0];
int* p;
p = g(n, elements, ary); // or p = f(n, elements, ary);
if (p != NULL)
{
printf("Found at address %p - value %d\n", (void*)p, *p);
}
else
{
printf("Not found. The function returned %p\n", (void*)p);
}
return 0;
}
Working on the specified requirements in your question (i.e. a function that searches for the number and returns the address of its last occurrence, or NULL), the code below gives one way of fulfilling those. The comments included are intended to be self-explanatory.
#include <stdio.h>
// Note that an array, passed as an argument, is converted to a pointer (to the
// first element). We can change this in our function, because that pointer is
// passed BY VALUE (i.e. it's a copy), so it won't change the original
int* FindLast(int* arr, size_t length, int find)
{
int* answer = NULL; // The result pointer: set to NULL to start off with
for (size_t i = 0; i < length; ++i) { // Note the use of < rather than <=
if (*arr == find) {
answer = arr; // Found, so set our pointer to the ADDRESS of this element
// Note that, if multiple occurrences exist, the LAST one will be the answer
}
++arr; // Move on to the next element's address
}
return answer;
}
int main(void)
{
int num = 3; // Number to find
int ary[6] = { 1,3,7,8,3,9 }; // array to search
size_t arrlen = sizeof(ary) / sizeof(ary[0]); // Classic way to get length of an array
int* result = FindLast(ary, arrlen, num); // Call the function!
if (result == NULL) { // No match was found ...
printf("No match was found in the array!\n");
}
else {
printf("The address of the last match found is %p.\n", (void*)result); // Show the address
printf("The element at that address is: %d\n", *result); // Just for a verification/check!
}
return 0;
}
Lots of answers so far. All very good answers, too, so I won't repeat the same commentary about array bounds, etc.
I will, however, take a different approach and state, "I want to use a NULL-pointer" is a silly prerequisite for this task serving only to muddle and complicate a very simple problem. "I want to use ..." is chopping off your nose to spite your face.
The KISS principle is to "Keep It Simple, St....!!" Those who will read/modify your code will appreciate your efforts far more than admiring you for making wrong decisions that makes their day worse.
Arrays are easy to conceive of in terms of indexing to reach each element. If you want to train in the use of pointers and NULL pointers, I suggest you explore "linked lists" and/or "binary trees". Those data structures are founded on the utility of pointers.
int main( void ) {
const int n = 3, ary[] = { 1, 3, 7, 8, 3, 9 };
size_t sz = sizeof ary/sizeof ary[0];
// search for the LAST match by starting at the end, not the beginning.
while( sz-- )
if( ary[ sz ] == n ) {
printf( "ary[ %sz ] = %d\n", sz, n );
return 0;
}
puts( "not found" );
return 1; // failed to find it.
}
Consider that the array to be searched is many megabytes. To find the LAST match, it makes sense to start at the tail, not the head of the array.
Simple...

How can I arrange the structs in an array of structs in an ascending order?

I am sorry if this sounds confusing, I will try to be as clear as possible. I have an array of structs, where the array stores a struct that I have defined as a Business Card. However, before adding any new business cards into the array, I have to store the structs in ascending order based on the integer value of the Employee ID.
Here is the struct:
typedef struct{
int nameCardID;
char personName[20];
char companyName[20];
} NameCard;
Hence, I tried to use relational operators to compare between the values of the ID and copy it in ascending order to another temporary array I named fakeHolder, before finally copying over to the actual array. However, I can't seem to understand why it is not in order after inputting my data as ID 9, 7, 5.
Here is my helper function:
int addNameCard(NameCard *nc, int *size){
int i = 0;
// Why is this a pointer?
NameCard fakeHolder[10];
char dummy[100];
char *p;
printf("addNameCard():\n");
if(*size == MAX){
printf("The name card holder is full");
// To quit the program
return 0;
}
// Keeps it to Fake Name Card Holder First
printf("Enter nameCardID:\n");
scanf("%d", &fakeHolder->nameCardID);
scanf("%c", &dummy);
printf("Enter personName:\n");
fgets(fakeHolder->personName, 20, stdin);
if(p = strchr(fakeHolder->personName, '\n')){
*p = '\0';
}
printf("Enter companyName:\n");
fgets(fakeHolder->companyName, 20, stdin);
if(p = strchr(fakeHolder->companyName, '\n')){
*p = '\0';
}
// Compare the ID value
for(int j = 0; j < *size; j += 1){
if(fakeHolder->nameCardID == (nc+j)->nameCardID){
printf("The nameCardID has already existed");
}
else if(fakeHolder->nameCardID < (nc+j)->nameCardID){
fakeHolder[(j+1)].nameCardID = (nc+j)->nameCardID;
strcpy(fakeHolder[(j+1)].personName,(nc+j)->personName);
strcpy(fakeHolder[(j+1)].companyName, (nc+j)->companyName);
}
}
*size += 1;
// Transfer to the Actual Name Card Holder
for(int k = 0; k < *size; k += 1){
(nc+k)->nameCardID = fakeHolder[k].nameCardID;
strcpy((nc+k)->personName, fakeHolder[k].personName);
strcpy((nc+k)->companyName, fakeHolder[k].companyName);
}
printf("The name card has been added successfully\n");
return 0;
}
Your current code has several problems, and you can rewrite it to be much more maintainable and easier to work with. For example,
i (in int i = 0;) is not being used
scanf("%c", &dummy); is there, I assume, to remove trailing \n - but a 100-char buffer for a single character to read is... surprising. See scanf() leaves the new line char in the buffer for lots of discussion on different approaches to "trailing stuff after integer".
splitting addNameCard into 2 functions, one to actually request a NameCard and another to insert it into the array, would divide up responsibilities better, and make your program easier to test. Avoid mixing input/output with program logic.
The question you ask can be solved via the standard library qsort function, as follows:
#include <stdlib.h>
typedef struct{
int nameCardID;
char personName[20];
char companyName[20];
} NameCard;
void show(NameCard *nc, int n) {
for (int i=0; i<n; i++, nc++) {
printf("%d,%s,%s\n",
nc->nameCardID, nc->personName, nc->companyName);
}
}
// comparison functions to qsort must return int and receive 2 const void * pointers
// they must then return 0 for equal, or <0 / >0 for lower/greater
int compareCardsById(const void *a, const void *b) {
return ((NameCard *)a)->nameCardID - ((NameCard *)b)->nameCardID;
}
int main() {
NameCard nc[10];
nc[0] = (NameCard){1, "bill", "foo"};
nc[1] = (NameCard){3, "joe", "bar"};
nc[2] = (NameCard){2, "ben", "qux"};
show(nc, 3);
// calling the libraries' sort on the array; see "man qsort" for details
qsort(nc, 3, sizeof(NameCard), compareCardsById);
show(nc, 3);
return 0;
}

Problems with passing arrays as parameters

I am a novice programmer in C and am running into an issue that is almost painfully simple. I am writing a basic program that creates two arrays, one of student names and one of student ID numbers, then sorts them and prints them in various ways, and finally allows the user to search the arrays by ID number. Here is the code:
#include <stdio.h>
#include <string.h>
#define ARRAY_SIZE 3
#define MAX_NAME_LENGTH 32
int main()
{
// Student info arrays
char NAME[ARRAY_SIZE][MAX_NAME_LENGTH];
int ID[ARRAY_SIZE];
// Array for student IDs, shifted twice to the right
int shiftedID[ARRAY_SIZE];
// Boolean value to keep while loop running and
// the ID search prompt repeating
int loop = 1;
// Counter variable for the for loop
int counter;
// Gets input values for the student info arrays
for (counter = 0; counter < ARRAY_SIZE; counter++)
{
printf("Input student name: ");
scanf("%s", NAME[counter]);
printf("Input student ID: ");
scanf("%d", &ID[counter]);
}
// Sorts the arrays
sort(NAME, ID);
// Prints the arrays
print_array(&NAME, ID);
// Shifts the ID value two bits to the right
shiftright(ID, shiftedID);
print_array(NAME, shiftedID);
// Repeatedely prompts the user for an ID to
// search for
while(loop == 1)
{
search_id(NAME, ID);
}
}
And here are the function definitions:
#define ARRAY_SIZE 3
#define MAX_NAME_LENGTH 32
// Sorts the two arrays by student ID. (Bubble sort)
void sort(char **nameArray, int idArray[])
{
// Counter variables for the for loop
int firstCounter = 0;
int secondCounter = 0;
for(firstCounter = 0; firstCounter < ARRAY_SIZE; firstCounter++)
{
for(secondCounter = 0; secondCounter < ARRAY_SIZE - 1;
secondCounter++)
{
if(idArray[secondCounter] > idArray[secondCounter + 1])
{
// Temporary variables for the sort algorithm
int tempInt = 0;
char tempName[32];
tempInt = idArray[secondCounter + 1];
idArray[secondCounter + 1] = idArray[secondCounter];
idArray[secondCounter] = tempInt;
strcpy(tempName, nameArray[secondCounter + 1]);
strcpy(nameArray[secondCounter + 1],
nameArray[secondCounter]);
strcpy(nameArray[secondCounter], tempName);
}
}
}
}
// Searches the ID array for a user input student
// ID and prints the corresponding student's info.
void search_id(char **nameArray, int idArray[])
{
// A boolean value representing whether or not
// the input ID value was found
int isFound = 0;
// The input ID the user is searching for
int searchID = 0;
printf("Input student ID to search for: ");
scanf("%d", &searchID);
// Counter variable for the for loop
int counter = 0;
while (counter < ARRAY_SIZE && isFound == 0)
{
counter++;
if (idArray[counter] == searchID)
{
// Prints the name associated with the input ID
isFound = 1;
printf("%s", nameArray[counter]);
}
}
// If the input ID is not found, prints a failure message.
if (isFound == 0)
{
printf("ID not found.\n");
}
}
// Prints the name and ID of each student.
void print_array(char **nameArray, int idArray[])
{
// Counter variable for the for loop
int counter = 0;
printf("Student Name & Student ID: \n");
for (counter = 0; counter < ARRAY_SIZE; counter++)
{
printf("%s --- %d\n", nameArray[counter], idArray[counter]);
}
}
// Shifts the ID value to the right by two bits
void shiftright(int idArray[], int shiftedID[])
{
// Counter variable for the for loop
int counter = 0;
for (counter = 0; counter < ARRAY_SIZE; counter++)
{
shiftedID[counter] = idArray[counter] >> 2;
}
}
I am aware that this program is fairly basic in nature, and more than anything it is an exercise to get me more well versed in a language such as C. I've been working on it for some time, and have worked through several problems, but seem to be stuck on three issues:
If the input ID numbers are not input already in order, a segmentation fault results. If the ID numbers are input already in order, the sort function never passes through the if statement, and no problems arise.
When passing the arrays of names/IDs to the print_array function, the IDs are printed just fine, but the names will be printed either entirely blank or as a series of strange characters.
When searching by ID at the end of the program, the ID number that was entered first (so, the number in ID[0]) displays an ID not found message, where all numbers at index 1 or greater will work fine - aside from the corresponding names that should be printed being printed as blank, as mentioned in the second issue.
Any advice that I can get would be greatly appreciated! I find the power behind the fine details needed in C to be both really interesting but also very confusing, intimidatingly so, and that means any help I can get makes a big difference.
The problem is that you are assuming that char [ARRAY_SIZE][MAX_NAME_LENGTH] and char ** are interchangeable
void sort(char **nameArray, int idArray[])
should be
void sort(char nameArray[][MAX_NAME_LENGTH], int idArray[])
or
void sort(char (*nameArray)[MAX_NAME_LENGTH], int idArray[])
in order to use a pointer to an array of MAX_NAME_LENGTH chars, same for your search_id function.
Take a look to question 6.13 of C-FAQ
I would advise you to restructure your program. Rather than storing two independent arrays for names and IDs, you can store one array of structs which contain all the necessary data:
typedef struct student
{
int id;
char name[MAX_NAME_LENGTH];
} student_t;
student_t students[ARRAY_SIZE];
Now you have a single array which can never become "mismatched" by sorting the IDs without the names, etc.
You can sort an array in C using the standard library function qsort():
qsort(students, ARRAY_SIZE, sizeof(student_t), comparator);
This requires you define a comparator, which is fairly simple. One example would be:
int comparator(const void *lhs, const void *rhs)
{
const student_t *s1 = lhs, *s2 = rhs;
return s1->id - s2->id;
}
You can use the same comparator with another standard library function bsearch() to search the array of students after it is sorted:
student_t key = { 42 }; // name doesn't matter, search by ID
student_t* result = bsearch(&key, students, ARRAY_SIZE, sizeof(student_t), comparator);
These standard functions are more efficient than what you had, and require you to write much less code, with fewer chances for mistakes.

Segmentation fault in Binary Search in C

I'm trying to write a program that scans an input file that contains the number of letters in the array, a sorted list of letters, the number of letters to search for, a list of letters to search for. It displays the search results in a format shown in the sample file.
I'm getting a segmentation fault error message at runtime with the code I have included below. Now before this post gets negative feedback for not including the right amount of code, I don't really know where the error is with this segmentation fault. I've included the relevant files here on Pastebin:
main.c
#include <stdio.h>
#include <stdlib.h>
#include "Proto.h"
int main()
{
/* Accepts number of elements from user */
scanf("%d", &elements);
/* Creates dynamic array */
array = (char *) calloc(elements, sizeof(char));
/* Copies sorted values to the dynamic array */
for(i = 0; i < elements; i++)
{
scanf("%s", &array[i]);
}
/* Accepts number of elements to search */
scanf("%d", &search);
/* Searches for elements in sorted array one at a time */
for(i = 1; i <= search; i++)
{
/* Accepts value to search */
scanf("%s", &value);
/* Resets counter to 0 */
count = 0;
/* Finds location of element in the sorted list using binary search */
location = binarySearch(array, value, 0, (elements-1));
/* Checks if element is present in the sorted list */
if (location == -1)
{
printf("%4s not found!\n", value);
}
else
{
printf("%4s found at %4d iteration during iteration %4d\n", value, location, count);
}
}
free(array);
}
BinarySearch.c
#include <stdio.h>
#include "Proto.h"
int binarySearch(char * nums, char svalue, int start, int end)
{
middle = (start + end) / 2;
/* Target found */
if (nums[middle] == svalue)
{
return middle;
}
/* Target not in list */
else if( start == end )
{
return -1;
}
/* Search to the left */
else if( nums[middle] > svalue )
{
count++;
return binarySearch( nums, svalue, start, (middle-1) );
}
/* Search to the right */
else if( nums[middle] < svalue )
{
count++;
return binarySearch( nums, svalue, (middle+1), end );
}
}
Proto.h
#ifndef _PROTO_H
#define _PROTO_H
char * array;
int elements, search, location, count, middle, i;
char value;
int binarySearch(char *, char, int, int);
#endif
Sample Input/Output
Sample Input file:
6
a d n o x y
3
n x z
Sample Output file:
n found at 2 during iteration 0.
x found at 4 during iteration 1.
z not found!
I did not check the whole code but I see this error inyour main.c
your code
/* Creates dynamic array */
array = (char *) calloc(elements, sizeof(char));
/* Copies sorted values to the dynamic array */
for(i = 0; i < elements; i++)
{
scanf("%s", &array[i]);
}
is wrong. your array shoud be double pointer char **array
/* Creates dynamic array */
array = calloc(elements, sizeof(char*));
/* Copies sorted values to the dynamic array */
for(i = 0; i < elements; i++)
{
scanf("%ms", &array[i]);
}
Try to divide your code and found out the part wich is the cause of the problem and back a gain with a small part of code this will help to find out the solution

Resources