python - separar - Encontrar si una cadena comienza con uno de los prefijos de longitud variable de una lista
separar palabra en letras python (11)
str.startswith (prefijo [, inicio [, final]]) ¶
Devuelva True si la cadena comienza con el prefijo, de lo contrario, devuelva False. prefijo también puede ser una tupla de prefijos para buscar. Con inicio opcional, pruebe la cadena que comienza en esa posición. Con el extremo opcional, deja de comparar la cuerda en esa posición.
$ 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
Necesito averiguar si un nombre comienza con alguno de los prefijos de una lista y luego eliminarlo, como:
if name[:2] in ["i_", "c_", "m_", "l_", "d_", "t_", "e_", "b_"]:
name = name[2:]
Lo anterior solo funciona para los prefijos de lista con una longitud de dos. Necesito la misma funcionalidad para los prefijos de longitud variable .
¿Cómo se hace de manera eficiente (poco código y buen desempeño)?
Un bucle for itera sobre cada prefijo y luego name.startswith(prefix)
para finalmente name.startswith(prefix)
el nombre de acuerdo con la longitud del prefijo, pero es una gran cantidad de código, probablemente ineficiente, y "no pitón".
¿Alguien tiene una buena solución?
¿Qué hay de usar el 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))
Tenga en cuenta que la comparación de cada elemento de la lista con los prefijos se detiene de manera eficiente en la primera coincidencia. Este comportamiento está garantizado por any
función que devuelve tan pronto como encuentra un valor True
, por ejemplo:
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
O, usando re.match
lugar 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))
Cuando se trata de búsqueda y eficiencia, siempre piense en técnicas de indexación para mejorar sus algoritmos. Si tiene una larga lista de prefijos, puede utilizar un índice en memoria simplemente indexando los prefijos por el primer carácter en un dict
.
Esta solución solo vale la pena si tiene una larga lista de prefijos y el rendimiento se convierte en un problema.
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
Esto edita la lista sobre la marcha, eliminando los prefijos. La break
omite el resto de los prefijos una vez que se encuentra uno para un elemento en particular.
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
Salida
[''this'', ''that'', ''blah'', ''cool'', ''what_this'']
Podría usar un regex simple.
import re
prefixes = ("i_", "c_", "longer_")
re.sub(r''^(%s)'' % ''|''.join(prefixes), '''', name)
O si algo que precede a un guión bajo es un prefijo válido:
name.split(''_'', 1)[-1]
Esto elimina cualquier número de caracteres antes del primer guión bajo.
Regex, probado:
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:])
Salida:
''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
Regexes probablemente te dará la mejor velocidad:
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():]
Si define el prefijo como los caracteres antes de un guión bajo, entonces puede verificar
if name.partition("_")[0] in ["i", "c", "m", "l", "d", "t", "e", "b", "foo"] and name.partition("_")[1] == "_":
name = name.partition("_")[2]
Un poco difícil de leer, pero esto funciona:
name=name[len(filter(name.startswith,prefixes+[''''])[0]):]
for prefix in prefixes:
if name.startswith(prefix):
name=name[len(prefix):]
break
import re
def make_multi_prefix_replacer(prefixes):
if isinstance(prefixes,str):
prefixes = prefixes.split()
prefixes.sort(key = len, reverse=True)
pat = r''/b(%s)'' % "|".join(map(re.escape, prefixes))
print ''regex patern :'',repr(pat),''/n''
def suber(x, reg = re.compile(pat)):
return reg.sub('''',x)
return suber
pfxs = "x ya foobar yaku foo a|b z."
replacer = make_multi_prefix_replacer(pfxs)
names = "xenon yadda yeti yakute food foob foobarre foo a|b a b z.yx zebra".split()
for name in names:
print repr(name),''/n'',repr(replacer(name)),''/n''
ss = ''the yakute xenon is a|bcdf in the barfoobaratu foobarii''
print ''/n'',repr(ss),''/n'',repr(replacer(ss)),''/n''