如何使用正则表达式检查字符串是回文?


93

那是我无法回答的面试问题:

如何使用正则表达式检查字符串是回文?

ps已有一个问题“ 如何检查给定的字符串是否为回文? ”,它用不同的语言给出了很多答案,但是没有答案使用正则表达式。



2
如今(2018年)并且正在寻找“回文正则表达式”的人,请参阅Prakhar的链接上有关PCRE支持递归模式的讨论,以及下面的我的递归正则表达式,并进行比较
彼得·克劳斯

Answers:


151

这个问题的答案是“不可能”。更具体地说,面试官想知道您是否在计算理论课上给予了关注。

在计算理论课上,您学习了有限状态机。有限状态机由节点和边组成。每个边都用来自有限字母的字母注释。一个或多个节点是特殊的“接受”节点,一个节点是“开始”节点。从给定单词中读取每个字母时,我们会在机器中遍历给定边沿。如果我们最终处于接受状态,那么我们就说机器“接受”了这个词。

正则表达式始终可以转换为等效的有限状态机。也就是说,接受和拒绝与正则表达式相同的单词的单词(在现实世界中,某些正则表达式语言允许使用任意函数,但这些函数不计在内)。

建立接受所有回文的有限状态机是不可能的。证明依赖于以下事实:我们可以轻松构建需要任意数量节点的字符串,即字符串

a ^ xba ^ x(例如aba,aabaa,aaabaaa,aaaabaaaa等)。

其中a ^ x是重复的x次。这至少需要x个节点,因为在看到'b'之后,我们必须倒数x次以确保它是回文。

最后,回到最初的问题,您可以告诉访问者,您可以编写一个正则表达式,以接受所有小于一定固定长度的回文。如果现实世界中有需要识别回文的应用程序,那么几乎可以肯定不会包括任意长的回文,因此,该答案将表明您可以将理论上的不可能性与现实世界中的应用区分开。尽管如此,实际的正则表达式仍会很长,比等效的4行程序要长得多(对读者来说容易练习:编写一个识别回文的程序)。


6
@SteveMoser在Ruby 1.9.x中,正则表达式不再是正则表达式(就自动机理论而言),因此可以进行诸如回文检查等操作。但是,出于意图和目的,不能使用常规正则表达式检查回文(有意义吗?)。

1
@SteveMoser有Ruby的正则表达式引擎的一个很好的书面记录(>=1.9在这里

@John是正确的,因此在问题中,Jose是正确的,而hqt是错误的。
史蒂夫·摩泽

2
用学术术语来说,正则表达式有特定的界限(定义DFA)。实际上,许多正则表达式引擎(主要是Perl和它的亲戚)都支持违反学术定义(成为NFA或什至更广泛)的反向引用。因此,根据提问者的参照系是什么,这个问题会有不同的答案。
jiggy 2014年

在口头测试中,邹应与“ formalz这是不可能的”并存,但是您应该指出,某些正则表达式引擎允许这样做。
奥利弗·A。

46

尽管PCRE引擎确实支持递归正则表达式(请参阅Peter Krauss的答案),但如果没有额外的代码,则不能在ICU引擎(例如,Apple 使用的)上使用正则表达式来实现此目的。您需要执行以下操作:

这可以检测到任何回文,但确实需要循环(由于正则表达式无法计数,因此需要循环)。

$a = "teststring";
while(length $a > 1)
{
   $a =~ /(.)(.*)(.)/;
   die "Not a palindrome: $a" unless $1 eq $3;
   $a = $2;
}
print "Palindrome";

4
好答案。这个问题并没有要求立即使用一个正则表达式来检测回文集,而只是询问一种使用正则表达式来检测回文集的方法。恭喜您有这种洞察力。
斯图尔特

1
另请参见仅使用一个正则表达式的最简单匹配(无需字符串操作)stackoverflow.com/a/48608623/287948
Peter Krauss,

谢谢@PeterKrauss。不知道PCRE有递归。参考了您的答案。
Airsource Ltd

32

这是不可能的。回文不是由常规语言定义的。(看,我DID学习了计算机理论)


2
大多数正则表达式引擎捕获的内容不止是常规语言(例如,net可以捕获匹配的括号)。仅标准正则表达式仅限于常规lang。
圣地亚哥帕拉迪诺

这个问题确实使用了“正则表达式”这个术语,所以ZCHudson的答案是正确的。
paxos1977

2
@austirg:ZCHudson的回答是正确的,但不完整。现代编程语言中使用的正则表达式与理论CS类中使用的正则表达式是不同的野兽。该术语仅是历史遗产。请参阅stackoverflow.com/questions/233243#235199和我的答案。
jfs

2
@JF塞巴斯蒂安-我必须在这一点上同意奥斯蒂格。当使用术语“正则表达式”时未提及特定的编程语言时,将适用comp sci定义。并非所有支持正则表达式的语言都可以做到这一点,因此我们不应该假定此处使用的一种语言也可以。
Ronologist

@Rontologist:我认为问题中对编程语言的选择没有任何限制,因此可以使用任何语言。看看右边:相关问题中“正则表达式”是什么意思?是否在其中提到了特定的编程语言?
jfs

27

使用Perl正则表达式:

/^((.)(?1)\2|.?)$/

但是,正如许多人指出的那样,如果您要严格的话,则不能将其视为正则表达式。正则表达式不支持递归。


这在PCRE中不起作用(它与“ ababa”不匹配),但是在Perl 5.10中却有效
newacct

你是对的。PCRE似乎确实将递归视为一个原子组,而Perl允许在其中进行回溯。我认为不可能在PCRE中进行此检查。
Markus Jarderot

1
令人惊讶的是,不适用于非拉丁语言,例如亚美尼亚语言。
Temujin

3
@Temujin这是因为unicode字符被匹配为编码字节(添加了/u修饰符),或者是由于组合符。(.\X转义序列代替)。
Markus Jarderot '16

1
我的模式在PCRE中不起作用。它确实在Perl中工作。子字符串重复时,您的模式失败。例如abababa。使用基于PCRE的正则表达式引擎时,不可能对每个输入都使用递归。Casimirs正则表达式使用另一种方法,即使用迭代和可变状态,非常引人入胜。
Markus Jarderot '19

15

这是一种用于检测4种字母回文(例如契约)的字符,用于任何类型的字符:

\(.\)\(.\)\2\1

这是一种用于检测5个字母的回文(例如雷达),仅检查字母的方法:

\([a-z]\)\([a-z]\)[a-z]\2\1

因此,似乎每个可能的字长我们都需要一个不同的正则表达式。 Python邮件列表上的该帖子包括有关原因的一些详细信息(有限状态自动机和抽水引理)。


14

根据您的自信程度,我会给出以下答案:

我不会用正则表达式来做。这不是正则表达式的适当用法。


3
我希望您能多解释一下,以表明您确实了解正则表达式的局限性。您的简单答案可能会被视为“我很困惑”。
Scott Wegner's

因此,他给了从属条款。
Will Bickford

13

是的,您可以在.Net中做到!

(?<N>.)+.?(?<-N>\k<N>)+(?(N)(?!))

您可以在这里检查!这是一个很棒的帖子!


.NET风格的Regex的全部要点是它们不是规则的,因为它们不是有限状态的自动机。从理论上讲,它们并不是真正的正则表达式。

12

StackOverflow充满了诸如“正则表达式?不,他们不支持它。他们支持它。”之类的答案。

事实是,正则表达式不再与正则语法有关现代正则表达式具有诸如递归和平衡组之类的功能,并且其实现的可用性不断增长(例如,请参见此处的Ruby示例)。以我的观点,坚持旧的信念,即我们领域中的正则表达式只不过是编程概念而已,只会适得其反。现在不是时候让他们讨厌不再适合的单词选择了,现在是我们接受事物并继续前进的时候了。

这是Perl本身的创建者Larry Wall引文

(…)通常与我们所说的“正则表达式”有关,后者仅与真正的正则表达式相关。但是,该术语随着我们的模式匹配引擎的功能而增长,因此在这里我不会尝试解决语言上的必要性。但是,我通常将它们称为“ regexes”(或当我处于盎格鲁-撒克逊语境时称为“ regexen”)。

而且这里有一个博客帖子PHP的核心开发者之一

由于这篇文章很长,这里总结了要点:

  • 在形式语言理论的背景下,程序员使用的“正则表达式”与原始的正则性概念几乎没有共通之处。
  • 正则表达式(至少是PCRE)可以匹配所有上下文无关的语言。因此,它们还可以匹配格式正确的HTML和几乎所有其他编程语言。
  • 正则表达式至少可以匹配某些上下文相关的语言。
  • 正则表达式的匹配是NP完全的。这样,您可以使用正则表达式解决任何其他NP问题。

话虽如此,您可以使用以下命令将正则表达式与回文匹配:

^(?'letter'[a-z])+[a-z]?(?:\k'letter'(?'-letter'))+(?(letter)(?!))$

...这显然与常规语法无关。
此处提供更多信息:http : //www.regular-expressions.info/balancing.html


9

正如一些人已经说过的那样,没有一个开箱即用的正则表达式可以检测到一般的回文,但是如果您想检测到一定长度的回文,可以使用类似

(.?)(.?)(.?)(.?)(.?).?\5\4\3\2\1


6

在ruby中,您可以使用命名的捕获组。所以这样的事情会起作用-

def palindrome?(string)
  $1 if string =~ /\A(?<p>| \w | (?: (?<l>\w) \g<p> \k<l+0> ))\z/x
end

试试看,它的工作原理...

1.9.2p290 :017 > palindrome?("racecar")
 => "racecar" 
1.9.2p290 :018 > palindrome?("kayak")
 => "kayak" 
1.9.2p290 :019 > palindrome?("woahitworks!")
 => nil 

1
命名捕获组不是严格的正则表达式。willamette.edu/~fruehr/LLC/lab5.html
史蒂夫·

2
你是对的。这就是为什么我指出您必须使用命名捕获组的原因。
泰勒

有人可以为一个新手逐字逐字地解释RE吗?我理解以下所有内容(用逗号分隔“原子”)/,\ A,(,|,\ w,|,(,(,\ w,),),),\ z,/,x,但我不知道我不理解这些?<p>,?:,?<l>,\ g <p>,\ k <l + 0>中的任何一个,而我正在使用rubular.com寻求帮助,似乎可以理解RE(当然),但这并不能帮助我看到它,甚至“对于完整的Ruby regex指南,请参阅镐。” 没有帮助,因为与“镐”链接的站点无法解释我无法理解的原子。我知道 ?跟随a匹配零或a之一,但是?在角色前面?
潜水员凯文·福特

嗯,命名为捕获组!真好 @SteveMoser现在是一个断开的链接,但是我发现了另一个。感谢泰勒提到它们,否则我将不知道?<p>和?<l>和?:(非捕获捕获组)以及\ g <p>和\ k <l + 0>。我还是看不到什么?<p> | 是的。不| 意思是“或”?我无法在RE中找到有关管道使用情况的文档。我仍然很高兴看到有关此非常好的RE的详细说明。
潜水员凯文·福特

5

实际上,使用字符串操作比使用正则表达式更容易:

bool isPalindrome(String s1)

{

    String s2 = s1.reverse;

    return s2 == s1;
}

我意识到这并不能真正回答面试问题,但是您可以使用它来展示您如何更好地完成一项任务,并且您不是典型的“拿着锤子的人”,他把所有问题都看成是钉子。”


尽管我非常喜欢这个答案,但我确实认为,使用BreakIterator将字符串适当地拆分为可视字符会获得额外的好处。
Trejkaz 2014年

5

这是我对Regex Golf第五级(一个男人,一个计划)的回答。使用浏览器的Regexp,它最多可以使用7个字符(我使用的是Chrome 36.0.1985.143)。

^(.)(.)(?:(.).?\3?)?\2\1$

这是最多9个字符的字符

^(.)(.)(?:(.)(?:(.).?\4?)?\3?)?\2\1$

为了增加最大字符数,您需要反复替换。?。(?:(。)。?\ n?)?


1
我设法用一个字符少一点的^(。)(。)(。)?。?\ 3 \ 2 \ 1 $
Ben Ellis

非常感谢您宠爱我:-)
U10-Forward,

为什么其余的人只有13个,但现在是19个
U10转发

5

递归正则表达式可以做到!

如此简单且不言而喻的算法来检测包含回文的字符串:

   (\w)(?:(?R)|\w?)\1

rexegg.com/regex-recursion上,本教程说明了其工作原理。


它可以在任何语言下正常工作,这里是一个示例,它使用PHP从与概念验证相同的来源(链接)改编而成:

$subjects=['dont','o','oo','kook','book','paper','kayak','okonoko','aaaaa','bbbb'];
$pattern='/(\w)(?:(?R)|\w?)\1/';
foreach ($subjects as $sub) {
  echo $sub." ".str_repeat('-',15-strlen($sub))."-> ";
  if (preg_match($pattern,$sub,$m)) 
      echo $m[0].(($m[0]==$sub)? "! a palindrome!\n": "\n");
  else 
      echo "sorry, no match\n";
}

输出

dont ------------> sorry, no match
o ---------------> sorry, no match
oo --------------> oo! a palindrome!
kook ------------> kook! a palindrome!
book ------------> oo
paper -----------> pap
kayak -----------> kayak! a palindrome!
okonoko ---------> okonoko! a palindrome!
aaaaa -----------> aaaaa! a palindrome!
bbbb ------------> bbb

比较中

正则表达式^((\w)(?:(?1)|\w?)\2)$ 执行相同的工作,但是是/否是“包含”。
PS:它使用的定义中,“ o”不是文物,“ able-elba”连字符格式不是回文,而是“ ableelba”。命名为definition1
当“ o”和“ able-elba”为回文教鞭时,命名为definition2

与其他“回文正则表达式”相比,

  • ^((.)(?:(?1)|.?)\2)$上面的base-regex \w不受限制地接受“ able-elba”。

  • ^((.)(?1)?\2|.)$@LilDevil)使用definition2(接受“ o”和“ able-elba”,因此在识别“ aaaaa”和“ bbbb”字符串方面也有所不同)。

  • ^((.)(?1)\2|.?)$@Markus)未检测到“异常”,也未检测到“ bbbb”

  • ^((.)(?1)*\2|.?)$@Csaba)使用definition2


注意:要进行比较,您可以在$subjects每个比较的正则表达式的处和行中添加更多单词,

  if (preg_match('/^((.)(?:(?1)|.?)\2)$/',$sub)) echo " ...reg_base($sub)!\n";
  if (preg_match('/^((.)(?1)?\2|.)$/',$sub)) echo " ...reg2($sub)!\n";
  if (preg_match('/^((.)(?1)\2|.?)$/',$sub)) echo " ...reg3($sub)!\n";
  if (preg_match('/^((.)(?1)*\2|.?)$/',$sub)) echo " ...reg4($sub)!\n";

5

您也可以不使用递归来做到这一点:

\A(?:(.)(?=.*?((?(2)\1\2|\1))\z))*?.?\2\z

允许一个字符:

\A(?:(?:(.)(?=.*?((?(2)\1\2|\1))\z))*?.?\2|.)\z

与Perl,PCRE一起使用

演示

对于Java:

\A(?:(.)(?=.*?(\1\2\z|(?<!(?=\2\z).{0,1000})\1\z)))*?.?\2\z

演示


1
这是对正则表达式问题的非常有趣的答案。实际上,这是通过我的一些测试的唯一模式。感谢这一封卡西米尔(Casimir):)
泡泡泡泡

1
@bobblebubble:感谢您的支持。如您所见,我最近编辑了此答案,因为以前的版本是错误的(三年来真是太可惜了)。
卡西米尔和希波吕特

4

关于PCRE表达(来自MizardX):

/^((.)(?1)\2|.?)$/

你测试过了吗?在Win XP Pro下,在我的PHP 5.3上它失败了:aaaba实际上,我对表达式expression进行了一些修改,使其显示为:

/^((.)(?1)*\2|.?)$/

我认为正在发生的事情是,当外在的两个角色锚定时,其余的内在的角色却没有。这不是一个完整的答案,因为尽管它错误地传递了“ aaaba”和“ aabaacaa”,但在“ aabaaca”上却确实失败了。

我想知道是否对此进行了修复,并且Perl示例(由JF Sebastian / Zsolt设计)是否可以正确通过我的测试?

维也纳的Csaba Gabor


4
/\A(?<a>|.|(?:(?<b>.)\g<a>\k<b+0>))\z/

对于Oniguruma引擎(在Ruby中使用)有效

取自实用书架


3

在Perl中(另请参见Zsolt Botykai的答案):

$re = qr/
  .                 # single letter is a palindrome
  |
  (.)               # first letter
  (??{ $re })??     # apply recursivly (not interpolated yet)
  \1                # last letter
/x;

while(<>) {
    chomp;
    say if /^$re$/; # print palindromes
}

2

正如ZCHudson指出的那样,由于回文集不是常规语言,因此无法使用常规的正则表达式来确定是否为回文集

当他说“不可能”不是面试官所寻求的答案时,我完全不同意AirsourceLtd。在面试过程中,当我遇到一个好的候选人时,我会提出这样的问题,以检查当我们建议他做错事时他是否能找到正确的论据。我不想雇用一个人,如果他知道的更好,他们会尝试以错误的方式做事。



2

我要向面试官解释,回文构成的语言不是常规语言,而是与上下文无关。

匹配所有回文的正则表达式将是无限的。相反,我建议他将自己限制在可以接受的最大回文数上。或者如果需要使用所有回文,至少要使用某种类型的NDPA,或者仅使用简单的字符串反转/等值技术。


2

在捕获组用尽之前,可以使用正则表达式做到最好:

/(.?)(.?)(.?)(.?)(.?)(.?)(.?)(.?)(.?).?\9\8\7\6\5\4\3\2\1/

这将匹配长度不超过19个字符的所有回文。

以编程方式求解所有长度都是微不足道的:

str == str.reverse ? true : false

您的正则表达式不起作用。例如,它将表明“ abac”是一个匹配项……
达尔文·

2

我还没有代表在内联发表评论,但是MizardX提供的正则表达式由Csaba修改,可以进一步修改以使其在PCRE中起作用。我发现的唯一失败是单字符字符串,但我可以对此进行单独测试。

/^((.)(?1)?\2|.)$/

如果可以使它在其他任何字符串上失败,请发表评论。


2
#!/usr/bin/perl

use strict;
use warnings;

print "Enter your string: ";
chop(my $a = scalar(<STDIN>));    
my $m = (length($a)+1)/2;
if( (length($a) % 2 != 0 ) or length($a) > 1 ) { 
  my $r; 
  foreach (0 ..($m - 2)){
    $r .= "(.)";
  }
  $r .= ".?";
  foreach ( my $i = ($m-1); $i > 0; $i-- ) { 
    $r .= "\\$i";
  } 
  if ( $a =~ /(.)(.).\2\1/ ){
    print "$a is a palindrome\n";
  }
  else {
    print "$a not a palindrome\n";
 }
exit(1);
}
print "$a not a palindrome\n";

2

根据自动机理论,不可能匹配任何长度的花粉症(因为这需要无限量的记忆力)。但是可以匹配固定长度的回历。说有可能编写一个正则表达式来匹配长度<= 5或<= 6等的所有回文,但在上限不清楚的情况下不匹配> = 5等


2

在Ruby中,您可以使用\b(?'word'(?'letter'[a-z])\g'word'\k'letter+0'|[a-z])\b匹配回文词,例如a, dad, radar, racecar, and redivider。ps:此正则表达式仅匹配长度为奇数个字母的回文词。

让我们看看这个正则表达式如何匹配雷达。单词边界\ b在字符串的开头匹配。正则表达式引擎进入捕获组“单词”。[az]匹配r,然后将其以递归级别零存储在捕获组“ letter”的堆栈中。现在,正则表达式引擎进入“单词”组的第一个递归。(?'letter'[az])匹配并捕获递归级别1的a。正则表达式进入组“单词”的第二次递归。(?'letter'[az])在第二递归级别捕获d。在接下来的两次递归中,该小组在第三和第四级捕获a和r。第五次递归失败,因为在字符串中没有[az]要匹配的字符。正则表达式引擎必须回溯。

正则表达式引擎现在必须在“单词”组内尝试第二种替代方法。正则表达式中的第二个[az]与字符串中的最后一个r相匹配。引擎现在从成功的递归退出,将一级返回到第三级递归。

匹配(&word)后,引擎达到\ k'letter + 0'。后向引用失败,因为正则表达式引擎已到达主题字符串的末尾。因此,它再次回溯。现在第二个替代项与a匹配。正则表达式引擎从第三次递归退出。

正则表达式引擎再次匹配(&word),并且需要再次尝试反向引用。向后引用指定+0或当前递归级别,即2。在此级别,捕获组与d匹配。后向引用失败,因为字符串中的下一个字符是r。再次回溯,第二个替代项匹配d。

现在,\ k'letter + 0'匹配字符串中的第二个a。这是因为正则表达式引擎返回了第一个递归,在此期间捕获组与第一个a匹配。正则表达式引擎退出第一个递归。

正则表达式引擎现在返回所有递归之外。表示在这个级别上,捕获组存储了r。现在,后向引用可以匹配字符串中的最后一个r。由于引擎不再处于任何递归内,因此在该组之后继续进行正则表达式的其余部分。\ b在字符串末尾匹配。到达正则表达式的末尾,并返回雷达作为整体匹配项。


2

这是PL / SQL代码,它使用正则表达式判断给定的字符串是否为回文式:

create or replace procedure palin_test(palin in varchar2) is
 tmp varchar2(100);
 i number := 0;
 BEGIN
 tmp := palin;
 for i in 1 .. length(palin)/2 loop
  if length(tmp) > 1 then  
    if regexp_like(tmp,'^(^.).*(\1)$') = true then 
      tmp := substr(palin,i+1,length(tmp)-2);
    else 
      dbms_output.put_line('not a palindrome');
      exit;
    end if;
  end if;  
  if i >= length(palin)/2 then 
   dbms_output.put_line('Yes ! it is a palindrome');
  end if;
 end loop;  
end palin_test;

2
my $pal='malayalam';

while($pal=~/((.)(.*)\2)/){                                 #checking palindrome word
    $pal=$3;
}
if ($pal=~/^.?$/i){                                         #matches single letter or no letter
    print"palindrome\n";
}
else{
    print"not palindrome\n";
}

2
尽管此代码可以回答问题,但提供有关如何和/或为什么解决问题的其他上下文将提高​​答案的长期价值。
唐老鸭

2

该正则表达式将检测多达22个字符的回文,而忽略空格,制表符,逗号和引号。

\b(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*(?:(\w)[ \t,'"]*\11?[ \t,'"]*\10|\10?)[ \t,'"]*\9|\9?)[ \t,'"]*\8|\8?)[ \t,'"]*\7|\7?)[ \t,'"]*\6|\6?)[ \t,'"]*\5|\5?)[ \t,'"]*\4|\4?)[ \t,'"]*\3|\3?)[ \t,'"]*\2|\2?))?[ \t,'"]*\1\b

在这里玩:https : //regexr.com/4tmui


0

对Airsource Ltd的方法进行了一些细微的改进,使用伪代码:

WHILE string.length > 1
    IF /(.)(.*)\1/ matches string
        string = \2
    ELSE
        REJECT
ACCEPT
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.