忽略Python字符串中的大小写

时间:2020-03-05 18:53:23  来源:igfitidea点击:

在不区分大小写的情况下,比较Python中字符串的最简单方法是什么?

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

我想我正在寻找一个等效于C的stricmp()。

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

假设我们要排序一个完整的字符串列表。我们只需执行List.sort()。
这是O(n * log(n))字符串比较,没有内存管理(因为所有
字符串和列表元素是某种智能指针)。你很高兴。

现在,我们想做同样的事情,但是忽略这种情况(让我们简化并说
所有字符串均为ascii,因此可以忽略区域设置问题)。
我们可以执行List.sort(key = lambda s:s.lower()),但随后会导致两个新
每个比较的分配,再加上重复的垃圾收集器负担
(降低的)字符串。
每个这样的内存管理噪声都比简单的字符串比较慢几个数量级。

现在,使用类似于stricmp()的函数,我们可以执行:theList.sort(cmp = stricmp)
它与List.sort()一样快且对内存友好。你又开心了。

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

找不到类似的内容,因此这里出现了问题。
(希望这可以澄清问题)。

解决方案

回答

我很确定我们必须使用.lower()或者使用正则表达式。我不知道内置的不区分大小写的字符串比较功能。

回答

这是我们如何使用re进行操作:

import re
p = re.compile('^hello$', re.I)
p.match('Hello')
p.match('hello')
p.match('HELLO')

回答

我们可以将str子类化,并创建自己的区分大小写的字符串类,但是恕我直言,这将是极其不明智的,并且会产生更多的麻烦。

回答

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

如果我们真的想不区分大小写地比较很多文本,则可以以某种方式保留字符串的小写形式,以避免最终确定和重新创建,或者将整个数据集标准化为小写形式。当然,这取决于数据集的大小。如果针头相对较少且干草堆较大,则用已编译的正则表达式对象替换针头是一种解决方案。如果不看具体例子就很难说。

回答

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

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

除非我们使用过长的字符串(如果重复使用太长的字符串,可能会导致内存问题),否则我将使其简单易用

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

你会没事的

回答

我们是否在对性能敏感的应用程序经常执行的路径中使用此比较?或者,我们是否在大小为兆字节的字符串上运行此代码?如果没有,那么我们不必担心性能,而只需使用.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

回答

我找不到任何其他不区分大小写的内置方法:python Cookbook食谱使用lower()。

但是,由于土耳其语I问题,在使用更低的进行比较时必须小心。不幸的是,Python对"土耳其语"的处理不好。 ?被转换为I,但我未转换为?。 ?被转换为i,但我未转换为?。

回答

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

请注意,此技术不仅可以用于处理大小写问题,还可以用于其他类型的排序,例如特定于语言环境的排序或者忽略主要文章并在对数据进行归一化之前对数据进行规范化的"库样式"标题排序。

回答

为了回应澄清...

我们可以使用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:不区分大小写的字符串
是创建不区分大小写的字符串类的方法。快速执行某些操作可能会导致终止,但是如果我们打算经常使用它们,可以为我们提供一种处理不区分大小写的字符串的通用方法。

### 回答

建议使用昂贵的计算键对值列表进行排序的惯用法是所谓的"装饰模式"。它仅包括从原始列表中构建(键,值)元组的列表,然后对该列表进行排序。然后,消除键并获得排序值列表很简单:

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

或者,如果我们喜欢单线:

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

如果我们真的担心调用lower()的开销,则可以只在任何地方存储(低位字符串,原始字符串)的元组。元组是Python中最便宜的容器,它们也是可哈希的,因此可以用作字典键,集合成员等。

### 回答

问题暗示我们不需要Unicode。试试下面的代码片段;如果它对我们有用,那么我们已经完成:

``` java
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"重复"字符串。

### 回答

这是一个基准测试,显示使用`str.lower`比接受的答案的建议方法(`libc.strcasecmp`)更快:

``` java
#!/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))
```

我的机器上的典型时间:

``` java
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上下文中,这种比较可能会给我们错误的结果。

### 回答

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

"如何编写Python扩展"似乎是不错的介绍。

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

### 回答

如果标准库中对某些内容的支持不佳,我总是会寻找PyPI软件包。随着虚拟化和现代Linux发行版的普及,我不再避免使用Python扩展。 PyICU似乎符合要求:https://stackoverflow.com/a/1098160/3461

现在还有一个纯python选项。经过充分测试的:https://github.com/jtauber/pyuca

旧答案:

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

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

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

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