2014-09-27 111 views
5

對於this question我創建了以下將代碼轉換爲UTF-8字符串的Lua代碼。有沒有更好的方法來做到這一點(在Lua 5.1+中)?在這種情況下,「更好」意味着「顯着更高效,或者優選更少的代碼行」更優雅,更簡單的將代碼點轉換爲UTF-8的方法

注意:我並不真的要求這種算法的code review;我要求更好的算法(或內置庫)。

do 
    local bytebits = { 
    {0x7F,{0,128}}, 
    {0x7FF,{192,32},{128,64}}, 
    {0xFFFF,{224,16},{128,64},{128,64}}, 
    {0x1FFFFF,{240,8},{128,64},{128,64},{128,64}} 
    } 
    function utf8(decimal) 
    local charbytes = {} 
    for b,lim in ipairs(bytebits) do 
     if decimal<=lim[1] then 
     for i=b,1,-1 do 
      local prefix,max = lim[i+1][1],lim[i+1][2] 
      local mod = decimal % max 
      charbytes[i] = string.char(prefix + mod) 
      decimal = (decimal - mod)/max 
     end 
     break 
     end 
    end 
    return table.concat(charbytes) 
    end 
end 

c=utf8(0x24)  print(c.." is "..#c.." bytes.") --> $ is 1 bytes. 
c=utf8(0xA2)  print(c.." is "..#c.." bytes.") --> ¢ is 2 bytes. 
c=utf8(0x20AC) print(c.." is "..#c.." bytes.") --> € is 3 bytes. 
c=utf8(0xFFFF) print(c.." is "..#c.." bytes.") --> is 3 bytes. 
c=utf8(0x10000) print(c.." is "..#c.." bytes.") --> is 4 bytes. 
c=utf8(0x24B62) print(c.." is "..#c.." bytes.") --> is 4 bytes. 

我覺得自己好像應該是擺脫整個bytebits預定義的表格和環只是爲了找到匹配條目的方式。從後面循環我可以連續%64並添加128以形成延續字節,直到值低於128,但我無法弄清楚如何優雅地生成要添加的前導碼。


編輯:這裏有一個稍微好一點的重構,速度優化。然而,這不是一個可以接受的答案,因爲算法仍然是基本相同的想法和大致相同數量的代碼。

do 
    local bytemarkers = { {0x7FF,192}, {0xFFFF,224}, {0x1FFFFF,240} } 
    function utf8(decimal) 
    if decimal<128 then return string.char(decimal) end 
    local charbytes = {} 
    for bytes,vals in ipairs(bytemarkers) do 
     if decimal<=vals[1] then 
     for b=bytes+1,2,-1 do 
      local mod = decimal%64 
      decimal = (decimal-mod)/64 
      charbytes[b] = string.char(128+mod) 
     end 
     charbytes[1] = string.char(vals[2]+decimal) 
     break 
     end 
    end 
    return table.concat(charbytes) 
    end 
end 
+0

試圖通過循環,我上面的最後評論描述了一個有缺陷的算法。例如,Unicode [代碼點0x10000](http://www.fileformat.info/info/unicode/char/10000/index.htm)需要UTF-8中的四個字節。在向右移位12位(兩個'/ 64')後,原始值下降到只有16位。似乎有些關於起始值,字節數和初始字節前導碼之間關係的硬編碼知識基本上是需要。 – Phrogz 2014-09-27 04:31:13

回答

3

如果我們談論的速度,在真實的場景中使用模式是非常重要的。但在這裏,我們處於一個真空狀態,所以讓我們繼續。

這種算法可能是你正在尋找什麼,當你說你的事情,你應該能夠擺脫bytebits爲:

do 
    local string_char = string.char 
    function utf8(cp) 
    if cp < 128 then 
     return string_char(cp) 
    end 
    local s = "" 
    local prefix_max = 32 
    while true do 
     local suffix = cp % 64 
     s = string_char(128 + suffix)..s 
     cp = (cp - suffix)/64 
     if cp < prefix_max then 
     return string_char((256 - (2 * prefix_max)) + cp)..s 
     end 
     prefix_max = prefix_max/2 
    end 
    end 
end 

而且還包括一些其他的優化中沒有特別有趣的是,對我來說大約是你優化的給定代碼的2倍。 (作爲獎勵,它應該工作一路攀升至U + 7FFFFFFF以及)

如果我們想微優化甚至更多,循環可以展開到:

do 
    local string_char = string.char 
    function utf8_unrolled(cp) 
    if cp < 128 then 
     return string_char(cp) 
    end 
    local suffix = cp % 64 
    local c4 = 128 + suffix 
    cp = (cp - suffix)/64 
    if cp < 32 then 
     return string_char(192 + cp, c4) 
    end 
    suffix = cp % 64 
    local c3 = 128 + suffix 
    cp = (cp - suffix)/64 
    if cp < 16 then 
     return string_char(224 + cp, c3, c4) 
    end 
    suffix = cp % 64 
    cp = (cp - suffix)/64 
    return string_char(240 + cp, 128 + suffix, c3, c4) 
    end 
end 

這是約爲優化代碼的5倍,但完全不雅。我認爲主要的好處是不必在堆上存儲中間結果,並且功能調用更少。

然而,最快的(只要我能找到)的做法是不要做的計算都:

do 
    local lookup = {} 
    for i=0,0x1FFFFF do 
    lookup[i]=calculate_utf8(i) 
    end 
    function utf8(cp) 
    return lookup[cp] 
    end 
end 

這是約30倍一樣快,你優化的代碼可能有資格作爲「顯着更多高效「(儘管內存使用是荒謬的)。但是,這也不是很有趣。 (在某些情況下,一個很好的折衷辦法是使用記憶。)

當然,任何純粹的c實現都可能比在Lua中完成的任何計算都快。

+0

關於記憶的一個很好的觀點。我一定會補充說,謝謝!我一定會分析一下你的算法,看看是否能獎勵接受。 – Phrogz 2014-10-07 14:20:06

+0

即使沒有展開和記憶您的代碼測試,我的機器上的速度比我的速度快4倍,因此值得一試。 (使用在'1'和'0x10FFFF'之間,以及在'1'和'0x20AC'之間的非現實世界均勻分佈的1000000個隨機碼點)。我確實證實了我們的答案對所有人都產生了相同的結果代碼指向'0x10FFFF'(RFC 3629)。做得好。 – Phrogz 2014-10-07 14:33:54

3

的Lua 5.3提供a basic UTF-8 library,其中功能utf8.char是你在找什麼:

接收零個或多個整數,將每個一個其對應的UTF-8字節序列,並返回一個字符串與所有這些序列的串聯。

c = utf8.char(0x24)  print(c.." is "..#c.." bytes.") --> $ is 1 bytes. 
c = utf8.char(0xA2)  print(c.." is "..#c.." bytes.") --> ¢ is 2 bytes. 
c = utf8.char(0x20AC) print(c.." is "..#c.." bytes.") --> € is 3 bytes. 
c = utf8.char(0xFFFF) print(c.." is "..#c.." bytes.") --> is 3 bytes. 
c = utf8.char(0x10000) print(c.." is "..#c.." bytes.") --> is 4 bytes. 
c = utf8.char(0x24B62) print(c.." is "..#c.." bytes.") --> is 4 bytes. 
+0

該算法可能符合問題提問者的標準。 – 2014-09-28 20:58:48

+0

@TomBlodget滿足我所有的標準,但不幸的是,需要Lua 5.1支持。 – Phrogz 2014-10-07 14:22:03

+0

@Progro你可以重寫它在Lua中,雖然沒有bit32庫,但它看起來並不優雅。 – 2014-10-07 17:55:20

相關問題