繁体   English   中英

是否有一种 Pythonic 方式来过滤列表中字符串的子字符串?

[英]Is there a Pythonic way of filtering substrings of strings in a list?

我有一个包含如下字符串的列表。

candidates = ["Hello", "World", "HelloWorld", "Foo", "bar", "ar"]

我希望列表被过滤为["HelloWorld", "Foo", "Bar"] ,因为其他的都是子字符串。 我可以这样做,但不要认为它很快或优雅。

def filter_not_substring(candidates):
    survive = []
    for a in candidates:
        for b in candidates:
            if a == b:
                continue
            if a in b:
                break
        else:
            survive.append(a)
    return survive

有什么快速的方法吗?

怎么样:

candidates = ["Hello", "World", "HelloWorld", "Foo", "bar", "ar"]
result = [c for c in candidates if not any(c in o and len(o) > len(c) for o in candidates)]
print(result)

与评论中的建议相反:

from timeit import timeit


def filter_not_substring(candidates):
    survive = []
    for a in candidates:
        for b in candidates:
            if a == b:
                continue
            if a in b:
                break
        else:
            survive.append(a)
    return survive


def filter_not_substring2a(candidates):
    return [c for c in candidates if not any(len(o) > len(c) and c in o for o in candidates)]


def filter_not_substring2b(candidates):
    return [c for c in candidates if not any(c in o and len(o) > len(c) for o in candidates)]


xs = ["Hello", "World", "HelloWorld", "Foo", "bar", "ar", "bar"]
print(filter_not_substring(xs), filter_not_substring2a(xs), filter_not_substring2b(xs))
print(timeit(lambda: filter_not_substring(xs)))
print(timeit(lambda: filter_not_substring2a(xs)))
print(timeit(lambda: filter_not_substring2b(xs)))

结果:

['HelloWorld', 'Foo', 'bar', 'bar'] ['HelloWorld', 'Foo', 'bar', 'bar'] ['HelloWorld', 'Foo', 'bar', 'bar']
1.5163685
4.6516653
3.8334089999999996

因此,OP 的解决方案要快得多,但filter_not_substring2b仍然比2a快约 20%。 因此,首先进行len比较并不能节省时间。

对于任何生产场景,OP 的 function 可能是最佳的 - 加速它的一种方法可能是将整个问题带入 C,但我怀疑这会显示出巨大的收益,因为逻辑已经非常简单了,我希望 ZA782173 到 ZA74217F32341B56B6也做得相当好。

用户@ming 指出,OP 的解决方案可以改进一点:

def filter_not_substring_b(candidates):
    survive = []
    for a in candidates:
        for b in candidates:
            if a in b and a != b:
                break
        else:
            survive.append(a)
    return survive

这个版本的 function 有点快,对我来说大约 10-15%

就渐近性能而言,我不相信O(n^2)算法会更快(其中n == len(candidates) ,并假设字符串很短/长度受常数限制)。

如果有一个字符串列表,其中没有字符串是列表中任何其他元素的 substring,您需要检查此属性是否适用于列表中的每对字符串。 您需要检查O(n^2)对这样的对,因此您的算法可能花费的最快时间是O(n^2)时间。

Grismar 使用列表理解给出了答案,该列表理解在执行比较之前检查字符串的长度。 虽然这在实践中可能更快,但它并没有改善渐近运行时间,因为这样的改进(可能)是不可能的。


你可能会错过一个边缘案例。 考虑这种情况["a", "a"] 两个a都是列表中另一个元素的 substring,因此两者都不应该存在。 但是,如果您知道字符串都是不同的,那就没有实际意义了。

如果您要求从算法的角度来看,我建议您使用trie data structure

算法草图:您将列表中的字符串一一插入其中。 例如候选人 = ["Hello","Hello World"]。 传统上,这些单词会存储为 'H'->'e'->'l'->'l'->'o' [word_flag]->->'W'->'o'->'r' ->'l'->'d'[word_flag]->无。 这里 word_flag 表示从根到该点的路径表示列表中的一个单词。 您唯一需要做的就是在遍历路径时只报告下一个元素为 None 的字符串。

复杂性:复杂性将是 O(sum([len(i) for i in Candidates])) 并且我相信这种复杂性应该非常接近最优,因为通常尝试是最高效的 memory 并且执行速度甚至超过hash 表在最坏的情况下。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM