2012-04-04 38 views
3

由于某些原因,我在unicode字符的范围比较中收到了意想不到的结果。将unicode字符与unicode字符范围进行比较时出现异常行为

总之,在我最小的测试代码,("\u1000".."\u1200") === "\u1100"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 

有人能够启发我吗?

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

+0

(a..b)是什么意思? – 2012-04-04 23:43:51

+0

@KitHo'(a..b)'产生一个[Range](http://www.ruby-doc.org/core-1.9.3/Range.html)对象,它具有最小值'a'和最大值的'b'并且可以列举或包含在内。 – dbenhur 2012-04-04 23:50:15

回答

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”。在范围之内,范围#===必须枚​​举String#succ序列,而不是在最小和最大范围内进行简单的边界检查。

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

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

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

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

因此,“===”的行为与“<=>”的行为有何不同?这似乎...很奇怪。我可以理解,如果比较是以某种方式依赖于语言环境,或者你有什么,但是如果范围成员资格和'<=>'使用一个非常不同的算法。我也找不到任何可能解释这个的文档... – Perry 2012-04-04 23:52:36

+0

为什么'#==='与#<=>'一样?前者是“Case Equality”操作符并生成bool,后者是特殊的三值比较操作符。 '==='没有提到任何关于小于/大于,仅仅是相等的情况,并且特殊的语义对于预期的情况条件很方便。 – dbenhur 2012-04-04 23:57:24

+0

我的_point_是罪魁祸首是String#succ。并且计算“\ u1036”.succ时使用默认编码和区域设置排序有一个错误或意外。 – dbenhur 2012-04-04 23:58:42

2

貌似范围并不意味着什么,我们认为它的意思。

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

这是造成范围#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作品expected--为Matt在评论中指出,“\ u1000”仅仅是文字“u1000”,因为没有Unicode。

串#SUCC C源代码不只是返回下codepooint:

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.  

范围正在做的东西不仅仅是未来的不同,下一步,下一步。

这些字符范围确实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 ":" 
+0

你在使用什么版本的Ruby和平台? 1.9.3似乎对我而言产生了一致的结果。 (我不认为它可以编码,因为我指定utf-8 - 你在使用哪一个?) – Perry 2012-04-04 23:18:33

+0

好吧,所以你在1.9.3和1.8.7上遇到同样的问题,表现得很好为你。这可能是一个错误,但似乎很难相信这个糟糕的bug可能会在发布之后幸存下来... – Perry 2012-04-04 23:24:27

+0

r.to_a.size'在1.9.3中为55,在1.8.7中为201。为什么这是我不知道的,我猜想在1.9.3中与字符串和编码有关,但它可以解释这种差异。 – matt 2012-04-04 23:29:49