前端

img kingofark

Python Cookbook 第二版 汉化版 [Recipe 1.8] 检测字符串是否包含特定的字符集合

发表于2007/4/12 8:42:00  11743人阅读

分类: 翻译文档

Recipe 1.8. Checking Whether a String Contains a Set of Characters
Recipe 1.8. 检测字符串是否包含特定的字符集合


Credit: Jürgen Hermann, Horst Hansen


问题

您须要检查字符串中是否出现了特定的字符集合。


解法

最简单的解法清晰、快捷、通用(不仅适用于字符串,还适用于任何序列;不仅适用于集合,还适用于任何您可以对其进行 membership 测试的容器):

def containsAny(seq, aset):
    """ Check whether sequence seq contains ANY of the items in aset. """
    for c in seq:
        if c in aset: return True
    return False

采用更高级、更复杂的解法可换得一些速度优势,利用标准库模块 itertools 以基本相同的思路来处理:

import itertools
def containsAny(seq, aset):
    for item in itertools.ifilter(aset.__contains__, seq):
        return True
    return False


讨论

大多数与集合(set)相关的问题最好是用 Python 2.4 引入的内建 set 类型来解决(在 Python 2.3 中您可以使用等效的标准库中的 sets.Set 类型)。但这其中也有例外。如下例所示,纯粹基于 set 的方案可以是:

def containsAny(seq, aset):
    return bool(set(aset).intersection(seq))

然而,此方案中的 seq 的全部项目都必须被检查。而本条目“解法”栏目中的函数采用的是“短路(short-circuit)”手法:一旦找到就立刻返回。当然,若结果为 False ,“解法”栏目中的函数仍然必须检查 seq 的全部项目——否则我们就无法确认 seq 中的每个项目都不在 aset 中。而在结果为 True 的情况下,我们经常能够很快地明确结果,因为只须找到某一项是 aset 的成员即可。当然,上述情况是否值得斟酌,完全取决于数据的具体情况。若 seq 很短或者结果大多是 False ,那么上述两种方案就没有实质上的区别;而对于很长的 seq 来说(通常可以很快地明确结果为 True),这区别就极为重要了。

“解法”栏目中 containsAny 的第一个版本的优点是简单、明晰,以一目了然的方式表达了核心思路。第二个版本可能显得“机巧”,而在 Python 世界中“机巧”并不是个褒义词,因为 Python 世界的核心价值观是简单和明晰。然而,第二个版本还是值得斟酌,因为它展示了一种基于标准库模块 itertools 的更高级的方案,而更高级的方案往往胜过较低级的方案(尽管在本条目中这一点颇具争议)。itertools.ifilter 接收一个 predicate(谓词)和一个 iterable(可迭代体),并将 iterable 中满足 predicate 的项目 yield 出来。这里将 anyset.__contains__ 作为 predicate ;当我们撰写 in anyset 形式的语句来进行 membership 测试时,anyset.__contains__ 就是被绑定的方法(bound method),语句内部会调用它。因此,只要 seq 中有哪个项目是属于 anyset ,ifilter 就会将其 yield 出来;一旦发生这种情况,我们就可以立刻返回 True 。如果代码执行到了 for 语句之后,就必然意味着 return True 从未被执行过,因为 seq 的任何一个项目都不属于 anyset ,由此应该 return False


---- BOX BEGIN ----
什么是“Predicate(谓词)”?
“Predicate(谓词)”是讨论编程时您会经常遇到的一个术语,意即“返回 True False 的函数(或其他可调用对象)”。若 predicate 返回结果为真,就称满足了 predicate 。
---- BOX E N D ----

若您的应用程序需要诸如 containsAny 这样的函数来检查一个字符串(或其他序列)是否包含某集合的成员,您可能还需要象下面这样的变体形式:

def containsOnly(seq, aset):
    """ Check whether sequence seq contains ONLY items in aset. """
    for c in seq:
        if c not in aset: return False
    return True

containsOnly containsAny 形式相同,只是在逻辑上反过来了。其他明显类似的功能本质上要求检查所有项目,无法应用“短路”手法,因此最好使用(Python 2.4 中)内建的 set 类型来处理(Python 2.3 中可使用 sets.Set ,用法相同):

def containsAll(seq, aset):
    """ Check whether sequence seq contains ALL the items in aset. """
    return not set(aset).difference(seq)

若您还没用惯 set(或 sets.Set)的 difference 方法,请注意该方法的语义:对于任意集合 a ,a.difference(b) 返回 a 中所有不属于 b 的元素的集合(如同 a-set(b))。例如:

>>> L1 = [1, 2, 3, 3]
>>> L2 = [1, 2, 3, 4]
>>> set(L1).difference(L2)
set([  ])
>>> set(L2).difference(L1)
set([4])

希望上述例子有助于理解如下事实:

>>> containsAll(L1, L2)
False
>>> containsAll(L2, L1)
True

(换句话说,请不要将 difference set 的另一个方法 symmetric_difference 搞混淆了,symmetric_difference 返回 a 和 b 中所有“属于 a 但不属于 b,或者属于 b 但不属于 a”的元素的集合)

[译注] 关于 symmetric_difference 请参考如下例子:
>>> L1 = [1, 2, 3, 5]
>>> L2 = [1, 3, 4, 8]
>>> set(L1).symmetric_difference(L2)
set([2, 4, 5, 8])
>>> set(L2).symmetric_difference(L1)
set([2, 4, 5, 8])

若您要处理的 seq aset 只是(单纯的,而非 Unicode)字符串,可能就不完全需要本条目提供的函数所具有的通用性,不妨考虑采用 Recipe 1.10 中讲到的更有针对性地方案(该方案基于字符串的 translate 方法以及标准库中的 string.maketrans 函数)。例如:

import string
notrans = string.maketrans('', '')           # identity "translation"
def containsAny(astr, strset):
    return len(strset) != len(strset.translate(notrans, astr))
def containsAll(astr, strset):
    return not strset.translate(notrans, astr)

这个方案略显巧妙,其原理在于:strset.translate(notrans, astr) 是由 strset 中那些“不属于 astr 的字符”所组成的子序列。若这个子序列与 strset 长度相同,那就说明 strset.translate 并没有移除 strset 中任何字符,因此也就说明在 strset 中没有字符是属于 astr 的。反之,若子序列为空,那就说明 strset.translate 移除了 strset 中所有的字符,因此也就说明 strset 中所有的字符也都在 astr 中。当您想要将字符串作为字符集合来对待时,自然而然地就会用到 translate 方法,因为该方法效率高,用起来顺手,还具伸缩性(详情请参见 Recipe 1.10)。

本条目的这两组解决方案具有非常不同的通用性。前一组方案非常通用,并不仅限于字符串处理,对操作对象的要求相当少。而后一组基于 tanslate 方法的方案仅在“astr strset 都是字符串”或“astr strset 的功能在表象上与普通字符串非常接近”的情况下才能凑效。Unicode 字符串不适用于基于 tanslate 方法的方案,因为 Unicode 字符串的 translate 方法与普通字符串的 translate 方法的签名式(signature)不一样——Unicode 字符串的 translate 方法只有一个参数(该参数为 dict 对象,其将代码数字映射至 Unicode 字符串或 None),而普通字符串的 translate 方法有两个参数(都是字符串)。


请参见

Recipe 1.10 ;Library Reference 和 Python in a Nutshell 中关于字符串及 Unicode 对象的 translate 方法,以及 string 模块的 maketrans 函数的文档;Library Reference 和 Python in a Nutshell 中关于内建 set 类型(仅限 Python 2.4 及后续版本)、sets itertools 模块,以及特殊方法 __contains__ 的文档。
 
阅读全文
0 0

相关文章推荐

img
取 消
img