Jak wyodrębnić podciąg między dwoma markerami?


335

Powiedzmy, że mam ciąg znaków 'gfgfdAAA1234ZZZuijjk'i chcę wyodrębnić tylko '1234'część.

Wiem tylko, jakie będą te postacie bezpośrednio przed AAAi po ZZZczęści, którą jestem zainteresowany 1234.

Dzięki sedtemu można zrobić coś takiego za pomocą łańcucha:

echo "$STRING" | sed -e "s|.*AAA\(.*\)ZZZ.*|\1|"

I to da mi 1234w rezultacie.

Jak zrobić to samo w Pythonie?

Odpowiedzi:


588

Używanie wyrażeń regularnych - dokumentacja do wglądu

import re

text = 'gfgfdAAA1234ZZZuijjk'

m = re.search('AAA(.+?)ZZZ', text)
if m:
    found = m.group(1)

# found: 1234

lub:

import re

text = 'gfgfdAAA1234ZZZuijjk'

try:
    found = re.search('AAA(.+?)ZZZ', text).group(1)
except AttributeError:
    # AAA, ZZZ not found in the original string
    found = '' # apply your error handling

# found: 1234

20
Drugie rozwiązanie jest lepsze, jeśli wzór pasuje przez większość czasu, ponieważ łatwiej jest prosić o wybaczenie niż pozwolenie. .
Bengt

7
Czy indeksowanie nie zaczyna się od 0? Więc musiałbyś użyć grupy (0) zamiast grupy (1)?
Alexander

22
@Alexander, nie, grupa (0) zwróci pełny dopasowany ciąg: AAA1234ZZZ, a grupa (1) zwróci tylko znaki dopasowane przez pierwszą grupę: 1234
Yurii K

1
@Bengt: Dlaczego tak jest? Pierwsze rozwiązanie wydaje mi się dość proste i zawiera mniej wierszy kodu.
HelloGoodbye,

5
W tym wyrażeniu? modyfikuje +, aby nie był chciwy, tj. będzie pasować dowolną liczbę razy od 1 w górę, ale tak mało, jak to możliwe, rozszerzając się tylko w razie potrzeby. bez? pierwsza grupa pasowałaby do gfgfAAA2ZZZkeAAA43ZZZonife jako 2ZZZkeAAA43, ale z? pasowałby tylko do 2, a następnie wyszukiwanie wielokrotności (lub usunięcie go i ponowne wyszukiwanie) pasowałoby do 43.
Dom,

114
>>> s = 'gfgfdAAA1234ZZZuijjk'
>>> start = s.find('AAA') + 3
>>> end = s.find('ZZZ', start)
>>> s[start:end]
'1234'

Następnie możesz użyć wyrażeń regularnych z modułem re, jeśli chcesz, ale nie jest to konieczne w twoim przypadku.


9
Pytanie wydaje się sugerować, że tekst wejściowy zawsze będzie zawierał zarówno „AAA”, jak i „ZZZ”. Jeśli tak nie jest, twoja odpowiedź okropnie się nie powiedzie (mam na myśli, że zwraca coś zupełnie niepoprawnego zamiast pustego ciągu lub zgłaszania wyjątku; pomyśl „witaj tam” jako ciąg wejściowy).
tzot

@ user225312 Czy remetoda nie jest jednak szybsza?
zmieszany 00

1
Głosuj, ale użyłbym „x = 'AAA”; s.find (x) + len (x) ”zamiast„ s.find (' AAA ') + 3 ”dla łatwości konserwacji.
Alex

1
Jeśli którykolwiek z tokenów nie można znaleźć w s, s.findzwróci -1. operator krojenia s[begin:end] zaakceptuje go jako prawidłowy indeks i zwróci niepożądane podciąg.
ribamar

@ confused00 find jest znacznie szybszy niż ponowne stackoverflow.com/questions/4901523/...
Claudiu Creanga

65

Wyrażenie regularne

import re

re.search(r"(?<=AAA).*?(?=ZZZ)", your_text).group(0)

Powyższe takie, jakie jest, zakończy się niepowodzeniem, AttributeErrorjeśli nie ma „AAA” i „ZZZ” wyour_text

metody strunowe

your_text.partition("AAA")[2].partition("ZZZ")[0]

Powyższe zwróci pusty ciąg, jeśli „AAA” lub „ZZZ” nie istnieją your_text.

Wyzwanie PS Python?


6
Ta odpowiedź prawdopodobnie zasługuje na więcej głosów. Metoda łańcuchowa jest najbardziej niezawodnym sposobem. Nie wymaga wypróbowania / wyjątku.
ChaimG,

... ładne, choć ograniczone. partycja nie jest oparta na wyrażeniach regularnych, więc działa tylko w tym przypadku, ponieważ ciąg wyszukiwania został ograniczony ustalonymi literałami
GreenAsJade

Świetnie, wielkie dzięki! - działa to na łańcuchy i nie wymaga wyrażenia regularnego
Alex

15
import re
print re.search('AAA(.*?)ZZZ', 'gfgfdAAA1234ZZZuijjk').group(1)

1
AttributeError: 'NoneType' object has no attribute 'groups'- jeśli nie ma AAA, ZZZ w ciągu ...
eumiro

12

Zaskoczony, że nikt nie wspomniał o tym, co jest moją szybką wersją jednorazowych skryptów:

>>> x = 'gfgfdAAA1234ZZZuijjk'
>>> x.split('AAA')[1].split('ZZZ')[0]
'1234'

@ user1810100 wspomniał w zasadzie, że prawie dokładnie 5 lat przed tym, zanim opublikowałeś to ...
John

10

możesz to zrobić za pomocą tylko jednego wiersza kodu

>>> import re

>>> re.findall(r'\d{1,5}','gfgfdAAA1234ZZZuijjk')

>>> ['1234']

wynik otrzyma listę ...


7

Możesz użyć do tego modułu re :

>>> import re
>>> re.compile(".*AAA(.*)ZZZ.*").match("gfgfdAAA1234ZZZuijjk").groups()
('1234,)

5

Za pomocą sed można zrobić coś takiego za pomocą łańcucha:

echo "$STRING" | sed -e "s|.*AAA\(.*\)ZZZ.*|\1|"

A to da mi 1234 wynik.

Możesz zrobić to samo z re.subfunkcją używając tego samego wyrażenia regularnego.

>>> re.sub(r'.*AAA(.*)ZZZ.*', r'\1', 'gfgfdAAA1234ZZZuijjk')
'1234'

W podstawowym sedu grupa przechwytująca jest reprezentowana przez \(..\), ale w pythonie była reprezentowana przez (..).


5

W Pythonie wyodrębnianie ciągu formularza podłańcuchowego można wykonać za pomocą findallmetody w remodule wyrażenia regularnego ( ).

>>> import re
>>> s = 'gfgfdAAA1234ZZZuijjk'
>>> ss = re.findall('AAA(.+)ZZZ', s)
>>> print ss
['1234']

4

Możesz znaleźć pierwszy podciąg z tą funkcją w swoim kodzie (według indeksu znaków). Możesz także znaleźć to, co jest po podciągu.

def FindSubString(strText, strSubString, Offset=None):
    try:
        Start = strText.find(strSubString)
        if Start == -1:
            return -1 # Not Found
        else:
            if Offset == None:
                Result = strText[Start+len(strSubString):]
            elif Offset == 0:
                return Start
            else:
                AfterSubString = Start+len(strSubString)
                Result = strText[AfterSubString:AfterSubString + int(Offset)]
            return Result
    except:
        return -1

# Example:

Text = "Thanks for contributing an answer to Stack Overflow!"
subText = "to"

print("Start of first substring in a text:")
start = FindSubString(Text, subText, 0)
print(start); print("")

print("Exact substring in a text:")
print(Text[start:start+len(subText)]); print("")

print("What is after substring \"%s\"?" %(subText))
print(FindSubString(Text, subText))

# Your answer:

Text = "gfgfdAAA1234ZZZuijjk"
subText1 = "AAA"
subText2 = "ZZZ"

AfterText1 = FindSubString(Text, subText1, 0) + len(subText1)
BeforText2 = FindSubString(Text, subText2, 0) 

print("\nYour answer:\n%s" %(Text[AfterText1:BeforText2]))


3
text = 'I want to find a string between two substrings'
left = 'find a '
right = 'between two'

print(text[text.index(left)+len(left):text.index(right)])

Daje

string

2

Na wypadek, gdyby ktoś musiał zrobić to samo, co ja. Musiałem wyodrębnić wszystko z nawiasu w linii. Na przykład, jeśli mam takie zdanie, jak „prezydent USA (Barack Obama) spotkał się z ...” i chcę uzyskać tylko „Barack Obama”, oto rozwiązanie:

regex = '.*\((.*?)\).*'
matches = re.search(regex, line)
line = matches.group(1) + '\n'

Tzn. Musisz zablokować nawias ze slash \znakiem. Chociaż problem dotyczy bardziej wyrażeń regularnych niż Python.

Ponadto w niektórych przypadkach przed definicją wyrażenia regularnego możesz zobaczyć symbole „r”. Jeśli nie ma przedrostka r, musisz użyć znaków zmiany znaczenia jak w C. Oto więcej dyskusji na ten temat.


2

Korzystanie z PyParsing

import pyparsing as pp

word = pp.Word(pp.alphanums)

s = 'gfgfdAAA1234ZZZuijjk'
rule = pp.nestedExpr('AAA', 'ZZZ')
for match in rule.searchString(s):
    print(match)

co daje:

[['1234']]


0

Oto rozwiązanie bez wyrażenia regularnego, które uwzględnia również scenariusze, w których pierwszy podłańcuch zawiera drugi podłańcuch. Ta funkcja znajdzie podciąg tylko wtedy, gdy drugi znacznik znajduje się za pierwszym znacznikiem.

def find_substring(string, start, end):
    len_until_end_of_first_match = string.find(start) + len(start)
    after_start = string[len_until_end_of_first_match:]
    return string[string.find(start) + len(start):len_until_end_of_first_match + after_start.find(end)]

0

Innym sposobem na to jest użycie list (zakładając, że szukany podciąg składa się tylko z liczb):

string = 'gfgfdAAA1234ZZZuijjk'
numbersList = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
output = []

for char in string:
    if char in numbersList: output.append(char)

print(f"output: {''.join(output)}")
### output: 1234

-1

Jeden wkładka, który zwraca inny ciąg, jeśli nie było dopasowania. Edycja: ulepszona wersja korzysta z nextfunkcji, "not-found"w razie potrzeby zastąp ją inną:

import re
res = next( (m.group(1) for m in [re.search("AAA(.*?)ZZZ", "gfgfdAAA1234ZZZuijjk" ),] if m), "not-found" )

Moja inna metoda, aby to zrobić, mniej optymalna, używa wyrażenia regularnego 2. raz, wciąż nie znalazłem krótszej drogi:

import re
res = ( ( re.search("AAA(.*?)ZZZ", "gfgfdAAA1234ZZZuijjk") or re.search("()","") ).group(1) )
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.