西里尔字符串 Я̆ Я̄ Я̈ return 长度为 2 而不是 ruby 和其他编程语言中的 1

cyrillic strings Я̆ Я̄ Я̈ return length 2 instead of 1 in ruby and other programming languages

在 Ruby、Javascript 和 Java(其他我没试过)中,有西里尔字符 Я̆ Я̄ Я̈ 长度 2。当我尝试检查字符串的长度时有了这些字符,我得到了错误的输出值。

"Я̈".mb_chars.length
#=> 2  #should be 1 (ruby on rails)

"Я̆".length
#=> 2  #should be 1 (ruby, javascript)

"Ӭ".length
#=> 1  #correct (ruby, javascript)

请注意,字符串以 UTF-8 编码,每个字符都表现为单个字符。

我的问题是为什么会有这样的行为,我怎样才能正确地得到带有这些字符的字符串的长度?

潜在的问题是 Я̈ 实际上是两个代码点:Я 和变音符号是分开的:

'Я̈'.chars
#=> ["Я", "̈"]

通常你会通过 unicode 规范化来解决这类问题,但单凭这一点对你没有帮助,因为 Я̈Я̆ 没有单一代码点(但有Ӭ).

您可以在检查长度之前去除变音符号:

'Я̆'.gsub(/\p{Diacritic}/, '')
#=> "Я" 
'Я̆'.gsub(/\p{Diacritic}/, '').length
#=> 1 

您将获得所需的长度,但字符串不会完全相同。这也适用于 Ӭ 之类的东西,它可以用一个代码点表示:

'Ӭ'.length
#=> 1
'Ӭ'.gsub(/\p{Diacritic}/, '')
#=> "Ӭ" 
'Ӭ'.gsub(/\p{Diacritic}/, '').length
#=> 1 

Unicode 很棒,很棒,解决了很多曾经困扰我们的问题。不幸的是,Unicode 也很可怕和复杂,因为人类语言和字形并不是完全设计出来的。

Ruby 2.5 添加 String#each_grapheme_cluster:

'Я̆Я̄Я̈'.each_grapheme_cluster.to_a   #=> ["Я̆", "Я̄", "Я̈"]
'Я̆Я̄Я̈'.each_grapheme_cluster.count  #=> 3

请注意,您不能使用等同于 each_char.sizeeach_grapheme_cluster.size,因此在上例中两者都将 return 6。 (这看起来像是一个错误,我刚刚提交了一个 bug report

尝试 unicode-display_width,它是为给出这个问题的准确答案而构建的:

require "unicode/display_width"
Unicode::DisplayWidth.of "Я̈" #=> 1