2012-04-24 24 views
7

Trong ruby ​​1.9.3, tôi có thể nhận được codepoints của một chuỗi:ruby ​​1.9 - điều nghịch đảo đơn giản nhất của `string.codepoints.to_a` là gì?

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

Có một phương pháp tích hợp để đi theo một hướng khác, tức là từ mảng số nguyên để chuỗi?

Tôi nhận thức được:

# 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 (đáp ứng với Niklas' câu trả lời)

thảo luận thú vị. pack("U*") luôn trả về một chuỗi UTF-8, trong khi phiên bản inject trả về một chuỗi trong mã hóa nguồn của tệp.

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

Đối với tôi, cuộc gọi inject trả về một chuỗi ISO-8859-1, trong khi pack trả về một UTF-8. Để ngăn chặn lỗi, tôi có thể sử dụng pack("U*").encode(__ENCODING__) nhưng điều đó làm cho tôi làm thêm công việc.

UPDATE 2

Rõ ràng String # < < không phải lúc nào thêm một cách chính xác tùy thuộc vào mã hóa của chuỗi. Vì vậy, có vẻ như gói vẫn là lựa chọn tốt nhất.

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

Bạn cũng có thể chỉ sử dụng UTF-8 làm mã hóa nguồn của mình. –

+0

Lưu ý rằng 'codepoints' không * không * trả về codepoint Unicode cho mã hóa không phải Unicode (ví dụ: GB18030 không phải là" Unicode "cho mục đích này mặc dù mã hóa tất cả Unicode). –

Trả lời

10

Các thích ứng rõ ràng nhất của nỗ lực riêng của bạn sẽ là

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

Tuy nhiên điều này không phải là một giải pháp tốt, vì nó chỉ hoạt động nếu chuỗi rỗng ban đầu theo nghĩa đen có một mã hóa có khả năng giữ toàn bộ phạm vi ký tự Unicode. Sau đây thất bại:

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

Vì vậy, tôi thực sự muốn khuyên

codepoints.pack("U*") 

Tôi không biết những gì bạn có ý nghĩa bởi "chỉ làm việc với UTF-8". Nó tạo ra một chuỗi Ruby với mã hóa UTF-8, nhưng UTF-8 có thể giữ toàn bộ phạm vi ký tự Unicode, vậy vấn đề là gì? Quan sát:

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

Tốt, nên nghĩ về điều đó. Tôi vẫn tự hỏi nếu có một phương pháp được xây dựng trong. – Kelvin

+0

@Kelvin: Kiểm tra cập nhật của tôi. '.pack (" U * ")' là con đường để đi. –

+0

Tôi đã thêm vào câu hỏi của tôi để đáp lại đề xuất của bạn để sử dụng 'gói'. Tôi nghĩ tiêm vẫn là giải pháp tổng quát hơn. – Kelvin

2

Tùy thuộc vào các giá trị trong mảng của bạn và giá trị của Encoding.default_internal, bạn có thể thử:

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

Bạn phải cẩn thận của các bảng mã. Lưu ý những điều sau:

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> 

Theo mặc định, 256.chr thất bại bởi vì nó thích trở về hoặc là US-ASCII hoặc ASCII-8 bit, tùy thuộc vào việc các điểm mã là trong 0..127 hoặc 128..256.

Điều này sẽ bao gồm điểm của bạn cho các giá trị 8 bit. Nếu bạn có các giá trị lớn hơn 255 (có thể là các điểm mã Unicode), thì bạn có thể thực hiện như sau:

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> 

Với mã hóa.default_internal thiết lập để "utf-8", các giá trị Unicode> 255 sẽ làm việc tốt (nhưng xem dưới đây):

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> 

Bây giờ nó trở nên thú vị - ASCII-8 bit và UTF-8 dường như không để trộn:

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 và UTF-8 có thể được nối, miễn là codepoints ASCII-8 bit là tất cả trong 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> 

này đưa chúng ta đến một giải pháp cuối cùng cho câu hỏi của bạn :

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

Vì vậy, tôi nghĩ câu trả lời chung nhất là, giả sử bạn muốn UTF-8, là:

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

Giả sử bạn biết giá trị của bạn trong 0..255, thì đây là dễ dàng hơn:

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

đem lại cho bạn:

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> 

tôi hy vọng điều này sẽ giúp (mặc dù hơi muộn, perha ps) - Tôi thấy điều này tìm kiếm một câu trả lời cho cùng một câu hỏi, vì vậy tôi đã tự nghiên cứu nó.

Các vấn đề liên quan