4

第一个问题。Lua中确定字符串中的最后一个字符是否不是多字节的最简单方法是什么。或者从字符串中删除最后一个字符的最简单方法是什么。

以下是有效字符串的示例,以及我希望函数的输出是什么

hello there     --- result should be:   hello ther
anñ             --- result should be:   an
כראע            --- result should be:   כרא
ㅎㄹㅇㅇㅅ       --- result should be:   ㅎㄹㅇㅇ

我需要类似的东西

function lastCharacter(string)
    --- some code which will extract the last character only ---
    return lastChar
end

或者如果它更容易

function deleteLastCharacter(string)
--- some code which will output the string minus the last character --- 
    return newString
end

这就是我要走的路

local function lastChar(string)
    local stringLength = string.len(string)
    local lastc = string.sub(string,stringLength,stringLength)
    if lastc is a multibyte character then
        local wordTable = {}
        for word in string:gmatch("[\33-\127\192-\255]+[\128-\191]*") do
            wordTable[#wordTable+1] = word
        end
    lastc = wordTable[#wordTable]
end
    return lastc
end
4

3 回答 3

9

首先,请注意 Lua 的string库中没有任何函数了解 Unicode/多字节编码(来源:Lua 编程,第 3 版)。就 Lua 而言,字符串只是由字节组成。如果您使用的是 UTF-8 编码字符串,则由您决定哪些字节构成一个字符。因此,string.len会给你字节数,而不是字符数。并且string.sub会给你一个字节子串而不是字符子串。

一些 UTF-8 基础知识:

如果您需要重新了解 Unicode 的概念基础知识,您应该查看这篇文章

UTF-8 是 Unicode 的一种可能(也是非常重要的)实现 - 并且可能是您正在处理的那个。与 UTF-32 和 UTF-16 不同,它使用可变数量的字节(从 1 到 4)对每个字符进行编码。特别是,ASCII 字符 0 到 127 用单个字节表示,因此可以使用 UTF-8 正确解释 ASCII 字符串(反之亦然,如果您只使用这 128 个字符)。所有其他字符都以 194 到 244 范围内的字节开头(这表示后面有更多字节来编码完整字符)。这个范围被进一步细分,因此您可以从这个字节中看出,后面是 1、2 还是 3 个字节。这些额外的字节称为连续字节,并保证只取自 128 到 191 的范围。因此,

  • 如果在 中[0,127],则为单字节 (ASCII) 字符
  • 如果它在 中[128,191],则它是较长字符的一部分,并且本身没有意义
  • 如果它在 中[191,244],它标志着一个较长字符的开始(并告诉我们该字符有多长)

这些信息足以计算字符数、将 UTF-8 字符串拆分为字符并执行各种其他 UTF-8 敏感操作。

一些模式匹配基础知识:

对于手头的任务,我们需要一些 Lua 的模式匹配结构:

[...]是一个字符类,它匹配类中的单个字符(或者更确切地说是byte)。例如,[abc]匹配a、 或bc。您可以使用连字符定义范围。因此[\33-\127],例如,匹配从33到的任何单个字节127。请注意,这\127是一个转义序列,您可以在任何Lua 字符串(不仅仅是模式)中使用它来通过其数值而不是相应的 ASCII 字符来指定一个字节。例如,"a"与 相同"\97"

您可以通过以 ( 开头的字符类来否定它,^以便它匹配属于该类的任何单个字节。

*重复前一个令牌 0 次或更多次(任意多次 - 尽可能频繁)。

$是一个锚。如果它是模式的最后一个字符,则模式将只匹配字符串的末尾。

结合所有这些...

...您的问题简化为单行:

local function lastChar(s)
    return string.match(s, "[^\128-\191][\128-\191]*$")
end

这将匹配不是 UTF-8 连续字符的字符(即,单字节字符或标记较长字符开头的字节)。然后它匹配任意数量的连续字符(由于选择的范围,这不能超过当前字符),然后是字符串的结尾($)。因此,这将为您提供构成字符串中最后一个字符的所有字节。它为您的所有 4 个示例生成所需的输出。

等效地,您可以使用gsub从字符串中删除最后一个字符:

function deleteLastCharacter(s)
    return string.gsub(s, "[^\128-\191][\128-\191]*$", "")
end

匹配是相同的,但是我们不返回匹配的子字符串,而是将其替换为""(即删除它)并返回修改后的字符串。

于 2013-04-12T20:56:17.990 回答
4

这是另一种方法;它显示了如何遍历 utf8 中的字符串:

function butlast (str)
    local i,j,k = 1,0,-1
    while true do
        s,e = string.find(str,".[\128-\191]*",i)
        if s then
            k = j
            j = e
            i = e + 1
        else break end
    end
    return string.sub(str,1,k)
end

样品用途:

> return butlast"כראע"
כרא
> return butlast"ㅎㄹㅇㅇㅅ"
ㅎㄹㅇㅇ
> return butlast"anñ"
an
> return butlast"hello there"
hello ther
> 
于 2013-04-12T21:14:41.120 回答
3

在这里通过 prapin 的解决方案:

function lastCharacter(str)
  return str:match("[%z\1-\127\194-\244][\128-\191]*$")
end

然后您可以获取返回值的长度以查看它是否为多字节;您还可以使用gsub以下函数将其从字符串中删除:

function deleteLastCharacter(str)
  -- make sure to add "()" around gsub to force it to return only one value
  return(str:gsub("[%z\1-\127\194-\244][\128-\191]*$", ""))
end

for _, str in pairs{"hello there", "anñ", "כראע"} do
  print(str, " -->-- ", deleteLastCharacter(str))
end

请注意,这些模式仅适用于有效的 UTF-8 字符串。如果你有一个可能无效的,你可能需要应用更复杂的逻辑

于 2013-04-12T21:14:04.817 回答