【问题标题】:How to simulate negative lookbehind in Go如何在 Go 中模拟负向回溯
【发布时间】:2016-02-04 12:59:20
【问题描述】:

我正在尝试编写一个可以提取命令的正则表达式,这是我目前使用否定后向断言得到的结果:

\b(?<![@#\/])\w.*

所以输入:

/msg @nickname #channel foo bar baz
/foo #channel @nickname foo bar baz 
foo bar baz

foo bar baz 每次都被提取。查看工作示例 https://regex101.com/r/lF9aG7/3

但是在 Go 中这不会编译 http://play.golang.org/p/gkkVZgScS_

它抛出:

panic: regexp: Compile(`\b(?<![@#\/])\w.*`): error parsing regexp: invalid or unsupported Perl syntax: `(?<`

我做了一些研究,发现该语言不支持负向回溯以保证 O(n) 时间。

我怎样才能重写这个正则表达式,以便它在没有负面后视的情况下做同样的事情?

【问题讨论】:

  • (?:^|[^@#/])\b(\w.*) 怎么样?
  • 你能举一个你想要输出的例子吗?
  • 在“foo \msg some stuff @name other stuff #chan blah blah”的情况下应该怎么做?
  • @MayurKosht​​i 操场链接显示预期输出,给定各种输入。
  • 我在下面更新了我的回复,以使用字符串过滤而不是正则表达式。当然,这只有在 您要过滤掉所有以 [#@/] 中的字符开头的单词时才有效。

标签: regex go


【解决方案1】:

由于在您的否定后视中,您只使用了一个简单的字符集;您可以将其替换为否定字符集:

\b[^@#/]\w.*

如果允许在字符串的开头,则使用 ^ 锚点:

(?:^|[^@#\/])\b\w.*

根据您问题中 Go 游乐场链接中的示例,我认为您希望过滤掉所有以 [#@/] 中的字符开头的单词。您可以使用filter 函数:

func Filter(vs []string, f func(string) bool) []string {
    vsf := make([]string, 0)
    for _, v := range vs {
        if f(v) {
            vsf = append(vsf, v)
        }
    }
    return vsf
}

还有一个Process 函数,它利用了上面的过滤器:

func Process(inp string) string {
    t := strings.Split(inp, " ")
    t = Filter(t, func(x string) bool {
        return strings.Index(x, "#") != 0 &&
            strings.Index(x, "@") != 0 &&
            strings.Index(x, "/") != 0
    })
    return strings.Join(t, " ")
}

可以在playgroundhttp://play.golang.org/p/ntJRNxJTxo 上看到它的作用

【讨论】:

  • 但这并不是正确的事情(TM)。后视用于排除 /foo 之类的匹配项 - 正则表达式匹配 foo,忽略斜线。幸运的是,可以通过将单词边界锚点向右移动一个标记来改进(在字符类/非捕获组之后)。
  • 查看 Go 操场示例,我认为您实际上想要捕获不以 [#@/] 字符之一开头的单词。对吗?
  • 看起来这就是他想要做的。至少这就是带有lookbehind的正则表达式正在做的事情。
  • @TimPietzcker 我想在这种情况下使用字符串过滤器是一种更好的方法。我也会通知 OP。 =)
  • 是的,更像是捕获一个不以任何 [#@/] 开头的单词开头的句子
【解决方案2】:

您实际上可以匹配前面的字符(或行首)并使用组来获取子表达式中所需的文本。

正则表达式

(?:^|[^@#/])\b(\w+)
  • (?:^|[^@#/]) 匹配^ 行首或[^@#/]@#/ 之外的任何字符
  • \b 断言单词开头的单词边界
  • (\w+) 生成子表达式
    • 并匹配\w+任意数量的单词字符

代码

cmds := []string{
    `/msg @nickname #channel foo bar baz`,
    `#channel @nickname foo bar baz /foo`,
    `foo bar baz @nickname #channel`,
    `foo bar baz#channel`}

regex := regexp.MustCompile(`(?:^|[^@#/])\b(\w+)`)


// Loop all cmds
for _, cmd := range cmds{
    // Find all matches and subexpressions
    matches := regex.FindAllStringSubmatch(cmd, -1)

    fmt.Printf("`%v` \t==>\n", cmd)

    // Loop all matches
    for n, match := range matches {
        // match[1] holds the text matched by the first subexpression (1st set of parentheses)
        fmt.Printf("\t%v. `%v`\n", n, match[1])
    }
}

输出

`/msg @nickname #channel foo bar baz`   ==>
    0. `foo`
    1. `bar`
    2. `baz`
`#channel @nickname foo bar baz /foo`   ==>
    0. `foo`
    1. `bar`
    2. `baz`
`foo bar baz @nickname #channel`    ==>
    0. `foo`
    1. `bar`
    2. `baz`
`foo bar baz#channel`   ==>
    0. `foo`
    1. `bar`
    2. `baz`

游乐场
http://play.golang.org/p/AaX9Cg-7Vx

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-03-22
    • 2015-07-12
    • 1970-01-01
    • 1970-01-01
    • 2017-08-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多