【问题标题】:Find most common substring in a list of strings?在字符串列表中查找最常见的子字符串?
【发布时间】:2020-02-23 08:56:13
【问题描述】:

我有一个字符串名称的 Python 列表,我想从所有名称中删除一个公共子字符串。

在阅读了类似的answer 之后,我几乎可以使用SequenceMatcher 达到预期的效果。

但仅当 所有 项具有公共子字符串时:

From List:
string 1 = myKey_apples
string 2 = myKey_appleses
string 3 = myKey_oranges

common substring = "myKey_"

To List:
string 1 = apples
string 2 = appleses
string 3 = oranges

但是,我有一个稍微嘈杂的列表,其中包含一些不符合相同命名约定的分散项目。

我想从大多数中删除“最常见”的子字符串:

From List:
string 1 = myKey_apples
string 2 = myKey_appleses
string 3 = myKey_oranges
string 4 = foo
string 5 = myKey_Banannas

common substring = ""

To List:
string 1 = apples
string 2 = appleses
string 3 = oranges
string 4 = foo
string 5 = Banannas

我需要一种方法来匹配“myKey_”子字符串,以便我可以从所有名称中删除它。

但是当我使用SequenceMatcher 时,项目“foo”会导致“最长匹配”等于空白“”。

我认为解决这个问题的唯一方法是找到“最常见的子字符串”。但这怎么可能实现呢?


基本示例代码:

from difflib import SequenceMatcher

names = ["myKey_apples",
"myKey_appleses",
"myKey_oranges",
#"foo",
"myKey_Banannas"]

string2 = names[0]
for i in range(1, len(names)):
    string1 = string2
    string2 = names[i]
    match = SequenceMatcher(None, string1, string2).find_longest_match(0, len(string1), 0, len(string2))

print(string1[match.a: match.a + match.size]) # -> myKey_

【问题讨论】:

  • 我认为您实际上不想删除最常见的子字符串。通常只有一个字母。例如,您的所有字符串都包含字母 s(例如,appless 结尾)。有两个感兴趣的数字:子字符串的长度和包含子字符串的搜索空间的百分比。当你增加一个时,你会减少另一个。

标签: python


【解决方案1】:

给定names = ["myKey_apples", "myKey_appleses", "myKey_oranges", "foo", "myKey_Banannas"]

我能想到的O(n^2) 解决方案是找到所有可能的子字符串并将它们与它们出现的次数一起存储在字典中:

substring_counts={}

for i in range(0, len(names)):
    for j in range(i+1,len(names)):
        string1 = names[i]
        string2 = names[j]
        match = SequenceMatcher(None, string1, string2).find_longest_match(0, len(string1), 0, len(string2))
        matching_substring=string1[match.a:match.a+match.size]
        if(matching_substring not in substring_counts):
            substring_counts[matching_substring]=1
        else:
            substring_counts[matching_substring]+=1

print(substring_counts) #{'myKey_': 5, 'myKey_apples': 1, 'o': 1, '': 3}

然后选择出现的最大子串

import operator
max_occurring_substring=max(substring_counts.iteritems(), key=operator.itemgetter(1))[0]
print(max_occurring_substring) #myKey_

【讨论】:

  • 这简直是天作之合!甚至使用了这个疯狂的列表:["sti_myKey_123","sti_myKey_233","stimm_myKey_676","sti_myKey_879","foo","sti_myKey_2345","sti_myKey_test3","ti_myKey_123"] Result="sti_myKey_"。非常感谢:)
【解决方案2】:

这里有一个过于冗长的解决方案:

def find_matching_key(list_in, max_key_only = True):
  """
  returns the longest matching key in the list * with the highest frequency
  """
  keys = {}
  curr_key = ''

  # If n does not exceed max_n, don't bother adding
  max_n = 0

  for word in list(set(list_in)): #get unique values to speed up
    for i in range(len(word)):
      # Look up the whole word, then one less letter, sequentially
      curr_key = word[0:len(word)-i]
      # if not in, count occurance
      if curr_key not in keys.keys() and curr_key!='':
        n = 0
        for word2 in list_in:
          if curr_key in word2:
            n+=1
        # if large n, Add to dictionary
        if n > max_n:
          max_n = n
          keys[curr_key] = n
    # Finish the word
  # Finish for loop  
  if max_key_only:
    return max(keys, key=keys.get)
  else:
    return keys    

# Create your "from list"
From_List = [
             "myKey_apples",
             "myKey_appleses",
             "myKey_oranges",
             "foo",
             "myKey_Banannas"
]

# Use the function
key = find_matching_key(From_List, True)

# Iterate over your list, replacing values
new_From_List = [x.replace(key,'') for x in From_List]

print(new_From_List)
['apples', 'appleses', 'oranges', 'foo', 'Banannas']

不用说,这个解决方案通过递归看起来会更整洁。不过我想我会为你勾勒出一个粗略的动态编程解决方案。

【讨论】:

    【解决方案3】:

    我会首先找到出现次数最多的起始字母。然后我会取每个有那个起始字母的单词,然后把所有这些单词都有匹配的字母。然后最后我会删除从每个起始词中找到的前缀:

    from collections import Counter
    from itertools import takewhile
    
    strings = ["myKey_apples", "myKey_appleses", "myKey_oranges", "berries"]
    
    def remove_mc_prefix(words):
        cnt = Counter()
        for word in words:
            cnt[word[0]] += 1
        first_letter = list(cnt)[0]
    
        filter_list = [word for word in words if word[0] == first_letter]
        filter_list.sort(key = lambda s: len(s)) # To avoid iob
    
        prefix = ""
        length = len(filter_list[0])
        for i in range(length):
            test = filter_list[0][i]
            if all([word[i] == test for word in filter_list]):
                prefix += test
            else: break
        return [word[len(prefix):] if word.startswith(prefix) else word for word in words]
    
    print(remove_mc_prefix(strings))
    

    输出:['apples', 'appleses', 'oranges', 'berries']

    【讨论】:

      猜你喜欢
      • 2022-01-25
      • 1970-01-01
      • 2019-07-16
      • 2019-04-25
      • 1970-01-01
      • 2015-11-29
      • 2012-12-08
      • 2020-03-20
      • 1970-01-01
      相关资源
      最近更新 更多