hardStuckProgrammer
hardStuckProgrammer

Reputation: 13

Arrange one array and other array do the same

sorry if English no good, but I have questions please: I have this array[quantity] that I will sort:

int main() {
     int numberOfFruits[] = {3, 5, 2, 4, 1};
     //I do sorting algorithm
}

but I also want other array do same thing and match.

     char fruits[] = {"apple", "orange", "bannana", "peach", "watermelon"};

So after algrithm it will be:

     int numberOfFruits[] = {1, 2, 3, 4, 5};
     char fruits[] = {"watermelon", "banana", "apple", "peach", "orange"};

How I do this? Thank you.

Upvotes: 0

Views: 114

Answers (2)

Dúthomhas
Dúthomhas

Reputation: 10083

I upvoted HAL9000’s answer because it suggests a better data structure than parallel arrays, but it doesn’t actually answer the title question: How do I sort parallel arrays?

Parallel Arrays

OP has presented an example of parallel arrays. I will add my favorite fruits to his:

int    fruit_IDs  [] = { 3,       5,        2,        4,       1              10,           -7           };
char * fruit_names[] = { "apple", "orange", "banana", "peach", "watermellon", "grapefruit", "strawberry" };

Notice how the ID has no relation to any ordering or indexing of the list. It is just another piece of information. Here is another parallel array structure:

char * first_names[] = { "Harry",  "Ron",     "Hermione", "Severus" };
char * last_names [] = { "Potter", "Weasley", "Granger",  "Snape"   };
int    year_born  [] = { 1980,     1980,      1979,       1960      };

So, how do we sort them without breaking their index association?

Index Array

The simplest would be to use an additional array which is simply used as indices into the parallel arrays. Sticking with the fruits:

int fruit_indexes[NUM_FRUITS] = { 0, 1, 2, 3, 4, 5, 6 };

Now we have a layer of indirection into our parallel arrays. To print the arrays, we might write:

  for (int n = 0;  n < NUM_FRUITS;  n++)
    printf( "%d %s\n", fruit_IDs[fruit_indexes[n]], fruit_names[fruit_indexes[n]] );

As you can imagine, if we reorder the elements of the index array, we also reorder how we view the parallel arrays. For example, to see the fruits in reverse order:

int fruit_indexes[NUM_FRUITS] = { 6, 5, 4, 3, 2, 1, 0 };

You should notice right away that this additional array has consequences:

  • On the plus side, we no longer have to move stuff around in the parallel arrays to reorder them.
  • On the minus side, we have an additional array to deal with.

Let’s qsort() with the index array!

First, we need a comparitor for the fruit IDs, accessed via indirection through the index array:

int compare_indexed_fruits_by_ID( const void * a, const void * b )
{
  // A and b are pointers to elements of the fruit_indices[] array
  int ai = *(const int *)a;
  int bi = *(const int *)b;

  // We use those indices to compare fruit IDs
  if (fruit_IDs[ai] < fruit_IDs[bi]) return -1;
  if (fruit_IDs[ai] > fruit_IDs[bi]) return  1;
  return 0;
}

Comparing by fruit name works the same way, except now we compare names instead of IDs:

int compare_indexed_fruits_by_name( const void * a, const void * b )
{
  // A and b are pointers to elements of the fruit_indices[] array
  int ai = *(const int *)a;
  int bi = *(const int *)b;

  // Use those indices to compare fruit names
  return strcmp( fruit_names[ai], fruit_names[bi] );
}

The call to QSort is thereafter very simple — pass the correct comparison function and sort the index array:

qsort( fruit_indexes, NUM_FRUITS, sizeof(*fruit_indexes), &compare_indexed_fruits_by_ID );

Printing our fruits will give you:

-7 strawberry
1 watermellon
2 banana
3 apple
4 peach
5 orange
10 grapefruit

We can sort by name just as easily:

qsort( fruit_indexes, NUM_FRUITS, sizeof(*fruit_indexes), &compare_indexed_fruits_by_name );

Producing:

3 apple
2 banana
10 grapefruit
5 orange
4 peach
-7 strawberry
1 watermellon

Dynamic data, static arrays

One of the nice things about the index array is that you can still use it when adding and removing data from other arrays. Remember, to set up static arrays you need a data structure something like this:

#define MAX_NUM_PEOPLE 1000
char * first_names[MAX_NUM_PEOPLE];
char * last_names [MAX_NUM_PEOPLE];
int    indices    [MAX_NUM_PEOPLE];
int    num_people = 0;

To add new data, just push it onto the back of your existing arrays as usual, checking of course that you have room:

if (num_people < MAX_NUM_PEOPLE)
{
  first_names[num_people] = new first name;
  last_names [num_people] = new last name;
  indices    [num_people] = num_people;
  num_people += 1;

The new element will naturally be out of order, so you will have to sort the index array again.

  qsort( indices, ... );
}

To delete an element you need only swap it off the end before sorting the index array again. Don’t forget to check your array bounds!

if (index_to_delete > 0 && index_to_delete < num_people)
{
  first_names[indices[index_to_delete]] = first_names[num_people-1];
  last_names [indices[index_to_delete]] = last_names [num_people-1];
  indices[index_to_delete] = indices[num_people-1];
  num_people -= 1;
  qsort( indices, ... );
}

Dynamic data, dynamic arrays

This works very much like the static array version, only your arrays are dynamically-allocated.

char ** first_names = NULL;
char ** last_names  = NULL;
int   * indices     = NULL;
int     num_people  = 0;

...

first_names = malloc( new_num_people * sizeof(*first_names) );
last_names  = malloc( new_num_people * sizeof(*last_names) );
indices     = malloc( num_num_people * sizeof(*indices) );

if (!first_names || !last_names || !indices)
{
  free( indices );
  free( last_names );
  free( first_names );
  fooey();
}

num_people = new_num_people;

Using realloc() to increase the size of your arrays works as usual as well, with the added complexity that you must check that every array was reallocated successfully.

Eliminating the index array

That index array can be kind of obnoxious. It would be nice to get rid of it after sorting. And, of course, you can!

Create it dynamically before sorting, initializing it to 0, 1, 2, ... as always, sort using the array intermediary, and then take the time to rearrange the other arrays with the new order.

void sort_fruits_by( void (*comparitor)(const void *, const void *) )
{
  int indices[num_fruits];
  for (int n = 0;  n < num_fruits;  n += 1)
    indices[n] = n;
  qsort( indices, num_fruits, sizeof(*indices), comparitor );
  reorder_people_by_index( indices );
}

This rearrange bit is what takes some time and space:

void reorder_people_by_index( int * indices )
{
  char * temp_names[num_people];

  memcpy( temp_names, first_names, num_people*sizeof(char*) );
  for (int n = 0;  n < num_people;  n += 1)
    first_names[indices[n]] = temp_names[n];

  memcpy( temp_names, last_names, num_people*sizeof(char*) );
  for (int n = 0;  n < num_people;  n += 1)
    last_names[indices[n]] = temp_names[n];
}

For the people we could reuse the char * temporary, but for fruits we would need both an int temporary and a char * — one for each type of parallel array.

Now the parallel arrays themselves are sorted differently. But you lose the sorting-an-array-of-integers-only advantage.

Conclusions

You can see that it really isn’t too difficult to maintain parallel arrays. The grief is in the details — you are maintaining multiple arrays!

This is why having a single array of structured data is ever so much more convenient.

But, if you have to do it with parallel arrays, it can be done.

Upvotes: 1

HAL9000
HAL9000

Reputation: 2188

First, a variable of type char is not able to hold a string (unless it holds the value 0, then it is an empty string). A string is a sequence of char, where the last char has the value 0. Typically a string is stored in an array, or you use a pointer char * to point to a string that is stored somewhere else. In your case, the name of the fruits are stored in a string literal, so you can use a char * to that. An since you want an array of strings, you should declare an array of char *:

char *fruits[] = {.....};

Now to your actual question:

When you have multiple things that belongs together, you can aggregate them together by declaring a struct:

struct fruit
{
   int number;
   char *name;
};

And you can define an array of struct fruit like this:

struct fruit fruits[] = 
  { 
     {1, "watermelon"},
     {2, "banana"},
     {3, "apple"},
     {4, "peach"},
     {5, "orange"}
  }

When you now swap two element of fruits, you swap both number and name together:

 /* Swap the i-th and j-th element in fruits */
 struct fruit tmp = fruits[i];
 fruits[i] = fruits[j];
 fruits[j] = tmp;

Upvotes: 2

Related Questions