2007-11-30

 

Python Cookbook 4.13 从字典中提取子集

需求:

我们需要从一个含有大量数据的字典中获取指定键集合的子集.

讨论:

如果需要保持原字典的完整性,可以这样写:

def sub_dict(somedict, somekeys, default=None):
    return dict([ (k, somedict.get(k, default)) for k in somekeys ])

如果想从原有字典中去除提取的子集,可以这样写:

def sub_dict_remove(somedict, somekeys, default=None):
    return dict([ (k, somedict.pop(k, default)) for k in somekeys ])

下面是两个例子:
>>> d = {'a': 5, 'b': 6, 'c': 7} 
>>> print sub_dict(d, 'ab'), d
{'a': 5, 'b': 6} {'a': 5, 'b': 6, 'c': 7}
>>> print sub_dict_remove(d, 'ab'), d
{'a': 5, 'b': 6} {'c': 7}
相关说明:

在Python中,有很多地方都会用到字典,比如表示数据库的行,主键和组合键,模板解析时的变量命名空间等.所以,我们经常会遇到需要基于一个字典来创建另一个.原来的字典包含有大量数据,而我们只需要其中的一部分.在大多数情况下,需要保持原数据不变,在某些情况下,则需要去除提取的数据.本节中讨论了两种情况的解决方案.两种方法的不同之处在于使用get来保持原数据不变 ,使用pop来去除提取数据.
如果有些情况下,在somedict字典中不包含somekey中的键key,本节的方法依然会有一个返回值,它使用了默认值(在函数声明的地方写的是None).所以,结果并不一定完全是somedict的子集.这样的需求在我自己的应用中是最多的.
也许你需要获得一个"键不存在"的异常,用于提示自己程序中可能有一个bug,它说明somekey中的有些key不是somedict中的键值.要注意: "错误永远不应默默的溜掉,除非明确的使用沉默",这是Python之禅的一句.所以,如果键不存在在你的应用程序中是一个错误,你需要立刻获得异常来提示用户,你可以将上面的代码稍微修改一下:
def sub_dict_strict(somedict, somekeys):
return dict([ (k, somedict[k]) for k in somekeys ])
def sub_dict_remove_strict(somedict, somekeys):
return dict([ (k, somedict.pop(k)) for k in somekeys ])
看,这个代码比以前的还要简单,这说明Python喜欢在不确定的情况下抛出异常!

或者,你需要忽略在somedict中不存在的key,可以这样修改:
 def sub_dict_select(somedict, somekeys):
return dict([ (k, somedict[k]) for k in somekeys if k in somedict])
def sub_dict_remove_select(somedict, somekeys):
return dict([ (k, somedict.pop(k)) for k in somekeys if k in somedict])
在递推式中的if语句实现了我们的需求,排除了不存在的key.
在Python2.4以后,你可以使用代码生成器,而不是递推式.仅仅把代码从dict([...])改为dict(...)即可.

关于Python之禅,使用import this即可.

2007-09-24

 

Python Cookbook 4.12 通过键,值交替的列表创建字典

需求:

你需要通过一个键,值对交替出现的列表创建字典.

讨论:

内建的dict类型提供了很多创建字典的方式,可是没有提供本节需求的解决方法,我们需要自己写函数来实现,一种方式是使用zip函数:
def dictFromList(keysAndValues):
return dict(zip(keysAndValues[::2], keysAndValues[1::2]))
另一种更一般的方式,可以适用与任何可迭代的对象,它将一个列表传递给两个迭代器,这是一个比较通用和迅速的方法:

def pairwise(iterable):
    itnext = iter(iterable).next
    while True:
        yield itnext( ), itnext( )

def dictFromSequence(seq):
    return dict(pairwise(seq))

定以pairwise也可以让其它的函数来使用它,比如mydict.update(pairwise(seq)).
上面的两种方面其实内在都使用了同样的构造字典的方式:在调用dict()的时候传递(key,value)的序列,不同之处是怎样构造这个值对序列.
dictFromList构造了键值对的列表,它使用了zip函数,使用了两个列表切片,一个是奇数, 一个是偶数.这样是可以工作的,不过有一个问题,就是参数必须是支持切皮的类型比如list,tuple,str等,而且,由于在内存里面使用了临时列表,当参数是很长的列表时,这样做会影响效率.
dictFromSequence和上面的方法不同,它将构造键值对的任务交给了pairwise函数.pairwise保证了任何可迭代的对象都可以工作,除了list,tuple和str外,还包括文件,字典等等.另外,pairwise一次只处理一对,并不使用临时队列,当输入队列很长的时候能改善效率.
pairwise的实现很有意思.在最开始的时候,它使用iter方法将参数构造为一个迭代器,并将next方法绑定给本地变量itnext,这样做看起来很奇怪,但这是Python中常用的方法:如果你使用一个对象,而且要循环调用这个对象的方法,你可以绑定它的方法,然后使用本地变量来表示那个方法.pairwise和下面这个写法的函数工作的结果是一样的,下面这个代码的写法在其它语言中是比较常见的:

def pairwise_slow(iterable):
    it = iter(iterable)
    while True:
        yield it.next( ), it.next( )

实际上,pairwise_slow版本的代码并不比pairwise简单多少,却比pairwise慢60%.我们建议程序员使用更Python的方式来写代码.不仅更简单,效率也会更高.


2007-09-19

 

Python Cookbook 4.11 快速构建字典

需求:

你想创建一个字典,它的键值都是字符串,而且你不希望一个个的添加它们.

讨论:

一旦你开始使用Python,你会发现需要使用很多很多的字典,当键都是字符串的时候,可以使用dict的命名参数语法来创建:

data = dict(red=1, green=2, blue=3)

它比下面这个等效的写法简洁许多:

data = {'red': 1, 'green': 2, 'blue': 3}

创建字典比较有效的方法就是使用dict函数,它比自己使用括号, 分号的语法来创建字典便捷许多.使用dict,可以避免使用过多的引号,只要键的名称个Python的默认保留字不同.你不能使用类似'12ab'或者'for',因为'12ab'不是以字母开头,而'for'是Python的保留字.
在Python中,用大括号来定义字典是唯一使用大括号的地方:假如你不喜欢使用大括号,或者键盘布局中它很难够到(意大利的键盘就是这样!),你可以使用dict()来代替{ }构造空字典.
使用dict还有别的好处,调用dict(d)可以返回已有字典的一个副本,就像调用d.copy()一样,不过,使用dict还有更多的好处,即使d是(key,value)形式的列表也能工作.一个常见的字典构造语法是:
d = dict(zip(the_keys, the_values))
其中the_keys是一个包含key的列表,the_values是和它对应的value的列表,内建函数zip返回一个(key,value)形式的列表, dict函数可以接收这样的列表为参数来构造一个字典.如果列表很长,比使用itertools模块要快一些:
import itertools
d = dict(itertools.izip(the_keys, the_values))
内建的zip方法在内存中构造一个列表,而izip一次只使用一个值对.在我的机器上,对于10,000的数字,后面的方法大约比前面的方法快两倍.
在调用dict的时候,你既可以使用位置参数,也可以使用命名参数(如果二者冲突,以命名参数为准),比如,你需要设置最开始说明的不能做为命名参数的键:

d = dict({'12ba':49, 'for': 23}, rof=41, fro=97, orf=42)

如果你想创建一个字典,其中每个键所对应的值是一样的, 使用dict.fromkeys(keys,value)(如果没有value,默认是None),比如,我们创建一个统计ascii码出现频率的字典:

import string
count_by_letter = dict.fromkeys(string.ascii_lowercase, 0)

相关说明:

fromkeys(...)
    dict.fromkeys(S[,v]) -> New dict with keys from S and values equal to v.
    v defaults to None.

2007-09-18

 

Python Cookbook 4.19 向字典中添加项

需求:

你需要使用字典d,并使用d[k]的值,如果d[k]不存在,给它添加新值.

讨论:

这就是字典的setdefault方法的功能.比如我们要构造一个单词页码对照表,字典中的单词和它所在的页号一一对应.在代码中关键的部分如下:
 def addword(theIndex, word, pagenumber):
theIndex.setdefault(word, [ ]).append(pagenumber)
这段代码和下面的冗长的代码作用相同:
def addword(theIndex, word, pagenumber):
if word in theIndex:
theIndex[word].append(pagenumber)
else:
theIndex[word] = [pagenumber]
或者:
def addword(theIndex, word, pagenumber):
try:
theIndex[word].append(pagenumber)
except KeyError:
theIndex[word] = [pagenumber]
所以使用setdefault来简化代码吧.
对于字典d,d.setdefault(k,v)和d.get(k,v)十分相似.它们之间基本的不同之处在于,假如k不是d中的一部分,setdefault会执行d[k]=v,然后再返回v,(get只返回v,而不会改变d的值).如果你即希望能像get一样取得字典中的值,又希望能适当的改变它,就使用setdefault.
对于d的value值是list的情况,setdefault显得特别有用.如本节演示的那样,最常用的写法就是:
somedict.setdefault(somekey, [  ]).append(somevalue)
对于不可修改值的情况,setdefault不是那么有用了,比如数字.如果你仅仅想统计数字的个数,比如单词数目,正确的方式是使用get,而不是setdefault:
theIndex[word] = theIndex.get(word, 0) + 1
因为你必然要重新绑定theIndex[word]变量(因为它是不可修改的),但对于我们的单词页码表的例子,你可不能像下面这样写:

def addword(theIndex, word, pagenumber):
    theIndex[word] = theIndex.get(word, [ ]) + [pagenumber]

最后一个版本的代码在每次执行的时候,使用了3个list,一个是[]做为参数,另一个包含pagenumber,最后一个包含N+1个元素.是由上面两个连接而成的.如此大量的使用列表必然会影响程序的效率.比如 ,在我的机器上,我统计了4个单词,每个都在1000页中的每页出现一次.以引用中的第一个版本为基准,try/except方法比它快10%,setdefault方法比它慢20%,在大多数的情况下,这点差异是可以忽略的.而get方法比它慢4倍,这样的效率差别是不可忽略的.

相关说明:

setdefault(...)
    D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D

2007-09-06

 

算法学习: 判读四点是否构成矩形

需求:

给定4个点的坐标,判断这4点是否构成一个矩形.

讨论:

这是我遇到的一个笔试题,在这里给出一个自己想的算法:

在四个点中取第一点为基点,分别算出到其它三点的距离.如果这三个值满足勾股定理,则:
1.该四点构成矩形
2.该四点构成平行四边形

对于第二种情况,再进行判读,如果其中心与重心重回,即四点在一个圆上,则该四点构成矩形.

程序如下:

def IsSquire(*arg):
    x1,y1 = arg[0]
    x2,y2 = arg[1]
    x3,y3 = arg[2]
    x4,y4 = arg[3]
    line1 = (x1-x2)**2 + (y1-y2)**2
    line2 = (x1-x3)**2 + (y1-y3)**2
    line3 = (x1-x4)**2 + (y1-y4)**2
    if (abs(line1-line2))==line3 or (line1+line2)==line3:
        x0 = (x1+x2+x3+x4)/4
        y0 = (y1+y2+y3+y4)/4
        if (x1-x0)**2+(y1-y0)**2 == (x2-x0)**2+(y2-y0)**2 == (x3-x0)**2+(y3-y0)**2 == (x4-x0)**2+(y4-y0)**2:
            return True
    return False
if __name__=="__main__":
    print IsSquire((0,0),(1,0),(0,1),(-1,1))

其实更简单一些的方法是判断中心是否在较长的边上即可,不过那样代码要些很多if.

2007-09-05

 

Python Cookbook 4.9 从字典中获得值

需求:

你需要从一个字典中获得值,而且不用处理异常情况,比如该值不存在.

讨论:

dict的get方法就能满足需求.比如你有一个字典d = {'key':'value'}.为了获得key所对应的值,并不考虑异常情况,使用:

print d.get('key', 'not found')

如果你想用完该数据后就删除它,请使用dict.pop(它进行get和remove操作),而不是dict.get(它不会执行remove操作).
要想获得一个数值,而且不希望遇到异常,就是用get方法.
如果你使用索引值来访问d[x],如果x对应的值在d中不存在,你就会获得KeyError异常,这个很正常,如果你需要的值在dict中不存在, 它就会以异常的方式提示你出错了.有时你只是想试探一下,看x对应的值在字典中是否存在,这样的情况下,不要使用in语句:

if 'key' in d:
    print d['key']
else:
    print 'not found'

使用try/except语法:

try:
    print d['key']
except KeyError:
    print 'not found'

和上面的处理不同,如果你使用get(x)语法,不会有异常抛出:如果x对应的值存在,将返回它,如果不存在, 会返回None.如果你不希望它返回None,可以使用d.get(x,somethingelse),在这种情况下,如果x对应的值不存在,不会返回None,而是somethingelse.
get是一个非常简单的方法,而且Python的文档中也描述的很详细,但是还有很多人都不知道它.与此类似,pop是另一个函数,它的大部分用法都和get相同.唯一不同的是,pop可以返回异常,当x不是d中的一个key是,d.pop(x)会抛出KeyError异常,如果你希望它的用法和get完全一样, 使用d.pop(x,None).

相关说明:

get(...)
    D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None.

pop(...)
    D.pop(k[,d]) -> v, remove specified key and return the corresponding value
    If key is not found, d is returned if given, otherwise KeyError is raised


2007-09-03

 

Python Cookbook 4.8 转置2维数组

需求:

你需要转置一个二维数组,将行列互换.

讨论:

你需要确保该数组的行列数都是相同的.比如:

arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]

列表递推式提供了一个简便的矩阵转置的方法:

print [[r[col] for r in arr] for col in range(len(arr[0]))]
[[1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12]]

另一个更快和高级一些的方法,可以使用zip函数:

print map(list, zip(*arr))

本节提供了关于矩阵转置的两个方法,一个比较清晰简单,另一个比较快速但有些隐晦.
有时候,数据到来的时候使用错误的方式,比如,你使用微软的ADO接口访问数据库,由于Python和MS在语言实现上的差别. Getrows方法在Python中可能返回的是列值,和方法的名称不同.本节给的出的方法就是这个问题常见的解决方案,一个更清晰,一个更快速.
在列表递推式版本中,内层递推式表示选则什么(行),外层递推式表示选择者(列).这个过程完成后就实现了转置.
在zip版本中,我们使用*arr语法将一维数组传递给zip做为参数,接着,zip返回一个元组做为结果.然后我们对每一个元组使用list方法,产生了列表的列表(即矩阵).因为我们没有直接将zip的结果表示为list, 所以我们可以我们可以使用itertools.izip来稍微的提高效率(因为izip并没有将数据在 内存中组织为列表).

import itertools
print map(list, itertools.izip(*arr))

但是,在特定的情况下,上面的方法对效率的微弱提升不能弥补对复杂度的增加.
关于*args和**kwds语法:
*args(实际上,*号后面跟着变量名)语法在Python中表示传递任意的位置变量,当你使用这个语法的时候(比如,你在定义函数时使用),Python将这个变量和一个元组绑定,并保留所有的位置信息, 而不是具体的变量.当你使用这个方法传递参数时,变量可以是任意的可迭代对象(其实可以是任何表达式,只要返回值是迭代器).
**kwds语法在Python中用于接收命名参数.当你用这个方式传递参数时,Python将变量和一个dict绑定,保留所有命名参数,而不是具体的变量值.当你传递参数时,变量必须是dict类型(或者是返回值为dict类型的表达式).

如果你要转置很大的数组,使用Numeric Python或其它第三方包,它们定义了很多方法,足够让你头晕的.

相关说明:

zip(...)
    zip(seq1 [, seq2 [...]]) -> [(seq1[0], seq2[0] ...), (...)]

    Return a list of tuples, where each tuple contains the i-th element
    from each of the argument sequences.  The returned list is truncated
    in length to the length of the shortest argument sequence.



This page is powered by Blogger. Isn't yours?