【问题标题】:python: find first string in stringpython:在字符串中查找第一个字符串
【发布时间】:2016-03-04 17:30:55
【问题描述】:

给定一个字符串和一个子字符串列表,我希望任何子字符串出现在字符串中的第一个位置。如果没有出现子字符串,则返回 0。我想忽略大小写。

还有什么比 Python 更经典的吗:

given = 'Iamfoothegreat'
targets = ['foo', 'bar', 'grea', 'other']
res = len(given)
for t in targets:
    i = given.lower().find(t)
    if i > -1 and i < res:
        res = i

if res == len(given):
    result = 0
else:
    result = res

该代码有效,但似乎效率低下。

【问题讨论】:

  • 您期望从"duuuuuh" 之类的输入中获得什么行为并在其中找到"uu"
  • @Rockybilly "任何子字符串出现在字符串中的第一个位置"
  • @foosion 我的模式匹配解决方案怎么样。应该很快,因为我认为正则表达式的实现速度非常快
  • @foosion,你的典型输入是什么样的?
  • padraic,例子已经够接近了

标签: python string match


【解决方案1】:

使用正则表达式

另一个例子只是使用正则表达式,因为认为 python 正则表达式实现非常快。不是我的正则表达式函数是

import re

given = 'IamFoothegreat'
targets = ['foo', 'bar', 'grea', 'other']

targets = [re.escape(x) for x in targets]    
pattern = r"%(pattern)s" % {'pattern' : "|".join(targets)}
firstMatch = next(re.finditer(pattern, given, re.IGNORECASE),None)
if firstMatch:
    print firstMatch.start()
    print firstMatch.group()

输出是

3
foo

如果什么也没找到,输出什么都没有。应该自我解释以检查是否找不到任何东西。

更正常的不是真正的pythonic

也给你匹配的字符串

given = 'Iamfoothegreat'.lower()
targets = ['foo', 'bar', 'grea', 'other']

dct = {'pos' : - 1, 'string' : None};
given = given.lower()

for t in targets:
    i = given.find(t)
    if i > -1 and (i < list['pos'] or list['pos'] == -1):
        dct['pos'] = i;
        dct['string'] = t;

print dct

输出是:

{'pos': 3, 'string': 'foo'}

如果没有找到元素:

{'pos': -1, 'string': None}

两者的性能比较

用这个字符串和模式

given = "hello world" * 5000
given += "grea" + given
targets = ['foo', 'bar', 'grea', 'other']

1000 个带 timeit 的循环:

regex approach: 4.08629107475 sec for 1000
normal approach: 1.80048894882 sec for 1000

10 个循环。现在有了更大的目标(目标 * 1000):

normal approach: 4.06895017624 for 10
regex approach: 34.8153910637 for 10

【讨论】:

  • 严格来说,在构建模式之前,您应该在每个目标上使用re.escape
  • @ekhumoro 好的,没错。谢谢你,我修好了。
  • 另外一件事:使用re.search(pattern, ...)更简单。
  • @PadraicCunningham。它要么返回match 对象,要么返回None
  • @PadraicCunningham 然后它有一个 MatchObject 并打印 0 和匹配
【解决方案2】:

我不会返回 0,因为它可能是起始索引,使用 -1、None 或其他一些不可能的值,您可以简单地使用 try/except 并返回索引:

def get_ind(s, targ):
    s = s.lower() 
    for t in targets:
        try:            
            return s.index(t.lower())
        except ValueError:
            pass
    return None # -1, False ...

如果您还想忽略输入字符串的大小写,则在循环之前设置s = s.lower()

你也可以这样做:

def get_ind_next(s, targ):
   s = s.lower() 
   return next((s.index(t) for t in map(str.lower,targ) if t in s), None)

但最坏的情况是对每个子字符串进行两次查找,而不是使用 try/except 进行一次查找。它至少也会在第一场比赛中短路。

如果你真的想要所有的最小值,那么改为:

def get_ind(s, targ):
    s = s.lower()
    mn = float("inf")
    for t in targ:
        try:
            i = s.index(t.lower()) 
            if i < mn:
                mn = i 
        except ValueError:
            pass
    return mn   

def get_ind_next(s, targ):
   s = s.lower()
   return min((s.index(t) for t in map(str.lower, targ) if t in s), default=None)

default=None 仅适用于 python >= 3.4,因此如果您使用的是 python2,那么您将不得不稍微更改逻辑。

时序python3:

In [29]: s = "hello world" * 5000
In [30]:  s += "grea" + s
In [25]: %%timeit
   ....: targ = [re.escape(x) for x in targets]
   ....: pattern = r"%(pattern)s" % {'pattern' : "|".join(targ)}
   ....: firstMatch = next(re.finditer(pattern, s, re.IGNORECASE),None)
   ....: if firstMatch:
   ....:     pass
   ....: 
100 loops, best of 3: 5.11 ms per loop
In [18]: timeit get_ind_next(s, targets)
1000 loops, best of 3: 691 µs per loop

In [19]: timeit get_ind(s, targets)
1000 loops, best of 3: 627 µs per loop

In [20]:  timeit  min([s.lower().find(x.lower()) for x in targets if x.lower() in s.lower()] or [0])
1000 loops, best of 3: 1.03 ms per loop

In [21]: s = 'Iamfoothegreat'
In [22]: targets = ['bar', 'grea', 'other','foo']
In [23]: get_ind_next(s, targets) == get_ind(s, targets) ==  min([s.lower().find(x.lower()) for x in targets if x.lower() in s.lower()] or [0])
Out[24]: True

Python2:

In [13]: s = "hello world" * 5000
In [14]:  s += "grea" + s

In [15]: targets = ['foo', 'bar', 'grea', 'other']
In [16]: timeit get_ind(s, targets)1000 loops, 
best of 3: 322 µs per loop

In [17]:  timeit  min([s.lower().find(x.lower()) for x in targets if x.lower() in s.lower()] or [0])
1000 loops, best of 3: 710 µs per loop

In [18]: get_ind(s, targets) ==  min([s.lower().find(x.lower()) for x in targets if x.lower() in s.lower()] or [0])
Out[18]: True

您也可以将第一个与 min 结合起来:

def get_ind(s, targ):
    s,mn = s.lower(), None
    for t in targ:
        try:
            mn = s.index(t.lower())
            yield mn
        except ValueError:
            pass
    yield mn

它做同样的工作,只是更好一点,也许稍微快一点:

In [45]: min(get_ind(s, targets))
Out[45]: 55000

In [46]: timeit min(get_ind(s, targets))
1000 loops, best of 3: 317 µs per loop

【讨论】:

  • 确保缩进正确。
  • 要正确忽略大小写,您还需要将s 小写。
  • @ekhumoro,我的答案底部有一个注释,但我完全添加了它
  • 我认为 OP 希望所有目标字符串中的起始索引最低(即使用 min 而不是 next 的相同解决方案)
  • eskaev 是正确的。这将返回列表中匹配的第一项,而不是最低的起始索引。
【解决方案3】:

您的代码相当不错,但您可以通过将.lower 转换移出循环来提高它的效率:无需为每个目标子字符串重复它。使用列表推导可以稍微压缩代码,尽管这并不一定会使其更快。我使用嵌套列表组合来避免为每个t 调用两次given.find(t)

我已将我的代码封装在一个函数中以便于测试。

def min_match(given, targets):
    given = given.lower()
    a = [i for i in [given.find(t) for t in targets] if i > -1]
    return min(a) if a else None

targets = ['foo', 'bar', 'grea', 'othe']

data = (
    'Iamfoothegreat', 
    'IAMFOOTHEGREAT', 
    'Iamfothgrease',
    'Iamfothgret',
)

for given in data:
    print(given, min_match(given, targets))    

输出

Iamfoothegreat 3
IAMFOOTHEGREAT 3
Iamfothgrease 7
Iamfothgret None

【讨论】:

    【解决方案4】:

    您可以使用以下内容:

    answer = min([given.lower().find(x.lower()) for x in targets 
        if x.lower() in given.lower()] or [0])
    

    演示 1

    given = 'Iamfoothegreat'
    targets = ['foo', 'bar', 'grea', 'other']
    
    answer = min([given.lower().find(x.lower()) for x in targets 
        if x.lower() in given.lower()] or [0])
    print(answer)
    

    输出

    3
    

    演示 2

    given = 'this is a different string'
    targets = ['foo', 'bar', 'grea', 'other']
    
    answer = min([given.lower().find(x.lower()) for x in targets 
        if x.lower() in given.lower()] or [0])
    print(answer)
    

    输出

    0
    

    我也觉得下面这个方案可读性很强:

    given = 'the string'
    targets = ('foo', 'bar', 'grea', 'other')
    
    given = given.lower()
    
    for i in range(len(given)):
        if given.startswith(targets, i):
            print i
            break
    else:
        print -1
    

    【讨论】:

    • OP 想要忽略大小写。
    • @padraiccunningham 我刚刚添加了一个替代解决方案 - 希望看到它添加到您的时间比较列表中!
    • @gtlambert,你需要传递一个元组给 str.startswith,我可以给你看结果,但你可能不喜欢!
    • @PadraicCunningham 谢谢 - 我刚刚在测试自己,它并不漂亮!
    • 即使从复杂性的角度来看,也很难击败 index 或 find正在谈论线性与二次。
    【解决方案5】:

    试试这个:

    def getFirst(given,targets):
        try:
            return min([i for x in targets for i in [given.find(x)] if not i == -1])
        except ValueError:
            return 0
    

    【讨论】:

    • 两次调用given.find() 并不是一个非常有效的解决方案。
    • OP 要求使用 Python 方式。
    • 他还说他自己的问题是效率低。此外,效率低下是 Pythonic 吗?
    • 我没有投反对票,但正如 zondo 所说,两次调用 .find(x) 的效率低于 OP 的代码。此外,OP 需要不区分大小写的匹配。顺便说一句,您可以传递 min 生成器表达式而不是列表推导式。
    • 如果您在[] 上致电min,您会收到ValueError
    猜你喜欢
    • 2020-02-14
    • 2018-01-19
    • 1970-01-01
    • 1970-01-01
    • 2013-11-19
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 2018-09-18
    相关资源
    最近更新 更多