更优雅、更简单的将代码点转换成UTF-8的方式

针对此问题,我编写了以下Lua代码,用于将Unicode代码点转换为UTF-8字符字符串。是否有更好的方法来实现这一点(在Lua 5.1+中)?在这种情况下,“更好的”意味着“效率显着更高,或者最好是代码更少”。

注意:我并不是真正要求这个算法的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,但我无法找出如何优雅地生成“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
点赞
用户1009479
用户1009479

Lua 5.3 提供了基本的 UTF-8 库,其中函数 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.
2014-09-28 03:39:53
用户4117435
用户4117435

如果我们谈论速度,实际应用场景中的使用模式非常重要。但在这里,我们处于真空状态,因此让我们继续吧。

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

2014-10-07 13:56:00