对于这个问题,我创建了以下Lua代码,将Unicode代码点转换为UTF-8字符串.有没有更好的方法(在Lua 5.1+中)?在这种情况下,"更好"意味着"显着提高效率,或者更优选更少的代码行".
注意:我并不是真的要求对此算法进行代码审查 ; 我要求更好的算法(或内置库).
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.
Run Code Online (Sandbox Code Playgroud)
我觉得应该有一种方法摆脱整个bytebits
预定义的表和循环只是为了找到匹配的条目.从后面循环我可以不断%64
添加128
以形成延续字节,直到值低于128,但我无法弄清楚如何优雅地生成要添加的0
/ 110
/ 1110
/ 11110
前导码.
编辑:这是一个稍微好一点的返工,速度优化.然而,这不是一个可接受的答案,因为算法仍然基本上是相同的想法和大约相同数量的代码.
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
Run Code Online (Sandbox Code Playgroud)
如果我们谈论速度,那么现实场景中的使用模式非常重要。但在这里,我们处于真空状态,所以无论如何我们都要继续。
当你说你应该能够摆脱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
Run Code Online (Sandbox Code Playgroud)
它还包括一些其他并不是特别有趣的优化,对我来说,速度大约是优化的给定代码的 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
Run Code Online (Sandbox Code Playgroud)
这大约是优化代码的 5 倍,但完全不优雅。我认为主要的好处是不必在堆上存储中间结果并且减少函数调用。
然而,最快的(据我所知)方法是根本不进行计算:
do
local lookup = {}
for i=0,0x1FFFFF do
lookup[i]=calculate_utf8(i)
end
function utf8(cp)
return lookup[cp]
end
end
Run Code Online (Sandbox Code Playgroud)
这大约是优化代码的 30 倍,优化后的代码可能会“效率大大提高”(尽管内存使用量非常可笑)。然而,这也没什么意思。(在某些情况下,一个好的折衷方案是使用记忆。)
当然,任何纯 c 实现都可能比 Lua 中完成的任何计算更快。
归档时间: |
|
查看次数: |
706 次 |
最近记录: |