如何查找字符串中任何一组字符的第一个索引

use*_*575 16 python string indexing

我想找到字符串中第一次出现任何"特殊"字符的索引,如下所示:

>>> "Hello world!".index([' ', '!'])
5
Run Code Online (Sandbox Code Playgroud)

...除了那些无效的Python语法.当然,我可以编写一个模拟此行为的函数:

def first_index(s, characters):
    i = []
    for c in characters:
        try:
            i.append(s.index(c))
        except ValueError:
            pass
    if not i:
        raise ValueError
    return min(i)
Run Code Online (Sandbox Code Playgroud)

我也可以使用正则表达式,但这两种解决方案似乎都有点矫枉过正.在Python中有没有"理智"的方法来做到这一点?

Pad*_*ham 18

您可以使用enumeratenext生成器表达式,获取第一个匹配或如果s中没有出现字符则返回None:

s = "Hello world!"

st = {"!"," "}
ind = next((i for i, ch  in enumerate(s) if ch in st),None)
print(ind)
Run Code Online (Sandbox Code Playgroud)

如果没有匹配项,您可以将您想要的任何值作为默认返回值传递给下一个.

如果要使用函数并引发ValueError:

def first_index(s, characters):
    st = set(characters)
    ind = next((i for i, ch in enumerate(s) if ch in st), None)
    if ind is not None:
        return ind
    raise ValueError
Run Code Online (Sandbox Code Playgroud)

对于使用集合的较小输入,如果有任何差异则不会产生太大影响,但对于大字符串,它将更有效.

一些时间:

在字符串中,字符集的最后一个字符:

In [40]: s = "Hello world!" * 100    
In [41]: string = s    
In [42]: %%timeit
st = {"x","y","!"}
next((i for i, ch in enumerate(s) if ch in st), None)
   ....: 
1000000 loops, best of 3: 1.71 µs per loop    
In [43]: %%timeit
specials = ['x', 'y', '!']
min(map(lambda x: (string.index(x) if (x in string) else len(string)), specials))
   ....: 
100000 loops, best of 3: 2.64 µs per loop
Run Code Online (Sandbox Code Playgroud)

不在字符串中,更大的字符集:

In [44]: %%timeit
st = {"u","v","w","x","y","z"}
next((i for i, ch in enumerate(s) if ch in st), None)
   ....: 
1000000 loops, best of 3: 1.49 µs per loop

In [45]: %%timeit
specials = ["u","v","w","x","y","z"]
min(map(lambda x: (string.index(x) if (x in string) else len(string)), specials))
   ....: 
100000 loops, best of 3: 5.48 µs per loop
Run Code Online (Sandbox Code Playgroud)

在字符串中是字符集的第一个字符:

In [47]: %%timeit
specials = ['H', 'y', '!']
min(map(lambda x: (string.index(x) if (x in string) else len(string)), specials))
   ....: 
100000 loops, best of 3: 2.02 µs per loop

In [48]: %%timeit
st = {"H","y","!"}
next((i for i, ch in enumerate(s) if ch in st), None)
   ....: 
1000000 loops, best of 3: 903 ns per loop
Run Code Online (Sandbox Code Playgroud)

  • 好的,所以*算法*这是最有效的,但是Python迭代会破坏它.很高兴知道 ._.这让我很郁闷lol.我*仍然认为这对某些数据集更有效. (2认同)
  • 所以我有点随意决定接受这个答案.感谢所有回答这个问题的人!还要感谢你们展示了"next"的用法,今天我学到了很多东西.:) (2认同)

Bha*_*Rao 5

使用gen-exp和find方法.

>>> a = [' ', '!']
>>> s = "Hello World!"
>>> min(s.find(i) for i in a)
5
Run Code Online (Sandbox Code Playgroud)

要删除-1它们,可以在列表comp中使用过滤器

>>> a = [' ', '!','$']
>>> s = "Hello World!"
>>> min(s.find(i) for i in a if i in s)
5
Run Code Online (Sandbox Code Playgroud)

或者你可以替代 None

>>> min(s.find(i) if i in s else None for i in a)
5
Run Code Online (Sandbox Code Playgroud)

添加timeit结果

$ python -m timeit "a = [' ', '\!'];s = 'Hello World\!';min(s.find(i) for i in a if i in s)"
1000000 loops, best of 3: 0.902 usec per loop
$ python -m timeit "a = [' ', '\!'];s = 'Hello World\!';next((i for i, ch  in enumerate(s) if ch in a),None)"
1000000 loops, best of 3: 1.25 usec per loop
$ python -m timeit "a = [' ', '\!'];s = 'Hello World\!';min(map(lambda x: (s.index(x) if (x in s) else len(s)), a))"
1000000 loops, best of 3: 1.12 usec per loop
Run Code Online (Sandbox Code Playgroud)

在你的例子中,Padraic的漂亮解决方案有点慢.然而,在大型测试案例中,它绝对是一个胜利者.(有点令人惊讶的是,alfasin的"Not as optimized"在这里也更快)

添加了实施细节

>>> def take1(s,a):
...     min(s.find(i) for i in a if i in s)
... 
>>> import dis
>>> dis.dis(take1)
  2           0 LOAD_GLOBAL              0 (min)
              3 LOAD_CLOSURE             0 (s)
              6 BUILD_TUPLE              1
              9 LOAD_CONST               1 (<code object <genexpr> at 0x7fa622e961b0, file "<stdin>", line 2>)
             12 MAKE_CLOSURE             0
             15 LOAD_FAST                1 (a)
             18 GET_ITER            
             19 CALL_FUNCTION            1
             22 CALL_FUNCTION            1
             25 POP_TOP             
             26 LOAD_CONST               0 (None)
             29 RETURN_VALUE        
>>> def take2(s,a):
...     next((i for i, ch  in enumerate(s) if ch in a),None)
... 
>>> dis.dis(take2)
  2           0 LOAD_GLOBAL              0 (next)
              3 LOAD_CLOSURE             0 (a)
              6 BUILD_TUPLE              1
              9 LOAD_CONST               1 (<code object <genexpr> at 0x7fa622e96e30, file "<stdin>", line 2>)
             12 MAKE_CLOSURE             0
             15 LOAD_GLOBAL              1 (enumerate)
             18 LOAD_FAST                0 (s)
             21 CALL_FUNCTION            1
             24 GET_ITER            
             25 CALL_FUNCTION            1
             28 LOAD_CONST               0 (None)
             31 CALL_FUNCTION            2
             34 POP_TOP             
             35 LOAD_CONST               0 (None)
             38 RETURN_VALUE        
>>> def take3(s,a):
...     min(map(lambda x: (s.index(x) if (x in s) else len(s)), a))
... 
>>> dis.dis(take3)
  2           0 LOAD_GLOBAL              0 (min)
              3 LOAD_GLOBAL              1 (map)
              6 LOAD_CLOSURE             0 (s)
              9 BUILD_TUPLE              1
             12 LOAD_CONST               1 (<code object <lambda> at 0x7fa622e44eb0, file "<stdin>", line 2>)
             15 MAKE_CLOSURE             0
             18 LOAD_FAST                1 (a)
             21 CALL_FUNCTION            2
             24 CALL_FUNCTION            1
             27 POP_TOP             
             28 LOAD_CONST               0 (None)
             31 RETURN_VALUE        
Run Code Online (Sandbox Code Playgroud)

正如你在帕德里克的情况下,清楚地看到全局性功能的加载nextenumerate是其与沿杀时间的人None在最后.在alfasin的解决方案中,主要的减速是lambda功能.

  • 不幸的是,这只有在字符串中实际包含所有字符时才有效.如果`s.find(i)`表达式之一返回`-1`,结果将为`-1`. (2认同)

Pet*_*son 5

我赞成该re模块,因为它内置并已经过测试.它也针对这种事情进行了优化.

>>> import re
>>> re.search(r'[ !]', 'Hello World!').start()
5
Run Code Online (Sandbox Code Playgroud)

您可能想要检查是否找到了匹配项,或者在没有找到匹配项时捕获异常.

有理由不使用re,但我希望看到一个好的评论来证明理性.认为你可以"做得更好"通常是不必要的,这使得其他人更难以阅读代码并且难以维护.

  • OP对早先删除的答案的评论---*正如问题中所述,我想避免使用正则表达式. - user3426575 49分钟前*---很好的答案,顺便说一句,和删除的答案一样:) (2认同)
  • @BhargavRao是的,但"看似像矫枉过正"并不是复制已在内置模块中实现的功能的一个很好的理由 (2认同)
  • 可悲的是,ReEx的答案很慢!(但是,我仍然喜欢正则表达式) (2认同)