【问题标题】:ruby 1.9 - what is easiest inverse of `string.codepoints.to_a`?ruby 1.9 - `string.codepoints.to_a`最简单的逆是什么?
【发布时间】:2012-04-24 19:32:32
【问题描述】:

在 ruby​​ 1.9.3 中,我可以获取字符串的代码点:

> "foo\u00f6".codepoints.to_a
 => [102, 111, 111, 246] 

有没有内置的方法可以反过来,即从整数数组到字符串?

我知道:

# not acceptable; only works with UTF-8
[102, 111, 111, 246].pack("U*")

# works, but not very elegant
[102, 111, 111, 246].inject('') {|s, cp| s << cp }

# concise, but I need to unshift that pesky empty string to "prime" the inject call
['', 102, 111, 111, 246].inject(:<<)

更新(回应 Niklas 的回答)

有趣的讨论。 pack("U*") 始终返回 UTF-8 字符串,而 inject 版本返回文件源编码中的字符串。

#!/usr/bin/env ruby
# encoding: iso-8859-1

p [102, 111, 111, 246].inject('', :<<).encoding
p [102, 111, 111, 246].pack("U*").encoding
# this raises an Encoding::CompatibilityError
[102, 111, 111, 246].pack("U*") =~ /\xf6/

对我来说,inject 调用返回一个 ISO-8859-1 字符串,而 pack 返回一个 UTF-8。为了防止错误,我可以使用pack("U*").encode(__ENCODING__),但这会让我做额外的工作。

更新 2

显然,String#

[225].inject(''.encode('utf-16be'), :<<)  # fails miserably
[225].pack("U*").encode('utf-16be')  # works

【问题讨论】:

  • 您也可以只使用 UTF-8 作为源编码。
  • 请注意,codepoints 确实返回非 Unicode 编码的 Unicode 代码点(例如,尽管对所有 Unicode 进行了编码,但 GB18030 不是为此目的的“Unicode”)。跨度>

标签: ruby unicode ruby-1.9


【解决方案1】:

你自己的尝试最明显的适应是

[102, 111, 111, 246].inject('', :<<)

然而,这不是一个好的解决方案,因为它仅在初始空字符串文字具有能够容纳整个 Unicode 字符范围的编码时才有效。以下失败:

#!/usr/bin/env ruby
# encoding: iso-8859-1
p "\u{1234}".codepoints.to_a.inject('', :<<)

所以我真的会推荐

codepoints.pack("U*")

我不知道您所说的“仅适用于 UTF-8”是什么意思。它创建了一个 UTF-8 编码的 Ruby 字符串,但是 UTF-8 可以容纳整个 Unicode 字符范围,那么有什么问题呢?观察:

irb(main):010:0> s = [0x33333, 0x1ffff].pack("U*")
=> "\u{33333}\u{1FFFF}"
irb(main):011:0> s.encoding
=> #<Encoding:UTF-8>
irb(main):012:0> [0x33333, 0x1ffff].pack("U*") == [0x33333, 0x1ffff].inject('', :<<)
=> true

【讨论】:

  • 很好,应该想到这一点。我还是想知道有没有内置的方法。
  • 我添加了我的问题以回应您使用pack 的建议。我认为注入仍然是更通用的解决方案。
  • @Kelvin:您的inject 方法可能更通用,因为它可以使用任意编码的字符串作为种子,但是当涉及到创建 Unicode 的实际问题时,它肯定没那么有用来自代码点列表的字符串(正如您正确指出的那样,如果源编码不能容纳整个 Unicode 范围,'' &lt;&lt; codepoint 通常会失败)。我想我们在这里漂流了。你的实际问题是什么?
  • 其实我觉得pack可能会更好,因为:&lt;&lt;似乎没有注意接收方的编码:[225].inject(''.encode('utf-16be'), :&lt;&lt;)没有返回正确的utf-16be字符串,但是[225].pack("U*").encode('utf-16be')似乎有效。
【解决方案2】:

根据数组中的值和Encoding.default_internal 的值,您可以尝试:

[102, 111, 111, 246].map(&:chr).inject(:+)

您必须小心编码。请注意以下几点:

irb(main):001:0> 0.chr.encoding
=> #<Encoding:US-ASCII>
irb(main):002:0> 127.chr.encoding
=> #<Encoding:US-ASCII>
irb(main):003:0> 128.chr.encoding
=> #<Encoding:ASCII-8BIT>
irb(main):004:0> 255.chr.encoding
=> #<Encoding:ASCII-8BIT>
irb(main):005:0> 256.chr.encoding
RangeError: 256 out of char range
        from (irb):5:in `chr'
        from (irb):5
        from C:/Ruby200/bin/irb:12:in `<main>'
irb(main):006:0>

默认情况下,256.chr 会失败,因为它喜欢返回 US-ASCII 或 ASCII-8BIT,具体取决于代码点是在 0..127 还是 128..256。

这应该涵盖您对 8 位值的观点。如果您的值大于 255(可能是 Unicode 代码点),那么您可以执行以下操作:

irb(main):006:0> Encoding.default_internal = "utf-8"
=> "utf-8"
irb(main):007:0> 256.chr.encoding
=> #<Encoding:UTF-8>
irb(main):008:0> 256.chr.codepoints
=> [256]
irb(main):009:0>

将 Encoding.default_internal 设置为“utf-8”,Unicode 值 > 255 应该可以正常工作(但见下文):

irb(main):009:0> 65535.chr.encoding
=> #<Encoding:UTF-8>
irb(main):010:0> 65535.chr.codepoints
=> [65535]
irb(main):011:0> 65536.chr.codepoints
=> [65536]
irb(main):012:0> 65535.chr.bytes
=> [239, 191, 191]
irb(main):013:0> 65536.chr.bytes
=> [240, 144, 128, 128]
irb(main):014:0>

现在变得有趣了——ASCII-8BIT 和 UTF-8 似乎没有混合:

irb(main):014:0> (0..127).to_a.map(&:chr).inject(:+).encoding
=> #<Encoding:US-ASCII>
irb(main):015:0> (0..128).to_a.map(&:chr).inject(:+).encoding
=> #<Encoding:ASCII-8BIT>
irb(main):016:0> (0..255).to_a.map(&:chr).inject(:+).encoding
=> #<Encoding:ASCII-8BIT>
irb(main):017:0> ((0..127).to_a + (256..1000000).to_a).map(&:chr).inject(:+).encoding
RangeError: invalid codepoint 0xD800 in UTF-8
        from (irb):17:in `chr'
        from (irb):17:in `map'
        from (irb):17
        from C:/Ruby200/bin/irb:12:in `<main>'
irb(main):018:0> ((0..127).to_a + (256..0xD7FF).to_a).map(&:chr).inject(:+).encoding
=> #<Encoding:UTF-8>
irb(main):019:0> (0..256).to_a.map(&:chr).inject(:+).encoding
Encoding::CompatibilityError: incompatible character encodings: ASCII-8BIT and UTF-8
        from (irb):19:in `+'
        from (irb):19:in `each'
        from (irb):19:in `inject'
        from (irb):19
        from C:/Ruby200/bin/irb:12:in `<main>'
irb(main):020:0>

ASCII-8BIT 和 UTF-8 可以串联,只要 ASCII-8BIT 码位都在 0..127 内:

irb(main):020:0> 256.chr.encoding
=> #<Encoding:UTF-8>
irb(main):021:0> (0.chr.force_encoding("ASCII-8BIT") + 256.chr).encoding
=> #<Encoding:UTF-8>
irb(main):022:0> 255.chr.encoding
=> #<Encoding:ASCII-8BIT>
irb(main):023:0> (255.chr + 256.chr).encoding
Encoding::CompatibilityError: incompatible character encodings: ASCII-8BIT and UTF-8
        from (irb):23
        from C:/Ruby200/bin/irb:12:in `<main>'
irb(main):024:0>

这为您的问题带来了终极解决方案:

irb(main):024:0> (0..0xD7FF).to_a.map {|c| c.chr("utf-8")}.inject(:+).encoding
=> #<Encoding:UTF-8>
irb(main):025:0>

所以我认为最一般的答案是,假设你想要 UTF-8,是:

[102, 111, 111, 246].map {|c| c.chr("utf-8")}.inject(:+)

假设您知道您的值在 0..255 范围内,那么这更容易:

[102, 111, 111, 246].map(&:chr).inject(:+)

给你:

irb(main):027:0> [102, 111, 111, 246].map {|c| c.chr("utf-8")}.inject(:+)
=> "fooö"
irb(main):028:0> [102, 111, 111, 246].map(&:chr).inject(:+)
=> "foo\xF6"
irb(main):029:0> [102, 111, 111, 246].map {|c| c.chr("utf-8")}.inject(:+).encoding
=> #<Encoding:UTF-8>
irb(main):030:0> [102, 111, 111, 246].map(&:chr).inject(:+).encoding
=> #<Encoding:ASCII-8BIT>
irb(main):031:0>

我希望这会有所帮助(尽管可能有点晚了)——我发现这是在寻找同一个问题的答案,所以我自己研究了它。

【讨论】:

  • 要添加到上述答案,您可以这样做[102, 111, 111, 246].map{|cp| cp.chr(__ENCODING__)}.join或更具体的[102, 111, 111, 246].map{|cp| cp.chr(Encoding::UTF_8)}.join
猜你喜欢
  • 2011-09-24
  • 1970-01-01
  • 2010-09-20
  • 2011-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-06
  • 2012-04-12
相关资源
最近更新 更多