簡體   English   中英

使用字符串列表作為模式拆分字符串

[英]Split a string using a list of strings as a pattern

考慮輸入字符串:

mystr = "just some stupid string to illustrate my question"

以及指示輸入字符串拆分位置的字符串列表:

splitters = ["some", "illustrate"]

輸出應該是這樣的

result = ["just ", "some stupid string to ", "illustrate my question"] 

我寫了一些代碼來實現以下方法。 對於splitters每個字符串,我在輸入字符串中找到它的出現,並插入一些我知道肯定不會是我的輸入字符串的部分(例如,這個'!!' )。 然后我使用剛剛插入的子字符串拆分字符串。

for s in splitters:
    mystr = re.sub(r'(%s)'%s,r'!!\1', mystr)

result = re.split('!!', mystr)

這個解決方案看起來很難看,有沒有更好的方法呢?

使用re.split拆分將始終從輸出中刪除匹配的字符串( 注意,這不完全正確,請參閱下面的編輯 )。 因此,您必須使用正向前瞻表達式( (?=...) )進行匹配而不刪除匹配項。 但是, re.split 忽略空匹配 ,因此只使用前瞻表達式不起作用。 相反,你將在以最低每分裂失去一個字符 (甚至企圖欺騙re使用“邊界”的比賽( \\b )不工作)。 如果你不關心在每個項目的末尾丟失一個空格/非單詞字符(假設你只分成非單詞字符),你可以使用像

re.split(r"\W(?=some|illustrate)")

這會給

["just", "some stupid string to", "illustrate my question"]

(請注意, justto之后的空格不見了)。 然后,您可以使用str.join編程方式生成這些正則表達式。 請注意,每個拆分標記都使用re.escape轉義,以便splitters項中的特殊字符不會以任何不需要的方式(例如,a )在其中一個字符串中影響正則表達式的含義,否則會導致正則表達式語法錯誤)。

the_regex = r"\W(?={})".format("|".join(re.escape(s) for s in splitters))

編輯HT到@Arkadiy ):對實際匹配進行分組,即使用(\\W)而不是\\W ,將作為單獨項目插入列表中的非單詞字符返回。 然后,連接每兩個后續項目也將根據需要生成列表。 然后,您還可以通過使用(.)而不是\\W來刪除具有非單詞字符的要求:

the_new_regex = r"(.)(?={})".format("|".join(re.escape(s) for s in splitters))
the_split = re.split(the_new_regex, mystr)
the_actual_split = ["".join(x) for x in itertools.izip_longest(the_split[::2], the_split[1::2], fillvalue='')]

因為普通文本和輔助字符交替, the_split[::2]包含正常的分割文本,而the_split[1::2]包含輔助字符。 然后, itertools.izip_longest用於將每個文本項與相應的刪除字符和最后一項(在刪除的字符中不匹配)組合使用fillvalue ,即'' 然后,使用"".join(x)連接這些元組中的每一個。 請注意,這需要導入itertools (當然,您可以在一個簡單的循環中執行此操作,但itertools為這些事情提供了非常干凈的解決方案)。 另請注意, itertools.izip_longest在Python 3中稱為itertools.zip_longest

這導致正則表達式的進一步簡化,因為可以用簡單的匹配組( (some|interesting)而不是(.)(?=some|interesting) )替換前瞻,而不是使用輔助字符:

the_newest_regex = "({})".format("|".join(re.escape(s) for s in splitters))
the_raw_split = re.split(the_newest_regex, mystr)
the_actual_split = ["".join(x) for x in itertools.izip_longest([""] + the_raw_split[1::2], the_raw_split[::2], fillvalue='')]

這里, the_raw_split上的切片索引已經交換,因為現在必須將偶數項目添加到項目中而不是在前面。 另請注意[""] +部分,這是將第一個項目與""配對以修復訂單所必需的。

(編輯結束)

或者,您可以(如果需要)為每個拆分器使用string.replace而不是re.sub (我認為在您的情況下這是一個偏好的問題,但通常它可能更有效)

for s in splitters:
    mystr = mystr.replace(s, "!!" + s)

此外,如果您使用固定令牌來指示拆分的位置,則不需要re.split ,但可以使用string.split

result = mystr.split("!!")

您還可以做什么(而不是依賴替換標記不在其他任何地方的字符串中或依賴於每個分割位置前面有非單詞字符)是使用string.find在輸入中查找拆分字符串並使用字符串切片提取件:

def split(string, splitters):
    while True:
        # Get the positions to split at for all splitters still in the string
        # that are not at the very front of the string
        split_positions = [i for i in (string.find(s) for s in splitters) if i > 0]
        if len(split_positions) > 0:
            # There is still somewhere to split
            next_split = min(split_positions)
            yield string[:next_split] # Yield everything before that position
            string = string[next_split:] # Retain the rest of the string
        else:
            yield string # Yield the rest of the string
            break # Done.

這里, [i for i in (string.find(s) for s in splitters) if i > 0]生成一個可以找到分割器的位置列表,對於字符串中的所有分割器(為此, i < 0排除i < 0並且不在開頭(我們(可能)剛分裂的地方,所以i == 0也被排除在外)。 如果字符串中有任何剩余,我們產生(這是一個生成器函數)一切直到(不包括)第一個拆分器(在min(split_positions) )並用剩余部分替換該字符串。 如果沒有,我們產生字符串的最后一部分並退出該函數。 因為它使用yield ,所以它是一個生成器函數,因此您需要使用list將其轉換為實際列表。

請注意,您也可以通過調用some_list.append來替換yield whatever (如果您之前定義了some_list )並在最后返回some_list ,但我不認為這是非常好的代碼樣式。


TL; DR

如果您對使用正則表達式沒問題,請使用

the_newest_regex = "({})".format("|".join(re.escape(s) for s in splitters))
the_raw_split = re.split(the_newest_regex, mystr)
the_actual_split = ["".join(x) for x in itertools.izip_longest([""] + the_raw_split[1::2], the_raw_split[::2], fillvalue='')]

否則,使用string.find和以下split函數也可以實現相同:

def split(string, splitters):
    while True:
        # Get the positions to split at for all splitters still in the string
        # that are not at the very front of the string
        split_positions = [i for i in (string.find(s) for s in splitters) if i > 0]
        if len(split_positions) > 0:
            # There is still somewhere to split
            next_split = min(split_positions)
            yield string[:next_split] # Yield everything before that position
            string = string[next_split:] # Retain the rest of the string
        else:
            yield string # Yield the rest of the string
            break # Done.

不是特別優雅,但避免正則表達式:

mystr = "just some stupid string to illustrate my question"
splitters = ["some", "illustrate"]
indexes = [0] + [mystr.index(s) for s in splitters] + [len(mystr)]
indexes = sorted(list(set(indexes)))

print [mystr[i:j] for i, j in zip(indexes[:-1], indexes[1:])]
# ['just ', 'some stupid string to ', 'illustrate my question']

我應該在此承認,如果splittersstr.index的單詞出現不止一次,則需要更多的工作,因為str.index只找到第一次出現的單詞的位置...

暫無
暫無

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

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