【问题标题】:Greedy/Non-Greedy pattern matching and optional suffixes in LuaLua 中的贪婪/非贪婪模式匹配和可选后缀
【发布时间】:2012-11-17 03:18:12
【问题描述】:

在 Lua 中,我正在尝试模式匹配和捕获:

+384 Critical Strike (Reforged from Parry Chance)

作为

(+384) (Critical Strike)

其中后缀 (Reforged from %s) 是可选的。

加长版

我正在尝试匹配 Lua using patterns (i.e. strfind) 中的字符串

注意:在 Lua 中,他们不称它们为正则表达式,而是称它们为模式,因为它们不是regular

示例字符串:

+384 Critical Strike
+1128 Hit

这分为我要捕获的两个部分:

  • 数字,带有前导的正或负指示符;他的情况是+384
  • 字符串,在本例中为Critical Strike

我可以使用相当简单的模式来捕捉这些:

这种模式在 lua 中有效:

local text = "+384 Critical Strike";
local pattern = "([%+%-]%d+) (.+)";
local _, _, value, stat = strfind(text, pattern);
  • 值 = +384
  • 统计 = Critical Strike

棘手的部分

Now 我需要扩展那个正则表达式 pattern 以包含一个可选 后缀:

+384 Critical Strike (Reforged from Parry Chance)

分为:

注意:我不特别关心可选的尾随后缀;这意味着我没有要求来捕捉它,虽然捕捉它会很方便。

这就是我开始解决贪婪捕获问题的地方。我已经拥有的模式立即做了我不希望它做的事情:

  • 模式 = ([%+%-]%d+) (.+)
  • 值 = +384
  • 统计 = Critical Strike (Reforged from Parry Chance)

但是让我们尝试在模式中包含后缀:

与模式:

pattern = "([%+%-]%d+) (.+)( %(Reforged from .+%))?"

我使用? 运算符来指示后缀的01 出现但匹配nothing

盲目地尝试将可选的后缀组从括号(更改为括号[

pattern = "([%+%-]%d+) (.+)[ %(Reforged from .+%)]?"

但现在比赛又贪心了:

  • 值 = +384
  • 统计 = Critical Strike (Reforged from Parry Chance)

基于Lua pattern reference):

  • x:(其中 x 不是魔法字符之一 ^$()%.[]*+-?)表示字符 x 本身。
  • .:(一个点)代表所有字符。
  • %a:代表所有字母。
  • %c:代表所有控制字符。
  • %d:代表所有数字。
  • %l:代表所有小写字母。
  • %p:代表所有标点符号。
  • %s:代表所有空格字符。
  • %u:代表全部大写字母。
  • %w:代表所有字母数字字符。
  • %x:代表所有十六进制数字。
  • %z:表示表示为0的字符。
  • %x:(其中 x 是任何非字母数字字符)表示字符 x。这是逃脱魔法角色的标准方法。任何标点符号(即使是非魔法符号)在用于在模式中表示自身时都可以在前面加上“%”。
  • [set]:表示集合中所有字符联合的类。可以通过用“-”分隔范围的结束字符来指定字符范围。上面描述的所有类 %x 也可以用作集合中的组件。集合中的所有其他角色都代表他们自己。例如,[%w_](或[_%w])表示所有字母数字字符加下划线,[0-7]表示八进制数字,[0-7%l%-]表示八进制数字加小写字母加上“-”字符。 范围和类之间的交互没有定义。因此,像 [%a-z] 或 [a-%%] 这样的模式没有意义。
  • [^set]:表示set的补集,其中set的解释如上。

对于所有由单个字母(%a、%c等)表示的类,对应的大写字母代表类的补码。例如,%S 代表所有非空格字符。

字母、空格和其他字符组的定义取决于当前的语言环境。特别是,[a-z] 类可能不等同于 %l。

还有魔法匹配器:

  • *,匹配类中 0 个或多个重复字符。这些重复项将始终匹配可能的最长序列;
  • +,匹配类中的 1 个或多个重复字符。这些重复项将始终匹配可能的最长序列;
  • -,也匹配类中 0 个或多个重复字符。与 '*' 不同,这些重复项将始终匹配最短的可能序列;
  • ?,匹配类中出现的 0 或 1 个字符;

我注意到有一个 greedy * 和一个 non-greedy - 修饰符。由于我的中间字符串匹配器:

(%d) (%s) (%s)

似乎一直在吸收文本,也许我应该尝试通过将 * 更改为 - 来使其不贪婪

oldPattern = "([%+%-]%d+) (.*)[ %(Reforged from .+%)]?"
newPattern = "([%+%-]%d+) (.-)[ %(Reforged from .+%)]?"

除了现在不匹配:

  • 值 = +384
  • 统计 =

而不是中间组捕获“任何”字符(即.),我尝试了一个包含所有内容的集合除了 (

pattern = "([%+%-]%d+) ([^%(]*)( %(Reforged from .+%))?"

然后车轮从马车上脱落:

local pattern = "([%+%-]%d+) ([^%(]*)( %(Reforged from .+%))?"
local pattern = "([%+%-]%d+) ((^%()*)( %(Reforged from .+%))?"
local pattern = "([%+%-]%d+) (%a )+)[ %(Reforged from .+%)]?"

我以为我很接近:

local pattern = "([%+%-]%d+) ([%a ]+)[ %(Reforged from .+%)]?"

捕获

- value = "+385"
- stat = "Critical Strike "  (notice the trailing space)

所以这就是我用头撞枕头睡觉的地方;我不敢相信我在这个正则表达式上花了四个小时......模式


@NicolBolas 使用伪正则表达式语言定义的所有可能字符串的集合是:

+%d %s (Reforged from %s)

在哪里

  • + 代表Plus Sign (+)"Minus Sign" (-)
  • %d 代表任何拉丁数字字符(例如0..9
  • %s 代表任何拉丁文大写或小写字母,或嵌入的空格(例如A-Za-z
  • 其余字符为文字。

如果我必须编写一个明显试图做我想做的事情的正则表达式:

\+\-\d+ [\w\s]+( \(Reforged from [\w\s]+\))?

但如果我解释得不够清楚,我可以给你几乎完整的列表,列出我在野外可能遇到的所有价值观。

  • +123 Parry 正数,单字
  • +123 Critical Strike 正数,两个字
  • -123 Parry 负数,单字
  • -123 Critical Strike 负数,两个字
  • +123 Parry (Reforged from Dodge) 正数,单字,单字可选后缀
  • +123 Critical Strike (Reforged from Dodge) 正数,两个单词,可选后缀出现两个单词
  • -123 Parry (Reforged from Hit Chance) 负数,单个单词,可选后缀出现两个单词
  • -123 Critical Strike (Reforged from Hit Chance) 负数,两个单词,可选的后缀出现两个单词

bonus 模式,很明显这些模式也会匹配:

  • +1234 Critical Strike Chance 四位数字,三个字
  • +12345 Mount and run speed increase 五位数字,五个字
  • +123456 Mount and run speed increase 六位数字,五个字
  • -1 MoUnT aNd RuN sPeEd InCrEaSe 一个数字,五个字
  • -1 HiT (Reforged from CrItIcAl StRiKe ChAnCe) 负的一位数字,一个单词,可选的后缀有 3 个单词

虽然理想模式应该与上述奖励条目匹配,但它没有到。

本地化

实际上我试图解析的所有“数字”都将被本地化,例如:

  • +123,456 英语(en-US)
  • +123.456 在德国(de-DE)
  • +123'456 法语 (fr-CA)
  • +123 456 爱沙尼亚语 (et-EE)
  • +1,23,456 阿萨姆语(as-IN)

任何答案都必须试图解释这些本地化问题。您不知道将显示数字的区域设置,这就是为什么从问题中删除了数字本地化的原因。您必须严格假设数字包含 plus signhyphen minus 和拉丁数字 09。我已经知道如何解析本地化数字。这个问题是关于尝试将可选后缀与贪婪模式解析器匹配。

编辑:您真的不必尝试处理本地化号码。在某种程度上试图在不知道语言环境的情况下处理它们是错误的。例如,我没有包括所有可能的数字本地化。另一方面:我不知道未来可能存在哪些本地化。

【问题讨论】:

  • 很难提供答案,因为您似乎没有描述所有可能的字符串。
  • 函数strfind来自旧版Lua 4.0。你真的在使用那个旧版本吗?
  • @prapin 从文字内容来看,这是魔兽世界中的Lua,也就是Lua 5.1。 strfind 可能只是 strfind = string.find

标签: lua


【解决方案1】:

嗯,我没有安装 Lua4,但这种模式在 Lua5 下有效。我希望它也适用于 Lua4。

更新 1:由于已指定附加要求(本地化),我已调整模式和测试以反映这些要求。

更新 2:更新了模式和测试以处理包含 @IanBoyd 在 cmets 中提到的数字的附加文本类。添加了解释 的字符串模式。

更新 3:针对问题的最后更新中提到的单独处理本地化号码的情况添加了变体。

试试:

"(([%+%-][',%.%d%s]-[%d]+)%s*([%a]+[^%(^%)]+[%a]+)%s*(%(?[%a%s]*%)?))"

或(不尝试验证数字本地化标记)- 只取任何不是字母且在模式末尾带有数字标记的字母:

"(([%+%-][^%a]-[%d]+)%s*([%a]+[^%(^%)]+[%a]+)%s*(%(?[%a%s]*%)?))"

上述两种模式都不是为了处理科学记数法中的数字(例如:1.23e+10)

Lua5 测试(经过编辑以清理 - 测试变得混乱):

function test(tab, pattern)
   for i,v in ipairs(tab) do
     local f1, f2, f3, f4 = v:match(pattern)
     print(string.format("Test{%d} - Whole:{%s}\nFirst:{%s}\nSecond:{%s}\nThird:{%s}\n",i, f1, f2, f3, f4))
   end
 end

 local pattern = "(([%+%-][',%.%d%s]-[%d]+)%s*([%a]+[^%(^%)]+[%a]+)%s*(%(?[%a%s]*%)?))"
 local testing = {"+123 Parry",
   "+123 Critical Strike",
   "-123 Parry",
   "-123 Critical Strike",
   "+123 Parry (Reforged from Dodge)",
   "+123 Critical Strike (Reforged from Dodge)",
   "-123 Parry (Reforged from Hit Chance)",
   "-123 Critical Strike (Reforged from Hit Chance)",
   "+122384    Critical    Strike      (Reforged from parry chance)",
   "+384 Critical Strike ",
   "+384Critical Strike (Reforged from parry chance)",
   "+1234 Critical Strike Chance (Reforged from CrItIcAl StRiKe ChAnCe)",
   "+12345 Mount and run speed increase (Reforged from CrItIcAl StRiKe ChAnCe)",
   "+123456 Mount and run speed increase (Reforged from CrItIcAl StRiKe ChAnCe)",
   "-1 MoUnT aNd RuN sPeEd InCrEaSe (Reforged from CrItIcAl StRiKe ChAnCe)",
   "-1 HiT (Reforged from CrItIcAl StRiKe ChAnCe)",
   "+123,456 +1234 Critical Strike Chance (Reforged from CrItIcAl StRiKe ChAnCe)",
   "+123.456 Critical Strike Chance (Reforged from CrItIcAl StRiKe ChAnCe)",
   "+123'456 Critical Strike Chance (Reforged from CrItIcAl StRiKe ChAnCe)",
   "+123 456 Critical Strike Chance (Reforged from CrItIcAl StRiKe ChAnCe)",
   "+1,23,456 Critical Strike Chance (Reforged from CrItIcAl StRiKe ChAnCe)",
   "+9 mana every 5 sec",
   "-9 mana every 20 min (Does not occurr in data but gets captured if there)"}
 test(testing, pattern)

以下是模式的细分:

local explainPattern =  
   "(" -- start whole string capture
   ..
   --[[
   capture localized number with sign - 
   take at first as few digits and separators as you can 
   ensuring the capture ends with at least 1 digit
   (the last digit is our sentinel enforcing the boundary)]]
   "([%+%-][',%.%d%s]-[%d]+)" 
   ..
   --[[
   gobble as much space as you can]]
   "%s*"
   ..
   --[[
   capture start with letters, followed by anything which is not a bracket 
   ending with at least 1 letter]]
   "([%a]+[^%(^%)]+[%a]+)"
   ..
   --[[
   gobble as much space as you can]]
   "%s*"
   ..
   --[[
   capture an optional bracket
   followed by 0 or more letters and spaces
   ending with an optional bracket]]
   "(%(?[%a%s]*%)?)"
   .. 
   ")" -- end whole string capture

【讨论】:

  • 对于“+384 暴击”,您的模式捕获“+384”、“暴击”和“e”。
  • 第二次捕获包括尾随空格(当存在后缀时,( 前面的空格)。不要认为有任何解决办法。
  • 实际上有 - 这是一个棘手的问题,但更新了示例。
  • 我使用了你以前的模式化身,因为我有一个库,它知道如何匹配(仅)在当前语言环境中的数字。为了满足我所有的单元测试,我确实必须对答案进行一次调整。我忘记了我必须解析的一类文本+9 Mana every 5 sec.。在这种情况下,我想捕获+9Mana every 5 sec。此类输入文本(实际上)永远不会跟随可选后缀。我将把它作为一个练习来找出处理这种情况的调整。
  • 干得好;我永远也想不通。也因为我不知道它现在是如何工作的!
【解决方案2】:

既然可以使用多个,为什么要在 one 模式中解析它?

首先,获取号码:

local num, rest = string.match(test_string, "([%+%-]?%d+)%S*(.+)")

然后制作一个表格,列出命中类型的可能性。

local hitTypes =
{
  "Hit",
  "Critical Strike",
  -- Insert more.
}

现在,遍历列表,对每一个进行测试。

local hitIndex = nil
local reforge = nil

for i, htype in ipairs(hitTypes) do
  local final = string.match(rest, htype .. "%S*(.*)")
  if(final) then
    hitIndex = i
    reforge = string.match(final, "%(Reforged from (.+)%)")
  end
end

Lua 模式是有限的,所以最好使用实际代码来避免它们的限制。

【讨论】:

  • “当你可以使用多个模式时,为什么要在一个模式中解析呢?”这看起来像 WoW 中的战斗日志解析。您可能会在每一帧中都进行 很多 的操作,并且您不想降低帧率。如果你能帮忙的话,通过字符串 1 次比 N 次要好得多。
  • @Mud:那么每帧只做5个;缓冲其余部分并从缓冲区中提取每一帧。
  • 随着你越来越落后,缓冲区会无限增长。没有实际意义,真的;我相信你的方法足够快。我只是回答你提出的问题:你通过数据而不是几个,因为它更快(probably)。无论如何,Anthill 发布了一个模式,可以一次性捕获 OP 想要的东西。
【解决方案3】:

简单多了,不只是匹配模式,可以直接查找短输出到你需要的字符串,可以使用string.gsub

例子:

local testing = {"+123 Parry",
"+123 Critical Strike",
"-123 Parry",
"-123 Critical Strike",
"+123 Parry (Reforged from Dodge)",
"+123 Critical Strike (Reforged from Dodge)",
"-123 Parry (Reforged from Hit Chance)",
"-123 Critical Strike (Reforged from Hit Chance)",
"+122384    Critical    Strike      (Reforged from parry chance)",
"+384 Critical Strike ",
"+384Critical Strike (Reforged from parry chance)",
"+1234 Critical Strike Chance (Reforged from CrItIcAl StRiKe ChAnCe)",
"+12345 Mount and run speed increase (Reforged from CrItIcAl StRiKe ChAnCe)",
"+123456 Mount and run speed increase (Reforged from CrItIcAl StRiKe ChAnCe)",
"-1 MoUnT aNd RuN sPeEd InCrEaSe (Reforged from CrItIcAl StRiKe ChAnCe)",
"-1 HiT (Reforged from CrItIcAl StRiKe ChAnCe)",
"+123,456 +1234 Critical Strike Chance (Reforged from CrItIcAl StRiKe ChAnCe)",
"+123.456 Critical Strike Chance (Reforged from CrItIcAl StRiKe ChAnCe)",
"+123'456 Critical Strike Chance (Reforged from CrItIcAl StRiKe ChAnCe)",
"+123 456 Critical Strike Chance (Reforged from CrItIcAl StRiKe ChAnCe)",
"+1,23,456 Critical Strike Chance (Reforged from CrItIcAl StRiKe ChAnCe)",
"+9 mana every 5 sec",
"-9 mana every 20 min (Does not occurr in data but gets captured if there)"}

for k,v in ipairs(testing) do
  local result = string.gsub(v, "([%+%-][',%.%d%s]-[%+%d]+)%s*([%a]+[^%(^%)]+[%a]+)%s*(%(?[%a%s]*%)?)", '(%1) (%2) %3')
  print(result)
end

输出

(+123) (Parry) 
(+123) (Critical Strike) 
(-123) (Parry) 
(-123) (Critical Strike) 
(+123) (Parry) (Reforged from Dodge)
(+123) (Critical Strike) (Reforged from Dodge)
(-123) (Parry) (Reforged from Hit Chance)
(-123) (Critical Strike) (Reforged from Hit Chance)
(+122384) (Critical    Strike) (Reforged from parry chance)
(+384) (Critical Strike) 
(+384) (Critical Strike) (Reforged from parry chance)
(+1234) (Critical Strike Chance) (Reforged from CrItIcAl StRiKe ChAnCe)
(+12345) (Mount and run speed increase) (Reforged from CrItIcAl StRiKe ChAnCe)
(+123456) (Mount and run speed increase) (Reforged from CrItIcAl StRiKe ChAnCe)
(-1) (MoUnT aNd RuN sPeEd InCrEaSe) (Reforged from CrItIcAl StRiKe ChAnCe)
(-1) (HiT) (Reforged from CrItIcAl StRiKe ChAnCe)
(+123,456 +1234) (Critical Strike Chance) (Reforged from CrItIcAl StRiKe ChAnCe)
(+123.456) (Critical Strike Chance) (Reforged from CrItIcAl StRiKe ChAnCe)
(+123'456) (Critical Strike Chance) (Reforged from CrItIcAl StRiKe ChAnCe)
(+123 456) (Critical Strike Chance) (Reforged from CrItIcAl StRiKe ChAnCe)
(+1,23,456) (Critical Strike Chance) (Reforged from CrItIcAl StRiKe ChAnCe)
(+9) (mana every 5 sec) 
(-9) (mana every 20 min) (Does not occurr in data but gets captured if there)

【讨论】:

    【解决方案4】:
    function match_some_stat_thing(str)
        local sign, amount, label, note = string.match(str.."()", "^([%+%-])(%d+) ([%a ]-) ?(%b())")
        return sign == "+" and amount or -amount, label, string.match(note, "%((.*)%)")
    end
    print(string.format("%d %q %q", match_some_stat_thing("+384 Critical Strike (Reforged from Parry Chance)")))
    print(string.format("%d %q %q", match_some_stat_thing("+384 Critical Strike")))
    print(string.format("%d %q %q", match_some_stat_thing("+384 Critical Strike ")))
    

    不是一个单一的模式,但它有效。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-29
      • 1970-01-01
      • 1970-01-01
      • 2017-10-16
      • 2015-02-11
      • 2014-07-06
      • 1970-01-01
      • 2011-05-04
      相关资源
      最近更新 更多