查找字符串是否以列表的可变长度前缀之一开头



我需要找出一个名称是否以列表的任何前缀开头,然后删除它,如:

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

只适用于长度为2的列表前缀。对于可变长度前缀,我需要相同的功能。

如何高效地完成(少量代码和良好的性能)?

for循环遍历每个前缀,然后检查name.startswith(prefix),最终根据前缀的长度对名称进行切片,但这是大量的代码,可能效率低下,并且"非python"。

有人有好的解决方案吗?

str。Startswith (prefix[, start[, end]])¶

如果字符串以前缀开头返回True,否则返回假的。Prefix也可以是要查找的前缀元组。与可选的开始,测试字符串从该位置开始。与可选结束,在该位置停止比较字符串。

$ 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

有点难读,但这是有效的:

name=name[len(filter(name.startswith,prefixes+[''])[0]):]
for prefix in prefixes:
    if name.startswith(prefix):
        name=name[len(prefix):]
        break

正则表达式可能会给你最好的速度:

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():]

如果您将prefix定义为下划线之前的字符,那么您可以检查

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

使用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))

注意,每个列表项与前缀的比较在第一次匹配时有效地停止。此行为由any函数保证,该函数在发现True值后立即返回,例如:

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

或者,用re.match代替startswith:

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

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:])
输出:

'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

当涉及到搜索和效率时,总是考虑索引技术来改进你的算法。如果你有一个很长的前缀列表,你可以使用一个内存索引,通过简单的索引前缀的第一个字符到一个dict

这个解决方案只有当你有一个很长的前缀列表并且性能成为一个问题时才值得使用。

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

这将动态编辑列表,删除前缀。一旦找到特定项的前缀,break将跳过其余的前缀。

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

可以用一个简单的正则表达式

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

或者下划线之前的任何内容都是有效的前缀:

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

这将删除第一个下划线之前的任意数量的字符。

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'

最新更新