正则表达式是一种用途非常广泛的_模式匹配_字符串的方式,它使用的是一门专门为此设计的领域特定语言(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"
使用正则的一个常见原因,是把匹配到的内容替换成另一个字符串。
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" "字符串里,编号捕获组和命名捕获组都可以用在替换中。
更多细节请参阅手册:这个话题总是逼着大多数程序员反复回去翻文档!
你受雇于一家正在开发聊天机器人的公司,担任正则表达式专家。
公司还处于非常基础的开发阶段,因此你的任务是使用正则表达式来提升聊天机器人理解和生成自然语言的能力。
为了学习目的,请使用正则表达式解决这些任务。
我们知道有时也可以使用其他方法(甚至可能性能更好!)
除了聪明,聊天机器人还是个忠诚的助手。
要向聊天机器人提问,用户必须在命令的第一个位置说出单词“Chatbot”。
关键字是大写还是小写并不重要。这里重要的是单词的位置。
实现函数is_valid_command(),帮助聊天机器人识别用户何时在发出命令。
julia> is_valid_command("Chatbot, play a song from the 80's.")
True
julia> is_valid_command("Hey Chatbot, where is the closest pharmacy?")
False
julia> is_valid_command("CHATBOT, do you have a solution for this challenge?")
True
聊天机器人很难理解人类如何用 emoji 表达情绪。
当聊天机器人收到用户消息时,每个 emoji 都表示为“emoji”后面跟着它的 id 编号。
实现remove_emoji()方法,接收一个字符串,并移除消息中所有 emoji 的加密内容。
不包含 emoji 文本的行应原样返回。
只移除 emoji 字符串。不要尝试调整空白。
julia> remove_emoji("I love playing videogames emoji3465 it's one of my hobbies")
"I love playing videogames it's one of my hobbies"
考虑到要在移动应用上下载聊天机器人功能,用户需要在对话过程中输入电话号码。
问题是,聊天机器人只能读取和验证特定格式的电话号码。
如果号码有效(与正则表达式指定的字符序列匹配),聊天机器人会回复一条消息,感谢用户并确认号码。如果号码无效,函数会通知用户该电话号码无效。
预期格式为:(+##) ###-###-###
julia> check_phone_number("(+34) 659-771-594")
"Thanks! You can now download me to your phone."
julia> check_phone_number("659-771-594")
"Oops, it seems like I can't reach out to 659-771-594"
聊天机器人是个非常好奇的软件。即使它能在互联网上搜索某个特定主题,它也喜欢向用户询问酷炫的网站或网址,以便找到相关信息。
对话示例:
实现函数getURL(),它能够返回一个数组,其中只包含每个网站的链接。
julia> getURL("I learned a lot from exercism.org")
["exercism.org"]
为了存储所有与它对话过的人的数据,聊天机器人能够从用户的个人资料中获取这种格式的全名:“smith, john”。
这样,我们希望聊天机器人非常礼貌,并留下好印象。
编写函数nice_to_meet_you(),它接收一个包含用户全名的字符串,并返回字符串**“Nice to meet you, John Smith”**。
julia> str = "Smith, John"
julia> nice_to_meet_you(str)
"Nice to meet you, John Smith"