【问题标题】:Is there a faster method to find dictionary keys with wildcards in the middle?有没有更快的方法来查找中间带有通配符的字典键?
【发布时间】:2020-01-16 19:32:05
【问题描述】:

假设我有一个字典,其中包含 0、1 和 '*' 字符串作为我的键值的通配符。

例如,我的字典的结构是这样的:

{'010*10000':'foo', '100*1*000':'bar'......}

每个字典值都有一个固定的字符串长度,但是,字符串中的通配符表示为“*”字符。因此,“010110000”或“010010000”的值都返回“foo”。

问题在于我的字典的长度。我正在使用的字典有超过 500,000 多个条目。因此,当我尝试遍历 dict 中的每个键以查找键是否存在时,O(n) 复杂度会花费很长时间。

理想情况下,我想找到一种方法来检查字典中是否存在诸如“010110000”之类的值,类似于没有通配符的常规 python 字典的 .get() 函数。

我已经尝试使用 fnmatch 迭代我的字典,如下面的Wildcard in dictionary key

for k in my_dict.keys():
    if fnmatch.fnmatch(string_of_1s_and_0s, k):
        print(my_dict[k])
        break
##Do some operation here if we have found the matching key pair...and then break.

但是,O(n) 复杂度太慢了。有没有办法实现 get() 但使用通配符?

【问题讨论】:

  • 密钥的实际长度是多少?我猜比您示例中的 9 位数字要长,因为否则您将无法获得 500K+ 唯一条目。
  • 字符串是“一、零和通配符”吗?具体来说,是否可以将第一个通配符之前的字符和最后一个通配符之后的字符连接起来,然后从“包含二进制数字的字符串”转换为固定大小的整数?如是;您可以快速找到开始和结束匹配的所有条目(使用从字符串到整数的转换、旋转计数和掩码)。
  • 通配符只是一个字符吗?多少个通配符?您可以将值两次(或多次)放入字典中以覆盖每个排列,然后使用精确匹配(使用散列的普通字典查找)吗?
  • 密钥长度为 90 长。是的,字符串是 1、0,仅此而已。通配符只是 1 个通配符。我考虑过把每一个排列。这是一个可能的步骤。只是想知道是否有更好的方法。感谢您的所有回复。

标签: python-3.x algorithm performance dictionary wildcard


【解决方案1】:

dicts 是基于哈希码的;如果正确实施,哈希码将因仅一个字符的差异而大相径庭。没有办法让dict 做你想做的事,但你正在做的事情可能最好首先使用dict 以外的东西。您是否考虑过一个关系数据库,LIKE 运算符可以在其中执行类似的操作?它可能仍然需要扫描数据库的大部分,但理想情况下,它可以在一端或另一端使用锚点,以至少将搜索范围缩小到匹配的前缀/后缀。

【讨论】:

  • 感谢您的快速回答。我一直在考虑重构整个数据结构。但我没有在 python 中使用 SQL 命令的经验。此外,字典键值是完全随机的。没有真正的方法来判断存在多少通配符以及它们将被放置在哪里。不过我同意,字典可能不适合这种情况。
  • @Tianen:是的,事实上,现在我重新阅读了您的问题,数据库可能不会有太大帮助。您正在尝试将固定字符串与一堆(任意复杂的)模式进行匹配。据我所知,没有任何好的方法可以实现这一点。你能提供更多关于你的问题的细节吗?我觉得必须有一个比你正在尝试的更好的解决方案。
【解决方案2】:

向左旋转原始模式(通过从开头获取字符并将它们放在结尾)同时跟踪旋转计数;像这样:

'010*10000' -> '*10000010', rotate_count = 3
'100*1*000' -> '*1*000100', rotate_count = 3

然后将其拆分为“复杂部分”和“简单部分”,并确定简单部分的长度,如下所示:

'010*10000' -> '*10000010', rotate_count = 3
               complex = '*`, simple = `10000010', simple_length = 8

'100*1*000' -> '*1*000100', rotate_count = 3
               complex = '*1*`, simple = `000100', simple_length = 6

如果字符串的固定长度为 16,那么rotate_count 的可能值将有 16 个,而对于每个字符串,simple_length 的可能值将有 16 个16 - rotate_count。这可以描述为一个嵌套循环:

    for(rotate_count = 0; rotate_count < 16; rotate_count++) {
        for(simple_length = 0; simple_length = 16 - rotate_count; simple_length++) {
        }
    }

您可以将“条目数组”与此关联,例如:

    entry_number = 0;
    for(rotate_count = 0; rotate_count < 16; rotate_count++) {
        for(simple_length = 0; simple_length = 16 - rotate_count; simple_length++) {

            entry_number++;
        }
    }

然后你可以使用条目号来查找哈希表,如:

    entry_number = 0;
    for(rotate_count = 0; rotate_count < 16; rotate_count++) {
        for(simple_length = 0; simple_length = 16 - rotate_count; simple_length++) {
            hash_table = array_of_hash_tables[entry_number];

            entry_number++;
        }
    }

您还可以通过 rotate_count 旋转您要查找的字符串并从中提取 simple_length 字符,将这些字符转换为哈希,并使用它从哈希表中查找条目列表,例如:

    entry_number = 0;
    for(rotate_count = 0; rotate_count < 16; rotate_count++) {
        rotated_string = rotate_string(original_string, rotate_count);
        for(simple_length = 0; simple_length = 16 - rotate_count; simple_length++) {
            hash_table = array_of_hash_tables[entry_number];
            if(hash_table != NULL) {
                hash = get_simple_hash(rotated_string, simple_length);
                list = hash_table[hash];
                // Use "list" and "original string" to do the hard stuff here...
            }
            entry_number++;
        }
    }

这将快速消除大量条目(开头和结尾不匹配)并为您提供“潜在匹配”列表,您必须在其中检查包含通配符的部分与原始字符串是否匹配有/没有实际匹配。

请注意,如果字符是“一和零”,这可以通过将“包含二进制数字的字符串”转换为整数来改进。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-27
    • 2018-09-08
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    • 2020-11-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多