2012-04-24 2 views
7

В рубин 1.9.3, я могу получить кодовые струны:ruby ​​1.9 - что проще всего инвертировать `string.codepoints.to_a`?

> "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(:<<) 

UPDATE (ответ на ответ Никлас)

Интересное обсуждение. 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__), но это заставляет меня выполнять дополнительную работу.

UPDATE 2

Видимо строка # < < не всегда присоединять правильно в зависимости от кодировки струны. Таким образом, похоже, что пакет по-прежнему является лучшим вариантом.

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

Вы также можете использовать UTF-8 в качестве исходной кодировки. –

+0

Обратите внимание, что 'codepoints' does * not * возвращает коды Unicode для кодировок без Unicode (например, GB18030 для этой цели не является« Unicode », несмотря на кодирование всего Юникода). –

ответ

10

Наиболее очевидная адаптация вашей собственной попытки будет

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

Это, однако, не является хорошим решением, так как он работает только тогда, когда исходная пустая строка литерала имеет кодировку, которая способна удерживать весь диапазон символов Юникода. Следующий сбой:

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

Так что я бы на самом деле рекомендую

codepoints.pack("U*") 

Я не знаю, что вы имеете в виду «работает только с UTF-8». Он создает строку Ruby с кодировкой UTF-8, но UTF-8 может содержать весь диапазон символов Юникода, так что в чем проблема? Обратите внимание:

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 
+0

Ницца, должен был подумать об этом. Мне все еще интересно, есть ли встроенный метод. – Kelvin

+0

@Kelvin: Проверьте мое обновление. '.pack (" U * ")' - путь. –

+0

Я добавил к своему вопросу в ответ на ваше предложение использовать 'pack'. Я думаю, что инъекция все же является более общим решением. – Kelvin

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> 

с кодировкой.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> 

Я надеюсь, что это помогает (хотя и немного поздно, perha ps) - Я нашел это, чтобы найти ответ на тот же вопрос, поэтому я сам его исследовал.

Смежные вопросы