Подтвердить что ты не робот

Как получить функциональность захвата группы в регулярных выражениях Go

Я переношу библиотеку с Ruby на Go и только что обнаружил, что регулярные выражения в Ruby не совместимы с Go (google RE2). Мне пришло в голову, что Ruby и Java (плюс другие языки используют регулярные выражения PCRE (совместимые с perl, которые поддерживают группы захвата)), поэтому мне нужно переписать мои выражения так, чтобы они компилировали ok в Go.

Например, у меня есть следующее регулярное выражение:

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

Это должно принимать входные данные, такие как:

2001-01-20

Группы захвата позволяют записывать год, месяц и день в переменные. Чтобы получить ценность каждой группы, это очень легко; вы просто индексируете в возвращаемые согласованные данные с именем группы, и вы возвращаете значение. Так, например, чтобы получить год, что-то вроде этого псевдокода:

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

Это шаблон, который я использую много в своих выражениях, поэтому мне нужно много переписывать.

Итак, есть ли способ получить такую ​​функциональность в Go regexp; как мне переписать эти выражения?

4b9b3361

Ответ 1

как мне переписать эти выражения?

Добавьте несколько P, как определено здесь:

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

re.SubexpNames() имена групп захвата ссылок с помощью re.SubexpNames().

И использовать следующим образом:

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())
}

Ответ 2

Я создал функцию для обработки выражений url, но она также подходит для ваших нужд. Вы можете проверить этот сниппет, но он просто работает следующим образом:

/**
 * 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
}

Вы можете использовать эту функцию, например:

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

Чтобы улучшить использование ОЗУ и ЦП без вызова анонимных функций внутри цикла и без копирования массивов в памяти внутри цикла с помощью функции "добавить", смотрите следующий пример:

Вы можете хранить более одной подгруппы с многострочным текстом, без добавления строки с '+' и без использования цикла 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]}
   }
}

Лучше использовать анонимные группы (улучшение производительности)

Использование "ReplaceAllGroupFunc", размещенного на Github, является плохой идеей, потому что:

  1. использует цикл внутри цикла
  2. использует анонимный вызов функции внутри цикла
  3. имеет много кода
  4. использует функцию "добавить" внутри цикла, и это плохо. Каждый раз, когда делается вызов для "добавления" функции, происходит копирование массива в новую позицию памяти

Ответ 4

Если вам нужно заменить на основе функции во время захвата групп, вы можете использовать это:

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

Ответ 5

Простой способ определить имена групп на основе ответа @VasileM.

Отказ от ответственности: это не про оптимизацию памяти/процессора/времени

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