如何在 Go 正则表达式中获取捕获组功能
How to get capturing group functionality in Go regular expressions
我正在将一个库从 Ruby 移植到 Go,并且刚刚发现 Ruby 中的正则表达式与 Go 不兼容(google RE2)。我注意到 Ruby & Java (加上其他语言使用 PCRE 正则表达式(perl 兼容,支持捕获组)),所以我需要重写我的表达式,以便它们编译正常在围棋中。
例如,我有以下正则表达式:
`(?<Year>\d{4})-(?<Month>\d{2})-(?<Day>\d{2})`
这应该接受如下输入:
2001-01-20
捕获组允许将年、月和日捕获到变量中。要获取每个组的值,这很容易;您只需使用组名索引返回的匹配数据,即可取回值。因此,例如要获取年份,可以使用如下伪代码:
m=expression.Match("2001-01-20")
year = m["Year"]
这是我在表达中经常使用的模式,所以我有很多重写工作要做。
那么,有没有办法在 Go regexp 中获得这种功能;我应该如何重写这些表达式?
how should I re-write these expressions?
添加一些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())
}
如果您需要在捕获组时根据函数进行替换,您可以使用此方法:
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)
我创建了一个函数来处理 url 表达式,但它也适合您的需要。您可以检查 this 片段,但它的工作原理如下:
/**
* 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]
要提高 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" 是个坏主意,因为:
- 正在循环中使用循环
- 在循环中使用匿名函数调用
- 有很多代码
- 在循环内使用 "append" 函数,这很糟糕。
每次调用 "append" 函数时,都会将数组复制到新的内存位置
根据@VasileM 回答确定组名的简单方法。
免责声明:这与 memory/cpu/time 优化无关
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])
}
}
}
您可以为此使用 regroup
库
https://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)
}
将打印:&{Year:2015 Month:5 Day:27}
从 GO 1.15 开始,您可以使用 Regexp.SubexpIndex
. You can check the release notes at 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 查看并执行此示例。
用于获取带零指针检查的正则表达式参数的函数。 Returns 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
}
我正在将一个库从 Ruby 移植到 Go,并且刚刚发现 Ruby 中的正则表达式与 Go 不兼容(google RE2)。我注意到 Ruby & Java (加上其他语言使用 PCRE 正则表达式(perl 兼容,支持捕获组)),所以我需要重写我的表达式,以便它们编译正常在围棋中。
例如,我有以下正则表达式:
`(?<Year>\d{4})-(?<Month>\d{2})-(?<Day>\d{2})`
这应该接受如下输入:
2001-01-20
捕获组允许将年、月和日捕获到变量中。要获取每个组的值,这很容易;您只需使用组名索引返回的匹配数据,即可取回值。因此,例如要获取年份,可以使用如下伪代码:
m=expression.Match("2001-01-20")
year = m["Year"]
这是我在表达中经常使用的模式,所以我有很多重写工作要做。
那么,有没有办法在 Go regexp 中获得这种功能;我应该如何重写这些表达式?
how should I re-write these expressions?
添加一些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())
}
如果您需要在捕获组时根据函数进行替换,您可以使用此方法:
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)
我创建了一个函数来处理 url 表达式,但它也适合您的需要。您可以检查 this 片段,但它的工作原理如下:
/**
* 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]
要提高 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" 是个坏主意,因为:
- 正在循环中使用循环
- 在循环中使用匿名函数调用
- 有很多代码
- 在循环内使用 "append" 函数,这很糟糕。 每次调用 "append" 函数时,都会将数组复制到新的内存位置
根据@VasileM 回答确定组名的简单方法。
免责声明:这与 memory/cpu/time 优化无关
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])
}
}
}
您可以为此使用 regroup
库
https://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)
}
将打印:&{Year:2015 Month:5 Day:27}
从 GO 1.15 开始,您可以使用 Regexp.SubexpIndex
. You can check the release notes at 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 查看并执行此示例。
用于获取带零指针检查的正则表达式参数的函数。 Returns 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
}