17

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

+384 Critical Strike (Reforged from Parry Chance)

作为

(+384) (Critical Strike)

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

长版

我正在尝试使用模式匹配Lua中的字符串(即)strfind

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

示例字符串:

+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

棘手的部分

现在我需要扩展该正则表达式 模式以包含一个可选的后缀:

+384 Critical Strike (Reforged from Parry Chance)

其中分为:

在此处输入图像描述

注意:我并不特别关心可选的尾随后缀;这意味着我不需要捕获它,虽然捕获它会很方便。

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

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

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

在此处输入图像描述

与模式:

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

而且我正在使用?运算符来指示后缀01外观,但不匹配任何内容

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

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

但是现在比赛又变得贪婪了:

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

基于Lua模式参考):

  • 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%-]表示八进制数字加小写字母加上“-”字符。范围和类之间的交互没有定义。因此,像 [%az] 或 [a-%%] 这样的模式没有意义。
  • [^set]:表示 set 的补集,其中 set 解释如上。

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

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

和魔法匹配器:

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

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

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

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

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)

在哪里

  • +表示加号 ( +)“减号” ( -)
  • %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) 负数,两个单词,可选后缀,两个单词

有一些奖励模式看起来很明显这些模式也会匹配:

  • +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 sign,hyphen minus和 拉丁数字09. 我已经知道如何解析本地化数字。这个问题是关于尝试将可选后缀与贪婪模式解析器匹配。

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

4

4 回答 4

8

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

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

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

更新 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
于 2012-11-29T06:39:25.147 回答
1

当您可以使用多个模式时,为什么要在一个模式中解析呢?

首先,获取号码:

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 模式是有限的,所以最好使用实际代码来避免它们的限制。

于 2012-11-29T06:05:06.450 回答
1

简单多了,不只是匹配模式,可以直接找短输出到你需要的字符串,就可以使用了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)
于 2019-05-29T22:31:11.970 回答
0
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 ")))

不是一个单一的模式,但它的工作原理。

于 2012-11-29T06:10:11.473 回答