RoadRunner
RoadRunner

Reputation: 26315

Sorting an array of structs Efficiently

I am sorting an array of structs using qsort, and I am looking for a more efficient way to extract the top three highest scores by partially sorting the array. My structure looks like this:

typedef struct {
    double score;
    int player_num;
} player_t;

and I have malloced an array of structures like this:

player_t *players = malloc(SIZE * sizeof(player_t));

The way I sort this array of structures is by first sorting the scores, and if the scores have ties, then sort by the player number.

My code looks like this with qsort:

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

#define SIZE 6
#define MAX 3

int scorecmp(const void *a, const void *b);
int ComparePlayerScore( const void* ap , const void* bp );
int CompareInt( const int a , const int b );

typedef struct {
    double score;
    int player_num;
} player_t;

int
main(int argc, char *argv[]) {
    int i;

    int player_numbers[] = {1, 2, 3, 4, 5, 6};
    double scores[] = {0.765, 0.454, 0.454, 0.345, 0.643, 0.532};

    player_t *players = malloc(SIZE * sizeof(player_t));

    for (i = 0; i < SIZE; i++) {
        players[i].score = scores[i];
        players[i].player_num = player_numbers[i];
    }

    qsort(players, SIZE, sizeof(*players), ComparePlayerScore);

    for (i = 0; i < MAX; i++) {
        printf("Player %d: Score: %.3f\n", players[i].player_num, players[i].score);
    }

    free(players);

    return 0;
}

int ComparePlayerScore( const void* ap , const void* bp ) {
    const player_t* const a = ap;
    const player_t* const b = bp;

    if( a->score == b->score ){
        return CompareInt( a->player_num , b->player_num );
    }
    else if( a->score > b->score ) {
        return -1;
    }
    else {
        return 1;
    }
}

int CompareInt( const int a , const int b ) {
    if( a < b ){
        return -1;
    }
    else if( a > b ){
        return 1;
    }

    return 0;
}

I am just looking for another way to do this, but a more efficient way to extract the top 3 the scores, along with the respective player numbers. Like a way where I can extract the top three elements of the array without having to sort the whole array first everytime.

Upvotes: 2

Views: 177

Answers (3)

ad absurdum
ad absurdum

Reputation: 21320

Here is a solution that keeps track of the top three scores by storing pointers to them. When you add a player or change a score, an update function is called to keep the top-score list current. The advantage here is that you only ever need to iterate over a list of three elements. I modified your original code to demonstrate how this might work:

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

#define SIZE 6
#define MAX 3

typedef struct {
    double score;
    int player_num;
} player_t;

void update_topscores(player_t **top, player_t *pplayer);

int
main(int argc, char *argv[]) {
    int i;

    int player_numbers[] = {1, 2, 3, 4, 5, 6};
    double scores[] = {0.765, 0.454, 0.454, 0.345, 0.643, 0.532};

    player_t *players = malloc(SIZE * sizeof(player_t));
    player_t **topscores = calloc(3, sizeof(player_t *));

    for (i = 0; i < SIZE; i++) {
        players[i].score = scores[i];
        players[i].player_num = player_numbers[i];
        update_topscores(topscores, &(players[i]));
    }

    for (i = 0; i < SIZE; i++) {
        printf("Player %d: Score: %.3f\n",
               players[i].player_num, players[i].score);
    }

    puts("Top Scores");
    for (i = 0; i < 3; i++) {
        printf("Player %d: Score: %.3f\n",
               topscores[i]->player_num, topscores[i]->score);    
    }

    /* Change score for player 4 */
    players[3].score = 0.755;
    update_topscores(topscores, &(players[3]));
    puts("New Top Scores");
    for (i = 0; i < 3; i++) {
        printf("Player %d: Score: %.3f\n",
               topscores[i]->player_num, topscores[i]->score);
    }

    free(players);
    free(topscores);

    return 0;
}

void update_topscores(player_t **top, player_t *pplayer)
{
    int i;
    player_t *temp;

    for (i = 0; i < 3; i++)
        if (top[i] == NULL) {
            top[i] = pplayer;
            return ;
        }
    for (i = 0; i < 3; i++) {
        if ((pplayer->score) > (top[i]->score)) {
            temp = top[i];
            top[i] = pplayer;
            update_topscores(top, temp);
            break;
        }
    }

    return ;
}

You can see that there is a function update_topscores() that is used to update the list. In the above code, this is used when building the initial list of players. Then, when the score of Player 4 is updated, the function is called again, resulting in a new list of top scores. The list is not sorted, but it would be easy to do so if desired, and you would only be sorting a list of three entries.

There was an additional allocation for three pointers to player pointers, topscores, which must of course be freed at the end.

Here is a sample output:

Player 1: Score: 0.765
Player 2: Score: 0.454
Player 3: Score: 0.454
Player 4: Score: 0.345
Player 5: Score: 0.643
Player 6: Score: 0.532
Top Scores
Player 1: Score: 0.765
Player 5: Score: 0.643
Player 6: Score: 0.532
New Top Scores
Player 1: Score: 0.765
Player 4: Score: 0.755
Player 5: Score: 0.643

Upvotes: 1

Roland Illig
Roland Illig

Reputation: 41617

Just a simple try, see online demonstration on http://ideone.com/8A1lnP.

struct top3_players {
  player_t *top[3];
};

void top3_determine(struct top3_players *top, player_t *players, size_t n) {
  player_t *top1 = NULL;
  player_t *top2 = NULL;
  player_t *top3 = NULL;
  for (size_t i = 0; i < n; i++) {
    player_t *player = &players[i];
    if (top1 == NULL || ComparePlayerScore(player, top1) < 0) {
      top3 = top2;
      top2 = top1;
      top1 = player;
    } else if (top2 == NULL || ComparePlayerScore(player, top2) < 0) {
      top3 = top2;
      top2 = player;
    } else if (top3 == NULL || ComparePlayerScore(player, top3) < 0) {
      top3 = player;
    }
  }
  top->top[0] = top1;
  top->top[1] = top2;
  top->top[2] = top3;
}

Upvotes: 1

Weather Vane
Weather Vane

Reputation: 34575

This is my offering. Since you have #define MAX for how many top scores you want to find, I have considered that.

The program makes a single pass of the data, and 1 pass of the topmost for each of the records. I have used a fixed array, you'll have to adapt to your needs.

#include <stdio.h>

#define SIZE 6
#define MAX 3

typedef struct {
    double score;
    int player_num;
} player_t;

int main(int argc, char *argv[])
{
    player_t players[SIZE] = {{2.0, 2}, {4.0, 5}, {1.0, 4}, {1.0, 1}, {5.0, 3}, {4.0, 6}};
    int order[MAX+1] = {0};
    int found = 1;
    int i, j;
    int bigger;

    for(i = 1; i < SIZE; i++) {
        bigger = 0;
        for(j = 0; j < found; j++) {
            if(players[i].score > players[ order[j] ].score) {
                bigger = 1;
                break;
            }
            else if(players[i].score == players[ order[j] ].score && 
                    players[i].player_num < players[ order[j] ].player_num) {
                bigger = 1;
                break;
            }
        }
        if(bigger) {
            memmove(order + j + 1, order + j, (found - j) * sizeof order[0]);
            order[j] = i;
            if(found < MAX) {
                found++;
            }
        }
    }

    for(i = 0; i < found; i++) {
        printf("%d %f\n", players[ order[i] ].player_num, players[ order[i] ].score); 
    }
    return 0;
}

Program output:

3 5.000000
5 4.000000
6 4.000000

Upvotes: 2

Related Questions