3

出于某种原因,我在 unicode 字符的范围比较中得到了意想不到的结果。

总而言之,在我最小化的测试代码中,("\u1000".."\u1200") === "\u1100"is 是false我期望的——而针对的true相同测试与预期的一样。我觉得这完全无法理解。运算符的结果也很有趣——它们相互矛盾。"\u1001"true<===

以下代码是一个很好的最小说明:

# encoding: utf-8

require 'pp'

a = "\u1000"
b = "\u1200"

r = (a..b)

x = "\u1001"
y = "\u1100"

pp a, b, r, x, y

puts "a < x = #{a < x}"
puts "b > x = #{b > x}"

puts "a < y = #{a < y}"
puts "b > y = #{b > y}"

puts "r === x = #{r === x}"
puts "r === y = #{r === y}"

我天真地期望这两种===操作都会在这里产生“真实”。然而,运行这个程序的实际输出是:

ruby 1.9.3p125 (2012-02-16 revision 34643) [x86_64-darwin11.3.0]
"\u1000"
"\u1200"
"\u1000".."\u1200"
"\u1001"
"\u1100"
a < x = true
b > x = true
a < y = true
b > y = true
r === x = true
r === y = false

有人可以启发我吗?

(注意我在 Mac OS X 上使用 1.9.3,并且我明确地将编码设置为 utf-8。)

4

2 回答 2

3

行动:我已将此行为作为错误 #6258 提交给 ruby​​-lang

该字符范围内的整理顺序有些奇怪

irb(main):081:0> r.to_a.last.ord.to_s(16)
=> "1036"
irb(main):082:0> r.to_a.last.succ.ord.to_s(16)
=> "1000"
irb(main):083:0> r.min.ord.to_s(16)
=> "1000"
irb(main):084:0> r.max.ord.to_s(16)
=> "1200"

范围的最小值和最大值是您输入的预期值,但如果我们将范围转换为数组,则最后一个元素是“\u1036”,它的后继元素是“\u1000”。在幕后, Range#=== 必须枚举String#succ序列,而不是对 min 和 max 进行简单的边界检查。

如果我们查看Range#===的源代码(单击切换),我们会看到它调度到Range#include? . 范围#包括?source 显示了对字符串的特殊处理——如果答案可以单独由字符串长度确定,或者所有 invloved 字符串都是 ASCII,我们得到简单的边界检查,否则我们分派给 super,这意味着 #include? 得到Enumerable#include 的回答?它使用Range#each进行枚举,它再次对字符串进行特殊处理,并分派给使用String#succ枚举的String#upto

当字符串包含 is_alpha 或 is_digit 数字(对于U+1036不应为真)时,String#succ 有一堆特殊处理,否则它会使用enc_succ_char. 在这一点上,我失去了踪迹,但大概这会使用与字符串关联的编码和整理信息来计算后继者。

顺便说一句,作为一种解决方法,如果您只关心单个字符,您可以使用一系列整数序数并针对序数进行测试。例如:

r = (a.ord..b.ord)
r === x.ord
r === y.ord
于 2012-04-04T23:41:43.800 回答
2

看起来范围并不意味着我们认为它意味着什么。

我认为正在发生的是您正在创建的是一个试图包含字母、数字和标点符号的 Range。Ruby 无法做到这一点,并且不“理解”您本质上想要一个代码点数组。

这导致 Range#to_a 方法分崩离析:

("\u1000".."\u1099").to_a.size  #=> 55
("\u1100".."\u1199").to_a.size  #=> 154
("\u1200".."\u1299").to_a.size  #=> 73

当您将所有三个放在一起时,最重要的是:

("\u1000".."\u1299").to_a.size  #=> 55

Ruby 1.8.7 按预期工作——正如马特在评论中指出的那样,“\u1000”只是文字“u1000”,因为没有 Unicode。

string#succ C 源代码不仅返回下一个代码点:

Returns the successor to <i>str</i>. The successor is calculated by                                                                                                                                                                                                          
incrementing characters starting from the rightmost alphanumeric (or                                                                                                                                                                                                         
the rightmost character if there are no alphanumerics) in the                                                                                                                                                                                                                
string. Incrementing a digit always results in another digit, and                                                                                                                                                                                                            
incrementing a letter results in another letter of the same case.                                                                                                                                                                                                            
Incrementing nonalphanumerics uses the underlying character set's                                                                                                                                                                                                            
collating sequence.     

Range 正在做一些不同于下一个、下一个、下一个的事情。

具有这些字符的范围执行 ACSII 序列:

('8'..'A').to_a
=> ["8", "9", ":", ";", "<", "=", ">", "?", "@", "A"]

但是使用#succ 完全不同:

'8'.succ
=> '9'

'9'.succ
=> '10'  # if we were in a Range.to_a, this would be ":"
于 2012-04-04T23:17:10.480 回答