Regexp找到两个字符串的最长公共前缀

时间:2012-02-02 14:53:16

标签: python ruby regex perl replace

是否有正则表达式会找到两个字符串中最长的公共前缀?如果一个正则表达式无法解决这个问题,那么使用regexp(perl,ruby,python,任何东西)的代码或oneliner最优雅的是什么。

PS:我可以通过编程轻松地做到这一点,我要求好奇心,因为在我看来这可以通过regexp解决。

PPS:使用正则表达式的O(n)解决方案的额外奖励。来吧,它应该存在!

14 个答案:

答案 0 :(得分:27)

如果某个字符不包含任何字符串 - 例如\0 - 您可以写

"$first\0$second" =~ m/^(.*).*\0\1/s;

,最长的公共前缀将保存为$1


编辑添加:这显然效率很低。我认为,如果效率是一个问题,那么这根本不是我们应该使用的方法;但我们至少可以通过将.*更改为[^\0]*来改善它,以防止无用的贪婪,只需要再次回溯,并将第二个[^\0]*包裹在(?>…)中以防止回溯无法帮助。这样:

"$first\0$second" =~ m/^([^\0]*)(?>[^\0]*)\0\1/s;

这将产生相同的结果,但效率更高。 (但仍然没有像 那样有效地作为一种简单的非基于正则表达式的方法。如果字符串都具有 n 的长度,我认为它最坏的情况至少要采取O( n 2 )时间,而直接的非基于正则表达式的方法在时需要O( n )时间/ em>最糟糕的情况。)

答案 1 :(得分:19)

这是一个Python单行:

>>> a = 'stackoverflow'
>>> b = 'stackofpancakes'
>>> a[:[x[0]==x[1] for x in zip(a,b)].index(0)]
0: 'stacko'
>>> a = 'nothing in'
>>> b = 'common'
>>> a[:[x[0]==x[1] for x in zip(a,b)].index(0)]
1: ''
>>> 

答案 2 :(得分:14)

这是使用正则表达式的一种相当有效的方法。代码在Perl中,但原则应该适用于其他语言:

my $xor = "$first" ^ "$second";    # quotes force string xor even for numbers
$xor =~ /^\0*/;                    # match leading null characters
my $common_prefix_length = $+[0];  # get length of match

(值得注意的是,Perl的字符串XOR运算符(^)实际上填充了带有空值的较短字符串以匹配较长字符串的长度。因此,如果字符串可能包含空字符,如果较短的字符串恰好是较长字符串的前缀,则使用此代码计算的公共前缀长度可能超过较短字符串的长度。)

答案 3 :(得分:7)

简单而有效

def common_prefix(a,b):
  i = 0
  for i, (x, y) in enumerate(zip(a,b)):
    if x!=y: break
  return a[:i]

答案 4 :(得分:4)

您将遇到的问题是正则表达式一次匹配一个字符串,因此不用于比较两个字符串。

如果有一个字符可以肯定不在任何一个字符串中,你可以使用它将它们分成一个字符串,然后使用对组的反向引用进行搜索。

所以在下面的例子中我使用空格作为分隔符

>>> import re
>>> pattern = re.compile("(?P<prefix>\S*)\S*\s+(?P=prefix)")
>>> pattern.match("stack stable").group('prefix')
'sta'
>>> pattern.match("123456 12345").group('prefix')
'12345'

答案 5 :(得分:3)

这是一个O(N)解决方案,在三元组上有类似Foma的伪代码正则表达式(对于lcp,你有两个输入和一个输出)。为了简单起见,我假设一个二进制字母{a,b}:

def match {a:a:a, b:b:b};
def mismatch {a:b:ε, b:a:ε};
def lcp match* ∪ (match* mismatch (Σ:Σ:ε)*)

现在你只需要一种实现多磁带传感器的语言。

答案 6 :(得分:2)

O(n)解决方案的另一种尝试:

$x=length($first); $_="$first\0$second"; s/((.)(?!.{$x}\2)).*//s;

它取决于。{n}是否被认为是O(1)或O(n),我不知道这是如何有效地实现的。

注意:1。\ 0不应该在任何一个字符串中,它用作分隔符2.结果在$ _

答案 7 :(得分:1)

受Ruakh的回答启发,这是O(n)正则表达式解决方案:

"$first \0$second" =~ m/^(.*?)(.).*\0\1(?!\2)/s;

注:  1.两个字符串都不包含\ 0  2.最长公共前缀将保存为$ 1  3.空间很重要!

编辑:这是不正确的rukach metions,但这个想法是正确的,但我们应该推动regexp机器不要反复检查开头的字母。基本思想也可以在这个perl oneliner中重写。

perl -e ' $_="$first\0$second\n"; while(s/^(.)(.*?)\0\1/\2\0/gs) {print $1;}; '

我想知道它是否可以合并到regexp解决方案中。

答案 8 :(得分:1)

在某些远程情况下可能很有用,所以请点击这里:

RegEx只有3个步骤的解决方案(无法一次创建RegEx ):

字符串A:abcdef
字符串B:abcxef

  • 第一遍:从String A 创建RegEx(第1部分)
    匹配:/(.)/g
    替换:\1(
    结果:a(b(c(d(e(f(
    解释演示:http://regex101.com/r/aJ4pY7

  • 第二遍:从1st pass result创建RegEx 匹配:/^(.\()(?=(.*)$)|\G.\(/g
    替换:\1\2)?+
    结果:a(b(c(d(e(f()?+)?+)?+)?+)?+)?+
    解释演示:http://regex101.com/r/xJ7bK7

  • 第3遍:对String B中创建的RegEx进行测试2nd pass 匹配:/a(b(c(d(e(f()?+)?+)?+)?+)?+)?+/
    结果:abcexplained demo

以下是PHP中的美化 单行

preg_match('/^'.preg_replace('/^(.\()(?=(.*)$)|\G.\(/','\1\2)?+',preg_replace('/(.)/','\1(',$a)).'/',$b,$longest);

代码现场:http://codepad.viper-7.com/dCrqLa

答案 9 :(得分:0)

每个迭代解决方案中的非正则表达式,非重复字符串:

def common_prefix(a, b):
   #sort strings so that we loop on the shorter one
   a, b = sorted((a,b), key=len)
   for index, letter in a:
      if letter != b[index]:
          return a[:index - 1]
   return a

答案 10 :(得分:0)

我对regexp的第二个回答(我在评论中建议的优化)。写入简单,但如果第一个字符串很长,则不能简单有效地运行。

这是一个高效的,非正则表达式,可读的单行答案:

$ perl -E '($n,$l)=(0,length $ARGV[0]); while ($n < $l) { $s = substr($ARGV[0], $n, 1); last if $s ne substr($ARGV[1], $n, 1); $n++ } say substr($ARGV[0], 0, $n)' abce abcdef
abc

答案 11 :(得分:0)

在Foma或Xfst中使用扩展正则表达式。

def range(x) x.l;
def longest(L) L - range(range(L ∘ [[Σ:ε]+ [Σ:a]*]) ∘ [a:Σ]*); 
def prefix(W) range(W ∘ [Σ* Σ*:ε]);
def lcp(A,B) longest(prefix(A) ∩ prefix(B));

这里最难的部分是定义“最长”。一般来说,要 优化,你构造一组非最佳字符串(恶化)和 然后删除这些(过滤)。

这实际上是一种纯粹的方法,可以避免非常规操作 这样的捕捉。

答案 12 :(得分:-1)

我认为这是效率最低的。没有错误的检查等。

#!/usr/bin/perl
use strict;
use warnings;

my($s1,$s2)=(@ARGV);
#find the shortest string put it into s1, if you will.

my $n=0;
my $reg;

foreach my $c (split(//,$s1)) { $reg .="($c"; $n++;}

$reg .= ")?" x $n;

$s2 =~ /$reg/; 

print $&,"\n";

答案 13 :(得分:-1)

这是我针对leetcode问题实施的解决方案:

def max_len(strs):
    """
    :type strs: List[str]
    :rtype: int
    """
    min_s = len(strs[0]);
    for s in strs:
        if (len(s) < min_s):
            min_s = len(s);
    return min_s;


class Solution2:
    def longestCommonPrefix(self, strs):
    """
    :type strs: List[str]
    :rtype: str
    """
    acc = -1;
    test_len = max_len(strs);
    for i in range(test_len):
        t = strs[0][i];
        acc2 = 0;
        for j in range(len(strs)):
            if (strs[j][i] == t):
                acc2 += 1;
        if (acc2 == len(strs)):
            acc += 1;

    if (acc == -1):
        return ""
    else:
        return strs[0][:acc + 1]

希望这会有所帮助