Julia中的正则表达式
导言
正则表达式(regex)是一种通用的工具,用于根据指定的模式搜索,提取和处理文本。 它们允许您解决检查电子邮件格式,提取电话号码或分析文本数据等任务。 在Julia中,正则表达式由于其易于集成和简洁的语法而特别有用。 Julia不需要额外的模块来使用正则表达式(与其他一些语言不同),并使用前缀 r"..." 来创建模板,这使得代码直观可读。 本材料将向您展示如何在Julia中使用正则表达式,重点介绍实际示例和语言特性。
Julia中的正则表达式语法
Julia使用[PCRE]语法(https://ru.wikipedia.org/wiki/PCRE ),它支持一组丰富的功能。 让我们来看看正则表达式的主要元素及其在Julia中的使用。
基本语法元素
-文字:常规字符(例如, a, b, 1)按原样在文本中搜索。 例如, r"cat" 对应于单词 cat.
-元字符:
.-除换行符以外的任何字符。 例如,r"c.t"回应cat,cot但不是c\nt.^-行的开头:r"^hello"会发现hello只有在行的开头。$\text{—} \text{конец} \text{строки}:r"world$"会发现world只有在行的末尾。\-屏蔽:r"\."搜索点作为字符,而不是任何字符。r"\^"寻找确切的符号^.
-量词:*-零或以上:r"a*"回应"",a,aa等。+-一个或多个:r"a+"回应a,aa但不是"".?-零或一:r"colou?r"回应color和colour.{n,m}-重复范围:r"a{2,4}"回应aa,aaa,aaaa.
-字符类:[abc]-符号之一:r"[abc]"回应a,b或c.[a-z]-范围:r"[a-z]+"它会找到任何小写字母单词。[^abc]-否认:r"[^abc]"除此以外的任何字符a,b,c.\d-数字:r"\d+"它会发现像这样的数字123.\w-字母、数字或_:r"\w+"会发现像hello123.\s-空间字符:r"\s+"它会找到空格或制表符。
-分组:()-捕获模板的一部分:r"(\d+)-(\d+)"从行中选择它12-34数字12和34并保存它。(?:...)-组,但不"保存"结果。 非封闭组对于简化表达式的结构很有用。
``'茱莉亚
regex=r"(?:abc)+(\d+)(?:def)+(&+)"
text="abcabc123defdefdef&&&"
match(regex,text)[1]#返回"123"
match(regex,text)[2]#将返回"&&&"
朱莉娅的特征
Julia提供了使用正则表达式的便捷方法。:
match(r"шаблон", строка):找到第一个匹配项。 返回一个对象RegexMatch或nothing.
Julia的特殊之处在于不需要双重转义(例如,
r"\d"而不是"\\d"),这使得编写模板更容易。
m = match(r"\d+", "Возраст: 42") #\d+ `\d` - выбери цифру `+` - одно или больше вхождений
println(m.match)
eachmatch(r"шаблон", строка):所有匹配的迭代器。
for m in eachmatch(r"\w+", "Здравствуй, дорогой друг!") # `\w` - буква, цифра или _ (`,` и `!` не подходят)
println(m.match)
end
replace(строка, r"шаблон" => "замена"):替换匹配项。
new = replace("Формат даты: 01-02-2025", r"\d" => "X")
println(new)
occursin(r"шаблон", строка):检查比赛。
@show occursin(r"[A-Z]", "Hello")
@show occursin(r"[A-Z]", "hello")
@show occursin(r"[A-Z]+", "HELLO");
实际应用
示例1:名字和姓氏提取
让温室参观者杂志的照片拍摄。
接下来,我们将此文档数字化并进行文本识别,接收"文档" OCR_text.
事实证明,一些字母变成了小写,在某处添加了额外的空格,并在某处消失了。 在某些情况下,中风被识别为字母。
# Текст с данными
OCR_text = """
Журнал посетителей:
Фамилия: иванов имя: Иван
фамилия : Петров имя : пётр l
Фамилия - Римский-Корсаков Имя -Николай
"""
通过指定标志:
i在表达式中r"..."i-我们指定大小写独立(大小写-insensitive)。 也就是说,"姓氏"和"姓氏"将被认为是等价的。m在表达式中r"..."m表示m腿长。^在表达式中,它将意味着每个之后的行的开始\n,而不仅仅是一个"大"字符串的开头OCR_text.x在表达式中r"..."x-我们可以使用空格,并指定使用注释#(x来自单词ex)
我们将在下面讨论括号的含义。
regex_fullname = r"
^Фамилия\s* # `Фамилия` в начале строки, а после 0 или более пробелов
[:-]\s* # далее один знак `:` или `-` и 0 или более пробелов
([\p{L}-]+) # [\p{L}-]+ - `\p{L}` - символы Unicode, `-` - дефис
\s* # после фамилии снова 0 или более пробелов
Имя\s*[:-]\s* # то же, что и с фамилией
(\p{L}+) # Любая последовательность букв (русских в том числе) это и есть имя"imx;
为了从我们的文档中提取有用的信息 OCR_text使用正则表达式 regex_fullname,我们将使用 eachmatch.
请注意,我们有3人。 每个人都有2个特征:姓和名。
eachmatch 返回包含RegexMatch类型对象的迭代器,其中每个对象表示文本中的一个模式匹配。
我们的模板包含姓氏和名字。 姓氏在表达式中排在第一位,因此对于姓氏,我们将使用 m.captures[1]. 我们的名字是第二个。
换句话说,我们创建了一个包含访问者姓氏和名字的元组数组。
fullnames = [(m.captures[1], m.captures[2]) for m in eachmatch(regex_fullname, OCR_text)]
我们将以标题格式输出名称和姓氏:
``'茱莉亚
titlecase("abc");#Abc
titlecase("aBC");#Abc
for (surname, name) in fullnames
println("Здравствуйте, $(titlecase(surname)) $(titlecase(name))!")
end
示例2:提取电话号码
假设我们需要找到一个格式的数字 +7-XXX-XXX-XX-XX 或 8-XXX-XXX-XX-XX:
说明:
\d{3}意思正好是3位数字,
\+将加号作为文字转义。
|表示***或 ***
(?:...)-"非捕获组",即这是我们要单独定义的表达式的子部分(+7或8,然后是一组数字和连字符)。
但是信息本身,是通过电话记录的吗?+7 或通过8 我们不感兴趣。 这就是为什么它不令人兴奋。
text1 = "Российские номера это +7-912-345-67-89 или 8-987-654-32-10, но не +1-234-567-89-10"
russian_phone_regex = r"(?:\+7|8)-\d{3}-\d{3}-\d{2}-\d{2}"
for m in eachmatch(russian_phone_regex, text1)
println("Найден российский номер: ", m.match)
end
示例3:检查电子邮件地址
让我们检查电子邮件的正确性:
email = "test_User-name.123@pochta.ru"
email_regex = r"^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-z]{2,}$"
if match(email_regex, email) !== nothing
println("Email корректен")
else
println("Некорректный email")
end
说明:
^[a-zA-Z0-9._-]+需要一个由字母、数字和一些符号组成的用户名,以及\.[a-z]{2,}$-长度为2+个字符的顶级域。
示例4:处理文献脚注
提取表单的脚注 [1], [1, 2]:
text2 = "Текст ссылается на [1], [2, 3] и [4], и содержит математические выражения: 1 + (2{3 - x[y-z]})."
ref_regex = r"\[\d+(?:,\s*\d+)*\]"
matches = [m.match for m in eachmatch(ref_regex, text2)]
println("Сноски: ", join(matches, ", "))
解释: (?:,\s*\d+)* -带有逗号的数字的非封闭组。
例5:接收莎士比亚十四行诗
让[威廉的十四行诗]给Шекспира](https://engee.com/community/ru/catalogs/projects/analiz-tekstovykh-dannykh-s-pomoshchiu-massivov-strok),用罗马数字编号。 让我们创建一个这些十四行诗的数组,按原始顺序编号,以便它们可以通过索引轻松访问。
sonnets_text = read("sonnets.txt",String);
print(sonnets_text[1:1000])
十四行诗中有换行符。 因此,点不能用于指示任何字符(请参阅"正则表达式"一章的开头)。 要解决这个问题,请使用:
\s 表示空格字符。
\S 表示不是一个坚不可摧的字符
这意味着 [\s\S] 表示任何字符。
function split_sonnets(text)
pattern = r"""
^ # Начало строки (с флагом m — для каждой строки)
\s* # Ноль или более пробелов перед римской цифрой
[IVXLCDM]+ # Одна или более римских цифр (I, V, X, L, C, D, M)
\s* # Ноль или более пробелов после цифры
$ # Конец строки (ограничивает строку только цифрой)
\s* # Пробелы или пустые строки после цифры
#___________________________________________________________________________________________________
( # Начало захватывающей группы для текста сонета
[\s\S]*? # Любой символ (включая \n), нежадно (до ближайшей остановки)
) # Конец захватывающей группы
#___________________________________________________________________________________________________
(?= # Положительный просмотр вперёд (условие остановки)
^ # Начало следующей строки
\s* # Пробелы перед следующей цифрой
[IVXLCDM]+ # Следующая римская цифра
\s* # Пробелы после неё
$ # Конец строки с цифрой
| # Или
\z # Абсолютный конец текста (для последнего сонета)
) # Конец просмотра вперёд
"""mx # Флаги: m (многострочный режим), x (расширенный режим)
sonnets = [strip(m.captures[1]) for m in eachmatch(pattern, text)]
return sonnets
end
sonnets = split_sonnets(sonnets_text)
现在让我们只打印前五首十四行诗的第一行。
要做到这一点,我们使用 split 每首十四行诗分为2部分。:
-第1部分:第一行
-第2部分:除第一行外的所有后续行
s = """1 строка
2 строка
3 строка
4 строка"""
split(s,'\n',limit=2)
for (i, sonnet) in enumerate(sonnets[1:5])
println("""Соннет $i:$(split(sonnet,'\n',limit=2)[1])\n...""")
end
让我们来衡量我们的功能的速度。
Pkg.add("BenchmarkTools")
using BenchmarkTools
@btime split_sonnets(sonnets_text);
1.24毫秒是一个相当不错的结果。 对于2.5千行的文件。 但是,您需要了解正则表达式可能不如经典方法。 在我们的例子中,我们可以以相当明确的方式解决问题。 (但你不能潜入它,但看看它的执行速度)
function split_sonnets_fast(text)
sonnets = String[]
current_sonnet = String[]
in_sonnet = false
for line in eachline(text)
if !isempty(line) # Проверяем до отбрасывания пробелов и \n через функцию strip
stripped = strip(line)
# Если каждый (all) символ строки принадлежит IVXLCDM, то это римское число
if all(c -> c in "IVXLCDM", stripped)
if in_sonnet && !isempty(current_sonnet)
push!(sonnets, join(current_sonnet, '\n'))
end
current_sonnet = String[]
in_sonnet = true
elseif in_sonnet
push!(current_sonnet, line)
end
end
end
if in_sonnet && !isempty(current_sonnet)
push!(sonnets, join(current_sonnet, '\n'))
end
return sonnets
end
# Проверка работы
sonnets = split_sonnets_fast("sonnets.txt")
@btime split_sonnets_fast("sonnets.txt");
结论
Julia中的正则表达式是处理文本的强大而方便的工具。 由于语法的简单性(r"..."),内置的功能,如 match 和 replace 除了语言的高性能,它们是数据处理和分析,搜索和替换任务的理想选择。 但重要的是要明白,正则表达式对于复杂(例如嵌套)解析任务可以缓慢工作。 结构,如JSON文件,HTML文件等。
尽管正则表达式语法很复杂,但扩展允许您进行注释。 与Julia中处理字符和字符串的内置函数相比,这是一个更通用的处理文本的工具。