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?
Un peu difficile à lire, mais cela fonctionne:
name=name[len(filter(name.startswith,prefixes+[''])[0]):]
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
for prefix in prefixes:
if name.startswith(prefix):
name=name[len(prefix):]
break
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]
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))
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():]
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
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
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
['this', 'that', 'blah', 'cool', 'what_this']
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.