Comment obtenir une chaîne après une sous-chaîne spécifique?

226

Comment puis-je obtenir une chaîne après une sous-chaîne spécifique?

Par exemple, je veux la chaîne après "world"enmy_string="hello python world , i'm a beginner "

havox
la source

Réponses:

399

La façon la plus simple est probablement de diviser votre mot cible

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

split prend le mot (ou le caractère) à fractionner et éventuellement une limite au nombre de divisions.

Dans cet exemple, divisez sur "monde" et limitez-le à une seule division.

Joran Beasley
la source
Si j'ai besoin de diviser un texte avec le mot «bas» et qu'il contient le mot inférieur avant, cela ne fonctionnera pas!
Leonardo Hermoso
1
vous diviseriez simplement 2xtarget.split('lower',1)[-1].split('low',1)[-1]
Joran Beasley
Et si la phrase était "Bonjour python Megaworld World, je suis un débutant". Comment puis-je faire en sorte que le mot tout entier ne fasse pas partie d'un autre comme «Megaworld»? Merci
pbou
1
alors la chaîne que vous recherchez est "world" ... ou utilisez regex pour le mot boundrys
Joran Beasley
6
my_string.partition("world")[-1](ou ...[2]) est plus rapide.
Martijn Pieters
66
s1 = "hello python world , i'm a beginner "
s2 = "world"

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

Si vous voulez traiter le cas où il s2n'est pas présent s1, utilisez s1.find(s2)plutôt que index. Si la valeur de retour de cet appel est -1, alors ce s2n'est pas le cas s1.

arshajii
la source
vous obtenez des identifiants distincts (qui sont séparés par plusieurs milliers) ... je ne suis pas sûr de ne pas créer de sous-chaînes inutiles avec cela
Joran Beasley
@JoranBeasley, nous appelons uniquement index (), len () et slice. Il n'y a aucune raison pour que index () et len ​​() créent des sous-chaînes, et s'ils le font (j'ai du mal à le croire), ce n'est qu'un détail d'implémentation inutile. Idem pour la tranche - il n'y a aucune raison pour qu'elle crée des sous-chaînes autres que celle retournée.
shx2
@ shx2print( s1[s1.index(s2) + len(s2):] is s1[s1.index(s2) + len(s2):])
Joran Beasley
@JoranBeasley quel point essayez-vous de faire avec cet extrait? Que sur plusieurs appels différents objets sont retournés? par "sous-chaînes inutiles", j'entends des sous-chaînes autres que celle renvoyée, c'est-à-dire des sous-chaînes qu'il n'est pas nécessaire de créer pour obtenir le résultat.
shx2
57

Je suis surpris que personne n'ait mentionné partition.

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

À mon humble avis, cette solution est plus lisible que celle de @ arshajii. En dehors de cela, je pense que @ arshajii est le meilleur pour être le plus rapide - il ne crée pas de copies / sous-chaînes inutiles.

shx2
la source
2
Ceci est une bonne solution et gère le cas où la sous-chaîne ne fait pas bien partie de la chaîne de base.
mattmc3
vous obtenez des identifiants distincts (qui sont séparés par plusieurs milliers) ... je ne suis pas sûr de ne pas créer de sous-chaînes inutiles avec cela (et je suis trop paresseux pour le profiler correctement)
Joran Beasley
1
@JoranBeasley, il a clairement fait créer substings inutiles. Je pense que vous avez mal lu ma réponse.
shx2
(tout comme Arashi je pense ...)
Joran Beasley
3
De plus, c'est plus rapide que str.split(..., 1).
Martijn Pieters
20

Vous souhaitez utiliser str.partition():

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

car cette option est plus rapide que les alternatives .

Notez que cela produit une chaîne vide si le délimiteur est manquant:

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

Si vous souhaitez avoir la chaîne d'origine, testez si la deuxième valeur renvoyée par str.partition()n'est pas vide:

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

Vous pouvez également utiliser str.split()avec une limite de 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 "

Cependant, cette option est plus lente . Dans le meilleur des cas, str.partition()est facilement environ 15% plus rapide par rapport à str.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%

Cela montre les temps par exécution avec des entrées ici, le délimiteur est soit manquant (pire scénario), placé en premier (meilleur scénario), soit dans la moitié inférieure, la moitié supérieure ou la dernière position. Le temps le plus rapide est marqué par [...]et<...> marque le pire.

Le tableau ci-dessus est produit par un contre-la-montre complet pour les trois options, produit ci-dessous. J'ai exécuté les tests sur Python 3.7.4 sur un Macbook Pro 15 pouces de modèle 2017 avec Intel Core i7 à 2,9 GHz et 16 Go de RAM.

Ce script génère des phrases aléatoires avec et sans le délimiteur sélectionné au hasard, et s'il est présent, à différentes positions dans la phrase générée, exécute les tests dans un ordre aléatoire avec des répétitions (produisant les résultats les plus équitables tenant compte des événements aléatoires du système d'exploitation se produisant pendant les tests), puis imprime un tableau des résultats:

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
la source
très bonne réponse! surtout parce que vous donnez la vraie raison pour laquelle c'est mieux: P
Joran Beasley
18

Si vous voulez le faire en utilisant l'expression régulière, vous pouvez simplement utiliser un groupe non capturant , pour obtenir le mot "monde" et ensuite récupérer tout après, comme ça

(?:world).*

L'exemple de chaîne est testé ici

Tadgh
la source
28
certaines personnes confrontées à un problème pensent «Je sais, je vais utiliser une expression régulière». ... maintenant vous avez 2 problèmes ...
Joran Beasley
2
haha, mon erreur, je pensais que c'était une expression régulière, alors j'ai essayé de donner une réponse régulière. Eh bien, c'est là maintenant.
Tadgh
1
c'est tout bon ... c'est certainement une façon d'écorcher ce chat ... trop pour ce problème (à mon humble avis)
Joran Beasley
Le lien de groupe non capturant ne pointe plus vers la bonne chose.
Apteryx
1
Pour ceux intéressés. Voici le code completresult = re.search(r"(?:world)(.*)", "hello python world , i'm a beginner ").group(1)
RaduS
5

Vous pouvez utiliser ce package appelé "sous-chaîne". Tapez simplement "pip install substring". Vous pouvez obtenir la sous-chaîne en mentionnant simplement les caractères / index de début et de fin.

Par exemple:

import substring

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

print(s)

Production:

s = defghijklmn

Sriram Veturi
la source
3

C'est une vieille question mais j'ai fait face au même scénario, j'ai besoin de diviser une chaîne en utilisant comme démilitre le mot "bas" le problème pour moi était que j'ai dans la même chaîne le mot ci-dessous et plus bas.

Je l'ai résolu en utilisant le module re de cette façon

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.'

utilisez re.split avec regex pour faire correspondre le mot exact

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

le code générique est:

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

J'espère que cela peut aider quelqu'un!

Leonardo Hermoso
la source
1
Peut-être pourriez-vous aussi simplement utiliser string.partition(" low ")[2]:? (Notez les espaces de chaque côté delow
Mtl Dev
1

Essayez cette approche générale:

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
la source