web-dev-qa-db-fra.com

Recherche d'une chaîne commençant par l'un des préfixes de longueur variable d'une liste

Je dois savoir si un nom commence par l'un des préfixes d'une liste, puis le supprimer, par exemple:

if name[:2] in ["i_", "c_", "m_", "l_", "d_", "t_", "e_", "b_"]:
    name = name[2:]

Ce qui précède ne fonctionne que pour les préfixes de liste d’une longueur de deux. J'ai besoin de la même fonctionnalité pour les préfixes de longueur variable .

Comment cela se fait-il efficacement (peu de code et de bonnes performances)?

Une boucle for itérant sur chaque préfixe puis vérifiant name.startswith(prefix) pour découper le nom en fonction de la longueur du préfixe fonctionne, mais il s'agit de beaucoup de code, probablement inefficace, et "non Pythonic".

Quelqu'un a-t-il une solution intéressante?

20
Kawu

Un peu difficile à lire, mais cela fonctionne:

name=name[len(filter(name.startswith,prefixes+[''])[0]):]
12
Vaughn Cato

str.startswith (préfixe [ début [ fin]]) ¶

Retourne True si la chaîne commence par le préfixe, sinon, retourne Faux. préfixe peut également être un nuage de préfixes à rechercher. Avec début facultatif, chaîne de test commençant à cette position. Avec fin facultative, arrête de comparer la chaîne à cette position.

$ ipython
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: prefixes = ("i_", "c_", "m_", "l_", "d_", "t_", "e_", "b_")

In [2]: 'test'.startswith(prefixes)
Out[2]: False

In [3]: 'i_'.startswith(prefixes)
Out[3]: True

In [4]: 'd_a'.startswith(prefixes)
Out[4]: True
40
dm03514
for prefix in prefixes:
    if name.startswith(prefix):
        name=name[len(prefix):]
        break
5
unutbu

Si vous définissez préfixe comme étant les caractères précédant un trait de soulignement, vous pouvez alors vérifier 

if name.partition("_")[0] in ["i", "c", "m", "l", "d", "t", "e", "b", "foo"] and name.partition("_")[1] == "_":
    name = name.partition("_")[2]
2
Foo Bah

Qu'en est-il d'utiliser filter?

prefs = ["i_", "c_", "m_", "l_", "d_", "t_", "e_", "b_"]
name = list(filter(lambda item: not any(item.startswith(prefix) for prefix in prefs), name))

Notez que la comparaison de chaque élément de la liste avec les préfixes s'interrompt efficacement lors de la première correspondance. Ce comportement est garanti par la fonction any qui retourne dès qu’elle trouve une valeur True, par exemple:

def gen():
    print("yielding False")
    yield False
    print("yielding True")
    yield True
    print("yielding False again")
    yield False

>>> any(gen()) # last two lines of gen() are not performed
yielding False
yielding True
True

Ou, en utilisant re.match au lieu de startswith:

import re
patt = '|'.join(["i_", "c_", "m_", "l_", "d_", "t_", "e_", "b_"])
name = list(filter(lambda item: not re.match(patt, item), name))
2
etuardu

Les regex vous donneront probablement la meilleure vitesse:

prefixes = ["i_", "c_", "m_", "l_", "d_", "t_", "e_", "b_", "also_longer_"]
re_prefixes = "|".join(re.escape(p) for p in prefixes)

m = re.match(re_prefixes, my_string)
if m:
    my_string = my_string[m.end()-m.start():]
2
Ned Batchelder

Regex, testé:

import re

def make_multi_prefix_matcher(prefixes):
    regex_text = "|".join(re.escape(p) for p in prefixes)
    print repr(regex_text)
    return re.compile(regex_text).match

pfxs = "x ya foobar foo a|b z.".split()
names = "xenon yadda yeti food foob foobarre foo a|b a b z.yx zebra".split()

matcher = make_multi_prefix_matcher(pfxs)
for name in names:
    m = matcher(name)
    if not m:
        print repr(name), "no match"
        continue
    n = m.end()
    print repr(name), n, repr(name[n:])

Sortie:

'x|ya|foobar|foo|a\\|b|z\\.'
'xenon' 1 'enon'
'yadda' 2 'dda'
'yeti' no match
'food' 3 'd'
'foob' 3 'b'
'foobarre' 6 're'
'foo' 3 ''
'a|b' 3 ''
'a' no match
'b' no match
'z.yx' 2 'yx'
'zebra' no match
1
John Machin

Quand il s’agit de recherche et d’efficacité, on pense toujours aux techniques d’indexation pour améliorer vos algorithmes. Si vous avez une longue liste de préfixes, vous pouvez utiliser un index en mémoire en indexant simplement les préfixes en fonction du premier caractère dans un dict.

Cette solution ne vaut que si vous avez une longue liste de préfixes et que les performances deviennent un problème.

pref = ["i_", "c_", "m_", "l_", "d_", "t_", "e_", "b_"]

#indexing prefixes in a dict. Do this only once.
d = dict()
for x in pref:
        if not x[0] in d:
                d[x[0]] = list()
        d[x[0]].append(x)


name = "c_abcdf"

#lookup in d to only check elements with the same first character.
result = filter(lambda x: name.startswith(x),\
                        [] if name[0] not in d else d[name[0]])
print result
1
Manuel Salvadores

Ceci édite la liste à la volée, en supprimant les préfixes. La break ignore le reste des préfixes une fois que l'on en trouve un pour un élément particulier.

items = ['this', 'that', 'i_blah', 'joe_cool', 'what_this']
prefixes = ['i_', 'c_', 'a_', 'joe_', 'mark_']

for i,item in enumerate(items):
    for p in prefixes:
        if item.startswith(p):
            items[i] = item[len(p):]
            break

print items

Sortie

['this', 'that', 'blah', 'cool', 'what_this']
0
Mark Tolonen

Pourrait utiliser un regex simple.

import re
prefixes = ("i_", "c_", "longer_")
re.sub(r'^(%s)' % '|'.join(prefixes), '', name)

Ou si quelque chose précédant un trait de soulignement est un préfixe valide:

name.split('_', 1)[-1]

Cela supprime tout nombre de caractères avant le premier trait de soulignement.

0
wihlke