Регулярни изрази

01.12.2015

История

Проблемна област

Най-общо: работа с текстови низове

Call me maybe?

Искаме да проверим дали даден низ е валиден мобилен номер.
Това означава:

Пф. Тривиално

func isValidMobilePhone(number string) bool {
    code := number[:3]
    if code != "087" && code != "088" && code != "089" {
        return false
    }

    if number[3] == '0' || number[3] == '1' {
        return false
    }

    return len(number[4:]) == 6
}

Пф. Тривиално и адекватно

func isValidMobilePhone(number string) bool {
    return regexp.MustCompile(`^08[789][2-9]\d{6}$`).MatchString(number)
}

Nice, a?

Понятия и терминология

Регулярните изрази в контекста на Go

godoc regexp
godoc regexp/syntax

Задаване на шаблон

Всеки символ, освен някои специални, означава себе си.

Цялата магия е в специалните символи:
. \| ( ) [ ] { } + \ ^ $ * ?

Някои символи са специални само в определен контекст (например символът -)

Компилация на регулярен израз

re, err := regexp.Compile(`Hello`)
re := regexp.MustCompile(`Hello`)

Второто изпада в паника, ако регулярният израз не е валиден

Пример

package main

import (
	"fmt"
	"regexp"
)

func main() {
    re := regexp.MustCompile(`Hello`)

    if re.MatchString("Hello Regular Expression.") {
        fmt.Println("Match")
    } else {
        fmt.Println("No match")
    }
}

func matcher(pattern, text string)

package main

import (
	"fmt"
	"regexp"
)

// second_start OMIT
func matcher(pattern, text string) {
	match := regexp.MustCompile(pattern).FindStringIndex(text)
	if match == nil {
		fmt.Printf("No match for `%s` in \"%s\"\n", pattern, text)
		return
	}

	begin := match[0]
	end := match[1]
	fmt.Printf("%s(%s)%s\n", text[:begin], text[begin:end], text[end:])
}

func main() {
    matcher(`pat`, "Find a pattern.") // Find a (pat)tern.
    matcher(`#`, "What the ###?")     // What the (#)##?
}

Магия от level 1 -- Повторения (quantifiers)

В последното можем да пропуснем m или n:

Примери

matcher(`test a*`, "the test aaaa bbbb") // the (test aaaa) bbbb
matcher(`test b*`, "the test aaaa bbbb") // the (test )aaaa bbbb

matcher(`test a?`, "the test aaaa bbbb") // the (test a)aaa bbbb
matcher(`test b?`, "the test aaaa bbbb") // the (test )aaaa bbbb

matcher(`test a+`, "the test aaaa bbbb") // the (test aaaa) bbbb
matcher(`test b+`, "the test aaaa bbbb") // no match

matcher(`test a{1,3}`, "the test aaaa bbbb") // the (test aaa)a bbbb

Алчност (greedy)

matcher(`[hH]o+`, "Hoooooohohooo...")  // (Hoooooo)hohooo...
matcher(`[hH]o+?`, "Hoooooohohooo...") // (Ho)ooooohohooo...

Примери

matcher(`o+`, "Goooooooogle")                  // G(oooooooo)gle

matcher(`[hH]o+`, "Hohohoho...")               // (Ho)hohoho...

Хм. Не искахме точно това. По-скоро:

matcher(`([hH]o)+`, "Hohohoho...")             // (Hohohoho)...

matcher(`([hH]o){2,3}`, "Hohohoho...")         // (Hohoho)ho...

Скоби и групиране

Символите ( и ) се използват за логическо групиране на части от шаблона с цел:

Повече за тях -- след малко

Значение на специалните символи

Символът | има смисъла на "или"

matcher(`day|nice`, "A nice dance-day.")  // A (nice) dance-day.
matcher(`da(y|n)ce`, "A nice dance-day.") // A nice (dance)-day.

NB! Единствено | се прилага не над непосредствените му
символи/класове, а на целия низ отляво/отдясно:

matcher(`ab|c|e`, "abcdef")   // (ab)cdef
matcher(`am|c|e`, "abcdef")   // ab(c)def
matcher(`a(m)|c|e`, "abcdef") // ab(c)def

Символни класове

Набор от символи, заграден от [ и ], например [aeoui].
Съвпадат с точно един от символите, описани в класа:

matcher(`[aeoui]`, "Google") // G(o)ogle

Отрицание на символен клас -- ^ в началото на класа:

matcher(`[^CBL][aeoui]`, "Cobol") // 'Co(bo)l'

Диапазон от символи -- - между два символа в символен клас:

matcher(`[0-9]{1,3}-[a-z]`, "Figure 42-b") // Figure (42-b)
matcher(`[^a-zA-Z-]`, "Figure-42-b")       // Figure-(4)2-b

Предефинирани класове от символи

Екраниране на специалните символи (escape-ване)

Групи и прихващане

Символите ( и ) се използват за логическо групиране на части от шаблона с цел:

re := regexp.MustCompile(`.at`)
res := re.FindAllStringSubmatch("The cat sat on the mat.", -1)
fmt.Printf("%v", res)
// [[cat] [sat] [mat]]

Find and replace

Можем да разменим местата на две думи

re := regexp.MustCompile("([a-z]+) ([a-z]+)")
re.ReplaceAllString("foo bar", "$2 $1")

или да имаме по-сложна логикa, върху всяко от съвпаденията

re.ReplaceAllStringFunc("foo with bar", func(match string) string {
        if len(match) > 3 {
                return match + "!!!"
        }
        return match
})
// foo with!!! bar

или ...

re := regexp.MustCompile("a(x*)b")
fmt.Println(re.ReplaceAllLiteralString("-ab-axxb-", "Щ"))
// -Щ-Щ-

Обяснете последното :)

Split

Често strings.Split() е крайно недостатъчен.

re := regexp.MustCompile("[0-9]")
re.Split("Помощ1не2ми3работи4space5клавиша!", -1)
// []string{"Помощ" "не" "ми" "работи" "space" "клавиша!"}

Вторият аргумент указва максималната дължина на върнатия слайс.

re := regexp.MustCompile("[0-9]")
re.Split("Помощ1не2ми3работи4space5клавиша!", 3)
// []string{"Помощ" "не" "ми3работи4space5клавиша!"}

ASCII character classes:

...

Unicode character class names--scripts:

matcher(`\p{Cyrillic}`, "Bаba") // B(a)ba

* първото а е "нашенско"

Котви

Флагове

regexp.Compile(`(?i)n`)

Кодът на matcher()

package main

import (
	"fmt"
	"regexp"
)

func matcher(pattern, text string) {
    match := regexp.MustCompile(pattern).FindStringIndex(text)
    if match == nil {
        fmt.Printf("No match for `%s` in \"%s\"\n", pattern, text)
        return
    }

    begin := match[0]
    end := match[1]
    fmt.Printf("%s(%s)%s\n", text[:begin], text[begin:end], text[end:])
}

func main() {
    matcher(`pat`, "Find a pattern.") // Find a (pat)tern.
    matcher(`#`, "What the ###?")     // What the (#)##?
}

След като вече всички сме наясно, че са мега яки...

Encouraging regular expressions as a panacea for all text processing
problems is not only lazy and poor engineering, it also reinforces
their use by people who shouldn't be using them at all.

-- Rob Pike

Regular Expression Matching Can Be Simple And Fast

but is slow in Java, Perl, PHP, Python, Ruby, ...

Russ Cox
rsc@swtch.com
January 2007

Хитринка

Помните ли повторителите в регулярните изрази? И при тях можем да изключваме алчността.
Променя държанието в това да се стреми да хване възможно най-малко.

Въпроси?