【问题标题】:Sort array alphabetically, upper letter always first按字母顺序排列数组,大写字母总是在前
【发布时间】:2017-06-23 18:27:56
【问题描述】:

我想按字母顺序对数组进行排序,但我希望总是先使用大写字母。我已经实现的是一个简单的排序,它不考虑字母的大小。要不要给它一个特殊的条件?

编辑:

这就是我想要实现的目标: AaaAdDcCFfgGhHI 应该这样排序:AAaaCcDdFfGgHhI

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

#define N 5

int compare(const void *w1, const void *w2);

int main(void) {
    char s1[N][15] = {
        { "azghtdffopAsAfp" },
        { "poiuyjklhgADHTp" },
        { "hgjkFfGgBnVUuKk" },
        { "lokijuhygtfrdek" },
        { "AaaAdDcCFfgGhHI" } };

    char *wsk;
    int i, j;
    wsk = s1;

    for (i = 0; i < N; i++) {
        for (j = 0; j < 15; j++) {
            printf("%c", s1[i][j]);
        }
        printf("\n");
    }
    for (i = 0; i < N; i++)
        qsort(s1[i], 15, sizeof(char), compare);

    printf("\n");
    for (i = 0; i < N; i++) {
        for (j = 0; j < 15; j++) {
            printf("%c", s1[i][j]);
        }
        printf("\n");
    }
    return 0;
}

int compare(const void *w1, const void *w2) {
    char *a1 = w1;
    char *a2 = w2;

    while (*a1 && *a2) {
        register r = tolower(*a1) - tolower(*a2);
        if (r)
            return r;
        ++a1;
        ++a2;

    }
    return tolower(*a1) - tolower(*a2);
}

【问题讨论】:

  • 你想如何排序'大写字母总是在前':“A”,“a”,“B”,“b”或“A”,“B”,“a”, “b”?
  • @IngoLeonhardt 是的,这正是我想要实现的目标
  • yes 不是“选项 1 或选项 2”的有效答案 :-)
  • @IngoLeonhardt 啊,抱歉没有第一眼看到。我的意思是第一个选项
  • @Buszman 显示你要达到的结果。

标签: c arrays sorting


【解决方案1】:

我们应该首先解决您代码中的一些问题。首先,您需要添加#include &lt;ctype.h&gt;。您已经声明了char *wsk;,并无缘无故地分配了wsk = s1;。更重要的是,这些是不兼容的类型,因为 s1 是一个指向 15 个 chars 的数组的指针。更重要的是,s1应该是 16 个chars 的数组!您忘记在字符数组中包含 '\0' 终止符的空间。所以,s1 的声明需要变成:

char s1[N][16] = { { "azghtdffopAsAfp" },
                   { "poiuyjklhgADHTp" },
                   { "hgjkFfGgBnVUuKk" },
                   { "lokijuhygtfrdek" },
                   { "AaaAdDcCFfgGhHI" } };

可以改进对qsort() 的调用。与其使用幻数15,不如将​​字符串的长度存储在一个变量中。此外,sizeof(char) 始终为 1:

for (i = 0; i<N; i++) {
    size_t s1_len = strlen(s1[i]);
    qsort(s1[i], s1_len, 1, compare);
}

compare()函数本身中,需要改为:

const unsigned char *a1 = w1;
const unsigned char *a2 = w2;

转换为const 将避免有关丢弃const 限定符的警告。转换为 unsigned 避免了未定义的行为,因为 ctype.h 函数需要一个可表示为 unsigned char 或等于 EOFint 参数。此外,register 是一个类型限定符:它需要限定一个类型。所以你需要register int r = ...

但是您的函数还依赖于标准不保证的执行字符集编码的属性:字母按字母顺序编码。您已经通过使用tolower() 函数向可移植性迈出了第一步,而不是添加幻数来更改字符的大小写。通过使用isupper()islower() 来测试字符的大小写,并通过使用strcoll() 来测试字符的顺序,我们可以实现接近最大可移植性的东西。 strcoll() 如果适用于区域设置,会自动将大写字母排在小写字母之前,但似乎所有大写字母都在小写字母之前,因此需要进行显式测试以排序两个在转换为小写后比较相等的字符。要克服的一个障碍是strcoll() 比较 strings 的词法顺序。要使用它来比较字符,我们可以部署复合文字:

register int r = strcoll((const char[]){tolower(*c1), '\0'},
                         (const char[]){tolower(*c2), '\0'});

compare() 函数中有一个循环,对我来说没有意义。 compare() 函数应该只比较两个 chars;不需要循环任何东西,所以我删除了这个循环。

我编写了一个新的compare() 函数,它使用strcoll() 和复合文字来可移植地比较两个chars。如果两个字符比较相等(直到大小写),则检查它们的大小写。如果大小写不同,则将大写字符放在小写字符之前。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>                 // added for strlen() and strcoll()
#include <ctype.h>                  // must add this

#define N 5

int compare(const void *w1, const void *w2);

int main(void) {
    /* Inner dimension should be 16 to include '\0' */
    char s1[N][16] = { { "azghtdffopAsAfp" },
                       { "poiuyjklhgADHTp" },
                       { "hgjkFfGgBnVUuKk" },
                       { "lokijuhygtfrdek" },
                       { "AaaAdDcCFfgGhHI" } };

//    char *wsk;                    // don't need this
    int i, j;
//    wsk = s1;                     // don't need this, also incompatible

    for (i = 0; i<N; i++) {
        for (j = 0; j<15; j++) {
            printf("%c", s1[i][j]);
        }
        printf("\n");
    }
    for (i = 0; i<N; i++) {
        size_t s1_len = strlen(s1[i]);
        qsort(s1[i], s1_len, 1, compare);  // improved call to qsort()
    }

    printf("\n");
    for (i = 0; i<N; i++) {
        for (j = 0; j<15; j++) {
            printf("%c", s1[i][j]);
        }
        printf("\n");
    }
    return 0;
}

int compare(const void *a1, const void *a2) {
    const unsigned char *c1 = a1;
    const unsigned char *c2 = a2;

    register int r = strcoll((const char[]){tolower(*c1), '\0'},
                             (const char[]){tolower(*c2), '\0'});
    if (r == 0) {
        if (isupper(*c1) && islower(*c2)) {
            r = -1;
        } else if (islower(*c1) && isupper(*c2)) {
            r = 1;
        }
    }

    return r;
}

程序输出:

azghtdffopAsAfp
poiuyjklhgADHTp
hgjkFfGgBnVUuKk
lokijuhygtfrdek
AaaAdDcCFfgGhHI

AAadfffghoppstz
ADgHhijkloppTuy
BFfGgghjKkknUuV
defghijkklortuy
AAaaCcDdFfGgHhI

【讨论】:

  • 迂腐笔记:1)while (*a1) 就足够了。 2) 当出现负值char 时,使用unsignedconst unsigned char *a1 = w1; 匹配strcmp()tolower(*a1) - tolower(*a2) 可能会在具有宽 char 的罕见平台上溢出。
  • @chux-- 感谢您的注释。 1)进一步检查,在我看来,循环是完全多余的。 2)这是有道理的;如果charint 一样宽,似乎即使使用演员表也会发生溢出,是吗? IAC,我已经大大扩展了我的答案,因为我在 OP 的代码中发现了其他问题。我没有使用减法,而是将compare() 改写为使用strcoll()。我相信这消除了转换为unsigned 的需要,并确保了最大的可移植性(或接近它)。不过,如果有任何进一步的意见,我将不胜感激。
  • @Buszman- 我已经大大扩展了我的答案;您可能有兴趣阅读它以找到更便携的解决方案。
  • 顺便说一句:注意到不需要循环的一个很好的改进。 “相信这消除了强制转换为无符号的需要”->一般来说没有,但在 OP 的有限情况下是的。对于const char *c1 = a1;tolower(*c1) 是 UB,然后 *c1 是负数“对于本子条款中的所有函数,每个字符都应被解释为具有 unsigned char 类型”。 §7.23.1 3
  • @chux-- 无意打扰,但我认为tolower() 的相关部分是 §7.4 1: "...value... 应表示为 unsigned char..." 我认为您指的是 §7.24.1 3,这是关于字符串处理函数的。这与strcoll() 相关;不过,在将字符(以字符串的形式)提供给 strcoll() 之前,不需要强制转换为 unsigned char,对吗?即“可表示为”与“解释为”。
【解决方案2】:

非常不清楚是要对每行中的所有字符进行排序,还是要对数组中的字符串数组进行排序(或两者都排序)。两者都可以实现,但两者对compare 的要求略有不同。

假设您想要对数组的数组进行排序(如果将它们设为字符串会更容易),您会期望输出如下:

$ ./bin/sortcapsfirst
azghtdffopAsAfp
poiuyjklhgADHTp
hgjkFfGgBnVUuKk
lokijuhygtfrdek
AaaAdDcCFfgGhHI

AaaAdDcCFfgGhHI
azghtdffopAsAfp
hgjkFfGgBnVUuKk
lokijuhygtfrdek
poiuyjklhgADHTp

否则,您需要先对每一行进行排序(先对每个大写字母排序,再对相同的小写字母排序),然后再对数组进行排序。这将导致输出如下:

$ ./bin/sortcapsfirst
azghtdffopAsAfp
poiuyjklhgADHTp
hgjkFfGgBnVUuKk
lokijuhygtfrdek
AaaAdDcCFfgGhHI

AAaaCcDdFfGgHhI
AAadfffghoppstz
ADgHhijkloppTuy
BFfGgghjKkknUuV
defghijkklortuy

你可能会让自己变得比需要的更难。通常,默认情况下,LOCALE 的自然字符串排序将首先对 Caps 进行排序。在对数组s1 排序的情况下,对行进行排序以便大写在小写之前排序,您只需将列数设置为16(为 nul-terminating 字符提供空间) 然后在您的compare 例程中调用strcmp,例如:

int compare(const void *w1, const void *w2) {

    const char *a1 = w1;
    const char *a2 = w2;

    return strcmp (a1, a2);
}

将它们放在一个示例中,并在遇到 nul-terminating 字符时正确终止每个 j 循环,您可以这样做:

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

#define N 5
#define R 16

int compare(const void *w1, const void *w2);

int main(void) {

    char s1[][R] = {{ "azghtdffopAsAfp" },
                    { "poiuyjklhgADHTp" },
                    { "hgjkFfGgBnVUuKk" },
                    { "lokijuhygtfrdek" },
                    { "AaaAdDcCFfgGhHI" }};
    int i, j;

    for (i = 0; i<N; i++) {
        for (j = 0; s1[i][j] && j<R; j++) {
            putchar(s1[i][j]);  /* don't use printf to print a single-char */
        }
        putchar('\n');
    }

    qsort (s1, N, sizeof *s1, compare);  /* sort array (rows) */

    putchar('\n');
    for (i = 0; i<N; i++) {
        for (j = 0; s1[i][j] && j<R; j++) {
            putchar(s1[i][j]);
        }
        putchar('\n');
    }
    return 0;
}

int compare(const void *w1, const void *w2) {

    const char *a1 = w1;
    const char *a2 = w2;

    return strcmp (a1, a2);
}

对于第二种情况,您将每行中的大写字母排在等效的小写字母之前,然后对数组进行排序,您只需添加第二个 qsort 比较函数并按原样调用它,然后再调用 @987654333 @ 在整个数组上。例如(将每个大写字母排在对应的小写字母之前):

int compare (const void *w1, const void *w2) {
    const char *a1 = w1;
    const char *a2 = w2;

    while (*a1 && *a2)
    {
        int r = tolower(*a1) - tolower(*a2);
        if (!r) {
            if (*a1 - *a2)
                return *a1 - *a2 > 0 ? 1 : -1;
        }
        else
            break;
        ++a1;
        ++a2;
    }
    // return *a1 - *a2; /* to sort ALLcapsfirst */
    return tolower(*a1) - tolower(*a2);
}

然后像第一个示例中那样调用qsort 对数组中的行进行排序:

int comparestr (const void *w1, const void *w2) {

    const char *a1 = w1;
    const char *a2 = w2;

    return strcmp (a1, a2);
}

将它们放在同一个示例中(使用 nul-terminated 行),您可以这样做:

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

#define N 5
#define R 16

int compare (const void *w1, const void *w2);
int comparestr (const void *w1, const void *w2);

int main (void) {

    char s1[][R] = {{"azghtdffopAsAfp"},
                    {"poiuyjklhgADHTp"},
                    {"hgjkFfGgBnVUuKk"},
                    {"lokijuhygtfrdek"},
                    {"AaaAdDcCFfgGhHI"}};
    int i, j;

    for (i = 0; i < N; i++) {
        for (j = 0; s1[i][j] && j < R; j++)
            putchar(s1[i][j]);
        putchar('\n');
    }

    for (i = 0; i < N; i++)                     /* sort arrays */
        qsort (s1[i], R - 1, sizeof *(s1[i]), compare);
    qsort (s1, N, sizeof *s1, comparestr);      /* sort array */

    putchar('\n');
    for (i = 0; i < N; i++) {
        for (j = 0; s1[i][j] && j < R; j++)
            putchar(s1[i][j]);
        putchar('\n');
    }
    return 0;
}

int compare (const void *w1, const void *w2)
{
    const char *a1 = w1;
    const char *a2 = w2;

    while (*a1 && *a2) {
        int r = tolower (*a1) - tolower (*a2);
        if (!r) {
            if (*a1 - *a2)
                return *a1 - *a2 > 0 ? 1 : -1;
        } else
            break;
        ++a1;
        ++a2;

    }
    // return *a1 - *a2; /* to sort ALLcapsfirst */
    return tolower (*a1) - tolower (*a2);
}

int comparestr (const void *w1, const void *w2)
{
    const char *a1 = w1;
    const char *a2 = w2;

    return strcmp (a1, a2);
}

最后,如上所述,如果您想对 ALLCapsfirst 进行排序,则只需返回 *a1 - *a2 之间的差而不是 tolower (*a1) - tolower (*a2)。例如使用return *a1 - *a2; 排序将是:

AACDFGHIaacdfgh
AAadfffghoppstz
ADHTghijkloppuy
BFGKUVfgghjkknu
defghijkklortuy

把事情看一遍。我可能完全误解了你的目标。如果是这样,请留言,我可以进一步提供帮助。

【讨论】:

  • 我没有看到comparestr() 函数在哪里使用。我认为这是为了在每个字符串的字符按顺序排列之后对字符串进行排序,但您也可以使用 compare()。有趣:我曾认为依靠语言环境在小写之前对大写进行排序会起作用,但这似乎导致 all 大写在所有小写之前,所以我恢复了我的答案。我之所以提到这一点,是因为事实上,由于compare(),此代码按OP 的预期工作,但如果将comparestr() 用于第二次排序,则所有大写字符串都在非大写字符串之前。
  • 不,没有技巧,这只是我的疏忽——很好。谢谢:)
【解决方案3】:

不是比较小写值,而是检查值 ASCII 值。在表中,大写字母在前,然后是小写字母: http://www.asciitable.com/

更新:如果您需要更多独立于平台和字符集的代码,只需添加一个额外的if,并使用isupper() 和/或islower() 检查字母大小写:

【讨论】:

  • 假设 ASCII 不可移植。 C 标准对执行字符集编码几乎没有限制。
  • 是的,但我不想要 ABCDabbcd 而想要 AaBbCcDd
【解决方案4】:

如果您希望对每个字符进行大写小写区分,那么您将排序为“A”、“Aa”、“AB”、“aa”、“B”、“b”,比较可以看像这样

int compare(const void *w1, const void *w2) {
    char *a1 = w1;
    char *a2 = w2;

    while (*a1 && *a2)
    {   
        register r = tolower(*a1) - tolower(*a2);
        if (r)
            return r;
        // this is the new part
        else if( isupper( *a1 ) && !isupper( *a2 ) ) {
            // w1 < w2
            return -1;
        } else if( !isupper( *a1 ) && isupper( *a2 ) ) {
            // w1 > w2
            return 1;
        }

        ++a1;
        ++a2;

    }
    return tolower(*a1) - tolower(*a2);

}

如果您希望“aa”在“AB”之前排序,它可能看起来像:

int compare(const void *w1, const void *w2) {
    char *a1 = w1;
    char *a2 = w2;
    register r;
    int caseDifference = 0;

    while (*a1 && *a2)
    {   
        r = tolower(*a1) - tolower(*a2);
        if (r)
            return r;
        // this is the new part
        else if( caseDifference == 0 && ( isupper( *a1 ) && !isupper( *a2 ) ) ) {
            // w1 < w2
            caseDifference = -1;
        } else if( caseDifference == 0 && ( !isupper( *a1 ) && isupper( *a2 ) ) ) {
            // w1 > w2
            caseDifference = 1;
        }

        ++a1;
        ++a2;

    }
    r = tolower(*a1) - tolower(*a2);
    if( r != 0 )
        return r;
    else
        return caseDifference;
}

【讨论】:

    【解决方案5】:

    您的比较函数不正确:它比较多个字符,而不仅仅是参数指向的字符。

    如果你可以假设 ASCII,这里有一个更简单的比较函数来解决这个问题:

    int compare(const void *w1, const void *w2) {
        int c1 = *(const unsigned char *)w1;
        int c2 = *(const unsigned char *)w2;
        int l1 = tolower(c1);
        int l2 = tolower(c2);
    
        /* sort first by alphabetical character, then by case */
        return l1 != l2 ? l1 - l2 : c1 - c2;
    }
    

    还要注意main() 函数也可以简化:

    #include <stdio.h>
    #include <stdlib.h>
    
    #define N 5
    
    int compare(const void *w1, const void *w2);
    
    int main(void) {
        char s1[N][15] = {
            { "azghtdffopAsAfp" },
            { "poiuyjklhgADHTp" },
            { "hgjkFfGgBnVUuKk" },
            { "lokijuhygtfrdek" },
            { "AaaAdDcCFfgGhHI" } };
    
        for (int i = 0; i < N; i++) {
            printf("%.15s\n", s1[i]);
        }
        for (int i = 0; i < N; i++) {
            qsort(s1[i], 15, sizeof(char), compare);
        }
    
        printf("\n");
        for (int i = 0; i < N; i++) {
            printf("%.15s\n", s1[i]);
        }
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-12-14
      • 2021-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-29
      相关资源
      最近更新 更多