2012-04-24 13 views
7

In ruby ​​1.9.3, posso ottenere i Codepoints di una stringa:ruby ​​1.9 - qual è l'inverso più semplice di `string.codepoints.to_a`?

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

Esiste un metodo incorporato per andare nella direzione opposta, cioè da matrice integer a stringa?

io sappia:

# 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 (risposta alla risposta Niklas')

Interessante discussione. pack("U*") restituisce sempre una stringa UTF-8, mentre la versione inject restituisce una stringa nella codifica di origine del file.

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

Per me, la chiamata inject restituisce una stringa ISO-8859-1, mentre pack restituisce un UTF-8. Per evitare l'errore, potrei usare pack("U*").encode(__ENCODING__) ma questo mi fa fare un lavoro extra.

UPDATE 2

A quanto pare lo String # < < non sempre accoda correttamente a seconda codifica della stringa. Quindi sembra che pack sia ancora l'opzione migliore.

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

Si potrebbe anche utilizzare UTF-8 come codifica sorgente. –

+0

Si noti che 'codepoints' non * restituisce * codepoints Unicode per codifiche non Unicode (ad esempio GB18030 non è" Unicode "per questo scopo nonostante codifichi tutto Unicode). –

risposta

10

L'adattamento più evidente della propria tentativo sarebbe

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

Questo non è comunque una buona soluzione, in quanto funziona solo se la stringa vuota iniziale letterale ha una codifica che è in grado di contenere l'intero intervallo di caratteri Unicode. Quanto segue non riesce:

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

quindi mi piacerebbe davvero consiglia

codepoints.pack("U*") 

Non so che cosa si intende per "funziona solo con UTF-8". Crea una stringa di Ruby con codifica UTF-8, ma UTF-8 può contenere l'intero intervallo di caratteri Unicode, quindi qual è il problema? Osservare:

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

Bello, avresti dovuto pensarci. Mi chiedo ancora se esiste un metodo integrato. – Kelvin

+0

@ Kelvin: Controlla il mio aggiornamento. '.pack (" U * ")' è la strada da percorrere. –

+0

Ho aggiunto alla mia domanda in risposta al tuo suggerimento di usare 'pack'. Penso che l'iniezione sia ancora la soluzione più generale. – Kelvin

2

A seconda dei valori nel vostro matrice e il valore di Encoding.default_internal, si potrebbe provare:

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

Bisogna stare attenti della codifica. Tenere presente quanto segue:

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> 

Per impostazione predefinita, 256.chr fallisce perché gli piace di tornare sia US-ASCII o ASCII-8 BIT, a seconda che il valore di codice è in 0..127 o 128..256.

Questo dovrebbe coprire il punto per i valori a 8 bit. Se si dispone di valori superiori a 255 (Codepoints presumibilmente Unicode), allora si può fare quanto segue:

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 codifica.default_internal insieme a "utf-8", i valori Unicode> 255 dovrebbe funzionare bene (ma vedi sotto):

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> 

Ora si fa interessante - ASCII-8 bit e UTF-8 non sembrano mescolare:

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-8 bit e UTF-8 può essere concatenato, fino a quando i codepoints ASCII-8bit sono tutti in 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> 

Questo ci porta ad una soluzione definitiva alla tua domanda :

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

Quindi penso che la risposta più generale è, supponendo che si desidera UTF-8, è:

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

supponendo che si conoscono questi valori sono in 0..255, allora questo è più facile:

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

dando:

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> 

spero che questo aiuta (anche se un po 'in ritardo, perha ps) - Ho trovato questo cercando una risposta alla stessa domanda, quindi l'ho ricercata personalmente.

Problemi correlati