将unicode字符与unicode字符范围进行比较时出现异常行为

时间:2012-04-04 22:35:48

标签: ruby

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

总结一下,在我最小化的测试代码中,("\u1000".."\u1200") === "\u1100"false,我希望它是true - 而针对"\u1001"的相同测试是{ {1}}如预期的那样。我觉得这完全不可理解。 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}" 操作都会产生“真”。但是,运行此程序的实际输出是:

===

有人可以启发我吗?

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

2 个答案:

答案 0 :(得分:3)

ACTION: 我已将此行为提交为bug #6258 to 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序列,而不是最小和最大的简单绑定检查。

如果我们查看Range#===的来源(点击切换),我们会看到它发送到Range#include?。范围#包括哪些内容? source显示字符串的特殊处理 - 如果可以仅通过字符串长度来确定答案,或者所有被调用的字符串都是ASCII,我们会得到简单的边界检查,否则我们会调度到super,这意味着#include? Enumerable#include?使用Range#each进行枚举,该String#upto使用String#succ进行枚举,该字符串再次对U+1036进行特殊处理,并使用{{3}}进行枚举。

当字符串包含is_alpha或is_digit数字({{3}}不应该为true)时,字符串#succ有一堆特殊处理,否则它会使用enc_succ_char递增最终字符。此时我失去了踪迹,但可能会使用与字符串关联的编码和校对信息计算后继者。

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

r = (a.ord..b.ord)
r === x.ord
r === y.ord

答案 1 :(得分:2)

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

我认为正在发生的是你创建的是一个试图包含字母,数字和标点符号的范围。 Ruby无法做到这一点,并没有“理解”你本质上需要一组代码点。

这导致Range#to_a方法崩溃:

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

zinger是你把所有三个放在一起的时候:

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

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

字符串#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 ":"