【问题标题】:Determine prefix from a set of (similar) strings从一组(相似的)字符串中确定前缀
【发布时间】:2011-07-16 15:03:31
【问题描述】:

我有一组字符串,例如

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。

【问题讨论】:

标签: python string prefix


【解决方案1】:

永远不要重写提供给您的内容: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 不完全是!字符串上的 min() 和 max() 是字典中的最小值和 mnaximum,就像字典中一样。因此,当最小值和最大值的首字母相同时,它们之间的所有其他单词也必须具有相同的字母,依此类推。
  • 参数是否需要是有效的路径名?如果他们不是,会发生什么?文档什么也没说,所以我不太确定这可以用于任意字符串。
  • @hochl 否。此代码只是查看字符串,而不是路径。如果它们恰好是所有路径,请注意此前缀 commonprefix({"/aaA/b", "/aaB/b"}) == "/aa",这可能不是您想要使用的路径。
  • @hochi 如果您确实需要有效路径,请查看姐妹函数os.path.commonpath。来自文档:“与 commonprefix() 不同,它返回一个有效路径。”
【解决方案2】:

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)
【解决方案3】:

这是我的解决方案:

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]

【讨论】:

    【解决方案4】:

    以下是可行的,但可能效率很低的解决方案。

    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

    【讨论】:

      【解决方案5】:

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

      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 可能会更好,这是一个非常优雅的函数。

      【讨论】:

        【解决方案6】:

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

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

        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)]
        

        【讨论】:

          【解决方案7】:

          这是一个简单的干净解决方案。这个想法是使用 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!虽然这段代码 sn-p 可以解决问题,包括说明 如何为什么 这解决了问题would really help 以提高您的帖子质量。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人!请edit您的答案添加解释,并说明适用的限制和假设。
          • 这样干净吗?
          • 怎么不干净?其他解决方案具有块中的代码。逻辑很简单,可以在一次作业中完成。
          【解决方案8】:

          这是另一种使用 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)])
          

          【讨论】:

            【解决方案9】:

            我的问题略有不同,谷歌将我发送到这里,所以我认为记录下来会很有用:

            我有一个类似的列表:

            • 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)
            

            【讨论】:

              【解决方案10】:

              在一行中不使用 itertools,没有特别的原因,虽然它确实遍历了每个字符:

              ''.join([z[0] for z in zip(*(list(s) for s in strings)) if all(x==z[0] for x in z)])
              

              【讨论】:

                【解决方案11】:

                从给定的输入字符串中找到所有单词的公共前缀,如果没有公共前缀打印-1

                stringList = ['my_prefix_what_ever', 'my_prefix_what_so_ever', 'my_prefix_doesnt_matter']
                len2 = len( stringList )
                if len2 != 0:
                    # let shortest word is prefix
                    prefix = min( stringList )
                    for i in range( len2 ):
                        word = stringList[ i ]
                        len1 = len( prefix )
                        # slicing each word as lenght of prefix
                        word = word[ 0:len1 ]
                        for j in range( len1 ):
                            # comparing each letter of word and prefix
                            if word[ j ] != prefix[ j ]:
                                # if letter does not match slice the prefix
                                prefix = prefix[ :j ]
                                break # after getting comman prefix move to next word
                    if len( prefix ) != 0:
                        print("common prefix: ",prefix)
                    else:
                        print("-1")
                else:
                     print("string List is empty") 
                

                【讨论】:

                  猜你喜欢
                  • 2013-09-06
                  • 2020-06-23
                  • 2021-06-08
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-05-08
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多