Sorting an array of structs in C with pointers - c

I have a coding assignment for my CIS class. The assignment is to write a program the will create an array of structures that will hold information on at max 10 dogs. At the end of the program, you are supposed to sort the array of dogs by either name or size. But I am unable to code the sorting of the array of dog. I was wondering how to sort the array of dogs for later use in the main function.
Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Dog{
char name[20];
int weight;
int age;
int ageDogYears;
char size[7];
};
typedef struct Dog DG;
void calc(DG[], int);
void sort(DG[], int);
void display();
int main(){
DG dArray[10];
int x = 0;
char str[80], *i;
FILE *inFile;
inFile = fopen("dogfile.txt", "r");
if (inFile == NULL){
printf("Error opening file");
exit(1);
}
while(fgets(str, 80,inFile) != NULL){
i = strtok(str, ", ");
strcpy(dArray[x].name, i);
puts(dArray[x].name);
i = strtok(NULL, ", ");
dArray[x].weight = atoi(i);
printf("%d\n", dArray[x].weight);
i = strtok(NULL, ", ");
dArray[x].age = atoi(i);
printf("%d\n", dArray[x].age);
x++;
}
calc(dArray, x);
sort(dArray, x);
return 0;
}
void calc(DG dog[], int numDogs){
int y, i, total;
for(i = 0; i < numDogs; ++i){
if(dog[i].weight <= 20){
//sets the dog size to small
strcpy(dog[i].size, "Small");
for(y = 0; y < dog[i].age; ++y){
if(y == 0)
total += 15;
else if(y == 1)
total += 8;
else if(y == 2)
total += 5;
else
total += 4;
}
}
else if(dog[i].weight <= 50){
//sets the dog size to medium
strcpy(dog[i].size, "Medium");
for(y = 0; y < dog[i].age; ++y){
if(y == 0)
total += 14;
else if(y == 1)
total += 9;
else if(y == 2)
total += 7;
else
total += 5;
}
}
else{
//sets the dog size to Large
strcpy(dog[i].size, "Large");
for(y = 0; y < dog[i].age; ++y){
if(y == 0)
total += 12;
else if(y == 1)
total += 9;
else if(y == 2)
total += 8;
else
total += 7;
}
}
dog[i].ageDogYears = total;
total = 0;
}
}
void sort(DG dog[], int numDogs){
int sortType, i, y, temp;
printf("\n wlould you like to sort by name(N) or size(S): ");
scanf("%c", &sortType);
switch(sortType){
case 'N': case 'n':
for(i = 0; i < numDogs; ++i){
for(y = 0; y < (numDogs); ++y){
if(dog[y].weight > dog[y+1].weight){
temp = dog[y];
dog[y] = dog[y + 1];
dog[y + 1] = temp;
}
}
}
break;
default:
if((sortType != 's') && (sortType != 'S'))
printf("\n invalid input! Setting sort type to size.");
//sorting of dog names
}
}
Sample Input
Fluffy,23,6
Fido,65,7
Pepper,44,5
Bowser,75,10
Victor,10,2
Sassy,51,1
Any help would be much appretated! Thanks.

In my opinion, your error resides (despite of other comments I'll do below) in specifying %c as format descriptor to pass a pointer to int to match. You have to pass a pointer to char to make scanf(3) to select the proper place to put the character into. You probably are not getting the right character or no character at all (this leads to Undefined Behaviour in your program)
Some other problems are that you are using the weight field of the structure when requested to sort by name (on n input char) and other errors like this. This includes that you use y < (numDogs) in the inner loop limit condition (it must be y < (numDogs - 1), or you'll compare the y-esim with the y+1-esim element (out of array bounds)
Also, as you are using the same algorithm for both sorting options, I should include the switch statement inside the inner loop (where you do the comparison) as doing otherwise will force you to copy the same sorting code twice (for the overall sorting algorithm), as in:
for(i = 0; i < numDogs; ++i){
for(y = 0; y < (numDogs - 1); ++y){
int sortResultGreater;
switch (sortType) {
case 'n': case 'N':
sortResultGreater = strcmp(dog[y].name, dog[y+1].name) > 0;
break;
default: { /* this should be done as soon as you know the sorting type, not here inside the loop, of course */
static int not_already_printed = 1;
if (not_already_printed) {
printf("Error, sortType must be [nNsS], defaulting to n\n");
not_already_printed = 0; /* so we don't get here again */
}
} /* scope of not_already_printed finishes here */
/* no break used here to fallback to the next case */
case 's': case 'S':
sortResultGreater = dog[y].weight > dog[y+1].weight;
break;
} /* switch */
if(sortResultGreater){
temp = dog[y];
dog[y] = dog[y + 1];
dog[y + 1] = temp;
}
}
}

Related

How to detect duplicate string using strcmp()

#include<stdio.h>
#include<stdio.h>
#include<string.h>
struct stud
{
char nam[20];
int num;
char letter[5];
};
int main()
{
struct stud s[5];
int i, j;
for(i = 0; i < 5; i++){
printf("Enter the name of student #%d: ", i+1);
scanf("%s", s[i].nam);
printf("Enter the number grade of student #%d: ", i+1);
scanf("%d", &s[i].num);
}
for (j = 0; j < i; j++) {
if (strcmp(s[i].nam, s[j].nam) == 0)
printf("Error. Duplicate name detected.");
}
for(i = 0; i < 5; i++){
if(s[i].num >= 90 )
strcpy(s[i].letter, "A");
else if(s[i].num >= 80)
strcpy(s[i].letter, "B");
else if(s[i].num >= 70)
strcpy(s[i].letter, "C");
else if(s[i].num >= 60)
strcpy(s[i].letter, "D");
else
strcpy(s[i].letter, "F");
}
for(i = 0; i < 5; i++)
printf("\n%s has a %s ", s[i].nam, s[i].letter);
return 0;
}
This program has the user enter 5 names and 5 numeric grades, which will then result in the output of their respective letter grades for that student. I'm trying to make it so if the user enters a duplicate name, and message will print saying they can't do that. My attempt in trying to do this is as follows:
for (j = 0; j < i; j++) {
if (strcmp(s[i].nam, s[j].nam) == 0)
printf("Error. Duplicate name detected.");
}
Where I believe that s[j] is the previous string, and compare to see if it equals 0(duplicate) and prints a message. This obviously doesn't work however so I would like to know how to fix this so it can correctly detect duplicate names. Thank you.
Also I have posted this question before but the person that provided an explanation deleted their response before I could provide further questions and ask for clarification. So I am posting this again with an attempt in seeking further aid in what I did wrong in my code.
At the start of the detection loop, i is already 5, so using s[i] is undefined behavior
In your detection loop, i is invariant. you are just comparing a name against the last one [except for the UB, of course].
You need two loops to compare all names against one another.
Also, using 5 everywhere is a "magic number". Better to use a #define (e.g. SMAX)
In the code below, I use cpp conditionals to denote old vs. new code:
#if 0
// old code
#else
// new code
#endif
#if 1
// new code
#endif
Here is the corrected code. It is annotated with the bugs and fixes:
#include <stdio.h>
#include <stdio.h>
#include <string.h>
struct stud {
char nam[20];
int num;
char letter[5];
};
#define SMAX 5 // maximum number of students
int
main()
{
struct stud s[SMAX];
int i, j;
for (i = 0; i < SMAX; i++) {
printf("Enter the name of student #%d: ", i + 1);
scanf("%s", s[i].nam);
printf("Enter the number grade of student #%d: ", i + 1);
scanf("%d", &s[i].num);
}
// NOTE/BUG: i is already SMAX, so using s[i] is UB (undefined behavior)
// NOTE/BUG: i never changes
#if 0
for (j = 0; j < i; j++) {
if (strcmp(s[i].nam, s[j].nam) == 0)
printf("Error. Duplicate name detected.");
}
#else
for (i = 0; i < (SMAX - 1); i++) {
for (j = i + 1; j < SMAX; j++) {
if (strcmp(s[i].nam, s[j].nam) == 0)
printf("Error. Duplicate name detected -- %s\n",s[j].nam);
}
}
#endif
for (i = 0; i < SMAX; i++) {
if (s[i].num >= 90)
strcpy(s[i].letter, "A");
else if (s[i].num >= 80)
strcpy(s[i].letter, "B");
else if (s[i].num >= 70)
strcpy(s[i].letter, "C");
else if (s[i].num >= 60)
strcpy(s[i].letter, "D");
else
strcpy(s[i].letter, "F");
}
// NOTE/BUG: newline should go at the end of the printf to prevent a hanging
// last line
#if 0
for (i = 0; i < SMAX; i++)
printf("\n%s has a %s ", s[i].nam, s[i].letter);
#else
for (i = 0; i < SMAX; i++)
printf("%s has a %s\n", s[i].nam, s[i].letter);
#endif
return 0;
}
UPDATE:
Thanks for the tip! On a side note, how would I make it so while the user is entering the duplicate names, the error message appears and the program ends right there.For example: Enter the name of student 1: dan Enter grade: 87 Enter the name of student 2: dan Enter the grade: 78 Error. No duplicate names allowed. And then the program ends there. –
User234567
Easy enough. I put the duplication detection code into functions.
But, I've added a few more enhancements so this may help you with your learning ;-)
I added reprompting the user if they enter a duplicate.
I hate scanf ;-) I reworked the prompting code by putting it into two functions. It will work better if input is a file. This is useful during testing
I changed the conversion from grade number to grade letter to use a table.
Anyway, here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
struct stud {
char nam[20];
int num;
char letter[5];
};
struct letter {
int num;
const char *letter;
};
#define LET(_num,_let) \
{ .num = _num, .letter = _let }
struct letter letters[] = {
LET(90,"A"),
LET(80,"B"),
LET(70,"C"),
LET(60,"D"),
LET(0,"F"),
LET(0,NULL)
};
#define SMAX 5 // maximum number of students
// chkall -- check entire array for duplicates
int
chkall(const struct stud *s,int smax)
{
int i;
int j;
int dup = 0;
for (i = 0; i < (smax - 1); i++) {
for (j = i + 1; j < smax; j++) {
if (strcmp(s[i].nam, s[j].nam) == 0) {
printf("Error. Duplicate name detected -- %s\n",s[j].nam);
dup += 1;
}
}
}
return dup;
}
// chkone -- check a given entry for duplicate (as they are added)
int
chkone(const struct stud *s,int i)
{
int j;
int dup = 0;
for (j = 0; j < i; j++) {
if (strcmp(s[i].nam, s[j].nam) == 0) {
printf("Error. Duplicate name detected -- %s\n",s[j].nam);
dup += 1;
}
}
return dup;
}
// prompt_string -- prompt user for a string
char *
prompt_string(const char *what,int i,char *buf,size_t siz)
{
static int tty = -1;
// decide if our input is tty or file
if (tty < 0) {
struct winsize ws;
tty = ioctl(0,TIOCGWINSZ,&ws);
tty = (tty >= 0);
}
printf("Enter the %s of student #%d: ", what, i + 1);
fflush(stdout);
char *cp = fgets(buf,siz,stdin);
do {
// handle EOF
if (cp == NULL)
break;
buf[strcspn(buf,"\n")] = 0;
// echo the data if input is _not_ a tty
if (! tty)
printf("%s\n",buf);
} while (0);
return cp;
}
// prompt_number -- prompt user for a number
long long
prompt_number(const char *what,int i)
{
char *cp;
char buf[100];
long long val;
while (1) {
cp = prompt_string(what,i,buf,sizeof(buf));
// handle EOF
if (cp == NULL) {
val = -1;
break;
}
// decode the number
val = strtoll(buf,&cp,10);
if (*cp == 0)
break;
printf("invalid number syntax -- '%s'\n",cp);
}
return val;
}
int
main(void)
{
struct stud s[SMAX];
int i;
for (i = 0; i < SMAX; i++) {
while (1) {
prompt_string("name",i,s[i].nam,sizeof(s[i].nam));
if (! chkone(s,i))
break;
}
s[i].num = prompt_number("number grade",i);
}
// recheck all entries
// this will _never_ report a duplicate because of the chkone above
chkall(s,SMAX);
for (i = 0; i < SMAX; i++) {
for (struct letter *let = letters; let->letter != NULL; ++let) {
if (s[i].num >= let->num) {
strcpy(s[i].letter,let->letter);
break;
}
}
}
for (i = 0; i < SMAX; i++)
printf("%s has a %s\n", s[i].nam, s[i].letter);
return 0;
}

I don't understand this crash

This is my code for solving the problem "Students Marks Sum" in Hackerrank:
(Link: https://www.hackerrank.com/challenges/students-marks-sum/problem?)
#include <string.h>
#include <math.h>
#include <stdlib.h>
//Complete the following function.
int marks_summation(int* marks, int number_of_students, char gender) {
int result = 0;
switch (gender){
case 'b':
while(marks != NULL){
result += (*marks);
marks += 2;
}
case 'g':
++marks;
while(marks != NULL){
result += (*marks);
marks += 2;
}
break;
}
return result;
}
int main() {
int number_of_students;
char gender;
int sum;
scanf("%d", &number_of_students);
int *marks = (int *) malloc(number_of_students * sizeof (int));
for (int student = 0; student < number_of_students; student++) {
scanf("%d", (marks + student));
}
scanf(" %c", &gender);
sum = marks_summation(marks, number_of_students, gender);
printf("%d", sum);
free(marks);
return 0;
}
When I move this code into an IDE (in my situation, it's DEV C/C++), it was crashed when my debug ran into my function marks_summation.
Error in line:
result += (*marks);
The problem is in your assumption that if you move out of the range of dynamically allocated memory the pointer value will change to NULL:
while(marks != NULL){
result += (*marks);
marks += 2;
}
There are a number of approaches you can pick to iterate over such array.
You can calculate the memory address that will no longer be valid to read and check at each iteration if your pointer is smaller than it:
int marks_summation(int* marks, int number_of_students, char gender) {
int result = 0;
int* marksEnd = marks + number_of_students;
switch (gender){
case 'b':
while(marks < marksEnd){
result += (*marks);
marks += 2;
}
case 'g':
++marks;
while(marks < marksEnd){
result += (*marks);
marks += 2;
}
break;
}
return result;
}

How do I add a Y/N to the following code?

I am learning from C Programming Absolute Beginner's Guide. I had to do the following exercise and I took it upon myself to try to add a:
"Would you like to sort these numbers in ascending order? (Y/N): "
after the numbers are randomly generated. However, I am struggling with this. I have tried if, else if, do while, etc. But I am struggling with it. How would you add that to this code?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int ctr, inner, outer, didSwap, temp, choice;
int nums[10];
time_t t;
srand(time(&t));
//First step is to fill the array with random numbers (from 1 to 10)
for (ctr = 0; ctr < 10; ctr++)
{
nums[ctr] = (rand() % 99 + 1);
}
//Now list the array as it currently is before sorting
puts("\nHere is the list before the sort:");
for (ctr = 0; ctr < 10; ctr++)
{
printf("%d\n", nums[ctr]);
}
//Now sort the array
for (outer = 0; outer < 10; outer++)
{
didSwap = 0; //Becomes 1 (true) if list is not yet ordered
for (inner = outer; inner < 10; inner++)
{
if (nums[inner] < nums [outer])
{
temp = nums[inner];
nums[inner] = nums [outer];
nums[outer] = temp;
didSwap = 1;
}
}
if (didSwap == 0)
{
break;
}
}
//Now list the array as it currently is after sorting
puts("\nHere is the list after the sort:");
for (ctr = 0; ctr <10; ctr++)
{
printf("%d\n", nums[ctr]);
}
return 0;
}
EDITED CODE:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int ctr, inner, outer, didSwap, temp, choice;
int nums[10];
time_t t;
srand(time(&t));
//First step is to fill the array with random numbers (from 1 to 10)
for (ctr = 0; ctr < 10; ctr++)
{
nums[ctr] = (rand() % 99 + 1);
}
//Now list the array as it currently is before sorting
puts("\nHere is the list before the sort:");
for (ctr = 0; ctr < 10; ctr++)
{
printf("%d\n", nums[ctr]);
}
puts("Would you like to sort in ascending order? ");
puts(("(Reply Y or N): "));
scanf(" %d", choice);
choice = toupper(choice);
if (choice = 'Y')
{
//Now sort the array
for (outer = 0; outer < 10; outer++)
{
didSwap = 0; //Becomes 1 (true) if list is not yet ordered
for (inner = outer; inner < 10; inner++)
{
if (nums[inner] < nums [outer])
{
temp = nums[inner];
nums[inner] = nums [outer];
nums[outer] = temp;
didSwap = 1;
}
}
if (didSwap == 0)
{
break;
}
}
//Now list the array as it currently is after sorting
puts("\nHere is the list after the sort:");
for (ctr = 0; ctr <10; ctr++)
{
printf("%d\n", nums[ctr]);
}
}
else if (choice = 'N')
{
puts("Have a good day!");
}
return 0;
}
You should use %c format specifier with char variable to read one character.
You will have to use unary & operator to get a pointer to a variable for scanf().
= is an assignment operator in C. You should use == operator to check equality.
/* remove choice from int variable declaration */
char choice; /* use char */
puts(("(Reply Y or N): "));
scanf(" %c", &choice); /* use %c and & */
choice = toupper(choice);
if (choice == 'Y') /* use == */
{
//Now sort the array
/* omit */
}
else if (choice == 'N') /* use == */
{
puts("Have a good day!");
}

While loop using realloc, won't update arrays when printing

The code will not produce a compiler error, but i am trying to update the memory allocation after every loop if the user types Y, and increase the size of the memory. But after the first while loop, when i try to print the list of numbers using the first for loop, it will just show one number which will be 0.00000. I can't get it to update (which is what I am trying to do in the second if loop). Any help is appreciated.
#include<stdio.h>
#include<stdlib.h>
int main()
{
double *userNum = (double*)malloc(sizeof(double));
double sum = 0;
char userChar = 'Y';
int i = 0;
int j = 0;
if (userNum == NULL) {
printf("Error with memory");
return 1;
}
while (userChar == 'Y' || userChar == 'y') {
printf("Enter a number\n");
scanf("%lf", userNum);
printf("List of numbers:\n");
for (j; j < (i + 1); j++) {
printf( "%lf\n", userNum[j]);
}
printf("More numbers (Y/N)? \n");
getchar();
scanf("%c", &userChar);
if (userChar == 'Y' || userChar == 'y') {
userNum = realloc(userNum, (i + 2) * sizeof(double));
i++;
}
}
return 0;
)
You need to reinitialize your loop counter to 0 when printing out the numbers :
for (j = 0; j < (i + 1); j++) {
printf("%lf\n", userNum[j]);
}
And you need to increment the position in userNum that you scan the numbers into :
scanf("%lf", &userNum[i]);

ArrayGame. Why doesn't it update the print?

This is my C Programming assignment. We're required to build a simple game that uses array. Our game is like the popular minesweeper game. At first, we initialise the 20*50 array area. Then we put some bombs randomly in the map. In the game, the player is required to travel from the starting point to the ending point to win the game. When the player moves, the movement will make the arrays hidden so that the user knows where did he start. However, in my case, the system doesn't update and make the array empty after the player moves. Can anyone help me with my 's' code? What is wrong?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define iMAX 20
#define jMAX 50
char array[20][50];
int i; //row
int j; //column
int z; //bomb
int n; //steps counter
int o; //x
int p; //y
o = 0;
p = 0;
int level;
int bomb;
char move;
int steps;
int main() {
printf("Welcome to the BombArray Game!\n");
printf("\nLevel 1 Begineer : 50 bombs\nLevel 2 Intermediate : 100 bombs\nLevel 3 Advance : 200 bombs\n");
printf("\nI want to challenge level ");
scanf_s("%d", &level);
printf("\n");
srand(time(NULL));
for (i = 0; i < 20; i++) {
for (j = 0; j < 50; j++) {
array[i][j] = '*';
}
}
array[0][0] = 'S';
array[19][49] = 'E';
if (level == 1) {
bomb = 50;
}
else if (level == 2) {
bomb = 100;
}
else if (level == 3) {
bomb = 200;
}
for (z = 0; z < bomb; z++) {
i = rand() % 20;
j = rand() % 50;
array[i][j] = '1';
}
do {
system("cls");
for (i = 0; i < iMAX; i++) {
for (j = 0; j < jMAX; j++) {
if (array[i][j] == 'S') {
printf("S");
}
else if (array[i][j] == '*') {
printf("*");
}
else if (array[i][j] == '1') {
printf("*");
}
else if (array[i][j] == 'E') {
printf("E");
}
else if (array[i][j] == '2') {
printf(" ");
}
}
printf("\n");
}
printf("\n\nMoving direction (w:up s:down a:left d:right e:exit): ");
scanf_s(" %c", &move);
printf("Steps? ");
scanf_s("%d", &steps);
if (move == 's') {
for (n = 0; n < steps; n++) {
i = o;
j = p;
i++;
array[i][j] = '2';
o = i;
p = j;
}
}
} while (array[19][49] != 2);
return 0;
}
if (move == 's') {
array[o][p] = '2';
for (n = 0; n < steps; n++) {
i = o;
j = p;
i++;
array[i][j] = '2';
o = i;
p = j;
}
array[o][p] = 'S';
}
You have to delete the S at the Start position and write it at the end position when you move
Some additional things: You don't need that much variables. You can remove i and j (or o and p).
If you enter something others than 1-3 for the level you will have an undefined number of bombs (if you declare the bomb variable as a local variable), therefore you should make a default case.
You never look if you are hitting a bomb, you just overwrite array[i][j] without prove if there's a bomb.
better:
if (move == 's') {
array[i][j] = '2';
for (n = 0; n < steps; n++) {
i++;
if (array[i][j] == '1') {
printf("bomb\n");
return 0;
}
array[i][j] = '2';
}
array[i][j] = 'S';
}

Resources