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(姓氏))$(titlecase(名称))!")
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("电子邮件是正确的")
else
println("电邮地址不正确")
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 строка
第二行
第3行
4 строка"""
split(s,'\n',limit=2)
for (i, sonnet) in enumerate(sonnets[1:5])
println("""十四行诗$i:split(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之前进行检查
stripped = strip(line)
# 如果字符串的每个(全部)字符属于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中处理字符和字符串的内置函数相比,这是一个更通用的处理文本的工具。