【问题标题】:Detect if last character is not multibyte in Lua检测 Lua 中最后一个字符是否不是多字节
【发布时间】:2013-04-05 11:05:55
【问题描述】:

第一个问题。 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

【问题讨论】:

  • 尝试使用正则表达式^(.*).$,然后返回第一个捕获组。我不太确定如何在 Lua 中做到这一点,但我猜这会做到。
  • 对不起:使用表达式^(.*)(.)$,然后返回第一个捕获组删除最后一个字符,或返回第二个组检索最后一个字母。
  • 你的模式看起来相当不错。尝试删除+ 并在末尾添加$+ 将确保您不会拾取额外的单字节字符,$ 将您的模式锚定到字符串的末尾。但是,string.len 会给出字节数,因此lastc 将只包含最后一个字节,而不是整个最后一个字符。
  • string.sub(str, stringLength,stringLength) 确实返回了str 中的最后一个字符。请确保不要将变量命名为 string,因为这与 string 表冲突。另外,您能否详细说明多字节字符的含义?
  • @Netfangled 是的,不,不起作用。它返回最后一个字节。对于第三个和第四个示例,这不是整个最后一个字符(如果是正确的 UTF-8 编码,第二个也不是)。它真的不能工作,因为 Lua 的内置 string 库没有 Unicode 的概念......它的字符串只包含字节,由你来理解它们。

标签: regex lua coronasdk multibyte


【解决方案1】:

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

一些 UTF-8 基础知识:

如果您需要重新了解 Unicode 的概念基础知识,您应该查看this article

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] 匹配 abc。您可以使用连字符定义范围。因此,例如[\33-\127] 匹配从33127 的任何一个字节。请注意,\127 是一个转义序列,您可以在 any 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

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

【讨论】:

  • 非常感谢。我希望我明白你做了什么,但会继续阅读直到我明白为止。该解决方案适用于所有情况,除了希伯来语字符向其他方向移动。
  • @learningphp 我的解决方案与您的模式没有太大不同。如果有什么具体的事情你没有得到,请随时问...也许我可以详细说明一下。
  • 要问的太多了 :)。我根本不知道 [^\128-\191][\128-\191]*$","") 是什么意思。我不知道什么是连续字符。我不了解 UTF 的基础知识等。阅读此stackoverflow.com/questions/9356169/utf-8-continuation-bytes 但这对我来说没有意义,因为这对我来说是全新的。也没有学习过模式匹配。
  • @learningphp 我试图扩展答案,以包括了解解决方案所需的关于 UTF-8 和模式匹配的最低限度知识。我希望它有所帮助。
  • 哇.. 这真的很有帮助。感谢您以这种方式描述它。我不能给你足够多的票!
【解决方案2】:

这是另一种方法;它展示了如何遍历 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
> 

【讨论】:

    【解决方案3】:

    按照prapin的解决方案here

    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 字符串。如果你有一个可能无效的,你可能需要申请一个more complex logic

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-06
      • 2018-10-15
      • 2018-07-17
      • 2020-07-15
      • 2020-01-27
      • 2021-07-08
      • 2011-02-03
      • 1970-01-01
      相关资源
      最近更新 更多