2014-09-27 51 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

相关问题