簡體   English   中英

如何找到多個字符串中最長的公共子字符串?

[英]How to find the longest common substring of multiple strings?

我正在寫一個python腳本,我有多個字符串。

例如:

x = "brownasdfoersjumps"
y = "foxsxzxasis12sa[[#brown"
z = "thissasbrownxc-34a@s;"

在所有這三個字符串中,它們有一個共同的子串,它是brown 我想以我想要創建字典的方式搜索它:

dict = {[commonly occuring substring] => 
           [total number of occurrences in the strings provided]}

這樣做的最佳方式是什么? 考慮到每次我將擁有超過200個字符串,這將是一種簡單/有效的方法嗎?

這是一個相對優化的天真算法。 首先將每個序列轉換為一組所有的ngrams。 然后你交叉所有集合並找到交叉點中最長的ngram。

from functools import partial, reduce
from itertools import chain
from typing import Iterator


def ngram(seq: str, n: int) -> Iterator[str]:
    return (seq[i: i+n] for i in range(0, len(seq)-n+1))


def allngram(seq: str) -> set:
    lengths = range(len(seq))
    ngrams = map(partial(ngram, seq), lengths)
    return set(chain.from_iterable(ngrams))


sequences = ["brownasdfoersjumps",
             "foxsxzxasis12sa[[#brown",
             "thissasbrownxc-34a@s;"]

seqs_ngrams = map(allngram, sequences)
intersection = reduce(set.intersection, seqs_ngrams)
longest = max(intersection, key=len) # -> brown

雖然這可能會讓您通過短序列,但這種算法在長序列上效率極低。 如果序列很長,可以添加一個啟發式來限制最大可能的ngram長度(即最長的公共子串)。 這種啟發式的一個明顯價值可能是最短序列的長度。

def allngram(seq: str, minn=1, maxn=None) -> Iterator[str]:
    lengths = range(minn, maxn) if maxn else range(minn, len(seq))
    ngrams = map(partial(ngram, seq), lengths)
    return set(chain.from_iterable(ngrams))


sequences = ["brownasdfoersjumps",
             "foxsxzxasis12sa[[#brown",
             "thissasbrownxc-34a@s;"]

maxn = min(map(len, sequences))
seqs_ngrams = map(partial(allngram, maxn=maxn), sequences)
intersection = reduce(set.intersection, seqs_ngrams)
longest = max(intersection, key=len)  # -> brown

這可能仍然需要太長時間(或使您的機器耗盡RAM),因此您可能希望了解一些最佳算法(請參閱我在您的問題評論中留下的鏈接)。

更新

計算每個ngram發生的字符串數

from collections import Counter
sequences = ["brownasdfoersjumps",
             "foxsxzxasis12sa[[#brown",
             "thissasbrownxc-34a@s;"]

seqs_ngrams = map(allngram, sequences)
counts = Counter(chain.from_iterable(seqs_ngrams))

Counterdict的子類,因此它的實例具有類似的接口:

print(counts)
Counter({'#': 1,
         '#b': 1,
         '#br': 1,
         '#bro': 1,
         '#brow': 1,
         '#brown': 1,
         '-': 1,
         '-3': 1,
         '-34': 1,
         '-34a': 1,
         '-34a@': 1,
         '-34a@s': 1,
         '-34a@s;': 1,
         ...

您可以過濾計數以使子串出現在至少n字符串中: {string: count for string, count in counts.items() if count >= n}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM