2012-04-24 9 views
7

En Ruby 1.9.3, puedo conseguir los puntos de código de una cadena:ruby ​​1.9 - ¿Cuál es la inversa más fácil de `string.codepoints.to_a`?

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

¿Hay un método integrado para ir la otra dirección, es decir, del conjunto entero en cadena?

yo sepa:

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

ACTUALIZACIÓN (respuesta a la respuesta Niklas')

Interesante debate. pack("U*") siempre devuelve una cadena UTF-8, mientras que la versión inject devuelve una cadena en la codificación de origen del archivo.

#!/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/ 

Para mí, la llamada inject devuelve una cadena ISO-8859-1, mientras que pack devuelve una codificación UTF-8. Para evitar el error, podría usar pack("U*").encode(__ENCODING__), pero eso me hace hacer un trabajo extra.

ACTUALIZACIÓN 2

Al parecer, la cadena # < < no siempre anexar correctamente dependiendo de la codificación de la cadena. Entonces parece que el paquete sigue siendo la mejor opción.

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

También podría usar UTF-8 como fuente de codificación. –

+0

Tenga en cuenta que 'codepoints' no * devuelve puntos de código Unicode para codificaciones que no sean Unicode (por ejemplo, GB18030 no es" Unicode "para este fin a pesar de codificar todo Unicode). –

Respuesta

10

La adaptación más evidente de su propio intento sería

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

Sin embargo, esto no es una buena solución, ya que sólo funciona si la cadena vacía inicial literal tiene una codificación que es capaz de mantener todo el rango de caracteres Unicode. El siguiente falla:

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

así que me recomiendan realmente

codepoints.pack("U*") 

No sé qué quiere decir con "sólo funciona con UTF-8". Crea una cadena de Ruby con codificación UTF-8, pero UTF-8 puede contener todo el rango de caracteres Unicode, entonces, ¿cuál es el problema? Observe:

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

Bien, debería haber pensado en eso. Todavía me pregunto si hay un método incorporado. – Kelvin

+0

@ Kelvin: comprueba mi actualización. '.pack (" U * ")' es el camino a seguir. –

+0

Agregué a mi pregunta en respuesta a su sugerencia de usar 'pack'. Creo que inyectar sigue siendo la solución más general. – Kelvin

2

Dependiendo de los valores en la matriz y el valor de Encoding.default_internal, puede intentar:

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

Usted tiene que tener cuidado de la codificación. Tenga en cuenta lo siguiente:

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> 

Por defecto, 256.chr falla porque le gusta a devolver cualquiera US-ASCII o ASCII-8BIT, dependiendo de si el punto de código se encuentra en 0..127 o 128..256.

Esto debería cubrir su punto para valores de 8 bits. Si tiene valores mayores de 255 (puntos de código Unicode presumiblemente), entonces usted puede hacer lo siguiente:

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> 

con la codificación.default_internal conjunto a "UTF-8", los valores Unicode> 255 debería funcionar bien (pero véase más abajo):

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> 

Ahora se pone interesante - ASCII-8BIT y no parecen UTF-8 para mezclar:

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 y UTF-8 se puede concatenar, siempre y cuando los puntos de código ASCII de 8 bits están todos en 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> 

Esto nos lleva a una solución definitiva a su pregunta :

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

Así que creo que la respuesta más general es, asumiendo que usted quiere UTF-8, es:

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

asumiendo que conoce sus valores están en 0..255, entonces esto es más fácil:

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

le da:

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> 

espero que esto ayuda (aunque un poco tarde, perha ps) - Encontré esto buscando una respuesta a la misma pregunta, así que investigué yo mismo.

Cuestiones relacionadas