Python:查找第一个不匹配的字符

13

在Python中,当你想要获取列表中子字符串或字符的第一次出现的索引时,可以使用以下代码:

s.find("f")

然而,我希望找到字符串中第一个不匹配的字符的索引。目前,我正在使用以下方法:

iNum = 0
for i, c in enumerate(line):
  if(c != mark):
    iNum = i
    break

有没有更有效的方法来做这件事,比如我不知道的内置函数?

5个回答

9

你可以使用正则表达式来匹配文本,例如:

>>> import re
>>> re.search(r'[^f]', 'ffffooooooooo').start()
4

[^f]会匹配除f以外的任何字符,而由re.search()返回的Match对象的start()方法将给出发生匹配的索引。

为了确保您还可以处理空字符串或仅包含f字符的字符串,您需要检查re.search()的结果不是None,如果无法匹配正则表达式,则该方法将返回None。例如:

first_index = -1
match = re.search(r'[^f]', line)
if match:
    first_index = match.start()

如果你不喜欢使用正则表达式,那么你的当前方法已经很好了。你可以使用类似于next(i for i, c in enumerate(line) if c != mark)的代码,但是你需要用tryexcept StopIteration语句块来处理空行或仅由mark字符组成的行。


2
我本来想避免使用正则表达式,因为与手动检查相比,它有时会表现出较差的性能。使用正则表达式的性能略低于我已经使用的方法(在1000万次迭代循环中慢了1秒),而使用next()方法在相同数量的迭代中需要4-5秒更长的时间。所以,是的,你说得对:我不会比现在的方法做得更好。 - Zauberin Stardreamer

2

我曾经遇到过同样的问题,并研究了这里的解决方案的时间(除了@wwii的地图/列表补充选项,它们比任何其他选项都要慢得多)。我还添加了原始版本的Cython版本。

我在Python v2.7中进行了所有制作和测试。我正在使用字节字符串(而不是Unicode字符串)。我不确定正则表达式方法是否需要与Python v3中的字节字符串一起使用。'mark'硬编码为null字节。这可以很容易地更改。

如果整个字节字符串是null-byte,则所有方法都返回-1。所有这些都在IPython中进行了测试(以%开头的行是特殊行)。

import re

def f1(s): # original version
    for i, c in enumerate(s):
        if c != b'\0': return i
    return -1

def f2(s): # @ChristopherMahan's version
    i = 0
    for c in s:
        if c != b'\0': return i
        i += 1
    return -1

def f3(s): # @AndrewClark's alternate version
    # modified to use optional default argument instead of catching StopIteration
    return next((i for i, c in enumerate(s) if c != b'\0'), -1)

def f4(s): # @AndrewClark's version
    match = re.search(br'[^\0]', s)
    return match.start() if match else -1

_re = re.compile(br'[^\0]')
def f5(s): # @AndrewClark's version w/ precompiled regular expression
    match = _re.search(s)
    return match.start() if match else -1

%load_ext cythonmagic
%%cython
# original version optimized in Cython
import cython
@cython.boundscheck(False)
@cython.wraparound(False)
def f6(bytes s):
    cdef Py_ssize_t i
    for i in xrange(len(s)):
        if s[i] != b'\0': return i
    return -1

计时结果:
s = (b'\x00' * 32) + (b'\x01' * 32) # test string

In [11]: %timeit f1(s) # original version
100000 loops, best of 3: 2.48 µs per loop

In [12]: %timeit f2(s) # @ChristopherMahan's version
100000 loops, best of 3: 2.35 µs per loop

In [13]: %timeit f3(s) # @AndrewClark's alternate version
100000 loops, best of 3: 3.07 µs per loop

In [14]: %timeit f4(s) # @AndrewClark's version
1000000 loops, best of 3: 1.91 µs per loop

In [15]: %timeit f5(s) # @AndrewClark's version w/ precompiled regular expression
1000000 loops, best of 3: 845 ns per loop

In [16]: %timeit f6(s) # original version optimized in Cython
1000000 loops, best of 3: 305 ns per loop

总的来说,@ChristopherMahan的版本比原版本稍微快一些(显然使用enumerate比使用自己的计数器慢)。使用next方法(@AndrewClark提供的另一种版本)虽然本质上与原始版本相同,但速度却比原始版本慢。

使用正则表达式(@AndrewClark的版本)比循环要快得多,特别是如果您预编译了正则表达式!

接下来,如果您可以使用Cython,则它是最快的。 OP关心使用正则表达式会变慢,这是有道理的,但在Python中使用循环更慢。 在Cython中的循环非常快。


当使用3200个0后跟一个非0进行测试时,f1f2f3的时间大致相同,约为200微秒(其中f3最快,f2最慢)。f4f5大约为26微秒,预编译只稍微快一点。f6版本仍然是最快的,但与正则表达式版本相比,其差距不再那么大。 - coderforlife
有关枚举和使用自己的计数器的更多信息,请访问https://dev59.com/TlPTa4cB1Zd3GeqPneMW...听起来在v3中已经改变了。 - JeopardyTempest

1

尽可能像Python一样简单。 将print(counter)替换为Python 2.x的print counter。

s = "ffffff5tgbh44frff"
counter = 0
for c in s:
    counter = counter + 1
    if c != "f":
        break

print (counter)

3
实际上,在Python2和Python3中都可以使用带括号的print语句。除了不使用enumerate()函数之外,我所做的与您所做的并没有太大的区别。 - Zauberin Stardreamer
是的,但是如果有人遇到你的代码,他们必须知道enumerate的作用,或者猜测它,这很简单。我不知道是否有更简单的方法。性能差吗,还是你有非常长的字符串? - Christopher Mahan
大多数时候,我会变得过于追求微观优化。虽然你的方法只比一个1000万次循环中稍微快了700毫秒,但它确实提供了略微更好的性能。 - Zauberin Stardreamer
啊,很高兴它没有变慢!另外,正则表达式的性能如何呢? - Christopher Mahan
在相同数量的迭代中慢了1秒,而next()方法则慢了4-5秒。 - Zauberin Stardreamer
1
嗯,似乎性能取决于变量或字符串直接比较。之前我对你的示例进行了剖析,将比较设为(c != mark)(因为标记会变化)。但是,当我将其更改为(c!=“*”)时,它比我的方法快了约2秒钟。尽管标记会变化,但这仍然是一个有趣的微观优化,我可以记住。 - Zauberin Stardreamer

0

这里是一行代码:

> print([a == b for (a_i, a) in enumerate("compare_me") for
(b_i, b) in enumerate("compar me") if a_i == b_i].index(False))
> 6
> "compare_me"[6]
> 'e'

0

现在我很好奇这两个怎么样。

>>> # map with a partial function
>>> import functools
>>> import operator
>>> f = functools.partial(operator.eq, 'f')
>>> map(f, 'fffffooooo').index(False)
5
>>> # list comprehension
>>> [c == 'f' for c in 'ffffoooo'].index(False)
4
>>>

好的,将它们通过我的分析器运行,每个测量值是三次运行中的最佳结果,每次运行为10,000,000次迭代:
  • 原始方法:11.49秒
  • 使用Map方法:21.81秒
  • 使用Comp方法:18.25秒
两种方法都使用了更多的时间。我认为这是因为它们读取整个字符串,而原始方法在找到不匹配的字符后就退出了循环。
- Zauberin Stardreamer
他们需要遍历一次字符串来生成True/False列表,然后再找到第一个False - 因此需要多次操作。我后来在车上想了想,意识到这样做会更慢 - 不知道是否因为没有事先考虑清楚而被投票否决。 - wwii

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接