0

I'm trying to create a top 5 ranking i C programming. The info are stored in a normal text file.

I want to sort the info in the history.txt file by the result of the %: The ints 1-3 are the type of test the user made.

26%       User1           1       01/01/2019
100%      User2           3       01/01/2019
73%       User3           1       01/01/2019
52%       User4           1       01/01/2019
75%       User5           2       01/01/2019
60%       User6           1       01/01/2019

I now I have wrong in my code but just walking in circles for the moment. it´s the char testTy[50]; It´s not an array but i don´t really know how to solve it to stay together with the result in %. I have for the moment solved the sorting of the result. But the rest of the text is just a repeated mess.

// Display Best result grade 5 (This is in a function)

fp = fopen("history.txt", "r");
int ch=0;
int lines=0;
while(!feof(fp))
{
    ch = fgetc(fp);
    if(ch == '\n')
    {
        lines++;
    }
}
fclose(fp);

fp = fopen("history.txt", "r");

int i =0, temp, swapped;
int topResult[lines];
char testTy[50];

char singelLine[100];
while(!feof(fp))
{
    fgets(singelLine, 100, fp);
    sscanf(singelLine, "%d%[^'\n']s",&topResult[i], testTy);
    i++;
}

fclose(fp);
while(1)
{
    swapped = 0;

    for( i= 0; i <lines-1; i++)
    {
        if(topResult[i]<topResult[i+1])
        {
            int temp = topResult[i];
            topResult[i] = topResult[i+1];
            topResult[i+1] = temp;
            swapped = 1;
        }
    }
    if(swapped == 0)
    {
        break;
    }
}

printf("Result:   User:      Test type:      Date:\n");
for (i = 0; i < 5; i++)
{
    printf("\n%d%25s", topResult[i], testTy);
}
printf("\n\n");
return;

The Result I want is info like this:

100%     user2  3 01/01/2019
75%      user5  2 01/01/2019
73%      user3  1 01/01/2019
60%      user6  1 01/01/2019
52%      user4  1 01/01/2019

My output now are:

100%     user1  1 01/01/2019
75%      user1  1 01/01/2019
73%      user1  1 01/01/2019
60%      user1  1 01/01/2019
52%      user1  1 01/01/2019
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 2
    Please read [Why is “while ( !feof (file) )” always wrong?](https://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong) – Some programmer dude Jan 01 '19 at 20:40
  • 1
    Also the [`scanf`](https://en.cppreference.com/w/c/io/fscanf) format `"%["` ends with `"]"`. There's no `"s"` in the format. And you will have some trouble if the last line doesn't end with a newline. – Some programmer dude Jan 01 '19 at 20:41
  • 1
    Lastly, where do you store the *different* user-names/dates? Perhaps it's time for you to learn about *structures*? And about the [`qsort`](https://en.cppreference.com/w/c/algorithm/qsort) function. – Some programmer dude Jan 01 '19 at 20:43
  • Hey, and thanks for some faste responses and help with formating. At the moment the "users" are only the name of the person who does the test. When you start the program it just ask the persons name and save it in the history file together with the result. `printf("What are your name?\n");` `printf(">> ");` `gets(&answerName);` `printf("\nWelcome, %s!\n",answerName);` Saving to history.txt ` fp = fopen("history.txt", "a");` ` fprintf(fp,"%.f%% \t %-12.12s\t %d \t %s\n",result, answerName,` `typeOfTest, date);` `fclose(fp)` – Blackscorpioz Jan 01 '19 at 20:49
  • If you are going to store all the user names and test types and test dates, then you need more that just one variable `testTy` — you need an array of those values too. And you'll need to swap that array in parallel with the scores array — or use a structure (which would be better from all sorts of points of view, except perhaps that you've not learned about structures yet). – Jonathan Leffler Jan 01 '19 at 21:46
  • I have learned little about structures but not so much that i thought this was a good place to use or how to fully implement it. My main study in school is Java but learn C-programming in a side course. Thanks for all the help from you all! – Blackscorpioz Jan 02 '19 at 08:11

2 Answers2

0

I think the problem is that testTy is not an array. You are splitting the input in 2 variables:

  • topResult: which is an array with the values to be sorted
  • testTy: the rest of string

When you print the result, the variable testTy always have the same output.

Davy Souza
  • 31
  • 4
0

Your immediate problem was pointed out in the comment and you were directed to Why is while ( !feof (file) ) always wrong?. Always condition the read loop on the read function itself, or loop continually and check the return of the read function within the loop and break; the loop when a successful read has occurred and all constraints are satisfied.

fp = fopen("history.txt", "r");

Don't hardcode filenames or use magic-numbers in your code. For filenames, either pass the filename to read as an argument to your program (that's what argv is for) or prompt for its entry. (preferably pass it as an argument). If you need constants within your code, #define one (or more) or use a global enum to accomplish the same thing.

Now to the crux of your problem. Anytime when you have to handle differing types of values as a single record or object, you should be thinking about using a structure to coordinate the different types as a single object. Here you can declare a struct with int members to store the percent and test type values and char[] (or char* and allocate) to hold the name and date information. You can then simply declare an array of struct to hold all values read from your history.txt file that can be easily sorted with qsort on any of the members you choose.

First, declare a simple struct, e.g.

struct users {     /* simple struct to hold values read from input */
    int pct,
        test;
    char name[MAXNM],
        date[MAXDT];
};

Then you can simply declare an array with struct users array[50]; However, you can make your life easier and avoid having to type struct users ... over and over by creating a typedef. In C you can even drop the struct label users and add the typedef label at the end, e.g.

typedef struct {    /* simple struct w/typedef to hold values read from input */
    int pct,
        test;
    char name[MAXNM],
        date[MAXDT];
} userhist_t;

Now you can simply create an array of struct with userhist_t array[50]; (it's up to you whether you use a typedef or not, it isn't required, it just saves typing)

If you are sorting values in C, go ahead and do yourself a favor and learn how to write compare functions for qsort. It is the swiss-army-knife for sorting in C. (it is also quite efficient and much more well tested than anything you will roll on your own). There is nothing difficult about writing a compare function. The prototype is:

int compare (const void *a, const void *b);

Where a and b are simply pointers to adjacent elements in the array you pass and you return -1 if a sorts before b, 0 if they are the same, or 1 if b sorts before a -- just like strcmp does. To do that you simply cast a and b to the element type for your array and then compare the values, returning an appropriate value. For instance:

/* qsort compare function sorting array of struct by percent (descending) */
int compare_pct (const void *a, const void *b)
{
    const userhist_t    *as = a,    /* cast the pointers to correct type */
                        *bs = b;

    /* return descending sort (conditionals avoids potential overflow)
     * for ascending sort use (as > bs) - (as < bs)
     */
    return (as->pct < bs->pct) - (as->pct > bs->pct);
}

Above, the pointers a and b are cast as a pointer to your struct (I just tagged on the s for struct to create different variable names, e.g. as and bs, you can name them anything you like).

While you could just return bs->pct - as->pct, there is a risk of overflow if the values exceed what can be returned as an int, (e.g. both large negative value that when subtracted would be less than INT_MIN. Instead, the conditionals are used to normalize the return to -1, 0, 1 just using the result of the conditional expressions.

(think through it, if (as->pct < bs->pct) the result evaluates to 1 and (as->pct > bs->pct) evaluates to 0 yielding 1 - 0 = 1 (so b sorts before a in the descending case))

To sort your array, you then simply call:

qsort (array, nelements, sizeof element, compare);

Now on to reading/filling the array of struct. So let's declare our constants, you have the struct above, let's declare an array of struct and then read values from the file into the array, e.g.

/* global enum defining constants for use in code */
enum { MAXDT = 12, MAXNM = 16, MAXS = 64 };
...
    userhist_t user[MAXS] = {{ .pct = 0 }};   /* array of struct */
    size_t n = 0;
    ...
    /* read/fill up to MAXS struct from file */
    while (n < MAXS && fscanf (fp, "%d%% %s %d %s", &user[n].pct,
            user[n].name, &user[n].test, user[n].date) == 4)
        n++;

While you can use fgets and sscanf to read a line and then parse the values separately, fscanf will do both for example purposes here. The key is to validate the return to insure 4-conversions succeeded and no input or matching failure occurred.

Putting it altogether, passing the filename as the 1st argument to the program (or reading from stdin by default if no argument is provided), handling the declarations, the read, the sort with qsort and the output, you could do something similar to the following:

#include <stdio.h>
#include <stdlib.h>

/* global enum defining constants for use in code */
enum { MAXDT = 12, MAXNM = 16, MAXS = 64 };

typedef struct {    /* simple struct to hold values read from input */
    int pct,
        test;
    char name[MAXNM],
        date[MAXDT];
} userhist_t;

/* qsort compare function sorting array of struct by percent (descending) */
int compare_pct (const void *a, const void *b)
{
    const userhist_t    *as = a,    /* cast the pointers to correct type */
                        *bs = b;

    /* return descending sort (conditionals avoids potential overflow)
     * for ascending sort use (as > bs) - (as < bs)
     */
    return (as->pct < bs->pct) - (as->pct > bs->pct);
}

int main (int argc, char **argv) {

    userhist_t user[MAXS] = {{ .pct = 0 }};   /* array of struct */
    size_t n = 0;
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    /* read/fill up to MAXS struct from file */
    while (n < MAXS && fscanf (fp, "%d%% %s %d %s", &user[n].pct,
            user[n].name, &user[n].test, user[n].date) == 4)
        n++;
    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    /* sort by calling compare_pct to sort by percent (descending order) */
    qsort (user, n, sizeof *user, compare_pct);

    for (size_t i = 0; i < n; i++)  /* output sorted results */
        printf ("%3d%%      %-8s    %2d     %s\n", 
                user[i].pct, user[i].name, user[i].test, user[i].date);

    return 0;
}

Example Input File

$ cat dat/history.txt
26%       User1           1       01/01/2019
100%      User2           3       01/01/2019
73%       User3           1       01/01/2019
52%       User4           1       01/01/2019
75%       User5           2       01/01/2019
60%       User6           1       01/01/2019

Example Use/Output

Using your input file, would result in the sorted order you describe:

$ ./bin/userhistsort dat/history.txt
100%      User2        3     01/01/2019
 75%      User5        2     01/01/2019
 73%      User3        1     01/01/2019
 60%      User6        1     01/01/2019
 52%      User4        1     01/01/2019
 26%      User1        1     01/01/2019

If you want to save the results to a new file, simply redirect the output, e.g.

$ ./bin/userhistsort dat/history.txt > sortedfile.txt

(or you can open a new file-stream within your code and output the information there)

Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Real thanks for the good and clear instructions and tips for the future. I Need to study more on structures and pointers. And read Why is while ( !feof (file) ) always wrong? in detail. Learned alot from all the explanations! – Blackscorpioz Jan 02 '19 at 08:07
  • Sure, glad to help. Structures just allow you to collect varying types, wrap it in `{...}` and give it a label. You refer to each member through the `'.'` operator (if you have a struct itself) or the `->` operator (if you have a pointer-to-struct). A pointer is simply a normal variable that holds the *address of* something else as its value, e.g. a pointer *points to* the address where something else can be found. You normally think of a variable holding an immediate values, such as `int a = 5;`, a pointer simply holds the address where `5` is stored in memory, e.g. `int *b = &a;`. – David C. Rankin Jan 02 '19 at 17:30