Регулярні вирази дають надзвичайно гнучкий спосіб шукати збіги за шаблоном у рядках тексту (англ. string) за допомогою спеціальної предметно-орієнтованої мови (DSL), призначеної саме для цього.
Як і чимало інших мов програмування, Julia не намагається реалізувати власну бібліотеку регулярних виразів. Замість цього вона використовує популярну бібліотеку PCRE2, завдяки чому синтаксис регулярних виразів такий самий, як (наприклад) у Gleam, і дуже схожий на JavaScript.
Цей силабус Julia припускає, що ми вже знайомі з базовим синтаксисом регулярних виразів. Ми зосередимося виключно на особливостях, властивих Julia.
Нижче наведено кілька ресурсів, щоб освіжити знання про регулярні вирази.
Інтерфейс Julia до регулярних виразів описано в посібнику.
Регулярний вираз у Julia записують просто як рядок тексту з r перед відкривальною лапкою ".
Уся базова функціональність входить до стандартної бібліотеки.
Насправді багато функцій, які ми вже розглядали в концепції Strings, стандартно розраховані на пошук за допомогою регулярних виразів, як-от occursin().
julia> re = r"test$"
r"test$"
julia> typeof(re)
Regex
# Does a string end with "test"?
julia> occursin(re, "this is a test")
true
julia> occursin(re, "these are tests")
false
Після закривальної лапки можуть стояти символи-модифікатори, як-от i для пошуку без урахування регістру.
julia> occursin(r"test", "Testing")
false
julia> occursin(r"test"i, "Testing")
true
Часто нам потрібно знати, що саме збіглося.
Для цього в регулярний вираз додають групи захоплення в дужках, а потім використовують функцію match().
julia> m = match(r"(\d+g) .* (\d+ml)", "dissolve 25g sugar in 200ml water")
RegexMatch("25g sugar in 200ml", 1="25g", 2="200ml")
julia> m.captures
2-element Vector{Union{Nothing, SubString{String}}}:
"25g"
"200ml"
# how many matches?
julia> length(m.captures)
2
# what matched?
julia> m[1], m[2]
("25g", "200ml")
# Starting positions of the matches (character index)
julia> m.offsets
2-element Vector{Int64}:
10
23
Звісно, пошук збігу може не вдатися.
Тоді результатом буде спеціальне значення Nothing замість RegexMatch, тож будьмо готові це перевіряти.
# failed match
m = match(r"(not here)", "dissolve 25g sugar in 200ml water")
julia> typeof(m)
Nothing
julia> isnothing(m)
true
Хоча match типово починає пошук із початку рядка тексту, ми також можемо вказати зсув n, щоб пропустити перші n символів.
# capture first match
julia> m = match(r"(\wat)", "cat, sat, mat")
RegexMatch("cat", 1="cat")
# ignore first 5 characters, then match
julia> m = match(r"(\wat)", "cat, sat, mat", 5)
RegexMatch("sat", 1="sat")
У Julia match() знаходить лише перший збіг у цільовому рядку тексту: глобального модифікатора, як у деяких інших мовах, тут немає.
Натомість є eachmatch(), яка повертає ітератор збігів.
Він обчислюється ліниво, тож його, ймовірно, доведеться перетворити на потрібний нам формат.
julia> matches = eachmatch(r"(\wat)", "cat, sat, mat")
Base.RegexMatchIterator{String}(r"(\wat)", "cat, sat, mat", false)
# convert to vector
julia> collect(matches)
3-element Vector{RegexMatch}:
RegexMatch("cat", 1="cat")
RegexMatch("sat", 1="sat")
RegexMatch("mat", 1="mat")
# convert with comprehension
julia> [m.match for m in matches]
3-element Vector{SubString{String}}:
"cat"
"sat"
"mat"
# broadcast an anonymous function
julia> (m -> m.match).(matches)
3-element Vector{SubString{String}}:
"cat"
"sat"
"mat"
Збіги, що перекриваються, типово не дозволені.
Щоб це змінити, додамо overlap = true як ключовий аргумент.
julia> eachmatch(r"aba", "abababa") |> collect # matches at positions 1, 5
2-element Vector{RegexMatch}:
RegexMatch("aba")
RegexMatch("aba")
julia> eachmatch(r"aba", "abababa"; overlap = true) |> collect # also matches at position 3
3-element Vector{RegexMatch}:
RegexMatch("aba")
RegexMatch("aba")
RegexMatch("aba")
Одна з поширених причин скористатися регулярним виразом - замінити знайдений збіг на інший рядок тексту.
Функцію replace() ми вже розглядали в концепції Strings, коли шукали за рядковими літералами.
Та сама функція може скористатися всією потугою пошуку за регулярними виразами.
julia> replace("some string", r"[aeiou]" => "*")
"s*m* str*ng"
julia> replace("first second", r"(\w+) (?<agroup>\w+)" => s"\g<agroup> \1")
"second first"
Другий приклад вище показує, як і в заміні, усередині рядка s" ", можна використати і нумеровані, і іменовані групи захоплення.
Докладніше про це можна прочитати в посібнику: це тема, яка постійно змушує більшість програмістів повертатися до документації!