如何使用任意语言环境比较“basic_string”

2023-12-02

我重新发布了今天早些时候提交的问题,但现在我引用了一个具体的例子来回应我收到的反馈。原问题可以找到here(请注意,这不是家庭作业):

我只是想确定 C++ 是否无法执行(有效的)不区分大小写比较一个basic_string对象也包含任意任意的因素locale目的。例如,似乎不可能编写如下所示的高效函数:

bool AreStringsEqualIgnoreCase(const string &str1, const string &str2, const locale &loc);

根据我目前的理解(但有人可以证实这一点),这个函数has呼叫两者ctype::toupper() and collate::compare()对于给定的locale(一如既往地使用提取use_facet())。然而,因为collate::compare()特别需要 4 个指针参数,您需要为需要比较的每个字符传递这 4 个参数(在第一次调用ctype::toupper()),或者,首先将两个字符串转换为大写,然后进行一次调用collate::compare().

第一种方法显然效率低下(为每个测试的字符传递 4 个指针),第二种方法要求您将两个字符串全部转换为大写(需要分配内存以及不必要地将两个字符串复制/转换为大写)。我对此是否正确,即,不可能有效地做到这一点(因为没有办法解决)collate::compare()).


试图以一致的方式处理世界上所有的书写系统的小烦恼之一是,实际上你认为你所了解的关于字符的知识实际上都是正确的。这使得执行“不区分大小写的比较”之类的事情变得很棘手。事实上,进行任何形式的区域设置比较都是很棘手的,而且不区分大小写也很棘手。

不过,在一些限制下,这是可以实现的。所需的算法可以使用正常的编程实践(以及一些静态数据的预计算)来“有效”地实现,但它不能像不正确的算法那样有效地实现。通常可以牺牲正确性来换取速度,但结果并不令人愉快。不正确但快速的语言环境实现可能会吸引那些语言环境正确实现的人,但对于语言环境产生意外结果的部分受众来说显然不能令人满意。

字典顺序对人类不起作用

对于具有大小写的语言,大多数语言环境(“C”语言环境除外)已经以预期的方式处理字母大小写,即仅在考虑所有其他差异后才使用大小写差异。也就是说,如果单词列表按照区域设置的排序顺序进行排序,则列表中仅大小写不同的单词将是连续的。大写单词位于小写单词之前还是之后取决于区域设置,但中间不会有其他单词。

该结果无法通过任何单遍从左到右逐个字符的比较(“字典顺序”)来实现。而且大多数语言环境都有其他排序规则的怪癖,这些怪癖也不会屈服于天真的词典顺序。

如果您有适当的区域设置定义,标准 C++ 排序规则应该能够处理所有这些问题。但它不能仅仅使用对成对的比较函数来简化为字典顺序比较whar_t,因此 C++ 标准库不提供该接口。

以下只是说明为什么区域设置感知排序规则如此复杂的几个示例;更长的解释,以及更多的例子,可以在Unicode 技术标准 10.

口音去哪儿了?

大多数浪漫语言(以及英语,在处理借用词时)都认为元音之上的重音是一种次要特征;也就是说,首先对单词进行排序,就像不存在重音符号一样,然后进行第二次排序,其中非重音字母位于重音字母之前。需要第三遍来处理大小写,这在前两遍中被忽略。

But that doesn't work for Northern European languages. The alphabets of Swedish, Norwegian and Danish have three extra vowels, which follow z in the alphabet. In Swedish, these vowels are written å, ä, and ö; in Norwegian and Danish, these letters are written å, æ, and ø, and in Danish å is sometimes written aa, making Aarhus the last entry in an alphabetical list of Danish cities.

In German, the letters ä, ö, and ü are generally alphabetised as with romance accents, but in German phonebooks (and sometimes other alphabetical lists), they are alphabetised as though they were written ae, oe and ue, which is the older style of writing the same phonemes. (There are many pairs of common surnames such as "Müller" and "Mueller" are pronounced the same and are often confused, so it makes sense to intercollate them. A similar convention was used for Scottish names in Canadian phonebooks when I was young; the spellings M', Mc and Mac were all clumped together since they are all phonetically identical.)

一个符号,两个字母。或者两个字母,一个符号

German also has the symbol ß which is collated as though it were written out as ss, although it is not quite identical phonetically. We'll meet this interesting symbol again a bit later.

In fact, many languages consider digraphs and even trigraphs to be single letters. The 44-letter Hungarian alphabet includes Cs, Dz, Dzs, Gy, Ly, Ny, Sz, Ty, and Zs, as well as a variety of accented vowels. However, the language most commonly referenced in articles about this phenomenon -- Spanish -- stopped treating the digraphs ch and ll as letters in 1994, presumably because it was easier to force Hispanic writers to conform to computer systems than to change the computer systems to deal with Spanish digraphs. (Wikipedia claims it was pressure from "UNESCO and other international organizations"; it took quite a while for everyone to accept the new alphabetization rules, and you still occasionally find "Chile" after "Colombia" in alphabetical lists of South American countries.)

总结:比较字符串需要多遍,有时需要比较字符组

使其全部不区分大小写

由于相比之下,区域设置可以正确处理大小写,因此实际上没有必要执行不区分大小写的排序。进行不区分大小写的等价类检查(“相等”测试)可能很有用,尽管这提出了其他哪些不精确的等价类可能有用的问题。 Unicode 规范化、重音删除、甚至转录为拉丁语在某些情况下都是合理的,但在其他情况下却非常烦人。但事实证明,大小写转换也不像您想象的那么简单。

Because of the existence of di- and trigraphs, some of which have Unicode codepoints, the Unicode standard actually recognizes three cases, not two: lower-case, upper-case and title-case. The last is what you use to upper case the first letter of a word, and it's needed, for example, for the Croatian digraph dž (U+01C6; a single character), whose uppercase is DŽ (U+01C4) and whose title case is Dž (U+01C5). The theory of "case-insensitive" comparison is that we could transform (at least conceptually) any string in such a way that all members of the equivalence class defined by "ignoring case" are transformed to the same byte sequence. Traditionally this is done by "upper-casing" the string, but it turns out that that is not always possible or even correct; the Unicode standard prefers the use of the term "case-folding", as do I.

C++ 语言环境不能完全胜任这项工作

因此,回到 C++,可悲的事实是 C++ 语言环境没有足够的信息来进行准确的大小写折叠,因为 C++ 语言环境的工作原理是假设字符串的大小写折叠仅包含顺序和单独的大写字母字符串中的每个代码点都使用将一个代码点映射到另一个代码点的函数。正如我们将看到的,这根本行不通,因此其效率问题是无关紧要的。另一方面,重症监护病房图书馆有一个接口,可以像 Unicode 数据库允许的那样正确地进行大小写折叠,并且它的实现是由一些非常优秀的编码人员精心设计的,因此它可能在限制范围内尽可能高效。所以我绝对推荐使用它。

如果您想很好地了解折叠案例的难度,您应该阅读 5.18 和 5.19 节统一码标准 (第 5 章的 PDF)。以下仅举几个例子。

大小写转换不是从单个字符到单个字符的映射

The simplest example is the German ß (U+00DF), which has no upper-case form because it never appears at the beginning of a word, and traditional German orthography didn't use all-caps. The standard upper-case transform is SS (or in some cases SZ) but that transform is not reversible; not all instances of ss are written as ß. Compare, for example, grüßen and küssen (to greet and to kiss, respectively). In v5.1, ẞ, an "upper-case ß, was added to Unicode as U+1E9E, but it is not commonly used except in all-caps street signs, where its use is legally mandated. The normal expectation of upper-casing ß would be the two letters SS.

并非所有表意文字(可见字符)都是单字符代码

Even when a case transform maps a single character to a single character, it may not be able to express that as a wchar→wchar mapping. For example, ǰ can easily be capitalized to , but the former is a single combined glyph (U+01F0), while the second is a capital J with a combining caron (U+030C).

There is a further problem with glyphs like ǰ:

天真的逐字符大小写折叠可能会导致非规范化

Suppose we upper-case ǰ as above. How do we capitalize ǰ̠ (which, in case it doesn't render properly on your system, is the same character with an bar underneath, another IPA convention)? That combination is U+01F0,U+0320 (j with caron, combining minus sign below), so we proceed to replace U+01F0 with U+004A,U+030C and then leave the U+0320 as is: J̠̌. That's fine, but it won't compare equal to a normalized capital J with caron and minus sign below, because in the normal form the minus sign diacritic comes first: U+004A,U+0320,U+030C (J̠̌, which should look identical). So sometimes (rarely, to be honest, but sometimes) it is necessary to renormalize.

撇开 unicode 的怪异不谈,有时大小写转换是上下文相关的

Greek has a lot of examples of how marks get shuffled around depending on whether they are word-initial, word-final or word-interior -- you can read more about this in chapter 7 of the Unicode standard -- but a simple and common case is Σ, which has two lower-case versions: σ and ς. Non-greeks with some maths background are probably familiar with σ, but might not be aware that it cannot be used at the end of a word, where you must use ς.

In short

  1. 大小写折叠的最佳可用正确方法是应用 Unicode 大小写折叠算法,该算法需要为每个源字符串创建一个临时字符串。然后,您可以在两个转换后的字符串之间进行简单的字节比较,以验证原始字符串是否位于同一等价类中。对转换后的字符串进行排序规则虽然可能,但效率比对原始字符串进行排序规则要低得多,并且出于排序目的,未转换的比较可能与转换后的比较一样好或更好。

  2. 理论上,如果您只对大小写相等感兴趣,则可以线性进行转换,请记住转换不一定是上下文无关的,也不是简单的字符到字符映射函数。不幸的是,C++ 语言环境不向您提供执行此操作所需的数据。 Unicode CLDR 更接近,但它是一个复杂的数据结构。

  3. 所有这些东西都非常复杂,并且充满了边缘情况。 (请参阅 Unicode 标准中有关立陶宛语重音的注释i例如。)您最好只使用维护良好的现有解决方案,其中最好的例子是 ICU。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用任意语言环境比较“basic_string” 的相关文章

  • 类型中的属性名称必须是唯一的

    我正在使用 Entity Framework 5 并且有以下实体 public class User public Int32 Id get set public String Username get set public virtual
  • 如何在 Cassandra 中存储无符号整数?

    我通过 Datastax 驱动程序在 Cassandra 中存储一些数据 并且需要存储无符号 16 位和 32 位整数 对于无符号 16 位整数 我可以轻松地将它们存储为有符号 32 位整数 并根据需要进行转换 然而 对于无符号 64 位整
  • std::vector 与 std::stack

    有什么区别std vector and std stack 显然 向量可以删除集合中的项目 尽管比列表慢得多 而堆栈被构建为仅后进先出的集合 然而 堆栈对于最终物品操作是否更快 它是链表还是动态重新分配的数组 我找不到关于堆栈的太多信息 但
  • 如何在 C# 中打开 Internet Explorer 属性窗口

    我正在开发一个 Windows 应用程序 我必须向用户提供一种通过打开 IE 设置窗口来更改代理设置的方法 Google Chrome 使用相同的方法 当您尝试更改 Chrome 中的代理设置时 它将打开 Internet Explorer
  • free 和 malloc 在 C 中如何工作?

    我试图弄清楚如果我尝试 从中间 释放指针会发生什么 例如 看下面的代码 char ptr char malloc 10 sizeof char for char i 0 i lt 10 i ptr i i 10 ptr ptr ptr pt
  • 如何使从 C# 调用的 C(P/invoke)代码“线程安全”

    我有一些简单的 C 代码 它使用单个全局变量 显然这不是线程安全的 所以当我使用 P invoke 从 C 中的多个线程调用它时 事情就搞砸了 如何为每个线程单独导入此函数 或使其线程安全 我尝试声明变量 declspec thread 但
  • 用于 FTP 的文件系统观察器

    我怎样才能实现FileSystemWatcherFTP 位置 在 C 中 这个想法是 每当 FTP 位置添加任何内容时 我都希望将其复制到我的本地计算机 任何想法都会有所帮助 这是我之前问题的后续使用 NET 进行选择性 FTP 下载 ht
  • WPF 数据绑定到复合类模式?

    我是第一次尝试 WPF 并且正在努力解决如何将控件绑定到使用其他对象的组合构建的类 例如 如果我有一个由两个单独的类组成的类 Comp 为了清楚起见 请注意省略的各种元素 class One int first int second cla
  • 如何获取 EF 中与组合(键/值)列表匹配的记录?

    我有一个数据库表 其中包含每个用户 年份组合的记录 如何使用 EF 和用户 ID 年份组合列表从数据库获取数据 组合示例 UserId Year 1 2015 1 2016 1 2018 12 2016 12 2019 3 2015 91
  • C# - 当代表执行异步任务时,我仍然需要 System.Threading 吗?

    由于我可以使用委托执行异步操作 我怀疑在我的应用程序中使用 System Threading 的机会很小 是否存在我无法避免 System Threading 的基本情况 只是我正处于学习阶段 例子 class Program public
  • 为什么这个字符串用AesCryptoServiceProvider第二次解密时不相等?

    我在 C VS2012 NET 4 5 中的文本加密和解密方面遇到问题 具体来说 当我加密并随后解密字符串时 输出与输入不同 然而 奇怪的是 如果我复制加密的输出并将其硬编码为字符串文字 解密就会起作用 以下代码示例说明了该问题 我究竟做错
  • C 编程:带有数组的函数

    我正在尝试编写一个函数 该函数查找行为 4 列为 4 的二维数组中的最大值 其中二维数组填充有用户输入 我知道我的主要错误是函数中的数组 但我不确定它是什么 如果有人能够找到我出错的地方而不是编写新代码 我将不胜感激 除非我刚去南方 我的尝
  • 空指针与 int 等价

    Bjarne 在 C 编程语言 中写道 空指针与整数零不同 但 0 可以用作空指针的指针初始值设定项 这是否意味着 void voidPointer 0 int zero 0 int castPointer reinterpret cast
  • LINQ:使用 INNER JOIN、Group 和 SUM

    我正在尝试使用 LINQ 执行以下 SQL 最接近的是执行交叉联接和总和计算 我知道必须有更好的方法来编写它 所以我向堆栈团队寻求帮助 SELECT T1 Column1 T1 Column2 SUM T3 Column1 AS Amoun
  • 复制目录下所有文件

    如何将一个目录中的所有内容复制到另一个目录而不循环遍历每个文件 你不能 两者都不Directory http msdn microsoft com en us library system io directory aspx nor Dir
  • 为什么 isnormal() 说一个值是正常的,而实际上不是?

    include
  • 使用特定参数从 SQL 数据库填充组合框

    我在使用参数从 sql server 获取特定值时遇到问题 任何人都可以解释一下为什么它在 winfom 上工作但在 wpf 上不起作用以及我如何修复它 我的代码 private void UpdateItems COMBOBOX1 Ite
  • 当文件流没有新数据时如何防止fgets阻塞

    我有一个popen 执行的函数tail f sometextfile 只要文件流中有数据显然我就可以通过fgets 现在 如果没有新数据来自尾部 fgets 挂起 我试过ferror and feof 无济于事 我怎样才能确定fgets 当
  • C++ 中的参考文献

    我偶尔会在 StackOverflow 上看到代码 询问一些涉及函数的重载歧义 例如 void foo int param 我的问题是 为什么会出现这种情况 或者更确切地说 你什么时候会有 对参考的参考 这与普通的旧参考有何不同 我从未在现
  • C# 使用“?” if else 语句设置值这叫什么

    嘿 我刚刚看到以下声明 return name null name NA 我只是想知道这在 NET 中叫什么 是吗 代表即然后执行此操作 这是一个俗称的 条件运算符 三元运算符 http en wikipedia org wiki Tern

随机推荐