python cookbook 读书笔记--数据结构和算法(三)

删除相同的元素并保持元素原有的顺序

  • 官方对于是否可哈希的解释说明:
    如果一个对象在其生命周期内有一个固定不变的哈希值 (这需要hash()方法) 且可以与其他对象进行比较操作 (这需要eq()方法) ,那么这个对象就是可哈希对象 (hashable) ,可哈希对象必须有相同的哈希值才算作相等。
    由于字典 (dict) 的键 (key) 和集合 (set) 内部使用到了哈希值,所以只有可哈希 (hashable) 对象才能被用作字典的键和集合的元素。
    所有python内置的不可变对象都是可哈希的,同时,可变容器 (比如:列表 (list) 或者字典 (dict) ) 都是不可哈希的。用户自定义的类的实例默认情况下都是可哈希的;它们跟其它对象都不相等 (除了它们自己) ,它们的哈希值来自id()方法

eg:

  • 如果值为一个可哈希对像,那么利用集合就可以做到
def deque(items):
     seen = set()
     for item in items:
         if item not in seen:
            yield item
         seen.add(item)
     

a = [1, 5, 2, 1, 9, 1, 5, 10]
print(list(deque(a))) 
  • 假如值为不可哈希对像时:
def dedupe(items, key=None):
    seen = set()
    for item in items:
        val = item if key is None else key(item)
        if val not in seen:
            yield item
        seen.add(val)
     

a = [ {'x':1, 'y':2}, {'x':1, 'y':3}, {'x':1, 'y':2}, {'x':2, 'y':4}]

print(list(dedupe(a, key=lambda d: (d['x'],d['y']))))  
print(list(dedupe(a, key=lambda d: d['x']))) 

命名切片相关

items = [0, 1, 2, 3, 4, 5, 6]
a = slice(2, 4) # 内置slice函数可以创建一个切片对像,用到任何切片允许使用的地方.
print(items[a])

序列中出现次数最多的元素

  • 使用collections.Counter 有用的most_common()
words = [ 'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes', 'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the', 'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into', 'my', 'eyes', "you're", 'under' ]
from collections import Counter
print(Counter(words)) # Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2, 'not': 1, "don't": 1, "you're": 1, 'under': 1})
print(Counter(words).most_common(3))  #[('eyes', 8), ('the', 5), ('look', 4)]

word_counts = Counter(words) #  Counter可以当作字典来操作.
print(word_counts['eyes'])  # 8  

通过某个关键字排序一个字典列表

  • 想根据一个字典的某个或者几个字典来排序这个列表,可以使用operatortemgetter函数.
rows = [ {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004} ]
from operator import itemgetter
rows_by_fname = sorted(rows, key=itemgetter('fname'))
rows_by_uid = sorted(rows, key=itemgetter('uid'))
print(rows_by_fname)
print(rows_by_uid)

上面这种情况也可以使用lambda来完成,比如
rows_by_fname = sorted(rows, key=lambda r: r['fname'])
但使用itemgetter会快一些,看个人爱好与需要.

通过某个字段将纪录分组

  • 想根据某个特定的字段来迭代访问分组,可以使用itertools.groupby
rows =[ {'address': '5412 NCLARK','date':'07/01/2012'}, {'address': '5148 NCLARK','date':'07/04/2012'}, {'address': '5800 E58TH','date':'07/02/2012'}, {'address': '2122 NCLARK','date':'07/03/2012'}, {'address': '5645 NRAVENSWOOD', 'date': '07/02/2012'}, {'address': '1060 WADDISON','date': '07/02/2012'}, {'address': '4801 NBROADWAY','date': '07/01/2012'}, {'address': '1039 WGRANVILLE', 'date': '07/04/2012'}, ]
from operator import itemgetter
from itertools import groupby
rows.sort(key=itemgetter('date'))
# print(rows)
for date, items in groupby(rows, key=itemgetter('date')):
    print(date)
    for i in items:
        print('',i)

输出为:

07/01/2012
 {'address': '5412 NCLARK', 'date': '07/01/2012'}
 {'address': '4801 NBROADWAY', 'date': '07/01/2012'}
07/02/2012
 {'address': '5800 E58TH', 'date': '07/02/2012'}
 {'address': '5645 NRAVENSWOOD', 'date': '07/02/2012'}
 {'address': '1060 WADDISON', 'date': '07/02/2012'}
07/03/2012
 {'address': '2122 NCLARK', 'date': '07/03/2012'}
07/04/2012
 {'address': '5148 NCLARK', 'date': '07/04/2012'}
 {'address': '1039 WGRANVILLE', 'date': '07/04/2012'}

假如根据date字段将数据分组到一个大的数据结构中去,并且允许随机访 问, 那么你最好使用 defaultdict() 来构建一个多值字典.

from collections import defaultdict
row_by_date = defaultdict(list)
for row in rows:
    row_by_date[row['date']].append(row)
print(row_by_date)

输出为:

defaultdict(<class 'list'>, {'07/01/2012': [{'address': '5412 NCLARK', 'date': '07/01/2012'}, {'address': '4801 NBROADWAY', 'date': '07/01/2012'}], '07/02/2012': [{'address': '5800 E58TH', 'date': '07/02/2012'}, {'address': '5645 NRAVENSWOOD', 'date': '07/02/2012'}, {'address': '1060 WADDISON', 'date': '07/02/2012'}], '07/03/2012': [{'address': '2122 NCLARK', 'date': '07/03/2012'}], '07/04/2012': [{'address': '5148 NCLARK', 'date': '07/04/2012'}, {'address': '1039 WGRANVILLE', 'date': '07/04/2012'}]})

假如需要可以根据
for r in row_by_date['07/01/2012']: print(r) 这种方式来获取.

defaultdict的主要目的是,当Key不存在时不会报错,比如下面的情况

a1 = dict()
s = 'mississippi'
# print(a1['a']) ====>TypeError: 'type' object does not support item assignment

d2 = defaultdict(int)
# print(d2['a'])===>运行正常
for k in s:
    d2[k] +=1
print(d2)

过滤序列元素

  • 有一个数据序列,想利用一些规则从中提取出需要的值或者是缩短序列
mylist= [1, 4, -5,10,-7,2,3,-1]
print([n for n in mylist if n > 0])
# 上面的缺点就是输入非常大的时候会占用非常大的结果集,占用大量内存,这时候可以使用生成器表达式迭代产生过滤元素.
post = (n for n in mylist if n > 0)
for x in post:
    print(x)

复杂情况可以使用filter()函数,filter() 函数创建了一个迭代器. 所以只需要写个函数来验证值,如下所示

values= ['1', '2', '-3','-','4', 'N/A', '5']
def is_int(val):
    try:
        x = int(val)
        return True
    except ValueError:
        return False
isvals = list(filter(is_int, values))
print(isvals)

但列表推导是首先可以考虑的一种形式

values= ['1', '2', '-3','-','4', 'N/A', '5']
def is_int(val):
    try:
        x = int(val)
        return True
    except ValueError:
        return False
isvals = list(filter(is_int, values))
print(isvals)
clip_neg = [n if n > 0 else 0 for n in mylist]
print(clip_neg)

从字典中提取子集

  • 比较快的情况,列表推导
prices= { 'ACME':45.23, 'AAPL':612.78, 'IBM':205.55, 'HPQ':37.20, 'FB': 10.75 }
# 返回一个价格大于200的字典
p1 = {key:value for key, value in prices.items() if value > 200}
print(p1) #{'AAPL': 612.78, 'IBM': 205.55}
tech_names= {'AAPL', 'IBM', 'HPQ', 'MSFT'}
p2 = {key:value for key,value in prices.items() if key in tech_names}
print(p2) #{'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.2}

** 映射名称到序列元素 **

  • 通过下标访问列表或者元组中元素的代码,但是这样有时候会使得你的代码难以 阅读, 于是你想通过名称来访问元素
    collections.namedtuple()函数通过使用一个普通的元组对象来帮你解决这个问题。 这个 函数实际上是一个返回Python中标准元组类型子类的一个工厂方法。 你需要传递一个类 型名和你需要的字段给它,然后它就会返回一个类,你可以初始化这个类,为你定义的字 段传递值等。
from collections import namedtuple
Subscriber = namedtuple('Subscriber', ['addr','joined'])
sub = Subscriber('jonesy@example.com', '2012-10-19')
print(sub.addr) # jonesy@example.com

另外一点就是_replace()方法,可以改变它的属性值.比如下面这样

Stock= namedtuple('Stock',['name', 'shares', 'price'])
s = Stock('ACME', 100, 123.45)
print(s) # Stock(name='ACME', shares=100, price=123.45)
print(s.shares) #会报错, 因为命名元组是不可以更改的
# 如果真要更改属于可以使用_replace()方法
s = s._replace(shares = 75)
print(s) # Stock(name='ACME', shares=75, price=123.45)

_replace在使用nametuple命名元组有缺失字段或者可选字段时它可以很方便的填充数据, 比如下面这样

Stock= namedtuple('Stock',['name', 'shares', 'price', 'date', 'time'])
stock_prototype = Stock('', 0, 0.0, None, None)
def dict_to_stock(s): 
    return stock_prototype._replace(**s)

a= {'name': 'ACME','shares':100,'price':123.45}
print(dict_to_stock(a)) #Stock(name='ACME', shares=100, price=123.45, date=None, time=None)
b= {'name': 'ACME','shares':100,'price':123.45, 'date':'12/17/2012'}
print(dict_to_stock(b)) # Stock(name='ACME', shares=100, price=123.45, date='12/17/2012', time=None)
  • 转换并且同时计算数据. 可以考虑推导与min(), max()等,些聚集函数比如 min() 和 max() 的时候你可能更加倾向于使用生成器版本, 它们接受的一个key关键字参数或许对你很有帮助
import os
files = os.listdir('.')
# for name in files:
#     if name.endswith('.py'):
#         print('aaa')
#     else:
#         print('not python file')
if any(name.endswith('.py')for name in files): 
    print('There be python!') 
else:
    print('Sorry,no python.')
    
    
portfolio =[ {'name':'GOOG', 'shares':50}, {'name':'YHOO', 'shares':75}, {'name':'AOL','shares': 20}, {'name':'SCOX', 'shares':65} ]
min_shares= min(s['shares'] for s in portfolio)
print(min_shares)
print(min(portfolio, key=lambda s:s['shares'])) # {'name': 'AOL', 'shares': 20}

合并多个字典或者映射

  • 现有多个字典或者映射,想从逻辑上合并为一个单一映射后执行某些操作,比如查找健值或者健是否存在,可以使用collections 中的ChainMap
    一个 ChainMap 接受多个字典并将它们在逻辑上变为一个字典。 然后,这些字典并不是真 的合并在一起了, ChainMap 类只是在内部创建了一个容纳这些字典的列表 并重新定义了 一些常见的字典操作来遍历这个列表。大部分字典操作都是可以正常使用的
    另外: 对于字典的更新或删除操作总是影响的是列表中第一个字典
from collections import ChainMap
a = {'x': 1, 'z':3 }
b = {'y': 2, 'z':4 }
c = ChainMap(a,b)
print(c)
print(c['x'])
print(c['y'])
print(c['z']) # 重复键,那么第一次出现的映射值会被返回。
print(len(c))
print(list(c.keys())) #['y', 'z', 'x']
print(list(c.values())) # [2, 3, 1]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355