简体   繁体   中英

qsort dynamic 2d char array with alphanumeric filenames - C program

I'm new here, so this is my first post. I've been struggling for 2 weeks to solve this problem. I'm trying to open a directory, capture and store the names of the files found, sort them in ascending order, and print the results. My issue is either qsort causes my program to crash entirely, or qsort doesn't sort the array at all because the files are alphanumeric. I even tried looping through a stored filename to output each character, just to see if I could eventually try comparing the characters between two array locations for sorting. But I noticed that it can't seem to see or recognize the numbers in the alphanumeric filename (for example: "f1.jpg" will only print "f", a blank, then "j", and that's it. I should note that I cannot change the file names because I don't know in advance the names or total files. I'm trying to make this to be dynamic. The following is the main code that I'm having problems with since it crashes at the 'qsort' keyword:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <conio.h>
#include <ctype.h>
#include <time.h>
#include <dirent.h>
#include <math.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

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

int main
{
   readInFilenames();
   system("pause");
}

int compare(const void *a, const void *b)
{
  return strcmp(*(char **)a, *(char **)b);
}

void readInFilenames()
{
  char cwd[1024];
  DIR *dir = NULL;
  struct dirent *pent = NULL;
  struct stat info;
  char file_path[50] = "files/";
  int total_files = 0;
  int file_size;

  // Change directory to file location
  chdir(file_path);

  if((getcwd(cwd, sizeof(cwd))) != NULL)
  {
    printf("Current Directory: %s\n", cwd);
  }

  // Open directory and count the total number of files found
  dir = opendir(cwd);

  if(dir != NULL)
  {
    while((pent = readdir(dir)) != NULL)
    {
        if(stat(pent->d_name, &info))
        {
            printf("ERROR: stat%s: %s\n", pent->d_name, strerror(errno));
        }
        else
        {
            if(S_ISREG(info.st_mode))
            {
                if((strcmp(pent->d_name, ".cproject") == 0) || (strcmp(pent->d_name, ".project") == 0))
                {
                    continue;
                }
                else
                {
                    total_files++;
                    file_size = sizeof(pent->d_name);
                }
            }
        }
    }
    printf("# of files found: %d\n", total_files);
    rewinddir(dir);    //reset pointer back to beginning of file directory

    // Create character array to store file names;
    char *filenames_arr[total_files][file_size];
    int size = sizeof(filenames_arr)/sizeof(filenames_arr[total_files]);

    total_files = 0;   //reset file counter back to 0;

    // Read and store file names in the character array
    while((pent = readdir(dir)) != NULL)
    {
        if(stat(pent->d_name, &info))
        {
            printf("ERROR: stat%s: %s\n", pent->d_name, strerror(errno));
        }
        else
        {
            if(S_ISREG(info.st_mode))
            {
                if((strcmp(pent->d_name, ".cproject") == 0) || (strcmp(pent->d_name, ".project") == 0))
                {
                    continue;
                }
                else
                {
                    strcpy(filenames_arr[total_files], pent->d_name);
                    //printf("%s\n", filenames_arr[i]);
                    total_files++;
                }
            }
        }
    }
    closedir(dir);

    // Print original array contents
    printf("Original List of Files\n");
    printf("----------------------\n");
    for(int i = 0; i < total_files; i++)
    {
      printf("%s\n", filenames_arr[i]);
    }

    // Sort array in ascending order
    qsort(filenames_arr, total_files, size, compare);
    //qsort(filenames_arr, total_files, sizeof(filenames_arr[0]), (char (*)(const void*, const void*))strcmp);

    // Print organized array contents
    printf("Sorted List of Files\n");
    printf("----------------------\n");
    for(int i = 0; i < total_files; i++)
    {
      printf("%s\n", filenames_arr[i]);
    }
    printf("\nFinished!\n");
  }
}

This portion of code is when I was trying to print each individual characters. This was originally located where the final array printing takes place in the previous code:

    int i = 0;
    int j = 0;
    while(i < total_files)
    {
        printf("File Name: %s\n", filenames_arr[i]);
        printf("String Length: %d\n", strlen(filenames_arr[i]));
        while(filenames_arr[i] != '\0')
        {
            printf("Checking filenames_arr[%d][%d]\n", i, j);

            if(isalpha((unsigned char)filenames_arr[i][j]) != 0)
            {
                printf("In isalpha\n");
                printf("Found: %c\n", filenames_arr[i][j]);
            }
            else if(isdigit((unsigned char)filenames_arr[i][j]) != 0)
            {
                printf("In isdigit\n");
                printf("Found: %d\n", filenames_arr[i][j]);
            }
            j++;
        }
        printf("-------------------------------------------\n");
        i++;
        j = 0;
    }

How do I sort a 2D array of alphanumeric character strings using qsort? What is it about qsort, or even my array setup that's causing my program to crash? Also, how does qsort work? I've tried searching forums and online course notes to find out whether or not qsort only sorts by looking just at the first character, all characters, or if it has problems with numbers. Thank you in advance!

UPDATE:

I made the following edits to my code. Its working much better, in that qsort no longer crashes program. But, qsort still isn't sorting. Here are the updates I made, followed by a screenshot of the results:

typedef struct{
  char *filename;
}filedata;

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

int main(void){
  readInFilenames();
  system("pause");
}

int compare (const void *a, const void *b ) {
  filedata *ia = (filedata *)a;
  filedata *ib = (filedata *)b;
  return strcmp(ia->filename, ib->filename);
}

readInFilenames(){
.
.
.
  printf("# of files found: %d\n", total_files);
  rewinddir(dir);

  filedata fn_data[total_files];
  total_files = 0;

  printf("Original Array: \n");
  while((pent = readdir(dir)) != NULL)
  {
    .
    .
    .
    if((strcmp(pent->d_name, ".cproject") == 0) || (strcmp(pent->d_name, ".project") == 0))
    {
        continue;
    }
    else
    {
        fn_data[total_files].filename = malloc(file_size + 1);
        strcpy(fn_data[total_files].filename, pent->d_name);
        printf("%s\n", fn_data[total_files].filename);
        total_files++;
    }
  }
  closedir(dir);

  printf("\n");
  qsort(fn_data, total_files, sizeof(filedata), compare);

  printf("Sorted Array:\n");
  for(int i = 0; i < total_files; i++)
    printf("%s\n", fn_data[i].filename);

  printf("Finished!\n");
}

Click here to see sorting results

The list should print: f0.dat, f1.dat, f2.dat, f3.dat,...,f20.dat. But instead it prints: f0.dat, f1.dat, f10.dat, f11.dat,...,f9.dat.

OP has fixed code to cope with "qsort dynamic 2d char array with filenames" by enabling warnings and using @Snohdo advice.

Yet code is still doing a compare with strcmp() which only treat digits as characters and not numerically to achieve f1.dat, f2.dat, f3.dat,...,f20.dat order.


Following is a compare functions that looks for digits to invoke an alternate compare for numeric sub-strings. Variations on this compare can be made by OP to suit detailed coding goals.

int AdamsOrder(const char *s1, const char *s2) {
  // Compare as `unsigned char` as that is `strcmp()` behavior.  C11 7.24.1 3
  const unsigned char *us1 = (const unsigned char *) s1;
  const unsigned char *us2 = (const unsigned char *) s2;
  while (*us1 && *us2) {
    if (isdigit(*us1) && isdigit(*us2)) {
      char *end;  // dummy
      unsigned long long l1 = strtoull(us1, &end, 10);  // Parse for a number
      unsigned long long l2 = strtoull(us2, &end, 10);
      if (l1 > l2) return 1;
      if (l1 < l2) return -1;
      // Continue on treating as text. OP needs to decide how to handle ties: "0001" vs "1"
    }
    if (*us1 > *us2) return 1;
    if (*us1 < *us2) return -1;
    us1++;
    us2++;
  }
  // At this point, at least one string ended (i.e. points to '\0').
  // The return statement below will behave as follows:
  // If a string ended, *us1/2 will be 0. Let an unfinished one be X > 0.
  // First string ended : ( 0 > X ) - ( 0 < X ) = false - true  = 0 - 1 = -1
  // Second string ended: ( X > 0 ) - ( X < 0 ) = true  - false = 1 - 0 =  1
  // Both strings ended : ( 0 > 0 ) - ( 0 < 0 ) = false - false = 0 - 0 =  0      
  return (*us1 > *us2) - (*us1 < *us2);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM