受到 Shradha Agarwal 在 Byte Size Go 上的帖子的启发:这里:我决定写下我的方法,它是不同的,并且想分享它。那篇文章写得很好,解决方案紧凑而简单,我建议也先阅读该文章。
这是一个 blogvent 系列,我也很想参加 blogvent,但不确定我是否会完成这个。
嗯,现在是 Code 2024 出现的第三天,我一直在直播。我落后了两天,但正在一一解决。到目前为止,我已经在 Go 中学到了很多东西。让我们开始第三天的生活吧。
任何 AOC 问题的第一部分似乎都很简单,但是一旦第二部分被揭示,真正的实现就开始令人费解(如果你不乐观或不深思熟虑的话)
今天的第 1 部分是解析一个包含 mul(X,Y) 表达式的字符串,其中 X 可以是任何 3 位数字。因此,字符串中可能有多个这样的表达式,目的是将单个表达式中的 X 和 Y 相乘并将它们相加。
xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))
在上面的例子中,有 4 个这样的表达式,如果我们将它们相乘,我们得到的答案是 161。
它看起来像正则表达式模式,在字符串中查找类似表达式的模式。因此,方法是使用正则表达式模式找到此类表达式,并将数字解析为整数,然后将它们相乘,简单地。
您可以继续编写解析器来迭代字符串中的每个字符并解析标记,然后评估表达式。这是一种有效的方法,但我选择这样做是因为我不知道如何诚实地编写解析器,我也想在最后尝试该解决方案。
但对于第一部分,快速正则表达式似乎是个好主意。
第一件事是编写 mul(X,Y) 部分的正则表达式,这是第一部分中唯一具有挑战性的部分。剩下的只是简单的数学。
所以,我们需要找到 mul,然后找到 a(然后找到任意 1 到 3 位数字长的数字,然后再找到 1 到 3 位数字长的数字,最后以 a 结尾)
翻译为:
mul\((\d{1,3}),(\d{1,3})\)
让我们细分一下:
mul 用于捕获字面词 mul
(这是表达式 mul() 中的第一个括号,如果我们想匹配它,我们需要在正则表达式中转义括号,所以我们在它之前使用。
然后我们有一个匹配组 (d{1,3}) ,这将是 mul(X,Y) 中的 X:
然后我们使用 , 作为 mul(X,Y) 表达式中 X 和 Y 操作数的分隔符
然后我们以类似的方式将 mul(X,Y) 中的 Y 与 (d{1,3}) 匹配组进行匹配
最后我们用 ) 来结束正则表达式
这非常简单,我们将行作为字符串获取,并使用 regexp.MustCompile 函数,它为我们提供了一个 Regexp 对象,该对象又具有一些与之关联的方法来查找、匹配、替换和其他可以执行的操作使用字符串上的正则表达式来完成。
一旦我们有了 mulRegex ,我们就可以使用与 regexp 包中的 Regexp 结构关联的 FindAllStringSubmatch 方法。该函数接受一个字符串来执行正则表达式,以及要返回的最大匹配数。我们想要所有结果,因此我们将它们传入 -1 以获得所有匹配项。
现在,此方法返回字符串切片的切片,每个切片都是一个匹配项,每个切片内都有一个字符串切片,其中包含匹配的字符串以及字符串中的后续匹配组(如果有)。
xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))
所以,上面的函数将返回类似这样的内容
mul\((\d{1,3}),(\d{1,3})\)
这是一个字符串列表,这些看起来像数字,但它们是 Golang 中的字符串类型。
现在我们有了这个,我们可以创建获取结果的实际逻辑部分,即解析这些表达式,将 X 和 Y 相乘并将每个表达式的结果相加。
func FindMulExpression(line string) [][]string { mulRegex := regexp.MustCompile(`mul\((\d{1,3}),(\d{1,3})\)`) return mulRegex.FindAllStringSubmatch(line, len(line)) }
这非常简单,我们迭代每个匹配项,即一个 mul(X,Y) 表达式,并将 X 和 Y 分别解析为整数并相乘,然后将获得的结果添加到分数中。我们对字符串(行)中找到的每个 mul(X,Y) 表达式执行此操作并返回最终分数。
现在,示例给了我们一个字符串,但我意识到输入中有六行(单独输入),因此我们需要解析每一行并将分数相加。
请记住这一点,因为它在第 2 部分中至关重要,我花了一些时间并质疑我的存在,才意识到我们需要组合所有行才能获得结果(在第 1 部分中不是必需的,但在第 2 部分中肯定是这样)。
这通常是出问题的地方。至少对我来说是这样。
我从一个非常幼稚的方法开始,使用永远循环并找到“做”或“不做”的索引并剥离这些部分,然后循环直到我们没有剩下“做”和“不做”。这对测试用例有效,但在实际输入上失败了。
我最终想出的方法,并通过稍微调整相同的方法来发挥作用。
我想到的是在整个字符串中找到 don’() 和 do() 字符串的第一个匹配位置,我们将其删除并删除 don’t() 之后或 do() 之前的部分。这样我们就可以将字符串修剪为仅有效/启用的 mul() 表达式。
因此,更明确定义的方法将是:
查找 dont() 和 do() 表达式的位置(索引)
我们需要跟踪前一个字符串是启用还是禁用,因此将保留一个标志以将启用的表达式(字符串的一部分)附加到最终结果
如果没有找到,则按原样返回字符串(行)
如果我们找到其中任何一个:
如果我们首先发现don't(dont()出现在do()之前)
如果我们发现do先出现(do()出现在dont()之前)
我们这样做,直到没有剩余的线串需要检查
我使用简单的 Strings.Index 来获取子字符串的第一个匹配索引,在本例中,我想要 dont() 和 do() 的第一个匹配索引。一旦我们有了两个匹配项的索引,我们就可以迭代,直到字符串中不再有任何该做或不该做的事情。
如果我们有 do 或 don ,我们会在字符串中附加 do not 之前的部分(如果启用)或在 do 之前的部分(如果启用),并相应地打开和关闭启用标志。在循环结束时,结果字符串将仅包含行/字符串的启用部分。
xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))
我将这个函数传递给乘法函数,在其中我获取 mul 表达式的匹配模式并进行数学计算。
strings.Index 方法接受一个字符串和一个要在该字符串中查找的子字符串,并返回该子字符串第一个出现的实例的索引。这样我们就可以识别行字符串是否包含 do() 或 dont() 表达式,如果不包含,我们只需返回该行,如果存在它们的实例,我们循环并修剪该行之前和之后的字符串。表达式取决于该标志是启用还是禁用。
让我们举个例子并演练一下逻辑:
xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))
在将结果传递给 FindMulExpression 函数后,我们使用与第一部分中使用的相同的 Multiply 函数来处理结果,该函数将返回结果行字符串中的所有 mul 表达式。
拼图的实际输入我认为是多行,所以我们需要在所有剩余的行中保留该行的状态。或者,我们可以更聪明,创建一个大字符串并处理它。两者都是有效的并且会给出相同的结果。我只是不想增加跟踪所有状态和行的开销,所以我只是连接所有行并处理该单个字符串。
本质上这是一个简单的问题,但如果您不了解正则表达式,您可能会陷入编写自己的解析器或有线字符串操作的兔子洞(就像我一样)。
这就是第三天,我将在周末甚至下周进行更多的直播解决问题。在 GitHub 上查找我的 AoC 解决方案的代码。
到那时,
快乐编码:)
以上是Code n Golang 的出现:做或不做正则表达式的详细内容。更多信息请关注PHP中文网其他相关文章!