searching within an array of structs in c - c

i see plenty of examples on how to search arrays to find a specific instance what i want to do is find all the instances and print them for example i have this struct
struct BookInfo
{
char title[50];
int numAuthors;
char authors[50][50];
int year;
int checkedout;
};
struct BookInfo library[500];
and i have a function to search within the years but it only gives me the first instance it finds how do i get it to give me bot instances???
heres the function
int yearsearch()
{
int target, i, l, r, mid;
l = 0;
r = 14-1;
printf("type a year to search for book");
scanf("%d", &target);
while(l <= r)
{
mid = (l+r)/2;
if(library[mid].year == target)
{
printf("\n%s",library[mid].title);
printf(" %d",library[mid].year);
printf("These are all the books with that year");
break;
}
else if (library[mid].year < target)
{
l = mid + 1;
}
else
{
r = mid - 1;
}
if(l > r)
printf("The target is not in the array.\n");
}
menu();
}

You're doing a kind of binary search over the array, which is not designed to find all instances without modification. Have you considered just doing a linear search (i.e., a for loop) over the length of the array and printing if the array element matches your search criteria? One might implement a naive linear search like this:
for (int i = 0; i<500; i++) {
if (library[i].year == target) {
// Do your printing here
}
// Otherwise do nothing!
}

Related

Searching a string by binary search

#include <stdio.h>
#include <string.h>
typedef struct {
char name[11];
int score;
} report;
int main() {
int n = 3;
report student[n];
for (int i = 0; i < 3; i++) {
scanf("%[^\#]#%d", student[i].name, &student[i].score);
}
// Input name that we search.
char search[11];
scanf("%s", search);
// bubble sort
for (int a = 0; a < n - 1; a++) {
for (int b = 0; b < n - 1 - a; b++) {
if (student[b].score < student[b+1].score) {
report temp;
strcpy(temp.name, student[b].name);
temp.score = student[b].score;
strcpy(student[b].name, student[b+1].name);
student[b].score = student[b+1].score;
strcpy(student[b+1].name, temp.name);
student[b+1].score = temp.score;
}
}
}
// binary search
int left = 0;
int right = n - 1;
int middleIndex;
int rank;
while (left <= right ) {
middleIndex = (int)(left + right) / 2;
if (strcmp(student[middleIndex].name, search) == 0) {
rank = middleIndex+1;
break;
} else if (strcmp(student[middleIndex].name, search) > 0) {
left = middleIndex + 1;
} else if (strcmp(student[middleIndex].name,search) < 0) {
right = middleIndex - 1;
}
}
// Rank of the student's name that we search.
printf("%d", rank);
return 0;
}
I want to create a program that will return a student ranking (from 3 students). The fourth line is the name that we searched. I put all the user input into a struct and sort it in descending order to represent students ranking. But the problem is, when it reach the binary search, it always return unexpected value. Could you guys help me solve the problem?
Sample Input :
Jojo#40
Ray#60
Liz#80
Jojo -> name that we searched.
""" [ {Liz, 80}, {Ray, 60}, {Jojo,40} ] """
Output : 3
At least one problem is that your names (except the first) will have a newline as the first character. That newline was left in the input stream when scanning the score.
Consequently your string compare doesn't work.
Add this
for (int a = 0; a < 3; a++) printf("|%s|\n", student[a].name);
printf("|%s|\n", search);
just after scan of search and you get the output:
|Jojo|
|
Ray|
|
Liz|
|Jojo|
As you can see there are "unexpected" newlines in front of both "Ray" and "Liz"
To solve that add a space here
scanf(" %[^\#]#%d"
^
space
As noted by #EricPostpischil in a comment, sorting by score and doing binary search by name makes no sense. The sort and the search must be based on the same.
BTW: When sorting arrays use qsort

Can someone explain to me how can i access a void* item that is inside a void** array, taking in account void** belongs to a struct

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct student{
int grade;
int enrollCode;
}student;
typedef struct colVoidStar{
int capacity;
int num_itens_curr;
void **arr;
int current_pos;
}colVoidStar;
colVoidStar *colCreate(int capacity){
if(capacity > 0){
colVoidStar *c = malloc(sizeof(colVoidStar));
if(c != NULL){
c->arr = (void**)malloc(sizeof(void*)*capacity);
if( c->arr != NULL){
c->num_itens_curr = 0;
c->capacity = capacity;
return c;
}
free(c->arr);
}
free(c);
}
return NULL;
}
int colInsert(colVoidStar *c, void *item){
if(c != NULL){
if(c->num_itens_curr < c->capacity){
c->arr[c->num_itens_curr] = (student*)item;
c->num_itens_curr++;
return 1;
}
}
return 0;
}
void *colRemove(colVoidStar *c, void *key, int compar1(void* a, void* b)){
int(*ptrCompar)(void*, void*) = compar1;
student* eleRemoved;
if(c != NULL){
if(c->num_itens_curr > 0){
int i = 0;
for(i; i < c->num_itens_curr; i++){
if(ptrCompar((void*)key, (void*)c->arr[i]) == 0){
eleRemoved = (student*)c->arr[i];
for(int j = i; j < c->num_itens_curr; j++){
c->arr[i] = c->arr[i + 1];
c->arr[i + 1] = 0;
}
return (void*)eleRemoved;
}
return NULL;
}
}
}
return NULL;
}
int compar1(void *a, void*b){
int key;
student *item;
key = *(int*)a;
item = (student*)b;
return (int)(key - item->enrollCode);
}
int main(){
int finishProgram = 0, choose, capacity, returnInsert, removeEnroll;
colVoidStar *c;
student *a, *studentRemoved;
while(finishProgram != 9){
printf("-----------------panel-----------------------\n");
printf("Type: \n");
printf("[1] to create a collection;\n");
printf("[2] to insert a student;\n");
printf("[3] to remove some student of collection;\n");
printf("--------------------------------------------------------\n");
scanf("%d", &choose);
switch(choose){
case 1:
printf("Type the maximum of students the collection will have: \n");
scanf("%d", &capacity);
c = (colVoidStar*)colCreate(capacity);
if(c == NULL){
printf("Error in create collection!\n");
}
break;
case 2:
if(c->num_itens_curr < capacity){
a = (student*)malloc(sizeof(student));
printf("%d student:(type the Grade and the Enroll code, back-to-back)\n", c->num_itens_curr + 1);
scanf("%d %d", &a->grade, &a->enrollCode);
returnInsert = colInsert(c, (void*)a);
if(returnInsert == 1){
for(int i = 0; i < c->num_itens_curr; i++){
printf("The student added has grade = %d e enrollCode = %d \n", (((student*)c->arr[i])->grade), ((student*)c->arr[i])->enrollCode);
}
}else{
printf("the student wasn't added in the collection\n");
}
}else{
printf("it's not possible to add more students to the colletion, since the limit of elements was reached!");
}
break;
case 3:
printf("Type an enrollcode to remove the student attached to it:\n");
scanf("%d", &removeEnroll);
studentRemoved = (student*)colRemove(c, &removeEnroll, compar1(&removeEnroll, c->arr[0]));
if(studentRemoved != NULL)
printf("the student removed has grade = %d and enrollcode %d.", studentRemoved->grade, studentRemoved->enrollCode);
else
printf("the number typed wasn't found");
break;
}
}
return 0;
}
---> As you can realize, what I'm trying to do, at least at this point, is access and remove an item(student* that initially will assume a void* type) of a student's collection(void** arr) using a sort of enrollment code. However, I'm having problems with Segmentation Fault and can't understand why and how can solve them, hence my question up there. Debugging the code I found out the errors lies at: if(ptrCompar((void)key, (void**)*c->arr[i]) == 0) inside of Remove function and return (int)(key - item->matricula) inside of Compar1.
Besides, if you can point me out some articles/documentations/whatever that helps me to understand how to cope with problems like that, I'll appreciate it a lot.
Here are the problems I see in colRemove:
(Not really a problem, just a matter of style) Although the function parameter int compar1(void* a, void* b) is OK, it is more conventional to use the syntax int (*compar1)(void* a, void* b).
(Not really a problem) Having both compar1 and ptrCompar pointing to the same function is redundant. It is probably better to name the parameter ptrCompar to avoid reader's confusion with the compar1 function defined elsewhere in the code.
The function is supposed to be general-purpose and shouldn't be using student* for the eleRemoved variable. Perhaps that was just for debugging? It should be void*.
After the element to be removed has been found, the remaining code is all wrong:
c->num_itens_curr has not been decremented to reduce the number of items.
The code is accessing c->arr[i] and c->arr[i + 1] instead of c->arr[j] and c->arr[j + 1].
c->arr[j + 1] may be accessing beyond the last element because the loop termination condition is off by 1. This may be because c->num_itens_curr was not decremented.
The assignment c->arr[j + 1] = 0; is not really needed because all but the last element will be overwritten on the next iteration, and the value of the old last element does not matter because the number of items should be reduced by 1.
(Not really a problem) There is unnecessary use of type cast operations in the function (e.g. casting void * to void *).
Here is a corrected and maybe improved version of the function (using fewer variables):
void *colRemove(colVoidStar *c, void *key, int (*ptrCompar)(void* a, void* b)){
void* eleRemoved = NULL;
if(c != NULL){
int i;
/* Look for item to be removed. */
for(i = 0; i < c->num_itens_curr; i++){
if(ptrCompar(key, c->arr[i]) == 0){
/* Found it. */
eleRemoved = c->arr[i];
c->num_itens_curr--; /* There is now one less item. */
break;
}
}
/* Close the gap. */
for(; i < c->num_itens_curr; i++){
c->arr[i] = c->arr[i + 1];
}
}
return eleRemoved;
}
In addition, this call of colRemove from main is incorrect:
studentRemoved = (student*)colRemove(c, &removeEnroll, compar1(&removeEnroll, c->arr[0]));
The final argument should be a pointer to the compar1 function, but the code is actually passing the result of a call to the compar1 function which is of type int. It should be changed to this:
studentRemoved = (student*)colRemove(c, &removeEnroll, compar1);
or, removing the unnecessary type cast of the the void* to student*:
studentRemoved = colRemove(c, &removeEnroll, compar1);
The colInsert function is also supposed to be general-purpose so should not use this inappropriate type cast to student*:
c->arr[c->num_itens_curr] = (student*)item;
Perhaps that was also for debugging purposes, but it should just be using item as-is:
c->arr[c->num_itens_curr] = item;
As pointed out by #chux in the comments on the question, the expression key - item->enrollCode in the return statement of compar1 may overflow. I recommend changing it to something like this:
return key < item->enroleCode ? -1 : key > item->enrolCode ? 1 : 0;
or changing it to use this sneaky trick:
return (key > item->enroleCode) - (key < item->enroleCode);

How to display an index in a array with a function

I am trying to display the lowest price index in the array that is found through my function. But I am struggling to figure out how to make it display the specific index. Where do I need to pass the index variable in order to make display the lowest index with my display function?
#define COLOR_SIZE 50
#define PLANT_ARRAY_SIZE 3
struct Plant
{
int plantID;
double price;
char color[COLOR_SIZE];
};
int main()
{
int index;
//create array
struct Plant totalPlants[PLANT_ARRAY_SIZE];
//initialize array
for (int count = 0; count < PLANT_ARRAY_SIZE; count++)
{
initializePlant(totalPlants);
}
// find lowest
index = findCheapestPlant(totalPlants, PLANT_ARRAY_SIZE);
//display lowest cost plant
displayPlant(totalPlants[index]);
return 0;
}
void initializePlant(struct Plant *x)
{
printf("\nEnter the information for the next plant\n");
printf("Enter plant ID as an integer>");
scanf("%d", &(x->plantID));
printf("Enter the plant price as a double>");
scanf("%lf", &(x->price));
printf("Enter the perdominant color of the plant>");
scanf("%s", &(x->color));
void displayPlant(struct Plant *x)
{
printf("\nThe cheapest plant is to be...\n");
printf("Plant ID %d which costs $%.2lf and the color is %s\n", x->plantID, x->price, x->color);
}
int findCheapestPlant(struct Plant x[], int length)
{
double lowest;
int index = -1;
int i = 0;
if (length > 0)
{
lowest = x[i].price;
}
for (i = 0; i < length; i++)
{
if (x[i].price < lowest)
{
index = i;
lowest = x[i].price;
}
}
return index;
}
}
I expect the display function to output the lowest price plant but i get errors.
There are multiple errors addressed in the comments, in addition to fixing these errors, the following should help you achieve what you want to do.
To get the code to display the index as well, maybe change the declaration of displayPlant to take the index as well:
displayPlant(struct Plant *x, int index)
and then pass the index you have in your main to the function like so
displayPlant(totalPlants[index], index);
Then of course you should alter your printf statement to something like:
printf("Plant ID %d (index: %d) which costs $%.2lf and the color is %s\n", x->plantID, index, x->price, x->color);
EDIT: I should also say that initializing your index to -1 is a bad idea, because it will result in seg faults unless your code includes checks for the index being >= 0.
Additionally add this index = i; or index = 0 here in your code:
if (length > 0)
{
lowest = x[i].price;
index = i; //here
}

Sorting an array using multiple sort criteria (QuickSort)

I am trying to find out how (using a quicksort algorithm) to sort an struct array by 2 criterias. For example say I had a struct of:
struct employee{
char gender[12];
char name[12];
int id;
};
Say my input is:
struct employee arr[3]=
{
{"male","Matt",1234},
{"female","Jessica",2345},
{"male","Josh",1235}
};
I am wanting to sort the elements by gender first then the IDs in ascending order. An example would be have all the males printed first with their IDs in order and then all the females with theirs. I am trying to do this without using the qsort function but I havent the slightest idea how to check . Here is my sorting function:
void quicksort(struct employee *arr, int left, int right)
{
int pivot, l, r, temp;
if(left < right)
{
p = left;
l = left;
r = right;
while(l < r)
{
while(arr[l].id <= arr[p].id && l <= right)
l++;
while(arr[r].id > arr[p].id && r >= left)
r--;
if(l < r)
{
temp = arr[l].id;
arr[l].id = arr[r].id;
arr[r].id = temp;
}
}
temp = arr[r].id;
arr[r].id = arr[p].id;
arr[p].id = temp;
quicksort(arr, left, r-1);
quicksort(arr, r+1, right);
}
}
Any suggestions? I was thinking I could use strcmp but I cant figure out where to include it within the function.
You can certainly inline the comparison function, and a swapper for that matter. This code below is pretty basic and relies on valid pointers, but you'l get the idea. I also took the liberty of trimming down your quicksort, fixing what was off along the way (I hope).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// employee record
struct employee
{
char gender[12];
char name[12];
int id;
};
// swap employee records
void swap_employee(struct employee *left, struct employee *right)
{
struct employee tmp = *right;
*right = *left;
*left = tmp;
}
// compare employee records
int compare_employee(const struct employee* left,
const struct employee* right)
{
int gender = strcmp(left->gender, right->gender);
return (gender ? gender : (left->id - right->id));
}
// quicksort for employees
static void quicksort_(struct employee *arr, int left, int right)
{
struct employee p = arr[(left+right)/2]; // as good as any
int l = left, r = right; // movable indicies
while (l <= r)
{
while (compare_employee(arr+l, &p) < 0)
++l;
while (compare_employee(arr+r, &p) > 0)
--r;
if (l <= r)
{
swap_employee(arr+l, arr+r);
++l; --r;
}
}
if (left < r)
quicksort_(arr, left, r);
if (l < right)
quicksort_(arr, l, right);
}
// exposed API
void quicksort(struct employee *arr, int count)
{
if (arr && (count>0))
quicksort_(arr, 0, count-1);
}
/* sample usage */
int main(int argc, char *argv[])
{
struct employee arr[]=
{
{"male","Matt",1234},
{"female","Jessica",2345},
{"male","Josh",1235},
{"female","Betsy",2344},
{"male","Roger",1233}
};
quicksort(arr, sizeof(arr)/sizeof(arr[0]));
for (int i=0;i<sizeof(arr)/sizeof(arr[0]);++i)
printf("%s, %s, %d\n", arr[i].gender,arr[i].name, arr[i].id);
return EXIT_SUCCESS;
}
Results
female, Betsy, 2344
female, Jessica, 2345
male, Roger, 1233
male, Matt, 1234
male, Josh, 1235
It's not hard. You just need a function (or block of code seeing you want it "hard coded") to compare your structs. In the sample code you've given you are comparing the current object using arr[l].id <= arr[p].id. That is you are only considering id to work out where your element fits. You just need to compare using the other fields at this point. It would be much tidier with a function (I gave you such a function in your earlier question).
You are also only shifting the id fields when you swap - leaving the name and gender unchanged in your data items. You should move the whole struct.
Just use the built-in qsort, and pass it a comparator function that compares gender first, and consults ID number only in case of "ties" in the first comparison.
I think you should sort your array by gender first, one for male, one for female. Then use the quicksort function to sort within those two arrays.
You can use strcmp to sort the original array into two arrays: one for male, one for female.
int compare_employee (struct employee * a, struct employee * b) {
int diff = strcmp(a->gender, b->gender);
if (diff) // if gender different
return -diff; // sort descending, please double check this and reverse in case I am wrong
return a->id - b->id; // else sort by id
}
Will output a negative number if a < b, positive if a > b, zero if they are equal.
Use it eigther in your own quicksort or as a qsort comparator.

Removing Duplicates in an array in C

The question is a little complex. The problem here is to get rid of duplicates and save the unique elements of array into another array with their original sequence.
For example :
If the input is entered b a c a d t
The result should be : b a c d t in the exact state that the input entered.
So, for sorting the array then checking couldn't work since I lost the original sequence. I was advised to use array of indices but I don't know how to do. So what is your advise to do that?
For those who are willing to answer the question I wanted to add some specific information.
char** finduni(char *words[100],int limit)
{
//
//Methods here
//
}
is the my function. The array whose duplicates should be removed and stored in a different array is words[100]. So, the process will be done on this. I firstly thought about getting all the elements of words into another array and sort that array but that doesn't work after some tests. Just a reminder for solvers :).
Well, here is a version for char types. Note it doesn't scale.
#include "stdio.h"
#include "string.h"
void removeDuplicates(unsigned char *string)
{
unsigned char allCharacters [256] = { 0 };
int lookAt;
int writeTo = 0;
for(lookAt = 0; lookAt < strlen(string); lookAt++)
{
if(allCharacters[ string[lookAt] ] == 0)
{
allCharacters[ string[lookAt] ] = 1; // mark it seen
string[writeTo++] = string[lookAt]; // copy it
}
}
string[writeTo] = '\0';
}
int main()
{
char word[] = "abbbcdefbbbghasdddaiouasdf";
removeDuplicates(word);
printf("Word is now [%s]\n", word);
return 0;
}
The following is the output:
Word is now [abcdefghsiou]
Is that something like what you want? You can modify the method if there are spaces between the letters, but if you use int, float, double or char * as the types, this method won't scale at all.
EDIT
I posted and then saw your clarification, where it's an array of char *. I'll update the method.
I hope this isn't too much code. I adapted this QuickSort algorithm and basically added index memory to it. The algorithm is O(n log n), as the 3 steps below are additive and that is the worst case complexity of 2 of them.
Sort the array of strings, but every swap should be reflected in the index array as well. After this stage, the i'th element of originalIndices holds the original index of the i'th element of the sorted array.
Remove duplicate elements in the sorted array by setting them to NULL, and setting the index value to elements, which is the highest any can be.
Sort the array of original indices, and make sure every swap is reflected in the array of strings. This gives us back the original array of strings, except the duplicates are at the end and they are all NULL.
For good measure, I return the new count of elements.
Code:
#include "stdio.h"
#include "string.h"
#include "stdlib.h"
void sortArrayAndSetCriteria(char **arr, int elements, int *originalIndices)
{
#define MAX_LEVELS 1000
char *piv;
int beg[MAX_LEVELS], end[MAX_LEVELS], i=0, L, R;
int idx, cidx;
for(idx = 0; idx < elements; idx++)
originalIndices[idx] = idx;
beg[0] = 0;
end[0] = elements;
while (i>=0)
{
L = beg[i];
R = end[i] - 1;
if (L<R)
{
piv = arr[L];
cidx = originalIndices[L];
if (i==MAX_LEVELS-1)
return;
while (L < R)
{
while (strcmp(arr[R], piv) >= 0 && L < R) R--;
if (L < R)
{
arr[L] = arr[R];
originalIndices[L++] = originalIndices[R];
}
while (strcmp(arr[L], piv) <= 0 && L < R) L++;
if (L < R)
{
arr[R] = arr[L];
originalIndices[R--] = originalIndices[L];
}
}
arr[L] = piv;
originalIndices[L] = cidx;
beg[i + 1] = L + 1;
end[i + 1] = end[i];
end[i++] = L;
}
else
{
i--;
}
}
}
int removeDuplicatesFromBoth(char **arr, int elements, int *originalIndices)
{
// now remove duplicates
int i = 1, newLimit = 1;
char *curr = arr[0];
while (i < elements)
{
if(strcmp(curr, arr[i]) == 0)
{
arr[i] = NULL; // free this if it was malloc'd
originalIndices[i] = elements; // place it at the end
}
else
{
curr = arr[i];
newLimit++;
}
i++;
}
return newLimit;
}
void sortArrayBasedOnCriteria(char **arr, int elements, int *originalIndices)
{
#define MAX_LEVELS 1000
int piv;
int beg[MAX_LEVELS], end[MAX_LEVELS], i=0, L, R;
int idx;
char *cidx;
beg[0] = 0;
end[0] = elements;
while (i>=0)
{
L = beg[i];
R = end[i] - 1;
if (L<R)
{
piv = originalIndices[L];
cidx = arr[L];
if (i==MAX_LEVELS-1)
return;
while (L < R)
{
while (originalIndices[R] >= piv && L < R) R--;
if (L < R)
{
arr[L] = arr[R];
originalIndices[L++] = originalIndices[R];
}
while (originalIndices[L] <= piv && L < R) L++;
if (L < R)
{
arr[R] = arr[L];
originalIndices[R--] = originalIndices[L];
}
}
arr[L] = cidx;
originalIndices[L] = piv;
beg[i + 1] = L + 1;
end[i + 1] = end[i];
end[i++] = L;
}
else
{
i--;
}
}
}
int removeDuplicateStrings(char *words[], int limit)
{
int *indices = (int *)malloc(limit * sizeof(int));
int newLimit;
sortArrayAndSetCriteria(words, limit, indices);
newLimit = removeDuplicatesFromBoth(words, limit, indices);
sortArrayBasedOnCriteria(words, limit, indices);
free(indices);
return newLimit;
}
int main()
{
char *words[] = { "abc", "def", "bad", "hello", "captain", "def", "abc", "goodbye" };
int newLimit = removeDuplicateStrings(words, 8);
int i = 0;
for(i = 0; i < newLimit; i++) printf(" Word # %d = %s\n", i, words[i]);
return 0;
}
Traverse through the items in the array - O(n) operation
For each item, add it to another sorted-array
Before adding it to the sorted array, check if the entry already exists - O(log n) operation
Finally, O(n log n) operation
i think that in C you can create a second array. then you copy the element from the original array only if this element is not already in the send array.
this also preserve the order of the element.
if you read the element one by one you can discard the element before insert in the original array, this could speedup the process.
As Thomas suggested in a comment, if each element of the array is guaranteed to be from a limited set of values (such as a char) you can achieve this in O(n) time.
Keep an array of 256 bool (or int if your compiler doesn't support bool) or however many different discrete values could possibly be in the array. Initialize all the values to false.
Scan the input array one-by-one.
For each element, if the corresponding value in the bool array is false, add it to the output array and set the bool array value to true. Otherwise, do nothing.
You know how to do it for char type, right?
You can do same thing with strings, but instead of using array of bools (which is technically an implementation of "set" object), you'll have to simulate the "set"(or array of bools) with a linear array of strings you already encountered. I.e. you have an array of strings you already saw, for each new string you check if it is in array of "seen" strings, if it is, then you ignore it (not unique), if it is not in array, you add it to both array of seen strings and output. If you have a small number of different strings (below 1000), you could ignore performance optimizations, and simply compare each new string with everything you already saw before.
With large number of strings (few thousands), however, you'll need to optimize things a bit:
1) Every time you add a new string to an array of strings you already saw, sort the array with insertion sort algorithm. Don't use quickSort, because insertion sort tends to be faster when data is almost sorted.
2) When checking if string is in array, use binary search.
If number of different strings is reasonable (i.e. you don't have billions of unique strings), this approach should be fast enough.

Resources