Problems with passing arrays as parameters - c

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.

Related

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;
}

Updating specific elements in C array

I'm having trouble conceptualising how to go about some of my code.
My C program wishes to compare each individual element of an array of structs aka arr_person[i].name against a user's input to see if there's a match. (i.e. if the user types in "Billy" and "Billy" is also a string in arr_person[].name array)
for(i=0;i<num_of_lines;i++)
{
if(strcmp(nameInput, arr_person[i].name)==0) {
printf("Match at element %d\n", i);
}
}
Then, a separate function finds reoccurring elements within arr_person[i].name by iterating through the array, and if the same name occurs twice, it will take the corresponding integer values of the same elemental positions and will add them up and store in new variable newChange. For example, if "Billy" occurs twice in the array, at arr_person[0].name and arr_person[4].name, and arr_person[0].number = 15 and arr_person[4].number = 10, then I want to update the number such that it becomes 25.
for(i = 0; i < num_of_lines; i++) {
for(j=0;j<num_of_lines;j++) {
if(strcmp(arr_person[j].name, arr_person[i].name)==0)
*newNumber = arr_person[i].number + arr_person[j].number;
}
}
How do I go about this so that any elements in the array that don't reoccur are still kept the same?
If the user inputs "Rachel" and Rachel only appears once in the array, and her corresponding number is 85, I want to print
Rachel 85
But if the user inputs "Billy" and Billy occurs twice, and he has the two numbers 10 and 15 as corresponding integers in another array, I want to print
Billy 25
I've only been programming for a few months. Thanks in advance.
Seems like the only thing you need to do is this:
int sum = 0;
for(int i=0;i<num_of_lines;i++)
{
if(strcmp(nameInput, arr_person[i].name)==0)
sum += arr_person[i].number;
}
I would structure it like this:
// Previous code from your post slightly modified to function
// returns -1 on no match and index otherwise
int match(struct person *arr_person, char *nameInput, int num_of_lines)
{
for(int i=0;i<num_of_lines;i++) {
if(strcmp(nameInput, arr_person[i].name)==0)
return i;
}
return -1;
}
int sum(struct person *arr_person, char *nameInput, int num_of_lines)
{
int sum = 0;
for(int i=0;i<num_of_lines;i++) {
if(strcmp(nameInput, arr_person[i].name)==0)
sum += arr_person[i].number;
}
return sum;
}
int main()
{
// Insert code for declaration and initialization
int index = match(arr_person, nameInput, num_of_lines);
if(index >= 0) {
printf("Match at element %d\n", index);
printf("%s %d\n", nameInput, sum(arr_person, nameInput, num_of_lines));
} else {
printf("No match\n");
}
}

Merge sort in array of structs

I have an array of structs, where each object is a student with a name and a grade, and I'm trying to merge sort this array (I want to sort them by grade in ascending order).
Here is the Student Struct:
struct Student
{
char grade[42];
char city[42];
};
grade is a char because I assign the grade value by getting an input from the user with fgets and sscanf.
I don't think it's necessary to put my whole code of the merge-sort algorithm but I want to share this part which I think might be problematic ?
int firstHalfSize = midElement - firstElement + 1;
int secondHalfSize = lastElement - midElement;
struct Student firstHalfArray[firstHalfSize];
struct Student secondHalfArray[secondHalfSize];
char *p;
char *s;
int index1 = 0;
int index2 = 0;
int mergedArrIndex = firstElement;
while (index1 < firstHalfSize && index2 < secondHalfSize)
{
if (strtol(firstHalfArray[index1].grade, &p, 10) <= strtol(secondHalfArray[index2].grade, &s, 10))
{
arr[mergedArrIndex] = firstHalfArray[index1];
index1++;
}
else
{
arr[mergedArrIndex] = secondHalfArray[index2];
index2++;
}
mergedArrIndex++;
}
the part where I sort the student object by comparing the grade is by converting the char grade into a long with strtol which I think I did good so it might not be a problem.
My problems is that I initialize my array of structs like the following:
struct Student students[5501];
and when I get a new input of the user I just add it into the array like the following:
struct Student aStudent;
int lineCounter = 0;
students[lineCounter] = aStudent;
and increase lineCounter by 1 every time I get a new input. (aStudent is changed when I get a new input)
Here I call the merge-sort function and get weird results:
mergeSort(students, 0, 5501); // 5501 is the size of the array
printArray(students, 5501);
Here is the printArray function created just to see if I get the grade in ascending order
void printArray(struct Student A[], int size)
{
char *p;
int i;
for (i=0; i < size; i++)
printf("%ld", strtol(A[i].grade, &p, 10));
}
but I keep getting that printed:
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000...
and I don't know why.
Also how can I do that If the user adds 3 students object in the array students
I only treat it as like an array of 3 elements and not an array of 5501 elements ?
Thanks! And sorry if it's a little long I really tried to be concise without having a loss of essential informations.
Since the user can input N numbers of students I recommend you to use a Linked List instead and put the node (struct Student) in his right position depending on the grade.
You can learn more about Linked Lists here.

trouble passing strings from 2-D array which meet condition to main function from separate function

In this task, you are to get a list of the most critical reviewers. A
critical reviewer is defined as:
A reviewer who has the same amount of negative recommendations (‘n’)
as the reviewer with the most negative recommendations.
Using this definition of what a critical reviewer is, you are to look
through the list of reviewer’s recommendations and determine if they
are a critical reviewer or not.
** The function’s return value should be the number of critical reviewers. **
In addition, the list of critical reviewer’s names created by the
function must also be accessible outside of the function.
In this example, the highest number of ‘n’ recommendations for a
single reviewer is 2. Once you have determined the highest amount of
‘n’ recommendations, you can check to see which reviewers are
“critical reviewers”. In this example we can determine that reviewers
"Larry", "Judi", "Manisha", "Dora", and "Nick" are critical reviewers
as they are the reviewers represented by array indices 1, 3, 6, 8,
and 9 respectively. The number 5 would be returned by the function’s
return value as that is the count of critical reviewers found in the
list. This function has no print statements.
struggling to pass the names of only the critical reviewers to the array to be displayed in main.
//Function prototypes
void Recommendations(); //task 1
int criticalReviewers(); //task 2
//MAIN FUNCTION
int main(void) {
//Variables
char reviewerNames[NUMBER_REVIEWERS][30] = { "Ritu",
"Larry",
"Dan",
"Judi",
"Eric",
"Isabelle",
"Manisha",
"Terra",
"Dora",
"Nick" };
char movieNames[NUMBER_MOVIES][50] = { "Star Wars",
"Incredibles",
"Gone with the wind" };
char userReviews[NUMBER_REVIEWERS][NUMBER_MOVIES];
char reviewerAnswers[10][3];
char negativeReviewers[10][30];
//TASK TWO
printf("\n**********************************************\n");
printf("Task 2: Get names of critical reviewers\n\n");
//call to task 2 function
printf("Number of Critical Reviewers: %d\n", criticalReviewers(reviewerAnswers, reviewerNames, negativeReviewers));
printf("Critical Reviewers: ");
for (int k=0; k<criticalReviewers(reviewerAnswers, reviewerNames, negativeReviewers); k++) {
printf("%s, ", negativeReviewers + k);
}
printf("%s", negativeReviewers + criticalReviewers(reviewerAnswers, reviewerNames, negativeReviewers));
//CALL TO TASK 3 FUNCTION
mostRecommended(reviewerAnswers, movieNames);
WINPAUSE; // REMOVE BEFORE SUBMITTING
return 0;
}
//TASK ONE FUNCTION
//TASK 2 FUNCTION
int criticalReviewers(char userAnswers[10][3], char Reviewers[][30], char critReviewers[][30]) {
int i=0;
int j=0;
int numCriticalReviewers = 0;
int criticalScore = 0;
int criticalReviewers[10];
int timesSkipped=0;
//loop to determine number of critical REVIEWERS
for (i=0; i<10; i++) {
criticalReviewers[i] = 0;
for (j=0; j<3; j++) {
if (userAnswers[i][j] == 'n') {
criticalReviewers[i] = criticalReviewers[i] + 1;
}
if (criticalReviewers[i] > criticalScore) {
criticalScore = criticalReviewers[i];
}
}
}
for (i=0; i<10; i++) {
if (criticalReviewers[i] == criticalScore) {
numCriticalReviewers = numCriticalReviewers + 1;
for (int k=i; k<i+1; k++) {
critReviewers[k-timesSkipped][30] = Reviewers[k][30];
timesSkipped = 0;
}
}
else {
timesSkipped = timesSkipped + 1;
}
}
for (i=0; i<10; i++) {
if (criticalReviewers[i] == criticalScore) {
critReviewers = Reviewers + i;
}
}
return numCriticalReviewers;
}
I have properly printed in main the number of critical reviewers, but below it should print the names of critical reviewers which i can not figure out. everytime i try to pass the values it prints a random string of letters and symbols.
The issue is where you assign the reviewers to the critReviewers array in function criticalReviewers. Notice how you have the second index as 30. By doing this, you are assigning only the 30th index in the array (which is beyond the bounds of the array, indices 0-29, but that is a different issue).
What you should be doing is either looping through the string to copy each index one by one, or copying the string using a function like strcpy in the string.h library. Otherwise, everything looks like it works fine.
Solution 1:
for(i = 0; i < 30; i++) {
array1[someIndex][i] = array2[someIndex][i];
}
This will copy each entry in array2[someIndex] one by one.
Solution 2:
strcpy(array1[someIndex], array2[someIndex]);
This will copy the entire string in array2[someIndex] to array1[someIndex].
I would also take a while to read up on 2d arrays, if you're still confused after reading this. I always like geeksforgeeks.com for stuff like this: https://www.geeksforgeeks.org/multidimensional-arrays-c-cpp/

C - Print the most frequent strings

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.

Resources