Le espressioni regolari sono un modo estremamente versatile di fare pattern matching sulle stringhe, utilizzando un linguaggio specifico del dominio (DSL) progettato proprio per questo scopo.
Come diversi altri linguaggi di programmazione, Julia non cerca di implementare una propria libreria di regex. Si appoggia invece alla popolare libreria PCRE2, offrendo così una sintassi regex identica a quella di (per esempio) Gleam e molto simile a quella di JavaScript.
Questo programma di Julia presuppone che tu abbia già familiarità con la sintassi di base delle espressioni regolari. Ci concentreremo solo sulle funzionalità specifiche di Julia.
Qui sotto trovi alcune risorse per ripassare le espressioni regolari.
L'interfaccia di Julia alle espressioni regolari è descritta nel manuale.
In Julia, un'espressione regolare è semplicemente una stringa preceduta da r prima della " di apertura.
Tutte le funzionalità di base fanno parte della libreria standard.
In realtà, molte delle funzioni già viste nel concetto Strings sono pensate per la ricerca con regex, come 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 caratteri modificatori possono seguire le virgolette di chiusura, come i per una corrispondenza che non distingue maiuscole e minuscole.
julia> occursin(r"test", "Testing")
false
julia> occursin(r"test"i, "Testing")
true
Spesso vogliamo sapere cosa corrisponde. Per farlo, includiamo dei gruppi di cattura tra parentesi all'interno della regex e poi usiamo la funzione 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
Naturalmente, le corrispondenze possono fallire.
In quel caso il risultato sarà il valore speciale Nothing invece di un RegexMatch, quindi preparati a verificarlo.
# failed match
m = match(r"(not here)", "dissolve 25g sugar in 200ml water")
julia> typeof(m)
Nothing
julia> isnothing(m)
true
Sebbene match parta per impostazione predefinita dall'inizio della stringa, possiamo anche specificare un offset n per ignorare i primi n caratteri.
# 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")
In Julia, match() trova solo la prima corrispondenza nella stringa di destinazione: non esiste un modificatore globale come in altri linguaggi.
Abbiamo invece eachmatch(), che restituisce un iteratore di corrispondenze.
La valutazione è pigra, quindi potresti doverlo convertire nel formato che ti serve.
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"
Le corrispondenze sovrapposte non sono consentite per impostazione predefinita.
Aggiungi overlap = true come argomento con parola chiave per sovrascrivere questo comportamento.
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")
Un motivo comune per usare una regex è sostituire la corrispondenza con una stringa diversa.
La funzione replace() è stata trattata nel concetto Strings, dove si usavano stringhe letterali per la ricerca.
La stessa funzione può sfruttare tutta la potenza della corrispondenza con regex.
julia> replace("some string", r"[aeiou]" => "*")
"s*m* str*ng"
julia> replace("first second", r"(\w+) (?<agroup>\w+)" => s"\g<agroup> \1")
"second first"
Il secondo esempio qui sopra mostra come usare nella sostituzione sia i gruppi di cattura numerati sia quelli con nome, all'interno di una stringa s" ".
Vedi il manuale per maggiori dettagli: è un argomento che costringe continuamente la maggior parte dei programmatori a tornare alla documentazione!