从一组(相似)字符串中确定前缀


72

我有一组字符串,例如

my_prefix_what_ever
my_prefix_what_so_ever
my_prefix_doesnt_matter

我只是想找到这些字符串中最长的公共部分,这里是前缀。在上面的结果应该是

my_prefix_

琴弦

my_prefix_what_ever
my_prefix_what_so_ever
my_doesnt_matter

应该导致前缀

my_

Python中是否有一种相对轻松的方法来确定前缀(而不必手动遍历每个字符)?

PS:我正在使用Python 2.6.3。


因此,您实际上是在寻求最长的公共子序列吗?
康拉德·鲁道夫

Answers:


136

永远不要重写提供给您的内容:os.path.commonprefix完全是这样:

返回最长的路径前缀(一个字符一个字符),它是列表中所有路径的前缀。如果列表为空,则返回空字符串('')。请注意,这可能返回无效路径,因为它一次可以处理一个字符。

为了与其他答案进行比较,下面是代码:

# Return the longest prefix of all list elements.
def commonprefix(m):
    "Given a list of pathnames, returns the longest common leading component"
    if not m: return ''
    s1 = min(m)
    s2 = max(m)
    for i, c in enumerate(s1):
        if c != s2[i]:
            return s1[:i]
    return s1

我认为这只能处理m中的两个字符串,不是吗?评论虽然说“所有列表元素,有点表示任何数量的元素”
sramij

@sramij不完全是!像字典中一样,字符串上的min()和max()是字典上的最小值和mnaximum。因此,当最小值和最大值具有相同的第一个字母时,则它们之间的所有其他单词也必须具有相同的字母,依此类推。
Peťan

1
参数是否必须是有效的路径名?如果不是,会发生什么?文档什么也没说,所以我不确定这是否可以用于任意字符串。
hochl

@hochl不。此代码仅查看字符串,而不查看路径。如果碰巧是所有路径,请小心此前缀commonprefix({"/aaA/b", "/aaB/b"}) == "/aa",这可能不是您要使用的路径。
杰西·奇斯霍尔姆

1
@hochi如果确实需要有效的路径,请查看姐妹函数os.path.commonpath。从文档中:“与commonprefix()不同,这将返回有效路径。”
AneesAhmed777

15

Ned Batchelder可能是正确的。但有趣的是,这是phimuemue的更有效版本答案的itertools

import itertools

strings = ['my_prefix_what_ever', 
           'my_prefix_what_so_ever', 
           'my_prefix_doesnt_matter']

def all_same(x):
    return all(x[0] == y for y in x)

char_tuples = itertools.izip(*strings)
prefix_tuples = itertools.takewhile(all_same, char_tuples)
''.join(x[0] for x in prefix_tuples)

作为对可读性的冒犯,这是一个单行版本:)

>>> from itertools import takewhile, izip
>>> ''.join(c[0] for c in takewhile(lambda x: all(x[0] == y for y in x), izip(*strings)))
'my_prefix_'

对于Python3,请替换itertools.izip(*strings)zip(*strings)
里吉斯

6

这是我的解决方案:

a = ["my_prefix_what_ever", "my_prefix_what_so_ever", "my_prefix_doesnt_matter"]

prefix_len = len(a[0])
for x in a[1 : ]:
    prefix_len = min(prefix_len, len(x))
    while not x.startswith(a[0][ : prefix_len]):
        prefix_len -= 1

prefix = a[0][ : prefix_len]

3

以下是一个有效的解决方案,但可能效率不高。

a = ["my_prefix_what_ever", "my_prefix_what_so_ever", "my_prefix_doesnt_matter"]
b = zip(*a)
c = [x[0] for x in b if x==(x[0],)*len(x)]
result = "".join(c)

对于少量的字符串,以上内容完全没有问题。但是对于较大的集合,我个人将编写另一种手动解决方案,该解决方案逐个检查每个字符并在出现差异时停止。

从算法上讲,这产生了相同的过程,但是,也许可以避免构造列表c


1

出于好奇,我想出了另一种方法:

def common_prefix(strings):

    if len(strings) == 1:#rule out trivial case
        return strings[0]

    prefix = strings[0]

    for string in strings[1:]:
        while string[:len(prefix)] != prefix and prefix:
            prefix = prefix[:len(prefix)-1]
        if not prefix:
            break

    return prefix

strings = ["my_prefix_what_ever","my_prefix_what_so_ever","my_prefix_doesnt_matter"]

print common_prefix(strings)
#Prints "my_prefix_"

正如Ned所指出的,最好使用os.path.commonprefix,这是一个非常优雅的功能。


1

第二行在输入字符串中的每个字符上使用reduce函数。它返回N + 1个元素的列表,其中N是最短输入字符串的长度。

批次中的每个元素都是(a)输入字符(如果所有输入字符串在该位置匹配),或(b)无。 lot.index(None)是批次中第一个None的位置:公共前缀的长度。 出来的是共同的前缀。

val = ["axc", "abc", "abc"]
lot = [reduce(lambda a, b: a if a == b else None, x) for x in zip(*val)] + [None]
out = val[0][:lot.index(None)]

0

这是使用OrderedDict和最少的代码执行此操作的另一种方法。

import collections
import itertools

def commonprefix(instrings):
    """ Common prefix of a list of input strings using OrderedDict """

    d = collections.OrderedDict()

    for instring in instrings:
        for idx,char in enumerate(instring):
            # Make sure index is added into key
            d[(char, idx)] = d.get((char,idx), 0) + 1

    # Return prefix of keys while value == length(instrings)
    return ''.join([k[0] for k in itertools.takewhile(lambda x: d[x] == len(instrings), d)])

0

这是一个简单的干净解决方案。这个想法是使用zip()函数将所有字符排列在第一字符列表,第二字符列表,...第n个字符列表中。然后,迭代每个列表以检查它们是否仅包含1个值。

a = ["my_prefix_what_ever", "my_prefix_what_so_ever", "my_prefix_doesnt_matter"]

list = [all(x[i] == x[i+1] for i in range(len(x)-1)) for x in zip(*a)]

print a[0][:list.index(0) if list.count(0) > 0 else len(list)]

输出:my_prefix_


欢迎使用Stack Overflow!尽管此代码段可以解决问题,但包括有关如何以及为什么解决该问题的说明,确实可以帮助提高您的帖子质量。请记住,您将来会为读者回答问题,而不仅仅是现在问的人!请编辑您的答案以添加解释,并指出适用的限制和假设。
Toby Speight

这个干净吗?
thang

怎么不干净?其他解决方案的代码块中。逻辑很简单,只需一次分配即可完成。
Patmanizer

0

我对该问题进行了细微的改动,谷歌将我发送到这里,因此我认为记录以下内容将非常有用:

我有一个像这样的清单:

  • my_prefix_what_ever
  • my_prefix_what_so_ever
  • my_prefix_doesnt_matter
  • some_noise
  • some_other_noise

所以我希望my_prefix能回来。可以通过以下方式完成:

from collections import Counter

def get_longest_common_prefix(values, min_length):
    substrings = [value[0: i-1] for value in values for i in range(min_length, len(value))]
    counter = Counter(substrings)
    # remove count of 1
    counter -= Counter(set(substrings))
    return max(counter, key=len)
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.