Jak uzyskać ciąg po określonym podciągu?

226

Jak mogę uzyskać ciąg znaków po określonym podciągu?

Na przykład, chcę uzyskać ciąg po "world"wmy_string="hello python world , i'm a beginner "

havox
źródło

Odpowiedzi:

399

Najłatwiejszym sposobem jest prawdopodobnie podzielenie na słowo docelowe

my_string="hello python world , i'm a beginner "
print my_string.split("world",1)[1] 

split bierze słowo (lub znak) do podziału i opcjonalnie ogranicza liczbę podziałów.

W tym przykładzie podzielono na „świat” i ograniczono go tylko do jednego podziału.

Joran Beasley
źródło
Jeśli będę musiał podzielić tekst ze słowem „niskim”, a przed nim będzie słowo „niższe”, to nie zadziała!
Leonardo Hermoso,
1
po prostu podzieliłbyś 2xtarget.split('lower',1)[-1].split('low',1)[-1]
Joran Beasley
co jeśli zdanie brzmiałoby: „Witaj, świecie Megaworld, jestem początkującym”. Jak sprawić, by patrzyło na całe słowo, a nie na inne jako „Megaworld”? Dzięki
pbou,
1
następnie szukany ciąg to „world” ... lub użyj wyrażenia regularnego dla słowa boundrys
Joran Beasley
6
my_string.partition("world")[-1](lub ...[2]) jest szybszy.
Martijn Pieters
66
s1 = "hello python world , i'm a beginner "
s2 = "world"

print s1[s1.index(s2) + len(s2):]

Jeśli chcemy mieć do czynienia z przypadkiem, gdy s2to nie występuje w s1, a następnie użyć s1.find(s2)jako przeciwny index. Jeśli zwracana jest wartość tego połączenia -1, s2to nie ma go s1.

arshajii
źródło
dostajesz wyraźne identyfikatory (oddzielone kilkoma tysiącami) ... nie jestem pewien, czy nie stworzysz w ten sposób niepotrzebnych podciągów
Joran Beasley
@JoranBeasley, wywołujemy tylko index (), len () i slice. Nie ma powodu, aby index () i len () tworzyły podciągi, a jeśli tak, to trudno mi uwierzyć, to tylko niepotrzebne szczegóły implementacji. To samo dotyczy wycinka - nie ma powodu, aby tworzyć podciągi inne niż zwracane.
shx2,
@ shx2print( s1[s1.index(s2) + len(s2):] is s1[s1.index(s2) + len(s2):])
Joran Beasley,
@JoranBeasley, jaki punkt starasz się osiągnąć dzięki temu fragmentowi? Czy przy wielu połączeniach zwracane są różne obiekty? przez „niepotrzebne podłańcuchy” rozumiem podłańcuchy inne niż zwrócone, tj. podłańcuchy, które nie są konieczne do utworzenia w celu uzyskania wyniku.
shx2,
56

Dziwię się, że nikt nie wspomniał partition.

def substring_after(s, delim):
    return s.partition(delim)[2]

IMHO, to rozwiązanie jest bardziej czytelne niż @ arshajii. Poza tym uważam, że @ arshajii's jest najlepszy, ponieważ jest najszybszy - nie tworzy niepotrzebnych kopii / podciągów.

shx2
źródło
2
To miłe rozwiązanie i ładnie radzi sobie z przypadkiem, w którym podciąg nie jest częścią łańcucha podstawowego.
mattmc3
dostajesz różne identyfikatory (oddzielone kilkoma tysiącami) ... nie jestem pewien, czy nie stworzysz w ten sposób niepotrzebnych podciągów (i jestem zbyt leniwy, aby odpowiednio go profilować)
Joran Beasley
1
@JoranBeasley, wyraźnie nie tworzyć niepotrzebnych substings. Myślę, że źle odczytałeś moją odpowiedź.
shx2
(tak mi się wydaje, arashi's ...)
Joran Beasley
3
Co więcej, jest to szybsze niż str.split(..., 1).
Martijn Pieters
20

Chcesz użyć str.partition() :

>>> my_string.partition("world")[2]
" , i'm a beginner "

ponieważ ta opcja jest szybsza niż alternatywy .

Zauważ, że tworzy to pusty ciąg, jeśli brakuje separatora:

>>> my_string.partition("Monty")[2]  # delimiter missing
''

Jeśli chcesz mieć oryginalny ciąg znaków, sprawdź, czy druga wartość została zwróconastr.partition() nie jest pusta:

prefix, success, result = my_string.partition(delimiter)
if not success: result = prefix

Możesz także użyć str.split() z limitem 1:

>>> my_string.split("world", 1)[-1]
" , i'm a beginner "
>>> my_string.split("Monty", 1)[-1]  # delimiter missing
"hello python world , i'm a beginner "

Ta opcja jest jednak wolniejsza . W najlepszym przypadku str.partition()jest to około 15% szybsze w porównaniu dostr.split() :

                                missing        first         lower         upper          last
      str.partition(...)[2]:  [3.745 usec]  [0.434 usec]  [1.533 usec]  <3.543 usec>  [4.075 usec]
str.partition(...) and test:   3.793 usec    0.445 usec    1.597 usec    3.208 usec    4.170 usec
      str.split(..., 1)[-1]:  <3.817 usec>  <0.518 usec>  <1.632 usec>  [3.191 usec]  <4.173 usec>
            % best vs worst:         1.9%         16.2%          6.1%          9.9%          2.3%

Pokazuje czasy na wykonanie z wejściami w tym przypadku brak separatora (scenariusz najgorszego przypadku), umieszczany jako pierwszy (scenariusz najlepszego przypadku) lub w dolnej połowie, górnej połowie lub ostatniej pozycji. Najszybszy czas jest oznaczony [...]i <...>oznacza najgorszy.

Powyższa tabela została opracowana na podstawie kompleksowej próby czasowej dla wszystkich trzech opcji przedstawionych poniżej. Testy przeprowadziłem na Pythonie 3.7.4 na 15-calowym MacBooku Pro z 2017 roku z procesorem Intel Core i7 2,9 GHz i 16 GB pamięci RAM.

Ten skrypt generuje losowe zdania z obecnym losowo wybranym ogranicznikiem i bez niego, a jeśli jest obecny, w różnych pozycjach w generowanym zdaniu, uruchamia testy w losowej kolejności z powtórzeniami (tworząc najodpowiedniejsze wyniki uwzględniające losowe zdarzenia systemu operacyjnego zachodzące podczas testowania), a następnie drukuje tabelę wyników:

import random
from itertools import product
from operator import itemgetter
from pathlib import Path
from timeit import Timer

setup = "from __main__ import sentence as s, delimiter as d"
tests = {
    "str.partition(...)[2]": "r = s.partition(d)[2]",
    "str.partition(...) and test": (
        "prefix, success, result = s.partition(d)\n"
        "if not success: result = prefix"
    ),
    "str.split(..., 1)[-1]": "r = s.split(d, 1)[-1]",
}

placement = "missing first lower upper last".split()
delimiter_count = 3

wordfile = Path("/usr/dict/words")  # Linux
if not wordfile.exists():
    # macos
    wordfile = Path("/usr/share/dict/words")
words = [w.strip() for w in wordfile.open()]

def gen_sentence(delimiter, where="missing", l=1000):
    """Generate a random sentence of length l

    The delimiter is incorporated according to the value of where:

    "missing": no delimiter
    "first":   delimiter is the first word
    "lower":   delimiter is present in the first half
    "upper":   delimiter is present in the second half
    "last":    delimiter is the last word

    """
    possible = [w for w in words if delimiter not in w]
    sentence = random.choices(possible, k=l)
    half = l // 2
    if where == "first":
        # best case, at the start
        sentence[0] = delimiter
    elif where == "lower":
        # lower half
        sentence[random.randrange(1, half)] = delimiter
    elif where == "upper":
        sentence[random.randrange(half, l)] = delimiter
    elif where == "last":
        sentence[-1] = delimiter
    # else: worst case, no delimiter

    return " ".join(sentence)

delimiters = random.choices(words, k=delimiter_count)
timings = {}
sentences = [
    # where, delimiter, sentence
    (w, d, gen_sentence(d, w)) for d, w in product(delimiters, placement)
]
test_mix = [
    # label, test, where, delimiter sentence
    (*t, *s) for t, s in product(tests.items(), sentences)
]
random.shuffle(test_mix)

for i, (label, test, where, delimiter, sentence) in enumerate(test_mix, 1):
    print(f"\rRunning timed tests, {i:2d}/{len(test_mix)}", end="")
    t = Timer(test, setup)
    number, _ = t.autorange()
    results = t.repeat(5, number)
    # best time for this specific random sentence and placement
    timings.setdefault(
        label, {}
    ).setdefault(
        where, []
    ).append(min(dt / number for dt in results))

print()

scales = [(1.0, 'sec'), (0.001, 'msec'), (1e-06, 'usec'), (1e-09, 'nsec')]
width = max(map(len, timings))
rows = []
bestrow = dict.fromkeys(placement, (float("inf"), None))
worstrow = dict.fromkeys(placement, (float("-inf"), None))

for row, label in enumerate(tests):
    columns = []
    worst = float("-inf")
    for p in placement:
        timing = min(timings[label][p])
        if timing < bestrow[p][0]:
            bestrow[p] = (timing, row)
        if timing > worstrow[p][0]:
            worstrow[p] = (timing, row)
        worst = max(timing, worst)
        columns.append(timing)

    scale, unit = next((s, u) for s, u in scales if worst >= s)
    rows.append(
        [f"{label:>{width}}:", *(f" {c / scale:.3f} {unit} " for c in columns)]
    )

colwidth = max(len(c) for r in rows for c in r[1:])
print(' ' * (width + 1), *(p.center(colwidth) for p in placement), sep="  ")
for r, row in enumerate(rows):
    for c, p in enumerate(placement, 1):
        if bestrow[p][1] == r:
            row[c] = f"[{row[c][1:-1]}]"
        elif worstrow[p][1] == r:
            row[c] = f"<{row[c][1:-1]}>"
    print(*row, sep="  ")

percentages = []
for p in placement:
    best, worst = bestrow[p][0], worstrow[p][0]
    ratio = ((worst - best) / worst)
    percentages.append(f"{ratio:{colwidth - 1}.1%} ")

print("% best vs worst:".rjust(width + 1), *percentages, sep="  ")
Martijn Pieters
źródło
świetna odpowiedź! tym bardziej, że podasz prawdziwy powód, dla którego jest to lepsze: P
Joran Beasley,
18

Jeśli chcesz to zrobić za pomocą wyrażenia regularnego, możesz po prostu użyć grupy nie przechwytywania , aby uzyskać słowo „świat”, a następnie pobrać wszystko później, tak

(?:world).*

Przykładowy ciąg jest testowany tutaj

Tadgh
źródło
28
niektórzy ludzie w obliczu problemu myślą „Wiem, używam wyrażeń regularnych”. ... teraz masz 2 problemy ...
Joran Beasley
2
haha, mój błąd, myślałem, że to oznaczono regex, więc spróbowałem udzielić odpowiedzi. No cóż, już tam jest.
Tadgh
1
wszystko jest w porządku ... to z pewnością jeden ze sposobów obdziergania kota ... przesada za ten problem (imho)
Joran Beasley
Nie przechwycony link do grupy nie wskazuje już właściwej rzeczy.
Apteryx,
1
Dla zainteresowanych. Oto pełny kodresult = re.search(r"(?:world)(.*)", "hello python world , i'm a beginner ").group(1)
RaduS
5

Możesz użyć tego pakietu o nazwie „substring”. Po prostu wpisz „substr install pip”. Podciąg można uzyskać, po prostu wspominając początkowe i końcowe znaki / indeksy.

Na przykład:

import substring

s = substring.substringByChar("abcdefghijklmnop", startChar="d", endChar="n")

print(s)

Wynik:

s = defghijklmn

Sriram Veturi
źródło
3

To stare pytanie, ale stanąłem przed tym samym scenariuszem. Muszę podzielić ciąg znaków, używając jako demilitera słowa „niski”. Problem polegał na tym, że mam ten sam ciąg słowa poniżej i poniżej.

Rozwiązałem go za pomocą modułu re w ten sposób

import re

string = '...below...as higher prices mean lower demand to be expected. Generally, a high reading is seen as negative (or bearish), while a low reading is seen as positive (or bullish) for the Korean Won.'

użyj re.split z wyrażeniem regularnym, aby dopasować dokładne słowo

stringafterword = re.split('\\blow\\b',string)[-1]
print(stringafterword)
' reading is seen as positive (or bullish) for the Korean Won.'

ogólny kod to:

re.split('\\bTHE_WORD_YOU_WANT\\b',string)[-1]

Mam nadzieję, że to może komuś pomóc!

Leonardo Hermoso
źródło
1
Być może możesz po prostu użyć string.partition(" low ")[2]:? (Zwróć uwagę na spacje po obu stronachlow
Mtl Dev
1

Wypróbuj to ogólne podejście:

import re
my_string="hello python world , i'm a beginner "
p = re.compile("world(.*)")
print (p.findall(my_string))

#[" , i'm a beginner "]
Hadij
źródło