【问题标题】:Lexicographical sorting for non-ascii characters非ASCII字符的字典排序
【发布时间】:2020-03-12 04:18:20
【问题描述】:

我已经通过以下代码对 ascii 字符进行了字典排序:

std::ifstream infile;
std::string line, new_line;
std::vector<std::string> v;
while(std::getline(infile, line))
            {
                // If line is empty, ignore it
                if(line.empty())
                    continue;
                new_line = line + "\n";
                // Line contains string of length > 0 then save it in vector
                if(new_line.size() > 0)
                    v.push_back(new_line);
            }   
sort(v.begin(), v.end());

结果应该是: 一种 啊哈 abyutrw bb 贝赫尔 心电图 切特鲁 ....

但我不知道如何按如下顺序对 ascii 和非 ascii 字符进行字典排序:a A À Á Ã brg Baq ckrwg CkfgF d Dgrn... 请告诉我如何编写代码它。谢谢!

【问题讨论】:

  • 首先您必须决定如何对它们进行排序。 ????‍♀️按字典顺序出现在☃之前还是之后?编写一个函数来执行你的决定。
  • @RaymondChen 我想让 ascii 出现在非 ascii 字符之前。非 ascii 字符应遵循规则,例如:`A, 'A, ^A, ~A...
  • 一般来说,std::map 可能是您的解决方案(将字符映射到您想要的订单索引)。然后std::sort 可以与自定义谓词一起使用,考虑到订单索引进行比较。然而,“`A”不是一个字符,这是两个。还是您的意思是“À”?请edit您的问题澄清。 (给出一个您喜欢的顺序示例,涉及非 ASCII。)
  • 如果您想根据某个特定的语言环境进行排序,那么您应该使用该语言环境进行比较。您需要了解编码、区域设置(语言)和操作系统。默认区域设置“C”通常仅适用于您想要为内部目的对数据进行排序(查找数据)但不按用户自己语言的字母顺序向用户显示数据时,除非数据仅限于 ascii 字符(通常是电子邮件、url 、邮政编码、某些编程语言中的标识符等仅使用 ascii 字符)

标签: c++ sorting non-ascii-characters lexicographic


【解决方案1】:

OP 没有,但我觉得值得一提:说到非 ASCII 字符,也应该考虑编码。

The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)

À、Á 和 Â 等字符不是 7 bit ASCII 的一部分,而是在各种 8 位编码中考虑,例如Windows 1252。因此,不允许某个字符(不是 ASCII 的一部分)在任何编码中具有相同的代码点(即数字)。 (大多数字符在大多数编码中都没有数字。)

但是,Unicode 提供了一个唯一的编码表,其中包含任何其他编码的所有字符(我相信)。有一些实现

  • UTF-8,其中代码点由 1 个或多个 8 位值表示(使用 char 存储)
  • UTF-16,其中代码点用 1 个或 2 个 16 位值表示(使用 std::char16_twchar_t 存储)
  • UTF-32,其中代码点用 1 个 32 位值表示(使用 std::char32_t 存储,或者,如果有足够的大小,则可能是 wchar_t)。

关于wchar_t的大小:Character types

话虽如此,我在示例中使用了wchar_tstd::wstring 以使变音符号区域设置和平台的使用独立。


std::sort() 中用于对一系列T 元素进行排序的顺序默认使用
bool &lt; operator(const T&amp;, const T&amp;) 定义&lt; 运算符T
但是,有一些 std::sort() 的风格可以改为定义自定义谓词。

自定义谓词必须匹配签名并且必须提供strict weak ordering relation

因此,我建议使用 std::map 将字符映射到导致预期顺序的索引。

这是我在示例中使用的谓词:

  // sort words
  auto charIndex = [&mapChars](wchar_t chr)
  {
    const CharMap::const_iterator iter = mapChars.find(chr);
    return iter != mapChars.end()
      ? iter->second
      : (CharMap::mapped_type)mapChars.size();
  };

  auto pred
    = [&mapChars, &charIndex](const std::wstring &word1, const std::wstring &word2)
  {
    const size_t len = std::min(word1.size(), word2.size());
    // + 1 to include zero terminator
    for (size_t i = 0; i < len; ++i) {
      const wchar_t chr1 = word1[i], chr2 = word2[i];
      const unsigned i1 = charIndex(chr1), i2 = charIndex(chr2);
      if (i1 != i2) return i1 < i2;
    }
    return word1.size() < word2.size();
  };

  std::sort(words.begin(), words.end(), pred);

从下到上:

  1. std::sort(words.begin(), words.end(), pred); 使用第三个参数调用,该参数为我的自定义订单提供谓词 pred
  2. lambda pred(),逐个字符比较两个std::wstrings。 因此,比较是使用std::map mapChars 完成的,它将wchar_t 映射到unsigned,即按我的顺序将一个字符与其排名。
  3. mapChars 仅存储所有字符值的选择。因此,在mapChars 中可能找不到任务中的角色。为了处理这个问题,我们使用了一个辅助 lambda charIndex(),它在这种情况下返回 mapChars.size()——它被授予高于所有出现的索引。

CharMap 类型只是一个typedef

typedef std::map<wchar_t, unsigned> CharMap;

要初始化CharMap,使用了一个函数:

CharMap makeCharMap(const wchar_t *table[], size_t size)
{
  CharMap mapChars;
  unsigned rank = 0;
  for (const wchar_t **chars = table; chars != table + size; ++chars) {
    for (const wchar_t *chr = *chars; *chr; ++chr) mapChars[*chr] = rank;
    ++rank;
  }
  return mapChars;
}

它必须使用一个字符串数组来调用,其中包含按预期顺序排列的所有字符组:

const wchar_t *table[] = {
  L"aA", L"äÄ", L"bB", L"cC", L"dD", L"eE", L"fF", L"gG", L"hH", L"iI", L"jJ", L"kK", L"lL", L"mM", L"nN",
  L"oO", L"öÖ", L"pP", L"qQ", L"rR", L"sS", L"tT", L"uU", L"üÜ", L"vV", L"wW", L"xX", L"yY", L"zZ"
};

完整示例:

#include <string>
#include <sstream>
#include <vector>

static const wchar_t *table[] = {
  L"aA", L"äÄ", L"bB", L"cC", L"dD", L"eE", L"fF", L"gG", L"hH", L"iI", L"jJ", L"kK", L"lL", L"mM", L"nN",
  L"oO", L"öÖ", L"pP", L"qQ", L"rR", L"sS", L"tT", L"uU", L"üÜ", L"vV", L"wW", L"xX", L"yY", L"zZ"
};

static const wchar_t *tableGerman[] = {
  L"aAäÄ", L"bB", L"cC", L"dD", L"eE", L"fF", L"gG", L"hH", L"iI", L"jJ", L"kK", L"lL", L"mM", L"nN",
  L"oOöÖ", L"pP", L"qQ", L"rR", L"sS", L"tT", L"uUüÜ", L"vV", L"wW", L"xX", L"yY", L"zZ"
};

typedef std::map<wchar_t, unsigned> CharMap;

// fill a look-up table to map characters to the corresponding rank
CharMap makeCharMap(const wchar_t *table[], size_t size)
{
  CharMap mapChars;
  unsigned rank = 0;
  for (const wchar_t **chars = table; chars != table + size; ++chars) {
    for (const wchar_t *chr = *chars; *chr; ++chr) mapChars[*chr] = rank;
    ++rank;
  }
  return mapChars;
}

// conversion to UTF-8 found in https://stackoverflow.com/a/7561991/7478597
// needed to print to console
// Please, note: std::codecvt_utf8() is deprecated in C++17. :-(
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;

// collect words and sort accoring to table
void printWordsSorted(
  const std::wstring &text, const wchar_t *table[], const size_t size)
{
  // make look-up table
  const CharMap mapChars = makeCharMap(table, size);
  // strip punctuation and other noise
  std::wstring textClean;
  for (const wchar_t chr : text) {
    if (chr == ' ' || mapChars.find(chr) != mapChars.end()) {
      textClean += chr;
    }
  }
  // fill word list with sample text
  std::vector<std::wstring> words;
  for (std::wistringstream in(textClean);;) {
    std::wstring word;
    if (!(in >> word)) break; // bail out
    // store word
    words.push_back(word);
  }
  // sort words
  auto charIndex = [&mapChars](wchar_t chr)
  {
    const CharMap::const_iterator iter = mapChars.find(chr);
    return iter != mapChars.end()
      ? iter->second
      : (CharMap::mapped_type)mapChars.size();
  };
  auto pred
    = [&mapChars, &charIndex](const std::wstring &word1, const std::wstring &word2)
  {
    const size_t len = std::min(word1.size(), word2.size());
    // + 1 to include zero terminator
    for (size_t i = 0; i < len; ++i) {
      const wchar_t chr1 = word1[i], chr2 = word2[i];
      const unsigned i1 = charIndex(chr1), i2 = charIndex(chr2);
      if (i1 != i2) return i1 < i2;
    }
    return word1.size() < word2.size();
  };
  std::sort(words.begin(), words.end(), pred);
  // remove duplicates
  std::vector<std::wstring>::iterator last = std::unique(words.begin(), words.end());
  words.erase(last, words.end());
  // print result
  for (const std::wstring &word : words) {
    std::cout << utf8_conv.to_bytes(word) << '\n';
  }
}

template<typename T, size_t N>
size_t size(const T (&arr)[N]) { return sizeof arr / sizeof *arr; }

int main()
{
  // a sample string
  std::wstring sampleText
    = L"In the German language the ä (a umlaut), ö (o umlaut) and ü (u umlaut)"
      L" have the same lexicographical rank as their counterparts a, o, and u.\n";
  std::cout << "Sample text:\n"
    << utf8_conv.to_bytes(sampleText) << '\n';
  // sort like requested by OP
  std::cout << "Words of text sorted as requested by OP:\n";
  printWordsSorted(sampleText, table, size(table));
  // sort like correct in German
  std::cout << "Words of text sorted as usual in German language:\n";
  printWordsSorted(sampleText, tableGerman, size(tableGerman));
}

输出:

Words of text sorted as requested by OP:
a
and
as
ä
counterparts
German
have
In
language
lexicographical
o
ö
rank
same
the
their
u
umlaut
ü
Words of text sorted as usual in German language:
ä
a
and
as
counterparts
German
have
In
language
lexicographical
o
ö
rank
same
the
their
u
ü
umlaut

Live Demo on coliru

注意:

我的初衷是用std::wcout做输出。这对ä、ö、ü 不起作用。因此,我查找了simple way to convert wstrings to UTF-8。我已经知道 coliru 支持 UTF-8。


@Phil1970 提醒我,我忘了提点别的:

字符串的排序(根据“人类字典”顺序)通常由std::locale 提供。 std::collate 提供依赖于语言环境的字符串字典顺序。

语言环境起着重要作用,因为字符顺序可能因不同的语言环境而异。 std::collate 文档。有一个很好的例子:

Default locale collation order: Zebra ar förnamn zebra ängel år ögrupp
English locale collation order: ängel ar år förnamn ögrupp zebra Zebra
Swedish locale collation order: ar förnamn zebra Zebra år ängel ögrupp

UTF-16 ⇔ UTF-32 ⇔ UTF-8 的转换可以仅通过位运算来实现。对于任何其他编码的转换(ASCII 除外,它是 Unicode 的一个子集),我会推荐一个库,例如libiconv.

【讨论】:

  • 说真的,你不想定义自己的排序函数!它不能处理所有语言,并且很容易忘记一些鲜为人知的规则或字符。人们几乎应该总是喜欢图书馆中的一种。例如。 how want 应该如何处理 oe, œ, oé, ôé, ôe, oè 等。例如在法语中,没有变音符号的比较是向前进行的,然后是变音符号的反向比较。此外,随着时间的推移,规则可能会得到改进(例如,Windows 文件资源管理器中的排序现在知道在 picture 10 之前对 picture 9 等数字进行热排序)。否则,该答案中有很多有用的想法。
  • @Phil1970 你是对的。我忘了我也想提到std::locale::collate。数字排序(例如在资源管理器中)总是让我很开心。其实我不需要这个。如果我想对文件进行排序,我会给它相应的。名称(以 0 开头)。 ;-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-19
  • 2012-11-02
  • 1970-01-01
  • 2011-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多