【问题标题】:Partial Match Binary Search of Complex Strings复杂字符串的部分匹配二分查找
【发布时间】:2022-08-11 23:38:03
【问题描述】:

使用 python,我希望遍历包含数千个条目的列表。对于列表中的每个项目,它需要与其他列表中的项目(包含数万个条目)进行比较,并进行部分比较检查。一旦找到高于设定比例的匹配项,它将停止并移动到下一个项目。

一个挑战:我无法安装任何额外的 python 包来完成这项工作,并且仅限于 python 3.4.2 发行版。

下面是我正在使用的一些示例代码。如果列表很小,它工作得很好,但是一旦我将它应用于非常大的列表,运行时可能需要几个小时才能完成。

from difflib import SequenceMatcher

ref_list = [] #(contains 4k sorted entries - long complex strings)
list1 = [] #(contains 60k sorted entries - long complex strings)
list2 = [] #(contains 30k sorted entries - long complex strings)
all_lists = [list1,list2]

min_ratio = 0.93
partMatch = \'\'

for ref in ref_list:
    for x in range(len(all_lists)):
        for str1 in all_lists[x]:
            check_ratio = SequenceMatcher(None, ref, str1).quick_ratio()
            if check_ratio > min_ratio:
                partMatch = str1 #do stuff with partMatch later
                break

我在想对 all_lists[x] 进行二分搜索可以解决这个问题。如果我的计算是正确的,一个 60k 的列表只需要 16 次尝试就可以找到部分匹配。

但是,问题在于字符串的类型。一个典型的字符串长度可以是 80 到 500 个字符,例如

lorem/ipsum/dolor/sit/amet/consectetur/adipiscing/elit/sed/do/eiusmod/tempor/incididunt/ut/labore/et/dolore/magna/aliqua/Ut/enim/ad/minim/veniam/quis/nostrud/exercitation

尽管列表已排序,但我不确定如何验证中点。例如,如果我缩短字符串以使其更易于阅读并提供以下列表:

ref_list = [\'past/pre/dest[5]\']
list1 = [\'abc/def/ghi\',\'xry/dos/zanth\']
list2 = [\'a/bat/cat\', \'ortho/coli\', \'past/pre/dest[6]\', \'past/tar/lot\', \'rif/six/1\', \'tenta[17]\', \'ufra/cos/xx\']

我们可以看到ref_list 中字符串的部分匹配是list2[2]。但是,使用二分搜索,我如何确定部分匹配肯定在 list2 的前半部分?

我真的很感激这方面的任何帮助。考虑到我需要处理包含数万个条目的列表,效率是这里最重要的因素。

  • 我不确定您所说的 SequenceMatcher 是什么不必要的噪音。提供的代码的第一行是from difflib import SequenceMatcher。在我的用例中,SequenceMatcher 用于比较两个字符串并提供它们匹配程度的比率值。 0 表示没有匹配的字符,1 表示字符串相同。我已将最小比率设置为 0.93,找到的第一个满足此要求的字符串被识别为部分匹配。
  • 对不起,我误读了那部分的问题。
  • 我建议花更多时间创建一个最小的可重现示例,并具有明确的最小输入和输出。很难正确优化不清楚的东西。
  • 我很确定您不能使用二进制搜索,因为输入没有根据您正在计算的部分匹配函数进行排序,例如gbcd 将与 abcdzbcd 进行部分匹配,但任何以不同于 az 的单词开头的单词都将介于两者之间。

标签: python


【解决方案1】:

所以我对字符串比较的背景做了更多的研究,结果发现最初的问题并不像我最初想象的那么难。

要获得二分搜索的中点,我可以简单地使用 <> 运算符。由于每个 ASCII 字符都有一个值,python 似乎会逐个字符地检查字符串。在这种情况下,字符串的复杂程度并不重要。

但是,需要注意的是,列表中的某些字符串可能具有罕见的大写字符命名差异。为了解决这个问题,我在生成高/低/中点时添加了 str().lower()。

工作代码如下。我在这里降低了 min_ratio 值,以适应较短的测试字符串,但我会在我的主程序中增加它。

#!/usr/bin/env python
# Copyright 2009-2017 BHG http://bw.org/

from difflib import SequenceMatcher

def binary_search_partmatch(arr, x):
    low = 0
    high = len(arr) - 1
    mid = 0
    min_ratio = 0.85
    partMatch = ''
 
    while low <= high:
        mid = (high + low) // 2
        # If midpoint is lower, ignore the left half of array
        if str(arr[mid]).lower() < str(x).lower():
            low = mid + 1
        # If midpoint is higher, ignore the right half of array
        elif str(arr[mid]).lower() > str(x).lower():
            high = mid - 1
        # x is present at the midpoint
        else:
            return -1
    # If we reach here, then the exact element was not present. Check for a close match.
    check_ratio = SequenceMatcher(None, x, str(arr[mid])).ratio()
    if check_ratio > min_ratio:
        partMatch = str(arr[mid])
        return partMatch
    else:
        return -2


def main():
    ref_list = ['past/pre/dest[5]', 'rif/six/1', 'testcase_no_match']
    list1 = ['abc/def/ghi','xry/dos/zanth']
    list2 = ['a/bat/cat', 'ortho/coli', 'past/Pre/dest[6]', 'past/tar/lot', 'rif/six/1', 'tenta[17]', 'ufra/cos/xx']
    all_lists = [list1,list2]

    for ref in ref_list:
        for x in range(len(all_lists)):
            result = binary_search_partmatch(all_lists[x], ref)
            if result == -1:
                print('Exact match found for "' + ref + '"' )
                break
            elif result == -2:
                if x == (len(all_lists)-1):
                    print('No match or partial match found for "' + ref + '"')
            else:                
                print('Partial match found for "' + ref + '": "' + str(result)+ '"')
                break


if __name__ == '__main__': 
    main()

输出:

>>> Partial match found for "past/pre/dest[5]": "past/Pre/dest[6]"
>>> Exact match found for "rif/six/1"
>>> No match or partial match found for "testcase_no_match"

我仍然欢迎我在这里的测试场景中提出任何建议或不可预见的错误。我不是专业的程序员,所以我可能忽略了一些重要的事情。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-04
    • 2013-02-25
    • 2016-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-26
    相关资源
    最近更新 更多