正则表达式(regex)是 Elixir 中处理字符串的强大工具。Elixir 中的正则表达式遵循PCRE规范(Perl Compatible Regular Expressions)。表示正则表达式含义的字符串模式会先被编译,然后用于匹配整个字符串或其中的一部分。
在 Elixir 中,创建正则表达式最常见的方式是使用~r sigil。Sigil 为 Elixir 中的常见任务提供了_语法糖_式的快捷写法。要匹配一个_字符串字面量_,我们可以把字符串本身作为跟在 sigil 后面的模式。
~r/test/
=~/2运算符用于对字符串执行正则匹配并返回boolean结果,十分有用。
"this is a test" =~ ~r/test/
# => true
关于使用 sigil,有两点说明:
/
\)进行_转义_用方括号[]匹配一个字符范围,就定义了一个_字符类_。它会从该类包含的字符中匹配任意一个字符。你也可以指定像a-z这样的字符范围,只要起点和终点代表一段连续的码点。
regex = ~r/[a-z][ADKZ][0-9][!?]/
"jZ5!" =~ regex
# => true
"jB5?" =~ regex
# => false
_简写字符类_能让模式更简洁。例如:
\d是[0-9]的简写(任意数字)\w是[A-Za-z0-9_]的简写(任意“单词”字符)\s是[ \t\r\n\f]的简写(任意空白字符)当_简写字符类_用在 sigil 之外时,必须进行转义:"\\d"
_选择_使用|作为特殊字符,表示匹配其中之一。
regex = ~r/cat|bat/
"bat" =~ regex
# => true
"cat" =~ regex
# => true
_量词_允许正则表达式中出现重复的模式。它们作用于量词前面的分组。
{N, M},其中N是重复次数的最小值,M是最大值{N,}匹配N次或更多次重复
{0,}也可以写成*:匹配零次或更多次重复{1,}也可以写成+:匹配一次或更多次重复{,N}匹配最多N次重复圆括号()用来表示_分组_和_捕获_。在某些情况下,分组还可以被_捕获_,以便返回使用。在 Elixir 中,捕获可以命名,也可以不命名。捕获的命名方式是在左括号后面追加?<name>。分组会作为一个整体起作用,比如在它后面跟有_量词_时。
regex = ~r/(h)at/
Regex.replace(regex, "hat", "\\1op")
# => "hop"
regex = ~r/(?<letter_b>b)/
Regex.scan(regex, "blueberry", capture: :all_names)
# => [["b"], ["b"]]
_锚点_用于把正则表达式固定到待匹配字符串的开头或结尾:
^锚定到字符串的开头$锚定到字符串的结尾由于~r是"pattern" |> Regex.escape() |> Regex.compile!()的快捷写法,你也可以使用字符串插值来动态构建正则表达式模式:
anchor = "$"
regex = ~r/end of the line#{anchor}/
"end of the line?" =~ regex
# => false
"end of the line" =~ regex
# => true
你的任务是编写一个用于接收事件的服务。每个事件都带有一个日期,但你注意到,提交到你服务端点的日期有 3 种不同的格式:
"01/01/1970""January 1, 1970""Thursday, January 1, 1970"你能看出它们之间有一些相似之处,于是决定编写一些可组合的正则表达式模式。
实现 day/0、month/0 和 year/0,返回一个字符串模式。该模式编译后,能够匹配"01/01/1970"(dd/mm/yyyy)中的数字部分。日和月可能以1或01的形式出现(左侧用零补齐)。
"31" =~ DateParser.day() |> Regex.compile!()
# => true
"12" =~ DateParser.month() |> Regex.compile!()
# => true
"1970" =~ DateParser.year() |> Regex.compile!()
# => true
实现 day_names/0 和 month_names/0,返回一个字符串模式。该模式编译后,能够分别匹配以名称表示的星期和以名称表示的月份。
"Tuesday" =~ DateParser.day_names() |> Regex.compile!()
# => true
"June" =~ DateParser.month_names() |> Regex.compile!()
# => true
实现 capture_day/0、capture_month/0、capture_year/0、capture_day_name/0、capture_month_name/0,返回一个字符串模式,把相应的部分捕获为名称:"day"、"month"、"year"、"day_name"、"month_name"
DateParser.capture_month_name()
|> Regex.compile!()
|> Regex.named_captures("December")
# => %{"month_name" => "December"}
实现 capture_numeric_date/0、capture_month_name_date() 和 capture_day_month_name_date/0,返回一个字符串模式,按照对应的日期格式捕获第 3 部分中的各个部分:
"01/01/1970"
"January 1, 1970"
"Thursday, January 1, 1970"
DateParser.capture_numeric_date()
|> Regex.compile!()
|> Regex.named_captures("01/01/1970")
# => %{"day" => "01", "month" => "01", "year" => "1970"}
实现 match_numeric_date/0、match_month_name_date/0 和 match_day_month_name_date/0,返回一个已编译的正则表达式。它只匹配日期,同时也能捕获各个部分。
"Thursday, January 1, 1970 was the Unix epoch." =~ DateParser.match_day_month_name_date()
# => false
"Thursday, January 1, 1970" =~ DateParser.match_day_month_name_date()
# => true
DateParser.match_day_month_name_date()
|> Regex.named_captures("Thursday, January 1, 1970")
# => %{
# "day" => "1",
# "day_name" => "Thursday",
# "month_name" => "January",
# "year" => "1970"
# }