【问题标题】:Forming a target string using minimal substrings of words from a word list使用单词列表中单词的最小子串形成目标字符串
【发布时间】:2019-09-06 18:47:03
【问题描述】:

我有一个包含名字的文本文件,但每年都会添加新名字。

我需要一个 Python 程序,它从文本文件中获取部分名称,并找到这些名称的子字符串的某种组合,这些子字符串可以连接起来创建一个匹配用户输入的字符串。

程序应使用文本文件中尽可能少的可用名称来执行此操作。

例如,如果文本文件包含以下内容:

Joppe
Fien
Katrijn
Sven
Kobe

程序要求输入一个不在文本文件中的名称。例如:

Please fill in a name: Katrien

然后它应该打印这个:

Katri => Katrijn
ien => Fien

不像这样——它正确地构建了名称,但有一个更好的解决方案,使用更少的单词:

K => Kobe
a => Katrijn
tr => Katrijn
ien => Fien

如果文本文件包含:

Joppe
Fien
Makatrijn
Sven
Kobe

它也可以打印这个:

Katr => Makatrijn
ien => Fien

我试过了,但没有结果:

name_input = input('Fill in a name: ')    

with open('namen.txt', 'r') as file:
    for name in file.readlines():
        for letter_name_input in name_input:
            for letter in name:
                if letter == letter_name_input:
                    print(letter)

【问题讨论】:

  • 如果您有任何问题。我很乐意回答。
  • 感谢 @ggorlen 让外观更好,我是 stackoverflow 的新手。
  • 最好的匹配是使用尽可能少的名字来命名给定的名字。
  • 哦,我想我明白了。您输入一个词,程序会检查文本文件中的可用词,以尝试使用尽可能少的可用词来构建目标词。有道理。
  • @ggorlen man...我可以隐约理解这个问题,但我做梦也想不到像你刚才所做的那样措辞......

标签: python-3.x algorithm search


【解决方案1】:

您可以使用将目标名称和一组名称作为输入的函数,尝试将目标名称的前缀与名称集中的每个名称匹配,从最长到最短,并为每个匹配的名称, 递归地从删除了匹配名称的名称集中找到将形成目标名称的名称(删除了前缀),并生成每个返回的组合,并将当前前缀和名称作为元组前缀:

def form_name(target, names):
    if target:
        for i in range(len(target), 0, -1):
            prefix = target[:i]
            matching_names = [name for name in names if prefix.lower() in name.lower()]
            if matching_names:
                for name in matching_names:
                    for fragments in form_name(target[i:], names - {name}):
                        yield [(prefix, name), *fragments]
    else:
        yield []

这样就可以使用min函数,以len为key函数,得到名字最少的组合:

from io import StringIO
file = StringIO('''Joppe
Fien
Katrijn
Sven
Kobe''')
for fragment, name in min(form_name('Katrien', set(file.read().split())), key=len):
    print(fragment, '=>', name)

输出:

Katri => Katrijn
en => Fien

演示:https://repl.it/repls/IllustriousTrustingIntegrationtesting

请注意,您的示例输入中的FienSven 都将匹配en 片段并使用最少的名称生成有效答案,因此min 函数将任意返回其中一个(这很好根据您的要求)。另请注意,您不应期望目标名称的片段重叠,因此在从目标名称 Katrien 中删除第一个片段 Katri 后,第二个片段应该是 en,而不是 ien

如果您有兴趣查看所有有效答案,可以先计算所有组合的最小长度,然后输出所有最小长度的组合:

combinations = list(form_name('Katrien', set(file.read().split())))
min_len = min(map(len, combinations))
for combination in combinations:
    if len(combination) == min_len:
        for fragment, name in combination:
            print(fragment, '=>', name)
        print()

这个输出:

Katri => Katrijn
en => Sven

Katri => Katrijn
en => Fien

Katr => Katrijn
ien => Fien

【讨论】:

  • 这完美无瑕。非常感谢。这将用于真实的东西。谢谢
【解决方案2】:

假设您希望在找到最短答案后立即停止搜索,这是我的解决方案:

首先,您需要一个函数将单词从最大可能的集合开始分解为所有可能的部分:

def breakWord(word, n):
  list = []
  for k in range(len(word)):
    subword = word[k:]
    out = [(subword[i:i+n]) for i in range(0, len(subword), n)] 
    if(k > 0): 
      out.append(word[:k])
    list.append(out)
  return list

请注意,如果您使用:

breakWord(yourWord, len(yourWord)-1)

它将单词分成所有可能的两部分。 然后是一个检查给定字符串是否在名称列表中的函数:

def isInNames(word):
  for name in name_list:
    if(word in name):
      return true
  return false

最后遍历所有可能的字符组合:

def findWordCombination(word):
  resultSet = []
  resultSize = 50  #Something large to ensure it gets changed
  for i in range(len(word)-1, 0, -1): #Will go from max to     minimum
    testSet = breakWord(word, i)
    for set in testSet:
      isValid = true #assumes true at first
      for part in set:
        if(not isInNames(part)):
          isValid = false
      #Once all parts of the set are checked we find
      #If the set is valid. i.e. it is a valid combination.
      if(isValid and len(set) < resultSize):
        resultSize = len(set)
        resultList = set
  return resultList

这将返回从您的搜索查询中找到具有最小可能子词组合的第一组。您可以对其进行调整,让它从产生结果集的列表中存储单词名称。

【讨论】:

    【解决方案3】:

    另一种方法(我已经赞成@blhsing 的递归解决方案,非常优雅,我喜欢它)

    import itertools as it
    from collections import defaultdict
    
    
    def get_all_substrings(input_string):
      length = len(input_string)
      return [input_string[i:j+1] for i in range(length) for j in range(i,length)]
    
    names = ['Joppe', 'Fien', 'Katrijn', 'Sven', 'Kobe']
    d = defaultdict(list)  # each key is a substring of any of the names and the value is the list of names that contain it
    for name in names:
        for subname in get_all_substrings(name):
            d[subname].append(name)
    
    input_name  = 'Katrien'
    input_subs = get_all_substrings(input_name)
    
    sub_combs = [it.combinations(input_subs, n) for n in range(1,len(input_name))]
    whole_combs = [el for co in sub_combs for el in co if ''.join(el) == input_name]  # those combs that can form the input name
    
    saved = [wc for wc in whole_combs if all((c in d for c in wc))]  # those whole combinations that actually appear
    
    shortest_comb = min(saved, key=len)
    
    shortest_sub_and_name = [(s, d[s]) for s in shortest_comb]
    for s, ns in shortest_sub_and_name:
        print(f"{s} => {ns}")
    

    生产

    Katr => ['Katrijn']
    ien => ['Fien']
    

    注意:如您所见,输出显示了可以对每个特定子字符串做出贡献的所有名称

    【讨论】:

    • 您的解决方案也有效。当有多个解决方案时,它甚至会打印多个名称。例如: input_name = 'Koen' 结果: Ko => ['Kobe'] en => ['Fien', 'Sven'] 所以它会打印两个名字,因为它们都是正确的答案。谢谢
    【解决方案4】:

    你可以试试:

    import difflib
    
    name = input('Please fill in a name: ')
    
    with open('namen.txt', 'r') as file:
      file_data = file.readlines()
      # either you are looking for
      print([i for i in file_data if difflib.SequenceMatcher(a = i,b = name).ratio() >= 0.5])
      #or you are looking for
      print(difflib.get_close_matches(name,file_data,len(file_data),0.5))
    ['Katrijn\n', 'Fien\n']
    

    【讨论】:

    • print(difflib.get_close_matches(d,c,len(c),0.5))。这给出了未定义的错误 d,c。 print([i for i in file_data if difflib.SequenceMatcher(a = i,b = name).ratio() &gt;= 0.5]) 这个有点用,但是当我填写例如`Koen`时,它会打印Kobe,因为它认为它可以切换名称中的字母。
    • @jopw 您可以使用比率默认为 0.6 即认为两个字符串匹配。在此之下,两者不被认为是匹配的。但我只是用 0.5 来给你想要的输出,即使我知道两者并不接近
    • 当我尝试使用Koenprint(difflib.get_close_matches(name,file_data,len(file_data),0.5))时,我得到了这个结果Please fill in a name: Koen ['Kobe'] 这是不对的,因为你只能取名字的一部分而不能切换字母。
    • difflib 真的很酷,但我认为这不会产生任何类似于所需输出的东西。 OP 不是在寻找近似匹配,OP 是在寻找包含子字符串的最小单词系列,这些子字符串可以连接起来以构建目标单词。
    • 我明天可以回复。
    猜你喜欢
    • 1970-01-01
    • 2013-01-21
    • 1970-01-01
    • 1970-01-01
    • 2020-08-22
    • 1970-01-01
    • 1970-01-01
    • 2021-10-19
    • 1970-01-01
    相关资源
    最近更新 更多