52

在Python中比较字符串的最简单方法是什么,忽略大小写?

当然可以做 (str1.lower() <= str2.lower()) 等,但这会创建两个额外的临时字符串(具有明显的 alloc/gc 开销)。

我想我正在寻找与 C 的 stricmp() 等效的东西。

[需要更多上下文,所以我将用一个简单的例子来演示:]

假设您要对一个冗长的字符串列表进行排序。您只需执行 List.sort()。这是 O(n * log(n)) 字符串比较并且没有内存管理(因为所有字符串和列表元素都是某种智能指针)。你很快乐。

现在,您想做同样的事情,但忽略大小写(让我们简化并假设所有字符串都是 ascii,因此可以忽略语言环境问题)。您可以执行 theList.sort(key=lambda s: s.lower()),但是每次比较都会导致两个新的分配,加上重复(降低)字符串的垃圾收集器负担。每个这样的内存管理噪音都比简单的字符串比较慢几个数量级。

现在,使用就地类似 stricmp() 的函数,您可以执行以下操作:theList.sort(cmp=stricmp),它与 ​​theList.sort() 一样快速且内存友好。你又快乐了。

问题是任何基于 Python 的不区分大小写的比较都涉及隐式字符串重复,所以我期待找到基于 C 的比较(可能在模块字符串中)。

找不到类似的东西,因此这里的问题。(希望这可以澄清问题)。

4

16 回答 16

74

这是一个基准,表明 usingstr.lower比接受的答案的建议方法 ( libc.strcasecmp) 更快:

#!/usr/bin/env python2.7
import random
import timeit

from ctypes import *
libc = CDLL('libc.dylib') # change to 'libc.so.6' on linux

with open('/usr/share/dict/words', 'r') as wordlist:
    words = wordlist.read().splitlines()
random.shuffle(words)
print '%i words in list' % len(words)

setup = 'from __main__ import words, libc; gc.enable()'
stmts = [
    ('simple sort', 'sorted(words)'),
    ('sort with key=str.lower', 'sorted(words, key=str.lower)'),
    ('sort with cmp=libc.strcasecmp', 'sorted(words, cmp=libc.strcasecmp)'),
]

for (comment, stmt) in stmts:
    t = timeit.Timer(stmt=stmt, setup=setup)
    print '%s: %.2f msec/pass' % (comment, (1000*t.timeit(10)/10))

我机器上的典型时间:

235886 words in list
simple sort: 483.59 msec/pass
sort with key=str.lower: 1064.70 msec/pass
sort with cmp=libc.strcasecmp: 5487.86 msec/pass

因此,具有 的版本str.lower不仅是迄今为止最快的,而且是此处提出的所有解决方案中最便携和最 Python 的。我没有分析内存使用情况,但原始海报仍然没有给出令人信服的理由来担心它。另外,谁说对 libc 模块的调用不会复制任何字符串?

注意:lower()字符串方法还具有依赖于语言环境的优点。在编写自己的“优化”解决方案时,您可能不会做对。即便如此,由于 Python 中的错误和缺失功能,这种比较可能会在 unicode 上下文中给出错误的结果。

于 2008-09-23T14:32:08.267 回答
7

您是否在高性能敏感应用程序的非常频繁执行的路径中使用此比较?或者,您是否在兆字节大小的字符串上运行它?如果没有,那么你不应该担心性能,只需使用 .lower() 方法。

下面的代码演示了在我的 1.8GHz 台式计算机上,通过调用 .lower() 对两个大小几乎为兆字节的字符串进行不区分大小写的比较大约需要 0.009 秒:

from timeit import Timer

s1 = "1234567890" * 100000 + "a"
s2 = "1234567890" * 100000 + "B"

code = "s1.lower() < s2.lower()"
time = Timer(code, "from __main__ import s1, s2").timeit(1000)
print time / 1000   # 0.00920499992371 on my machine

如果这确实是非常重要的、对性能至关重要的代码部分,那么我建议用 C 编写一个函数并从您的 Python 代码中调用它,因为这将允许您进行真正有效的不区分大小写的搜索。可以在此处找到有关编写 C 扩展模块的详细信息:https ://docs.python.org/extending/extending.html

于 2008-09-15T13:52:43.053 回答
7

您的问题意味着您不需要 Unicode。试试下面的代码片段;如果它适合你,你就完成了:

Python 2.5.2 (r252:60911, Aug 22 2008, 02:34:17)
[GCC 4.3.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import locale
>>> locale.setlocale(locale.LC_COLLATE, "en_US")
'en_US'
>>> sorted("ABCabc", key=locale.strxfrm)
['a', 'A', 'b', 'B', 'c', 'C']
>>> sorted("ABCabc", cmp=locale.strcoll)
['a', 'A', 'b', 'B', 'c', 'C']

澄清:如果乍一看并不明显, locale.strcoll 似乎是您需要的功能,避免使用 str.lower 或 locale.strxfrm “重复”字符串。

于 2008-09-15T22:20:06.347 回答
6

我找不到任何其他内置方式来进行不区分大小写的比较:python 食谱使用 lower()。

但是,由于土耳其语 I 的问题,在使用 lower 进行比较时必须小心。不幸的是,Python 对土耳其语的处理并不好。ı 转换为 I,但 I 未转换为 ı。İ 转换为 i,但 i 未转换为 İ。

于 2008-09-15T15:15:10.017 回答
3

没有与您想要的功能等效的内置功能。

您可以编写自己的函数,一次将每个字符转换为 .lower() 以避免重复两个字符串,但我确信它会占用大量 CPU 资源并且效率极低。

除非您使用的是极长的字符串(如果重复会导致内存问题),那么我会保持简单并使用

str1.lower() == str2.lower()

你会没事的

于 2008-09-15T13:46:39.420 回答
2

当标准库不能很好地支持某些东西时,我总是寻找 PyPI 包。随着虚拟化和现代 Linux 发行版的普及,我不再避免使用 Python 扩展。PyICU 似乎符合要求:https ://stackoverflow.com/a/1098160/3461

现在还有一个选项是纯 python。它经过了很好的测试:https ://github.com/jtauber/pyuca


老答案:

我喜欢正则表达式解决方案。由于 python 的块结构支持,这是一个可以复制并粘贴到任何函数中的函数。

def equals_ignore_case(str1, str2):
    import re
    return re.match(re.escape(str1) + r'\Z', str2, re.I) is not None

由于我使用匹配而不是搜索,因此不需要在正则表达式中添加插入符号 (^)。

注意:这仅检查相等性,这有时是需要的。我也不会说我喜欢它。

于 2010-04-26T03:41:19.423 回答
2

这个问题问的是两个非常不同的事情:

  1. 在Python中比较字符串的最简单方法是什么,忽略大小写?
  2. 我想我正在寻找与 C 的 stricmp() 等效的东西。

由于#1 已经得到很好的回答(即:str1.lower() < str2.lower())我将回答#2。

def strincmp(str1, str2, numchars=None):
    result = 0
    len1 = len(str1)
    len2 = len(str2)
    if numchars is not None:
        minlen = min(len1,len2,numchars)
    else:
        minlen = min(len1,len2)
    #end if
    orda = ord('a')
    ordz = ord('z')

    i = 0
    while i < minlen and 0 == result:
        ord1 = ord(str1[i])
        ord2 = ord(str2[i])
        if ord1 >= orda and ord1 <= ordz:
            ord1 = ord1-32
        #end if
        if ord2 >= orda and ord2 <= ordz:
            ord2 = ord2-32
        #end if
        result = cmp(ord1, ord2)
        i += 1
    #end while

    if 0 == result and minlen != numchars:
        if len1 < len2:
            result = -1
        elif len2 < len1:
            result = 1
        #end if
    #end if

    return result
#end def

仅在有意义时才使用此函数,因为在许多情况下小写技术会更好。

我只使用 ascii 字符串,我不确定这将如何处理 unicode。

于 2011-08-30T05:45:39.957 回答
1

这就是您使用 re 的方式:

import re
p = re.compile('^hello$', re.I)
p.match('Hello')
p.match('hello')
p.match('HELLO')
于 2008-09-15T13:05:22.403 回答
1

使用计算成本高的键对值列表进行排序的推荐习惯用法是所谓的“装饰模式”。它只是从原始列表中构建一个(键,值)元组列表,然后对该列表进行排序。然后消除键并获得排序值列表是微不足道的:

>>> original_list = ['a', 'b', 'A', 'B']
>>> decorated = [(s.lower(), s) for s in original_list]
>>> decorated.sort()
>>> sorted_list = [s[1] for s in decorated]
>>> sorted_list
['A', 'a', 'B', 'b']

或者,如果您喜欢单线:

>>> sorted_list = [s[1] for s in sorted((s.lower(), s) for s in original_list)]
>>> sorted_list
['A', 'a', 'B', 'b']

如果您真的担心调用 lower() 的成本,您可以在任何地方存储 (lowered string, original string) 的元组。元组是 Python 中最便宜的容器,它们也是可散列的,因此可以用作字典键、集合成员等。

于 2008-09-15T21:54:03.723 回答
0

我很确定您要么必须使用 .lower() 要么使用正则表达式。我不知道内置的不区分大小写的字符串比较函数。

于 2008-09-15T12:59:43.477 回答
0

对于偶尔甚至重复的比较,一些额外的字符串对象应该无关紧要,只要这不会发生在核心代码的最内层循环中,或者您没有足够的数据来实际注意到性能影响。看看你是否这样做:以“愚蠢”的方式做事,如果你也少做的话,就不会那么愚蠢了。

如果您真的想在不区分大小写的情况下继续比较大量文本,您可以以某种方式保留字符串的小写版本以避免最终确定和重新创建,或者将整个数据集规范化为小写。这当然取决于数据集的大小。如果针数相对较少且干草堆较大,则用编译的正则表达式对象替换针是一种解决方案。如果没有看到一个具体的例子就很难说。

于 2008-09-15T13:43:59.720 回答
0

您可以将每个字符串转换为小写一次 --- 仅在需要时懒惰地转换,或者如果您知道要对整个字符串集合进行排序,则作为排序的预通行证。有几种方法可以将此比较键附加到正在排序的实际数据,但这些技术应在单独的问题中解决。

请注意,此技术不仅可用于处理大写/小写问题,还可用于其他类型的排序,例如特定于区域设置的排序,或忽略主要文章的“图书馆风格”标题排序,并在排序之前对数据进行规范化。

于 2008-09-15T19:13:20.430 回答
0

只需使用该str().lower()方法,除非高性能很重要 - 在这种情况下,将该排序方法编写为 C 扩展。

“如何编写 Python 扩展”似乎是一个不错的介绍。

更有趣的是,本指南比较了使用 ctypes 库与编写外部 C 模块(ctype 比 C 扩展慢得多)。

于 2008-10-11T08:44:14.977 回答
0
import re
if re.match('tEXT', 'text', re.IGNORECASE):
    # is True
于 2014-03-07T11:37:53.967 回答
-1

您可以子类str化并创建自己的不区分大小写的字符串类,但恕我直言,这将是非常不明智的,并且造成的麻烦远远超过其价值。

于 2008-09-15T13:06:08.657 回答
-11

回应您的澄清...

您可以使用ctypes来执行 c 函数“strcasecmp”。Ctypes 包含在 Python 2.5 中。它提供了调用 dll 和共享库(如 libc)的能力。这是一个简单的示例(Linux 上的 Python;请参阅 Win32 帮助链接):

from ctypes import *
libc = CDLL("libc.so.6")  // see link above for Win32 help
libc.strcasecmp("THIS", "this") // returns 0
libc.strcasecmp("THIS", "THAT") // returns 8

可能还想参考strcasecmp 文档

不确定这是更快或更慢(尚未测试),但它是一种使用 C 函数进行不区分大小写的字符串比较的方法。

~~~~~~~~~~~~~~

ActiveState 代码 - 配方 194371:不 区分大小写的字符串是用于创建不区分大小写的字符串类的配方。对于一些快速的东西来说可能有点过头了,但如果你打算经常使用它们,它可以为你提供一种处理不区分大小写字符串的常用方法。

于 2008-09-15T20:20:53.387 回答