簡體   English   中英

在較大的字符串中查找子字符串的位置

[英]Finding a substring's position in a larger string

我有一個大字符串和大量較小的子字符串,我試圖檢查每個子字符串是否存在於較大的字符串中,並獲取每個子字符串的位置。

string="some large text here"
sub_strings=["some", "text"]

for each_sub_string in sub_strings:
    if each_sub_string in string:
        print each_sub_string, string.index(each_sub_string)

問題是,因為我有大量的子串(大約一百萬),所以需要大約一個小時的處理時間。 有沒有辦法減少這個時間,可能是通過使用正則表達式或其他方式?

解決此問題的最佳方法是使用樹實現。 正如里沙夫所說,你在這里重復了很多工作。 理想情況下,這應該實現為基於樹的FSM。 想象一下以下示例:

Large String: 'The cat sat on the mat, it was great'
Small Strings: ['cat', 'sat', 'ca']

然后想象一棵樹,每個級別是一個額外的字母。

small_lookup = {
    'c': 
        ['a', {
            'a': ['t']
        }], {
    's':
        ['at']
    }
}

粗略格式化的道歉,但我認為直接映射回python數據結構是有幫助的。 您可以構建一個樹,其中頂級條目是起始字母,並且它們映射到可以完成的潛在最終子字符串列表。 如果你點擊了一個列表元素並且沒有更多嵌套在你的下面你已經擊中了一個葉子,你知道你已經擊中了該子串的第一個實例。

在內存中保存這個樹有點大,但如果你只有一百萬字符串,這應該是最有效的實現。 您還應該確保在找到第一個單詞實例時修剪樹。

對於那些有CS chops的人,或者如果你想了解更多關於這種方法的知識,它是Aho-Corasick字符串匹配算法的簡化版本。

如果您有興趣了解這些方法的更多信息,實踐中使用了三種主要算法:

  • Aho-Corasick (fgrep的基礎)[最壞情況:O(m + n)]
  • Commentz-Walter (香草GNU grep的基礎)[最壞情況:O(mn)]
  • Rabin-Karp (用於抄襲檢測)[最壞情況:O(mn)]

有些領域所有這些算法都會勝過其他算法,但基於你正在搜索的子字符串非常多的事實,它們之間可能存在很多重疊,我敢打賭Aho-Corasick將為您提供比其他兩種方法更好的性能,因為它避免了O(mn)最壞情況

還有一個很棒的python庫,它實現了這里Aho-Corasick算法,可以讓你避免自己編寫粗略的實現細節。

根據子串長度的分布,您可以使用預處理來節省大量時間。

假設子串的長度集合形成集合{23,33,45} (意味着您可能有數百萬個子串,但每個子串都采用這三個長度中的一個)。

然后,對於每個長度,在大字符串上找到Rabin窗口 ,並將結果放入該長度的字典中。 也就是說,讓我們走23.轉過大字符串,找到23窗口的哈希值。 假設位置0的哈希是13.所以你插入字典rabin23被映射到[0] 然后你會看到,對於位置1,哈希值也是13。 然后在rabin23 ,更新13映射到[0, 1] 然后在位置2,哈希值為4.因此在rabin23 ,4被映射到[2]。

現在,給定一個子字符串,您可以計算其Rabin哈希並立即檢查相關字典中的出現的索引(然后您需要比較)。


順便說一句,在很多情況下,你的子串的長度將表現出帕累托行為,其中90%的字符串是10%的長度。 如果是這樣,您只能為這些長度執行此操作。

與其他答案相比,這種方法是次優的,但無論如何都可能足夠好,並且易於實施。 我們的想法是改變算法,以便不是依次針對較大的字符串測試每個子字符串,而是迭代大字符串並在每個位置測試可能匹配的子字符串,使用字典來縮小數量。你需要測試的子字符串。

輸出將與原始代碼不同,因為它將按索引的升序排序,而不是按子字符串排序,但是如果需要,可以對輸出進行后處理以按子字符串排序。

創建一個字典,其中包含以每個可能的1-3個字符開頭的子字符串列表。 然后遍歷字符串並在每個字符后面讀取1-3個字符,並檢查字符中每個以1-3個字符開頭的子字符串在該位置的匹配:

string="some large text here"
sub_strings=["some", "text"]

# add each of the substrings to a dictionary based the first 1-3 characters
dict = {}
for s in sub_strings:
    if s[0:3] in dict:
        dict[s[0:3]].append(s)
    else:
        dict[s[0:3]] = [s];

 # iterate over the chars in string, testing words that match on first 1-3 chars
for i in range(0, len(string)):
    for j in range(1,4):
        char = string[i:i+j]
        if char in dict:        
            for word in dict[char]:
                if string[i:i+len(word)] == word:
                    print word, i

如果你不需要匹配1或2個字符長的任何子字符串,那么你可以擺脫for j循環,只需用char = string[i:3]賦值char char = string[i:3]

使用第二種方法,我通過閱讀托爾斯泰的“ 戰爭與和平”並將其分成獨特的單詞來計算算法,如下所示:

with open ("warandpeace.txt", "r") as textfile:
    string=textfile.read().replace('\n', '')    
sub_strings=list(set(string.split()))

對文本中的每個唯一單詞進行完整搜索並輸出每個單獨的實例需要124秒。

暫無
暫無

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

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