更优雅,更简单的方法将代码转换为UTF-8

Phr*_*ogz 5 lua utf-8

对于这个问题,我创建了以下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)

teh*_*tmi 2

如果我们谈论速度,那么现实场景中的使用模式非常重要。但在这里,我们处于真空状态,所以无论如何我们都要继续。

当你说你应该能够摆脱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 中完成的任何计算更快。