【问题标题】:Sorting a string with std::sort so that capital letters come after lower case使用 std::sort 对字符串进行排序,以便大写字母排在小写字母之后
【发布时间】:2013-10-22 04:41:56
【问题描述】:

我想对向量进行排序,使大写字母跟在小写字母之后。如果我有类似的东西

This is a test
this is a test
Cats
cats
this thing

我希望输出是

cats
Cats
this is a test
This is a test
this thing

标准库排序会输出

Cats
This is a test
cats
this is a test
this thing

我想向 std::sort 传递一个谓词,以便它比较我作为参数传递的字符串的小写版本。

bool compare(std::string x, std::string y)
{
    return lowercase(x) < lowercase(y);
}

我尝试降低函数中的每个字符,然后进行比较,但没有奏效。我想通过其他方法将字符串转换为小写来测试这种方法。如何将字符串转换为小写?

编辑::

其实我发现了问题所在。这行得通。当我第一次编写函数时,我有 tolower(ref) 而不是 ref = tolower(ref) 没有重新分配给 ref 所以它什么也没做。

bool compare(std::string x, std::string y)
{
    for(auto &ref:x)
        ref = tolower(ref);
    for(auto &ref:y)
        ref = tolower(ref);
    return x < y;
}

编辑::

此代码实际上有时会先排序大写字母,有时会先排序大写字母,因此并不能完全解决问题。

【问题讨论】:

  • 我想您可以使用std::mismatch 来查找第一个不匹配的字符,然后使用std::islowerstd::tolower 确定哪个更少。
  • 不过,这不会按照您想要的方式进行排序。它将不分青红皂白地对大小写进行排序。最简单的解决方案可能是使用具有您想要的顺序的区域设置排序,例如:std::sort(v.begin(), v.end(), std::locale("en_US.UTF-8"));(您需要#include &lt;locale&gt;
  • 我认为你的更新会给你 Cat,cats,This is a test,this is a test,this thing See here
  • @P0W。是的,我去测试了几个不同的字符串,它按字母顺序排序,但大写字母有时在前面,有时在后面。
  • @Dochevsky:这是因为您刚刚告诉std::sort,小写和大写字母是相同的,因此第一个或第二个并不重要:) 我有一个关于你的例子,奇怪的是"this" "This" "this"(整圈!),所以我想知道你是否打算进行依赖于任意数量的字符/单词的比较,或者如果你只希望Tt 之后到达,在这种情况下,顺序应该是cats, Cats, this is a test, this thing, This is a test

标签: c++ string sorting


【解决方案1】:

执行此操作的常用方法是构建一个排序规则表。这只是一个表格,给出了每个字符的相对顺序。在您的情况下,您希望每个大写字母紧跟在相应的小写字母之后。

我们可以这样做:

class comp_char { 
    std::vector<int> collation_table;
public:
    comp_char() : collation_table(std::numeric_limits<unsigned char>::max()) {
        std::iota(collation_table.begin(), collation_table.end(), 0);

        for (int i = 0; i < 26; i++) {
            collation_table['a' + i] = i * 2;
            collation_table['A' + i] = i * 2 + 1;
        }
    }

    bool operator()(unsigned char a, unsigned char b) {
        return collation_table[a] < collation_table[b];
    }
};

目前,我忽略了字母与其他字符的相对顺序(可能是棘手的)问题。正如它所写的那样,其他所有内容都排在字母之前,但是很容易更改(例如)字母排在其他任何内容之前。不过,这两种方式可能都没有太大的区别——大多数人对 'a'

无论如何,一旦排序表构建并可用,您就想用它来比较字符串:

struct cmp_str {
    bool operator()(std::string const &a, std::string const &b) {
        comp_char cmp;
        size_t i = 0;
        while (a[i] == b[i] && i < a.size())
            ++i;
        return cmp(a[i], b[i]);
    }
};

...我们可以使用它来进行排序,如下所示:

int main(){
    std::vector<std::string> inputs {
        "This is a test",
        "this is a test",
        "Cats",
        "cats",
        "this thing"
    };

    std::sort(inputs.begin(), inputs.end(), cmp_str());
    std::copy(inputs.begin(), inputs.end(),
        std::ostream_iterator<std::string>(std::cout, "\n"));
}

目前,我只编写了整理表来处理基本的 US-ASCII 字母。对于实际使用,您通常希望在其相应的非重音等价物旁边放置带有重音符号的字母之类的东西。为此,您通常最终会预先构建表格以(部分)匹配诸如 Unicode 规范之类的事物应如何排序的内容。

请注意,此输出与原始问题所说的不太匹配,但我认为在这种情况下,问题有误。我看不出有什么方法可以产生这样的订单:

this is a test
This is a test
this thing

这在 在“t”之前都有“T”排序,这似乎没有意义(或者至少不适合词法排序,这是人们几乎总是想要字符串)。

【讨论】:

  • 我喜欢它,但如果我没记错的话,你错过了一个额外的比较器级别,因为传递给 std::sort 的比较器应该有两个字符串。
  • @P0W:链接中的输出不是 OP 期望的输出。
  • @BenjaminLindley 啊哈 :(
  • @BenjaminLindley:是的,在重新阅读问题后,我意识到我有点不对劲。我想我现在已经纠正了。
  • 我认为您犯的错误与我在原始答案中所犯的错误相同。一个简单的基于字符的字典比较是不够的。我认为 OP 正在寻找您可能在字典中找到的顺序,它将认为 tT 相同,除非完整单词的拼写完全相同,不同仅大写。
【解决方案2】:

最简单的解决方案是使用标准locale 对象提供的排序感知排序。

区域设置的 operator()(std::string, std::string) 正是区域设置的可识别比较运算符,因此您可以将其直接插入到对 std::sort 的调用中:

// Adjust to the locale you actually want to use
std::sort(strings.begin(), strings.end(), std::locale("en_US.UTF-8"));

ideone 上的示例

【讨论】:

    【解决方案3】:

    你的解决方案差不多了,如果字符串的小写版本相等,你只需要做一个特殊情况:

    std::string to_lower(std::string s)
    {
        for (auto & c : s)
            c = std::tolower(c);
        return s;
    }
    
    bool string_comp(std::string const & lhs, std::string const & rhs)
    {
    
        auto lhs_lower = to_lower(lhs);
        auto rhs_lower = to_lower(rhs);
        if (lhs_lower == rhs_lower)
            return rhs < lhs;
        return lhs_lower < rhs_lower;
    }
    

    这可以使用一些优化。不需要复制字符串。当然,您可以就地进行不区分大小写的比较。但这个功能在标准库中并不方便,所以我将把这个练习留给你。

    【讨论】:

    • 它给出same 结果yours here
    • 这可以通过将rhs &gt; lhs 更改为lhs &gt; rhs 来解决,但实际顺序就像不区分大小写的排序,然后是相邻相等元素的排序。
    • @chris:我相信它现在已经修复了,我之前完全偏离了标准。简单的字典比较是不够的。
    【解决方案4】:

    明确地说,我的目标是通常的字典类型比较,但如果字符串相同,则以某种方式使大写跟随小写。

    这需要两步比较:

    1. 在不区分大小写模式下比较字符串
    2. 如果在不区分大小写模式下两个字符串相等,我们需要区分大小写比较的反向结果(将大写放在首位)

    所以,比较器给出:

    class Comparator {
    public:
       bool operator()(std::string const& left, std::string const& right) {
           size_t const size = std::min(left.size(), right.size());
    
           // case-insensitive comparison
           for (size_t i = 0; i != size; ++i) {
               if (std::tolower(left[i]) < std::tolower(right[i])) { return true; }
           }
    
           if (left.size() != right.size()) { return size == left.size(); }
    
           // and now, case-sensitive (reversed)
           return right < left;
       }
    }; // class Comparator
    

    【讨论】:

      【解决方案5】:

      您需要一次比较一个字符,在第一个不同的字符处停止,然后首先根据大小写转换返回结果,否则返回原始字符:

      bool mylt(const std::string& a, const std::string& b) {
          int i=0, na=a.size(), nb=b.size();
          while (i<na && i<nb && a[i]==b[i]) i++;
          if (i==na || i==nb) return i<nb;
          char la=std::tolower(a[i]), lb=std::tolower(b[i]);
          return la<lb || (la==lb && a[i]<b[i]);
      }
      

      警告:未经测试的早餐代码

      【讨论】:

        【解决方案6】:

        要么使用locals 已经有你想要的排序,要么写一个逐字符的比较函数然后使用std::lexicographical_compare 把它变成一个字符串比较函数。

        我会先尝试locals,但如果这证明令人沮丧,那么字典并不可怕。

        要比较chqracters,请创建lower_case_letterunchanged_letter 中的两个tuples 或pairs,并在其上调用&lt;。这将首先按小写字母排序,然后如果失败则按原样排序。我忘记了大写和小写的排序顺序:但是如果顺序是倒序的,只需交换哪个小写字母与哪个大写字母配对,你就会颠倒顺序!

        【讨论】:

          猜你喜欢
          • 2012-11-18
          • 2019-07-10
          • 2016-03-06
          • 1970-01-01
          • 2018-06-14
          • 2018-03-16
          • 2013-02-01
          • 2017-11-21
          • 2020-08-24
          相关资源
          最近更新 更多