0

I'm new to C programming and I'm trying to write a code which sorts words in alphabetical order. I considered capital letters and small letters different. My rules for sorting is first to consider alphabetical order, and then capital letters have priority, and words with fewer characters has priority. For all words we consider only first and second letters characters, if they are similar we go to next word.
Finally when 0 is entered, the program should finish.
Here is an example of what should it do:
input : alireza Mohammad Arash anahita sarah Milad john Alireza Maryam 0
output : Alireza alireza anahita Arash john Maryan Milad Mohammad sarah

#include<stdio.h>
#include<string.h>
int main() {
    int i=0, j=0, count;
    char str[25][25], temp[25];
    while (1) {
        gets(str[i]);
        if(str[i][0]=='0')
            break;
        i++;
        j++;
    }
    count=i;
    for(i=0; i<=count; i++)
        for(j=i+1; j<=count; j++) {
            if(strcmp(str[i], str[j]) > 0) {
                strcpy(temp, str[i]);
                strcpy(str[i], str[j]);
                strcpy(str[j], temp);
            }
         }
    for(i=0; i<count; i++)
        printf("%s ", str[i]);
    return 0;
}

but my code only sort words by comparing their ASCII code which cause all capital come first like
input : aa bb AA we WE 0
my output : AA WE aa bb we
but it should be:
output : AA aa bb WE we
I was thinking if I could do something like creating new ASCII code for chars but it also seem impossible. How could sort strings like this?

Burak
  • 2,251
  • 1
  • 16
  • 33
hanie
  • 1,863
  • 3
  • 9
  • 19

3 Answers3

1

You need a "new" strcmp() tailored to your specific needs:

enum /*untagged*/ { AbeforeB = -1, AequalsB = 0, AafterB = 1 };

int tailored_strcmp(const char *a, const char *b) {
    static char baseorder[] = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
    //if a or b is the empty string
    if (*a == 0) return AbeforeB;
    if (*b == 0) return AafterB;
    int lena = strlen(a);
    int lenb = strlen(b);
    char *pa = strchr(baseorder, *a);
    char *pb = strchr(baseorder, *b);
    if (pa == NULL) return lena < lenb ? AbeforeB : AafterB;
    if (pb == NULL) return lena < lenb ? AbeforeB : AafterB;
    if (pa == pb) {
        //need to check second letter
        if (a[1] == 0) return AbeforeB;
        if (b[1] == 0) return AafterB;
        char *ppa = strchr(baseorder, a[1]);
        char *ppb = strchr(baseorder, b[1]);
        if (ppa == NULL) return lena < lenb ? AbeforeB : AafterB;
        if (ppb == NULL) return lena < lenb ? AbeforeB : AafterB;
        if (ppa == ppb) return lena < lenb ? AbeforeB : AafterB;
        return ppa < ppb ? AbeforeB : AafterB;
    }
    return pa < pb ? AbeforeB : AafterB;
}

See version running at ideone, or version with improved adherence to requirements or version checking 1-length strings

pmg
  • 106,608
  • 13
  • 126
  • 198
  • 1
    That is a lot of complexity just to avoid stdlib functions... – wildplasser Dec 26 '19 at 13:34
  • @wildplasser: and I didn't even (YET!) implement all of the functionality required by OP!! OP wants `"Ggxx"` to sort before `"Ggdddddd"` becuase it's shorter and the first two characters are equal :) – pmg Dec 26 '19 at 13:38
  • Huh? Because it is shorter? I resign! – wildplasser Dec 26 '19 at 13:41
  • LOL Don't resign: it's a fun project to "reinvent the [square] wheel" :) – pmg Dec 26 '19 at 13:50
  • @pmg:it's working great but I have a question what can I do so the function return one char sooner than two, like char a being printed before aa? – hanie Dec 27 '19 at 08:19
  • I missed that, code above updated new fork in ideone. Just needed to check if `a[1]` or `b[1]` is the `'\0'` terminator. – pmg Dec 27 '19 at 09:26
  • @pmg: thanks for your help I actually wanted to write function by myself not using functions in libraries.I have a question ,is the function in library similar to this one? – hanie Dec 27 '19 at 20:09
  • Do you mean [`strcmp()`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/strcmp.html), [`strncmp()`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/strncmp.html) and/or [`str[n]casecmp()`](https://pubs.opengroup.org/onlinepubs/9699919799/functions/strcasecmp.html)? Yes, they're similar, and they're the ones used most often. Your requirement of `"ggx"` sorting before `"ggddd"` because it's shorter means there is no library function that does exactly what you want. You can use the existing functions for an approximation (like in @wildplasser answer) or do it from scratch. – pmg Dec 28 '19 at 08:04
1

#include<stdio.h>
#include<string.h>

int main(void){
   int i=0,j=0,count;
   char str[25][25],temp[25];

   for (i=0; i < 25; i++) {
       if (!fgets(str[i], sizeof str[i], stdin)) break;
       if(str[i][0]=='0')break;
       str[i][ strcspn(str[i], "\r\n")] = 0;
       }
   count=i;

   for(i=0;i<count;i++) {
      for(j=i+1;j<count;j++){
          int rc;

          rc = strcasecmp(str[i],str[j]);
          if (rc < 0) continue;
            /* strings are equal, except for case: do the normal compare */
          if (rc == 0) rc = strcmp(str[i],str[j]);
          if (rc < 0) continue;
          strcpy(temp,str[i]);
          strcpy(str[i],str[j]);
          strcpy(str[j],temp);
      }
   }
   for(i=0;i<count;i++) {
          printf("%s ",str[i]);
   }

   return 0;
}

UPDATE: New version, implementing the {first_two_characters, length, rest_of_the string} , using a rather complex compare function:


#include<stdio.h>
#include<string.h>

int myverysillystringcompare(char *ll, char *rr)
{
size_t siz_l, siz_r, siz;
int rc;

siz_l = strlen(ll);
siz_r = strlen(rr);

if (!siz_l || !siz_r) return siz_r - siz_l;

siz = (siz_l < siz_r) ? siz_l : siz_r;

if( siz > 2) siz = 2;
        // Compare the first two characters, (if any) case INSIGNIFICANT
rc = strncasecmp( ll, rr, siz );
if (rc) return rc; // They differ

        // Compare the first two characters, (if any) case SIGNIFICANT
rc = strncmp( ll, rr, siz );
if (rc) return rc; // they differ

        // Compare the lengths; the shortest wins
if (siz_l != siz_r) return siz_l - siz_r;

        // Compare the rest of the string, (if any) case INSIGNIFICANT
rc = strcasecmp(ll+siz, rr+siz);
if (rc) return rc; // they differ

        // Compare the rest of the string, (if any) case SIGNIFICANT
rc = strcmp(ll+siz, rr+siz);
return rc;
}

int main(void){
   int i=0,j=0,count;
   char str[25][25],temp[25];

   for (i=0; i < 25; i++) {
       if (!fgets(str[i], sizeof str[i], stdin)) break;
       if(str[i][0]=='0')break;
       str[i][ strcspn(str[i], "\r\n")] = 0;
       }
   count=i;

   for(i=0;i<count;i++) {
      for(j=i+1;j<count;j++){
         int rc;
         rc = myverysillystringcompare(str[i],str[j]);
         if (rc <= 0) continue;
         strcpy(temp,str[i]);
         strcpy(str[i],str[j]);
         strcpy(str[j],temp);
      }
   }
   for(i=0;i<count;i++) {
          printf("%s ",str[i]);
   }

   return 0;
}

wildplasser
  • 43,142
  • 8
  • 66
  • 109
  • well it was a great suggestion to use this function,thanks ,but when I run your code in visual stdio it won't work due not understanding strcasecmp function ,do you have in idea why? – hanie Dec 27 '19 at 20:19
  • Maybe because Microsoft hates C? (IOW: dump it. It is beyond repair) BTW: there was a thing 20 years ago: you had to include ``, too. Maybe they forgot? – wildplasser Dec 27 '19 at 22:14
-1

What is wrong here is that you directly use strcmp without changing the first letter. Even the last letter 'Z' comes before 'a' because of that. The following code changes the first letters to uppercase, then compares the strings, then changes the strings to their original state.

#include <stdio.h>
#include <string.h>
bool islower(char c) {
    return ('a' <= c) && (c <= 'z');
}
void makeupper(char& c) {
    c = c - 'a' + 'A';
}
void makelower(char& c) {
    c = c - 'A' + 'a';
}
int main() {
    int i=0, j=0, count;
    char str[25][25], temp[25];
    while (1) {
        scanf("%s", str[i]);
        if(str[i][0]=='0')
            break;
        i++;
        j++;
    }
    count=i;
    // beware for loop conditions
    for(i=0; i<count-1; i++)
        for(j=i+1; j<count; j++) {
            bool lower1 = islower(str[i][0]);
            bool lower2 = islower(str[j][0]);
            if (lower1)
                makeupper(str[i][0]);
            if (lower2)
                makeupper(str[j][0]);
            // swap the strings under these conditions
            if (((strcmp(str[i], str[j]) == 0) && lower1 && !lower2) // string are equal but first string's first letter was lowercase and second string's first letter was uppercase
                    || (strcmp(str[i], str[j]) > 0)) { // second string comes alphabetically first
                strcpy(temp, str[i]);
                strcpy(str[i], str[j]);
                strcpy(str[j], temp);
                // undo changes
                if(lower1)
                    makelower(str[j][0]);
                if(lower2)
                    makelower(str[i][0]);
            }
            else { // undo changes
                if (lower1)
                    makelower(str[i][0]);
                if (lower2)
                    makelower(str[j][0]);
            }
        }
    for(i=0; i<count; i++)
        printf("%s ", str[i]);
    return 0;
}

This code only fixes the first letter but it does not check for the second letter. You can do a similar trick by changing lower1 and lower2 as bool arrays, and the rest of the code accordingly.

Burak
  • 2,251
  • 1
  • 16
  • 33