【问题标题】:Case insensitive sorting of an array of strings字符串数组的大小写不敏感排序
【发布时间】:2015-10-27 22:46:43
【问题描述】:

基本上,我必须使用选择排序来对string[] 进行排序。我已经完成了这部分,但这就是我遇到的困难。

然而,排序应该不区分大小写,这样“antenna”就会出现在“Jupiter”之前。 ASCII 从大写到小写排序,所以没有办法只交换排序字符串的顺序吗?还是有更简单的解决方案?

void stringSort(string array[], int size) {
    int startScan, minIndex;
    string minValue;

    for(startScan = 0 ; startScan < (size - 1); startScan++) {
        minIndex = startScan;
        minValue = array[startScan];

        for (int index = startScan + 1; index < size; index++) {
            if (array[index] < minValue) {
                minValue = array[index];
                minIndex = index;
            }
        }
        array[minIndex] = array[startScan];
        array[startScan] = minValue;
    }
}

【问题讨论】:

  • @Benny Saxeman 什么是数组字符串?
  • @VladfromMoscowsorry,意思是字符串数组
  • @ElliottFrisch 它是一个类分配,所以不想惹麻烦

标签: c++ string sorting case-insensitive


【解决方案1】:

C++ 为您提供了sort,它带有一个比较函数。在您使用vector&lt;string&gt; 的情况下,您将比较两个字符串。如果第一个参数更小,比较函数应该返回true

对于我们的比较函数,我们希望在应用tolower 之后找到strings 之间的第一个不匹配字符。为此,我们可以使用mismatch,它在两个返回true的字符之间进行比较,只要它们相等:

const auto result = mismatch(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend(), [](const unsigned char lhs, const unsigned char rhs){return tolower(lhs) == tolower(rhs);});

要确定lhs 是否小于馈送到mismatchrhs,我们需要测试3 件事:

  1. strings 的长度是否不等
  2. string lhs 更短了
  3. 或者是来自lhs的第一个不匹配的char小于来自rhs的第一个不匹配的char

可以通过以下方式执行此评估:

result.second != rhs.cend() && (result.first == lhs.cend() || tolower(*result.first) < tolower(*result.second));

最终,我们希望将其包装在 lambda 中,并将其插入 sort 作为我们的比较器:

sort(foo.begin(), foo.end(), [](const unsigned char lhs, const unsigned char rhs){
    const auto result = mismatch(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend(), [](const unsigned char lhs, const unsigned char rhs){return tolower(lhs) == tolower(rhs);});

    return result.second != rhs.cend() && (result.first == lhs.cend() || tolower(*result.first) < tolower(*result.second));
});

这将正确排序vector&lt;string&gt; foo。你可以在这里看到一个活生生的例子:http://ideone.com/BVgyD2

编辑:

刚刚看到您的问题更新。您也可以将sortstring array[] 一起使用。你只需要这样称呼它:sort(array, std::next(array, size), ...

【讨论】:

  • 你有使用字符串数组[]排序的例子吗?
  • @BennySaxeman 是的,请参阅我的编辑。我会这样做:sort(array, array + length, [](const auto&amp; lhs, const auto&amp; rhs){ const auto result = mismatch(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend(), [](const auto&amp; lhs, const auto&amp; rhs){return tolower(lhs) == tolower(rhs);}); return result.second != rhs.cend() &amp;&amp; (result.first == lhs.cend() || tolower(*result.first) &lt; tolower(*result.second)); }); 抱歉上线了!注意前两个参数的变化。
  • 如果效率不重要(在这种情况下)将两个字符串转换为小写并通过operator&lt;比较结果会简单得多。
  • @Slava 是的,我认为这种算法是处理它的最有效方法。但是因为 sort 是 O(n log(n)) 它会做 log(n) 额外的转换,而不是创建一个小写字符串的临时数组。但是这个算法利用了mismatch短路的事实,所以它只在需要比较的字母上调用tolower。这将取决于您的输入集,但这可能具有最少数量的tolowers 并且它避免了临时的创建(除了被比较的两个字符。)
【解决方案2】:
#include <algorithm>
#include <vector>
#include <string>

using namespace std;    

void CaseInsensitiveSort(vector<string>& strs)
{
    sort(
        begin(strs),
        end(strs),
        [](const string& str1, const string& str2){
            return lexicographical_compare(
                begin(str1), end(str1),
                begin(str2), end(str2),
                [](const char& char1, const char& char2) {
                    return tolower(char1) < tolower(char2);
                }
            );
        }
    );
}

【讨论】:

  • 虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高​​答案的长期价值。
【解决方案3】:

我使用这个 lambda 函数对字符串向量进行排序:

std::sort(entries.begin(), entries.end(), [](const std::string& a, const std::string& b) -> bool {
        for (size_t c = 0; c < a.size() and c < b.size(); c++) {
            if (std::tolower(a[c]) != std::tolower(b[c]))
                return (std::tolower(a[c]) < std::tolower(b[c]));
        }
        return a.size() < b.size();
    });

【讨论】:

    【解决方案4】:

    使用不区分大小写的字符串比较函数,而不是 &lt; 运算符。

    C89/C99 提供strcoll(字符串整理),它进行区域感知字符串比较。它在 C++ 中以std::strcoll 的形式提供。在某些(大多数?)语言环境中,例如 en_CA.UTF-8、Aa(以及它们的所有重音形式)属于同一个等价类。我认为 strcoll 仅在整个字符串相等的情况下才在等价类中作为平局进行比较,这与不区分大小写的比较提供了非常相似的排序顺序。排序规则(至少在 GNU/Linux 上的英语语言环境中)会忽略某些字符(例如 [)。所以ls /usr/share | sort 给出的输出类似于

    acpi-support
    adduser
    ADM_scripts
    aglfn
    aisleriot
    

    我通过sort 进行管道传输,因为ls 进行自己的排序,这与sort 基于区域设置的排序不太一样。

    如果您想将一些用户输入的任意字符串排序为用户可以直接看到的顺序,那么通常需要进行区域感知的字符串比较。仅在大小写或重音上不同的字符串不会比较相等,因此如果您使用稳定的排序并依赖于区分大小写的字符串来比较相等,那么这将不起作用,但否则您会得到很好的结果。根据用例,比普通的不区分大小写的比较更好。

    FreeBSD's strcoll 对于 POSIX (ASCII) 以外的语言环境过去和现在可能仍然区分大小写。该论坛帖子表明,在大多数其他系统上,它不区分大小写。

    MSVC 为不区分大小写的排序规则提供_stricoll,这意味着其正常的strcoll 是区分大小写的。但是,这可能只是意味着不会发生在等价类中进行比较的回退。也许有人可以用 MSVC 测试以下示例。


    // strcoll.c: show that these strings sort in a different order, depending on locale
    #include <stdio.h>
    #include <locale.h>
    
    int main()
    {
        // TODO: try some strings containing characters like '[' that strcoll ignores completely.
        const char * s[] = { "FooBar - abc", "Foobar - bcd", "FooBar - cde" };
    #ifdef USE_LOCALE
        setlocale(LC_ALL, ""); // empty string means look at env vars
    #endif
        strcoll(s[0], s[1]);
        strcoll(s[0], s[2]);
        strcoll(s[1], s[2]);
        return 0;
    }
    

    gcc -DUSE_LOCALE -Og strcoll.c &amp;&amp; ltrace ./a.out 的输出(或运行 LANG=C ltrace a.out):

    __libc_start_main(0x400586, 1, ...
    setlocale(LC_ALL, "")                        = "en_CA.UTF-8"   # my env contains LANG=en_CA.UTF-8
    strcoll("FooBar - abc", "Foobar - bcd")      = -1
    strcoll("FooBar - abc", "FooBar - cde")      = -2
    strcoll("Foobar - bcd", "FooBar - cde")      = -1
      # the three strings are in order
    +++ exited (status 0) +++
    

    gcc -Og -UUSE_LOCALE strcoll.c &amp;&amp; ltrace ./a.out:

    __libc_start_main(0x400536, ...
    # no setlocale, so current locale is C
    strcoll("FooBar - abc", "Foobar - bcd")      = -32
    strcoll("FooBar - abc", "FooBar - cde")      = -2
    strcoll("Foobar - bcd", "FooBar - cde")      = 32   # s[1] should sort after s[2], so it's out of order
    +++ exited (status 0) +++
    

    POSIX.1-2001 提供strcasecmp。但是,POSIX 规范说,对于普通 ASCII 以外的语言环境,结果是“未指定的”,所以我不确定常见的实现是否正确处理 utf-8。

    this post for portability issues with strcasecmp, e.g. to Windows。有关进行不区分大小写字符串比较的其他 C++ 方法,请参阅该问题的其他答案。


    一旦有了不区分大小写的比较函数,就可以将它与其他排序算法一起使用,例如 C 标准库 qsort 或 c++ std::sort,而不是编写自己的 O( n^2) 选择排序。


    正如 b.buchhold 的回答所指出的,即时进行不区分大小写的比较可能比将所有内容都转换为小写一次并对索引数组进行排序要慢。每个字符串的小写版本需要多次。 std::strxfrm 将转换一个字符串,以便结果上的strcmp 将给出与原始字符串上的strcoll 相同的结果。

    【讨论】:

    • 你是说strcoll不区分大小写吗?因为我从来没有考虑过,但如果是这样的话......
    • @JonathanMee:我远不是语言环境专家。我只使用了C(POSIX ASCII)和en_CA.UTF-8 语言环境。我认为对于使用罗马字母进行不区分大小写的排序规则的语言环境,以及使用其非重音版本对重音字符进行排序,这是典型的。如果您想将一些用户输入的任意字符串排序为用户将直接看到的顺序,则通常需要区域感知字符串比较。其他用途(如作为数据结构的一部分)可能关心字节数,需要使用memcmp 或类似的。
    • strcoll 区分大小写的,per:en.cppreference.com/w/cpp/string/byte/strcoll “在等价类中,小写字符在大写对应之前排序。”在我的测试中,我发现这是真的。这还击倒了strxfrm,其行为类似于strcoll。只留下不幸的平台相关实现的答案:(
    • @JonathanMee:实际上,我认为在考虑等价类的所有成员时,对于相等的字符串,我认为大小写和重音或非重音(即在等价类中进行比较)只是作为一个平局发挥作用作为平等。这对于排序非常有用,但在测试相等性时可能会或可能不会是您想要的。我同意这个答案没有任何很好的具体建议,只是一些指向要检查的东西的指针,以及指向主要“c++ 不区分大小写的字符串比较”问题的链接。
    【解决方案5】:

    您可以在比较的每个字符上调用tolower。这可能是最简单但不是很好的解决方案,因为:

    • 您会多次查看每个 char,因此您会比必要更频繁地调用该方法
    • 您需要格外小心地处理宽字符 w.r.t 的编码(UTF8 等)

    您也可以用自己的函数替换比较器。 IE。会有一些地方可以比较stringone[i] &lt; stringtwo[j]charA &lt; charB 之类的内容。将其更改为 my_less_than(stringone[i], stringtwo[j]) 并实现您想要的确切顺序。

    另一种方法是将每个字符串转换为小写一次并创建一个对数组。然后你只根据小写值进行比较,但是你交换整对,这样你的最终字符串也将是正确的顺序。

    最后,您可以创建一个小写版本的数组并对其进行排序。每当你交换这一个中的两个元素时,你也交换了原始数组。

    请注意,所有这些建议仍然需要正确处理宽字符(如果您需要的话)

    【讨论】:

    • 或者在排序时添加一个间接层:按它们在输入数组中的索引string 对索引数组进行排序。然后撤消间接层以获得排序的字符串列表。这具有较低的交换开销,但可能会稍高的访问开销。
    【解决方案6】:

    这个解决方案比 Jonathan Mee 的解决方案更容易理解,而且效率很低,但出于教育目的可能没问题:

    std::string lowercase( std::string s )
    {
       std::transform( s.begin(), s.end(), s.begin(), ::tolower );
       return s;
    }
    
    std::sort( array, array + length, 
               []( const std::string &s1, const std::string &s2 ) { 
                   return lowercase( s1 ) < lowercase( s2 ); 
               } );
    

    如果你必须使用你的排序功能,你可以使用相同的方法:

        ....
        minValue = lowercase( array[startScan] );
    
        for (int index = startScan + 1; index < size; index++) {
            const std::string &tstr = lowercase( array[index] );
            if (tstr < minValue) {
                minValue = tstr;
                minIndex = index;
            }
        }
        ...
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-26
      • 2011-02-06
      • 1970-01-01
      • 2013-10-31
      • 2019-04-15
      • 2019-06-25
      • 2016-05-19
      相关资源
      最近更新 更多