Main Content

动态正则表达式

简介

在动态表达式中,您可以要求 regexp 进行匹配的模式随输入文本的内容而动态变化。通过这种方式,您可以更紧密地匹配所解析的文本中的不同输入模式。此外,也可以在替代项中使用动态表达式以用于 regexprep 函数。这样,您就有办法令替代文本更好地适应所解析的输入。

您可以在以下命令的 match_exprreplace_expr 参量中包含任意数量的动态表达式:

regexp(text, match_expr)
regexpi(text, match_expr)
regexprep(text, match_expr, replace_expr)

以一个动态表达式为例,下面的 regexprep 命令将 internationalization 一词正确替换为其缩写形式 i18n。但是,要对其他词(例如 globalization)使用该命令,您必须使用一个不同的替代表达式:

match_expr = '(^\w)(\w*)(\w$)';

replace_expr1 = '$118$3';
regexprep('internationalization', match_expr, replace_expr1)
ans =

    'i18n'
replace_expr2 = '$111$3';
regexprep('globalization', match_expr, replace_expr2)
ans =

    'g11n'

通过动态表达式 ${num2str(length($2))},您可以将替代表达式建立在输入文本基础之上,这样您就不必每次更改该表达式。此示例使用动态替代语法 ${cmd}

match_expr = '(^\w)(\w*)(\w$)';
replace_expr = '$1${num2str(length($2))}$3';

regexprep('internationalization', match_expr, replace_expr)
ans =

    'i18n'
regexprep('globalization', match_expr, replace_expr)
ans =

    'g11n'

解析后,动态表达式必须与完整的有效正则表达式对应。此外,使用反斜杠转义字符 (\) 的动态匹配表达式还需要两个反斜杠:一个用于表达式的初始解析,一个用于完整匹配。将动态表达式括起来的括号创建捕获组。

有三种形式的动态表达式可用作匹配表达式,还有一种形式的动态表达式可用作替代表达式,下面分别对这几种动态表达式进行了介绍。

动态匹配表达式 - (??expr)

(??expr) 运算符解析表达式 expr,并将结果插回到匹配表达式中。然后,MATLAB® 会计算修改后的匹配表达式。

下面是一个使用此运算符的表达式类型的示例:

chr = {'5XXXXX', '8XXXXXXXX', '1X'};
regexp(chr, '^(\d+)(??X{$1})$', 'match', 'once');

此特殊命令的目的是在输入元胞数组存储的每个字符向量中定位一系列连续的 X 字符。但是请注意,X 的数量在每个字符向量中有所不同。如果数量没有变化,您可以使用表达式 X{n} 指示要匹配这些字符中的 n 个字符。但是,n 为常量值并不适用于此示例。

这里使用的解决方法是:捕获词元中的前导计数(例如元胞数组的第一个字符向量中的 5),然后在动态表达式中使用该计数。此示例中的动态表达式为 (??X{$1}),其中 $1 是词元 \d+ 捕获的值。运算符 {$1} 为该词元值创建限定符。由于该表达式是动态的,因此同一模式适用于元胞数组中的所有三个输入向量。对于第一个输入字符向量,regexp 查找五个 X 字符;对于第二个输入字符串,该命令查找八个字符,而对于第三个输入字符串,仅查找一个字符:

regexp(chr, '^(\d+)(??X{$1})$', 'match', 'once')
ans =

  1×3 cell array

    {'5XXXXX'}    {'8XXXXXXXX'}    {'1X'}

修改匹配表达式的命令 - (??@cmd)

MATLAB 使用 (??@cmd) 运算符将 MATLAB 命令的结果纳入匹配表达式中。此命令必须返回可在匹配表达式中使用的项。

例如,使用动态表达式 (??@flilplr($1)) 查找嵌入到较大的字符向量中的回文“Never Odd or Even”。

首先,创建输入字符串。确保所有字母都是小写字母,并删除所有非单词字符。

chr = lower(...
  'Find the palindrome Never Odd or Even in this string');

chr = regexprep(chr, '\W*', '')
chr =

    'findthepalindromeneveroddoreveninthisstring'

使用以下动态表达式查找字符向量中的回文:

palindrome = regexp(chr, '(.{3,}).?(??@fliplr($1))', 'match')
palindrome =

  1×1 cell array

    {'neveroddoreven'}

该动态表达式颠倒构成字符向量的字母的顺序,然后尝试匹配尽可能多的逆序字符向量。这需要一个动态表达式,因为 $1 的值依赖于词元 (.{3,}) 的值。

MATLAB 中的动态表达式有权访问当前活动的工作区。这意味着,只需更改工作区中的变量,即可更改动态表达式中使用的任何函数或变量。重复执行以上示例的最后一个命令,但这次使用基础工作区中存储的函数句柄定义要在表达式中调用的函数:

fun = @fliplr;

palindrome = regexp(chr, '(.{3,}).?(??@fun($1))', 'match')
palindrome =

  1×1 cell array

    {'neveroddoreven'}

满足功能性需求的命令 - (?@cmd)

(?@cmd) 运算符用于指定 regexpregexprep 要在解析整个匹配表达式时运行的 MATLAB 命令。与 MATLAB 中的其他动态表达式不同,此运算符不会更改其所在的表达式的内容。相反,您可以使用此功能让 MATLAB 仅报告在解析您的其中一个正则表达式的内容时所采取的步骤。此功能可用于诊断您的正则表达式。

以下示例解析由零个或多个字符后跟两个相同字符再后跟零个或多个字符组成的单词:

regexp('mississippi', '\w*(\w)\1\w*', 'match')
ans =

  1×1 cell array

    {'mississippi'}

为跟踪 MATLAB 在确定匹配项时采取的确切步骤,此示例在表达式中插入一个简短脚本 (?@disp($1)),以显示最终构成匹配项的字符。由于此示例使用积极限定符,因此 MATLAB 尝试匹配尽可能多的字符向量。这样,即使 MATLAB 在字符串的开头处找到匹配项,它也会继续查找更多匹配项,直至到达字符串的最末尾处。从该处开始,它会备份从字母 ip 以及接下来的 p,并停止在该位置,因为匹配项最终符合要求:

regexp('mississippi', '\w*(\w)(?@disp($1))\1\w*', 'match')
i
p
p

ans =

  1×1 cell array

    {'mississippi'}

现在,重新尝试同一示例,这一次将第一个限定符设置为消极限定符 (*?)。同样,MATLAB 生成相同的匹配项:

regexp('mississippi', '\w*?(\w)\1\w*', 'match')
ans =

  1×1 cell array

    {'mississippi'}

但是,通过插入动态脚本,这一次可以查看匹配项,MATLAB 以完全不同的方式与文本进行了匹配。在本例中,MATLAB 使用可找到的第一个匹配项,甚至不会考虑文本的其余部分:

regexp('mississippi', '\w*?(\w)(?@disp($1))\1\w*', 'match')
m
i
s

ans =

  1×1 cell array

    {'mississippi'}

为了演示此类型的动态表达式的灵活性,请尝试下面的示例。当 MATLAB 以迭代方式解析输入文本时,该示例会逐步设置一个元胞数组。在表达式结尾找到的 (?!) 运算符实际上是一个空的前向运算符,该运算符强制在每次迭代时失败。如果您要跟踪 MATLAB 在处理表达式时采取的步骤,则这种强制失败是必要的。

MATLAB 通过输入文本进行多次传递,每次都尝试另一个字母组合,以查看能否找到比上一个匹配项更好的匹配项。在未找到匹配项的任何传递中,测试将生成一个空字符向量。动态脚本 (?@if(~isempty($&))) 用于省略 matches 元胞数组中的空字符向量:

matches = {};
expr = ['(Euler\s)?(Cauchy\s)?(Boole)?(?@if(~isempty($&)),' ...
   'matches{end+1}=$&;end)(?!)'];

regexp('Euler Cauchy Boole', expr);

matches
matches =

  1×6 cell array

    {'Euler Cauchy Bo…'}    {'Euler Cauchy '}    {'Euler '}    {'Cauchy Boole'}    {'Cauchy '}    {'Boole'}

运算符 $&(或与之等效的 $0)、$`$' 分别指代输入文本中的当前匹配项部分、当前匹配项前面的所有字符以及当前匹配项后面的所有字符。在处理动态表达式(尤其是使用 (?@cmd) 运算符的动态表达式)时,这些运算符有时会很有用。

下面的示例解析输入文本以查找字母 g。在每次迭代扫描该文本时,regexp 都会将当前字符与 g 进行比较,如果没有找到匹配项,则会前进到下一个字符。此示例通过 ^ 字符标记要解析的当前位置,跟踪在该文本中扫描的进度。

$` 运算符捕获位于当前解析位置之前和之后的文本部分。当序列 出现在文本中时,您需要使用两个单引号 ($'') 来表示它。)

chr = 'abcdefghij';
expr = '(?@disp(sprintf(''starting match: [%s^%s]'',$`,$'')))g';

regexp(chr, expr, 'once');
starting match: [^abcdefghij]
starting match: [a^bcdefghij]
starting match: [ab^cdefghij]
starting match: [abc^defghij]
starting match: [abcd^efghij]
starting match: [abcde^fghij]
starting match: [abcdef^ghij]

替代表达式中的命令 - ${cmd}

${cmd} 运算符修改正则表达式替代模式的内容,使得该模式适用于输入文本中可能因用法而异的参数。与 MATLAB 中使用的其他动态表达式一样,您也可以在整个替代表达式中包含任意数量的这些表达式。

替代表达式中的命令只检查变量的局部工作区。替代表达式中的命令无法使用调用方和全局工作区

在此处显示的 regexprep 调用中,替代模式为 '${convertMe($1,$2)}'。在本例中,整个替代模式是一个动态表达式:

regexprep('This highway is 125 miles long', ...
          '(\d+\.?\d*)\W(\w+)', '${convertMe($1,$2)}');

该动态表达式指示 MATLAB 使用两个派生自所匹配的文本的词元 (\d+\.?\d*)(\w+),作为 convertMe 调用的参量,执行一个名为 convertMe 的函数。由于 $1$2 的值是在运行时生成的,因此替代模式需要动态表达式。

以下示例定义了一个名为 convertMe 的函数,该函数将测量值从英制单位转换为公制单位。

function valout  = convertMe(valin, units)
switch(units)
    case 'inches'
        fun = @(in)in .* 2.54;
        uout = 'centimeters';
    case 'miles'
        fun = @(mi)mi .* 1.6093;
        uout = 'kilometers';
    case 'pounds'
        fun = @(lb)lb .* 0.4536;
        uout = 'kilograms';
    case 'pints'
        fun = @(pt)pt .* 0.4731;
        uout = 'litres';
    case 'ounces'
        fun = @(oz)oz .* 28.35;
        uout = 'grams';
end
val = fun(str2num(valin));
valout = [num2str(val) ' ' uout];
end

在命令行上,通过 regexprep 调用 convertMe 函数,并传入要转换的数量值和英制单位名称:

regexprep('This highway is 125 miles long', ...
          '(\d+\.?\d*)\W(\w+)', '${convertMe($1,$2)}')
ans =

    'This highway is 201.1625 kilometers long'
regexprep('This pitcher holds 2.5 pints of water', ...
          '(\d+\.?\d*)\W(\w+)', '${convertMe($1,$2)}')
ans =

    'This pitcher holds 1.1828 litres of water'
regexprep('This stone weighs about 10 pounds', ...
          '(\d+\.?\d*)\W(\w+)', '${convertMe($1,$2)}')
ans =

    'This stone weighs about 4.536 kilograms'

与先前部分中讨论的 (??@ ) 运算符一样,${ } 运算符有权访问当前活动的工作区中的变量。以下 regexprep 命令使用基础工作区中定义的数组 A

A = magic(3)
A =

     8     1     6
     3     5     7
     4     9     2
regexprep('The columns of matrix _nam are _val', ...
          {'_nam', '_val'}, ...
          {'A', '${sprintf(''%d%d%d '', A)}'})
ans =

    'The columns of matrix A are 834 159 672'

另请参阅

| |

相关主题