【问题标题】:How to get capturing group functionality in Go regular expressions如何在 Go 正则表达式中获取捕获组功能
【发布时间】:2015-05-27 13:17:06
【问题描述】:

我正在将一个库从 Ruby 移植到 Go,并且刚刚发现 Ruby 中的正则表达式与 Go (google RE2) 不兼容。我注意到 Ruby 和 Java(加上其他语言使用 PCRE 正则表达式(perl 兼容,支持捕获组)),所以我需要重新编写我的表达式,以便它们在 Go 中编译正常。

例如,我有以下正则表达式:

`(?<Year>\d{4})-(?<Month>\d{2})-(?<Day>\d{2})`

这应该接受如下输入:

2001-01-20

捕获组允许将年、月和日捕获到变量中。要得到每个组的值,很容易;您只需使用组名索引返回的匹配数据,然后返回值。因此,例如要获取年份,类似于以下伪代码:

m=expression.Match("2001-01-20")
year = m["Year"]

这是我在表达中经常使用的模式,所以我有很多重写工作要做。

那么,有没有办法在 Go 正则表达式中获得这种功能?我应该如何重写这些表达式?

【问题讨论】:

    标签: regex go capture-group


    【解决方案1】:

    我应该如何重写这些表达式?

    添加一些Ps,定义为here:

    (?P<Year>\d{4})-(?P<Month>\d{2})-(?P<Day>\d{2})
    

    使用re.SubexpNames() 交叉引用捕获组名称。

    并使用as follows

    package main
    
    import (
        "fmt"
        "regexp"
    )
    
    func main() {
        r := regexp.MustCompile(`(?P<Year>\d{4})-(?P<Month>\d{2})-(?P<Day>\d{2})`)
        fmt.Printf("%#v\n", r.FindStringSubmatch(`2015-05-27`))
        fmt.Printf("%#v\n", r.SubexpNames())
    }
    

    【讨论】:

    • 好吧,这看起来很鼓舞人心,但是我如何才能访问各个值、年、月和日?
    • 忘记最后的评论,我刚刚找到了答案。正如你所说,它全部在 ?P 中:)
    • 我仍然对此感到困惑;我不确定它们是否可以按年、月等进行寻址。我返回一个包含四个值的数组并可以对其进行索引,仅此而已。
    • @thwd 现在,这是一个问题:如果您以相同的方式命名两个组会发生什么?这不是一个定义明确的行为,但正则表达式编译器不会抱怨它。例如,您的代码会丢弃第一个匹配项,但我可以想象在某些情况下,除了第一个之外,将所有匹配项都扔掉是有意义的,或者可能收集所有匹配项......设计一种语言有很多微妙之处......
    • @VladimirBauer 我不确定你在说什么。我知道它不是特定于 Go 的,我认为特别是在 Go 中,这个特性的内置库实现很糟糕,因为它复制了这个库的另一个更简单的特性,但是带有一个额外的无意义的句法元素。
    【解决方案2】:

    我创建了一个用于处理 url 表达式的函数,但它也适合您的需求。你可以查看thissn-p 但它的工作原理是这样的:

    /**
     * Parses url with the given regular expression and returns the 
     * group values defined in the expression.
     *
     */
    func getParams(regEx, url string) (paramsMap map[string]string) {
    
        var compRegEx = regexp.MustCompile(regEx)
        match := compRegEx.FindStringSubmatch(url)
    
        paramsMap = make(map[string]string)
        for i, name := range compRegEx.SubexpNames() {
            if i > 0 && i <= len(match) {
                paramsMap[name] = match[i]
            }
        }
        return paramsMap
    }
    

    你可以像这样使用这个函数:

    params := getParams(`(?P<Year>\d{4})-(?P<Month>\d{2})-(?P<Day>\d{2})`, `2015-05-27`)
    fmt.Println(params)
    

    输出将是:

    map[Year:2015 Month:05 Day:27]
    

    【讨论】:

      【解决方案3】:

      要提高 RAM 和 CPU 使用率,而无需在循环内调用匿名函数,也无需在循环内使用“append”函数复制内存中的数组,请参见下一个示例:

      您可以存储多个包含多行文本的子组,无需在字符串中附加“+”,也无需在 for 循环中使用 for 循环(与此处发布的其他示例一样)。

      txt := `2001-01-20
      2009-03-22
      2018-02-25
      2018-06-07`
      
      regex := *regexp.MustCompile(`(?s)(\d{4})-(\d{2})-(\d{2})`)
      res := regex.FindAllStringSubmatch(txt, -1)
      for i := range res {
          //like Java: match.group(1), match.gropu(2), etc
          fmt.Printf("year: %s, month: %s, day: %s\n", res[i][1], res[i][2], res[i][3])
      }
      

      输出:

      year: 2001, month: 01, day: 20
      year: 2009, month: 03, day: 22
      year: 2018, month: 02, day: 25
      year: 2018, month: 06, day: 07
      

      注意:res[​​i][0] =~ match.group(0) Java

      如果要存储此信息,请使用结构类型:

      type date struct {
        y,m,d int
      }
      ...
      func main() {
         ...
         dates := make([]date, 0, len(res))
         for ... {
            dates[index] = date{y: res[index][1], m: res[index][2], d: res[index][3]}
         }
      }
      

      最好使用匿名组(性能提升)

      使用发布在 Github 上的“ReplaceAllGroupFunc”是个坏主意,因为:

      1. 在循环内使用循环
      2. 在循环内使用匿名函数调用
      3. 有很多代码
      4. 在循环内使用“追加”函数,这很糟糕。 每次调用“追加”函数时,都会将数组复制到新的内存位置

      【讨论】:

      • 是的,如果您考虑到浪费的时钟周期、浪费的 RAM 等,则有更好和更差的解决方案。谦虚一点,您可以让农民在生产中发布代码。
      【解决方案4】:

      从 GO 1.15 开始,您可以使用 Regexp.SubexpIndex 简化流程。您可以通过https://golang.org/doc/go1.15#regexp查看发行说明。

      根据您的示例,您将拥有以下内容:

      re := regexp.MustCompile(`(?P<Year>\d{4})-(?P<Month>\d{2})-(?P<Day>\d{2})`)
      matches := re.FindStringSubmatch("Some random date: 2001-01-20")
      yearIndex := re.SubexpIndex("Year")
      fmt.Println(matches[yearIndex])
      

      您可以在https://play.golang.org/p/ImJ7i_ZQ3Hu查看并执行此示例。

      【讨论】:

        【解决方案5】:

        根据@VasileM 答案确定组名的简单方法。

        免责声明:这与内存/cpu/时间优化无关

        package main
        
        import (
            "fmt"
            "regexp"
        )
        
        func main() {
            r := regexp.MustCompile(`^(?P<Year>\d{4})-(?P<Month>\d{2})-(?P<Day>\d{2})$`)
        
            res := r.FindStringSubmatch(`2015-05-27`)
            names := r.SubexpNames()
            for i, _ := range res {
                if i != 0 {
                    fmt.Println(names[i], res[i])
                }
            }
        }
        

        https://play.golang.org/p/Y9cIVhMa2pU

        【讨论】:

          【解决方案6】:

          如果你需要在捕获组时基于函数进行替换,你可以使用这个:

          import "regexp"
          
          func ReplaceAllGroupFunc(re *regexp.Regexp, str string, repl func([]string) string) string {
              result := ""
              lastIndex := 0
          
              for _, v := range re.FindAllSubmatchIndex([]byte(str), -1) {
                  groups := []string{}
                  for i := 0; i < len(v); i += 2 {
                      groups = append(groups, str[v[i]:v[i+1]])
                  }
          
                  result += str[lastIndex:v[0]] + repl(groups)
                  lastIndex = v[1]
              }
          
              return result + str[lastIndex:]
          }
          

          例子:

          str := "abc foo:bar def baz:qux ghi"
          re := regexp.MustCompile("([a-z]+):([a-z]+)")
          result := ReplaceAllGroupFunc(re, str, func(groups []string) string {
              return groups[1] + "." + groups[2]
          })
          fmt.Printf("'%s'\n", result)
          

          https://gist.github.com/elliotchance/d419395aa776d632d897

          【讨论】:

            【解决方案7】:

            您可以为此使用regrouphttps://github.com/oriser/regroup

            例子:

            package main
            
            import (
                "fmt"
                "github.com/oriser/regroup"
            )
            
            func main() {
                r := regroup.MustCompile(`(?P<Year>\d{4})-(?P<Month>\d{2})-(?P<Day>\d{2})`)
                mathces, err := r.Groups("2015-05-27")
                if err != nil {
                    panic(err)
                }
                fmt.Printf("%+v\n", mathces)
            }
            

            将打印:map[Year:2015 Month:05 Day:27]

            或者,您可以像这样使用它:

            package main
            
            import (
                "fmt"
                "github.com/oriser/regroup"
            )
            
            type Date struct {
                Year   int `regroup:"Year"`
                Month  int `regroup:"Month"`
                Day    int `regroup:"Day"`
            }
            
            func main() {
                date := &Date{}
                r := regroup.MustCompile(`(?P<Year>\d{4})-(?P<Month>\d{2})-(?P<Day>\d{2})`)
                if err := r.MatchToTarget("2015-05-27", date); err != nil {
                    panic(err)
                }
                fmt.Printf("%+v\n", date)
            }
            

            将打印:&amp;{Year:2015 Month:5 Day:27}

            【讨论】:

              【解决方案8】:

              获取正则表达式参数和零指针检查的功能。如果发生错误,则返回 map[]

              // GetRxParams - Get all regexp params from string with provided regular expression
              func GetRxParams(rx *regexp.Regexp, str string) (pm map[string]string) {
                  if !rx.MatchString(str) {
                      return nil
                  }
                  p := rx.FindStringSubmatch(str)
                  n := rx.SubexpNames()
                  pm = map[string]string{}
                  for i := range n {
                      if i == 0 {
                          continue
                      }
              
                      if n[i] != "" && p[i] != "" {
                          pm[n[i]] = p[i]
                      }
                  }
                  return
              }
              

              【讨论】:

                猜你喜欢
                • 2015-07-24
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-04-08
                • 2018-03-11
                • 1970-01-01
                相关资源
                最近更新 更多