2018-03-01数据结构和算法(3)

1.11 命名切片

假定你有一段代码要从一个记录字符串中几个固定位置提取出特定的数据字段(比如文件或类似格式):

  1. ###### 0123456789012345678901234567890123456789012345678901234567890'
  2. record = '....................100 .......513.25 ..........'
  3. cost = int(record[20:23]) * float(record[31:37])

  与其那样写,为什么不想这样命名切片呢:

  1. SHARES = slice(20, 23)
  2. PRICE = slice(31, 37)
  3. cost = int(record[SHARES]) * float(record[PRICE])

  内置的 slice() 函数创建了一个切片对象,可以被用在任何切片允许使用的地方。比如:

  1. >>> items = [0, 1, 2, 3, 4, 5, 6]
  2. >>> a = slice(2, 4)
  3. >>> items[2:4]
  4. [2, 3]
  5. >>> items[a]
  6. [2, 3]
  7. >>> items[a] = [10,11]
  8. >>> items
  9. [0, 1, 10, 11, 4, 5, 6]
  10. >>> del items[a]
  11. >>> items
  12. [0, 1, 4, 5, 6]

  如果你有一个切片对象a,你可以分别调用它的 a.start , a.stop , a.step 属性来获取更多的信息。比如:

  1. >>> a = slice(5, 50, 2)
  2. >>> a.start
  3. 5
  4. >>> a.stop
  5. 50
  6. >>> a.step
  7. 2
  8. >>>

  另外,你还能通过调用切片的 indices(size) 方法将它映射到一个确定大小的序列上, 这个方法返回一个三元组 (start, stop, step) ,所有值都会被合适的缩小以满足边界限制, 从而使用的时候避免出现 IndexError 异常。比如:

  1. >>> s = 'HelloWorld'
  2. >>> a.indices(len(s))
  3. (5, 10, 2)
  4. >>> for i in range(*a.indices(len(s))):
  5. ... print(s[i])
  6. ...
  7. W
  8. r
  9. d
  10. >>>

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

collections.Counter 类就是专门为这类问题而设计的, 它甚至有一个有用的 most_common() 方法直接给了你答案。

为了演示,先假设你有一个单词列表并且想找出哪个单词出现频率最高。你可以这样做:

  1. words = [
  2. 'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
  3. 'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
  4. 'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
  5. 'my', 'eyes', "you're", 'under'
  6. ]
  7. from collections import Counter
  8. word_counts = Counter(words)
  9. # 出现频率最高的3个单词
  10. top_three = word_counts.most_common(3)
  11. print(top_three)
  12. # Outputs [('eyes', 8), ('the', 5), ('look', 4)]

  作为输入, Counter 对象可以接受任意的由可哈希(hashable)元素构成的序列对象。 在底层实现上,一个 Counter 对象就是一个字典,将元素映射到它出现的次数上。比如:

  1. >>> word_counts['not']
  2. 1
  3. >>> word_counts['eyes']
  4. 8
  5. >>>

  如果你想手动增加计数,可以简单的用加法:

  1. >>> morewords = ['why','are','you','not','looking','in','my','eyes']
  2. >>> for word in morewords:
  3. ... word_counts[word] += 1
  4. ...
  5. >>> word_counts['eyes']
  6. 9
  7. >>>

  

或者你可以使用 update() 方法:

  1. >>> word_counts.update(morewords)
  2. >>>

Counter 实例一个鲜为人知的特性是它们可以很容易的跟数学运算操作相结合。比如:

  1. >>> a = Counter(words)
  2. >>> b = Counter(morewords)
  3. >>> a
  4. Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2,
  5. "you're": 1, "don't": 1, 'under': 1, 'not': 1})
  6. >>> b
  7. Counter({'eyes': 1, 'looking': 1, 'are': 1, 'in': 1, 'not': 1, 'you': 1,
  8. 'my': 1, 'why': 1})
  9. >>> # Combine counts
  10. >>> c = a + b
  11. >>> c
  12. Counter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2,
  13. 'around': 2, "you're": 1, "don't": 1, 'in': 1, 'why': 1,
  14. 'looking': 1, 'are': 1, 'under': 1, 'you': 1})
  15. >>> # Subtract counts
  16. >>> d = a - b
  17. >>> d
  18. Counter({'eyes': 7, 'the': 5, 'look': 4, 'into': 3, 'my': 2, 'around': 2,
  19. "you're": 1, "don't": 1, 'under': 1})
  20. >>>

毫无疑问, Counter 对象在几乎所有需要制表或者计数数据的场合是非常有用的工具。 在解决这类问题的时候你应该优先选择它,而不是手动的利用字典去实现。

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

通过使用 operator 模块的 itemgetter 函数,可以非常容易的排序这样的数据结构。 假设你从数据库中检索出来网站会员信息列表,并且以下列的数据结构返回:

  1. rows = [
  2. {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
  3. {'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
  4. {'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
  5. {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
  6. ]

 根据任意的字典字段来排序输入结果行是很容易实现的,代码示例: 

  1. from operator import itemgetter
  2. rows_by_fname = sorted(rows, key=itemgetter('fname'))
  3. rows_by_uid = sorted(rows, key=itemgetter('uid'))
  4. print(rows_by_fname)
  5. print(rows_by_uid)
  6. #代码的输出如下:
  7. [{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'},
  8. {'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},
  9. {'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
  10. {'fname': 'John', 'uid': 1001, 'lname': 'Cleese'}]
  11. [{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'},
  12. {'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
  13. {'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},
  14. {'fname': 'Big', 'uid': 1004, 'lname': 'Jones'}]

  itemgetter() 函数也支持多个 keys,比如下面的代码:

  1. rows_by_lfname = sorted(rows, key=itemgetter('lname','fname'))
  2. print(rows_by_lfname)
    #输出结果如下:
  1. [{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
  2. {'fname': 'John', 'uid': 1001, 'lname': 'Cleese'},
  3. {'fname': 'Big', 'uid': 1004, 'lname': 'Jones'},
  4. {'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'}]

  

在上面例子中, rows 被传递给接受一个关键字参数的 sorted() 内置函数。 这个参数是 callable 类型,并且从 rows 中接受一个单一元素,然后返回被用来排序的值。 itemgetter() 函数就是负责创建这个 callable 对象的。

operator.itemgetter() 函数有一个被 rows 中的记录用来查找值的索引参数。可以是一个字典键名称, 一个整形值或者任何能够传入一个对象的 __getitem__() 方法的值。 如果你传入多个索引参数给 itemgetter() ,它生成的 callable 对象会返回一个包含所有元素值的元组, 并且 sorted() 函数会根据这个元组中元素顺序去排序。 但你想要同时在几个字段上面进行排序(比如通过姓和名来排序,也就是例子中的那样)的时候这种方法是很有用的。

itemgetter() 有时候也可以用 lambda 表达式代替,比如:

  1. rows_by_fname = sorted(rows, key=lambda r: r['fname'])
  2. rows_by_lfname = sorted(rows, key=lambda r: (r['lname'],r['fname']))

 

这种方案也不错。但是,使用 itemgetter() 方式会运行的稍微快点。因此,如果你对性能要求比较高的话就使用 itemgetter() 方式。

最后,不要忘了这节中展示的技术也同样适用于 min()max() 等函数。比如:

  1. >>> min(rows, key=itemgetter('uid'))
  2. {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}
  3. >>> max(rows, key=itemgetter('uid'))
  4. {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
  5. >>> 

1.14排序不支持原生比较对象

内置的 sorted() 函数有一个关键字参数 key ,可以传入一个 callable 对象给它, 这个 callable 对象对每个传入的对象返回一个值,这个值会被 sorted 用来排序这些对象。 比如,如果你在应用程序里面有一个 User 实例序列,并且你希望通过他们的 user_id 属性进行排序, 你可以提供一个以 User 实例作为输入并输出对应 user_id 值的 callable 对象。比如:

  1. class User:
  2. def __init__(self, user_id):
  3. self.user_id = user_id
  4.  
  5. def __repr__(self):
  6. return 'User({})'.format(self.user_id)
  7.  
  8. def sort_notcompare():
  9. users = [User(23), User(3), User(99)]
  10. print(users)
  11. print(sorted(users, key=lambda u: u.user_id))
  12. #另外一种方式是使用 operator.attrgetter() 来代替 lambda 函数:
  13. >>> from operator import attrgetter
  14. >>> sorted(users, key=attrgetter('user_id'))
  15. [User(3), User(23), User(99)]
  16. >>>

  同样需要注意的是,这一小节用到的技术同样适用于像 min()max() 之类的函数。比如:

  1. >>> min(users, key=attrgetter('user_id'))
  2. User(3)
  3. >>> max(users, key=attrgetter('user_id'))
  4. User(99)
  5. >>>

1.15通过某个字段将记录分组

  1. rows = [
  2. {'address': '5412 N CLARK', 'date': '07/01/2012'},
  3. {'address': '5148 N CLARK', 'date': '07/04/2012'},
  4. {'address': '5800 E 58TH', 'date': '07/02/2012'},
  5. {'address': '2122 N CLARK', 'date': '07/03/2012'},
  6. {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
  7. {'address': '1060 W ADDISON', 'date': '07/02/2012'},
  8. {'address': '4801 N BROADWAY', 'date': '07/01/2012'},
  9. {'address': '1039 W GRANVILLE', 'date': '07/04/2012'},
  10. ]

 现在假设你想在按 date 分组后的数据块上进行迭代。为了这样做,你首先需要按照指定的字段(这里就是 date )排序, 然后调用 itertools.groupby() 函数:

  1. from operator import itemgetter
  2. from itertools import groupby
  3.  
  4. # Sort by the desired field first
  5. rows.sort(key=itemgetter('date'))
  6. # Iterate in groups
  7. for date, items in groupby(rows, key=itemgetter('date')):
  8. print(date)
  9. for i in items:
  10. print(' ', i)

 运行结果:

  1. 07/01/2012
  2. {'date': '07/01/2012', 'address': '5412 N CLARK'}
  3. {'date': '07/01/2012', 'address': '4801 N BROADWAY'}
  4. 07/02/2012
  5. {'date': '07/02/2012', 'address': '5800 E 58TH'}
  6. {'date': '07/02/2012', 'address': '5645 N RAVENSWOOD'}
  7. {'date': '07/02/2012', 'address': '1060 W ADDISON'}
  8. 07/03/2012
  9. {'date': '07/03/2012', 'address': '2122 N CLARK'}
  10. 07/04/2012
  11. {'date': '07/04/2012', 'address': '5148 N CLARK'}
  12. {'date': '07/04/2012', 'address': '1039 W GRANVILLE'}

 

groupby() 函数扫描整个序列并且查找连续相同值(或者根据指定 key 函数返回值相同)的元素序列。 在每次迭代的时候,它会返回一个值和一个迭代器对象, 这个迭代器对象可以生成元素值全部等于上面那个值的组中所有对象。

一个非常重要的准备步骤是要根据指定的字段将数据排序。 因为 groupby() 仅仅检查连续的元素,如果事先并没有排序完成的话,分组函数将得不到想要的结果。

如果你仅仅只是想根据 date 字段将数据分组到一个大的数据结构中去,并且允许随机访问, 那么你最好使用 defaultdict() 来构建一个多值字典,关于多值字典已经在 1.6 小节有过详细的介绍。比如:

  1. from collections import defaultdict
  2. rows_by_date = defaultdict(list)
  3. for row in rows:
  4. rows_by_date[row['date']].append(row)

 这样的话你可以很轻松的就能对每个指定日期访问对应的记录:

  1. >>> for r in rows_by_date['07/01/2012']:
  2. ... print(r)
  3. ...
  4. {'date': '07/01/2012', 'address': '5412 N CLARK'}
  5. {'date': '07/01/2012', 'address': '4801 N BROADWAY'}
  6. >>>

 在上面这个例子中,我们没有必要先将记录排序。因此,如果对内存占用不是很关心, 这种方式会比先排序然后再通过 groupby() 函数迭代的方式运行得快一些。 

 

 

 

 

python3学习笔记3---引用http://python3-cookbook.readthedocs.io/zh_CN/latest/的更多相关文章

  1. python3学习笔记1---引用http://python3-cookbook.readthedocs.io/zh_CN/latest/

    2018-02-28数据结构和算法(1) 1.1解压序列赋值给多个变量: 任何的序列(或者是可迭代对象)可以通过一个简单的赋值语句解压并赋值给多个变量. 唯一的前提就是变量的数量必须跟序列元素的数量是 ...

  2. python3学习笔记4---引用http://python3-cookbook.readthedocs.io/zh_CN/latest/

    2018-03-01数据结构与算法(4) 1.16过滤序列元素 最简单的过滤序列元素的方法就是使用列表推导.比如: >>> mylist = [1, 4, -5, 10, -7, 2 ...

  3. python3学习笔记2---引用http://python3-cookbook.readthedocs.io/zh_CN/latest/2

    2018-03-01数据结构和算法(2) 1.6字典中的键映射多个值 一个字典就是一个键对应一个单值的映射.如果你想要一个键映射多个值,那么你就需要将这多个值放到另外的容器中, 比如列表或者集合里面. ...

  4. python3学习笔记(6)_iteration

    #python3 学习笔记17/07/10 # !/usr/bin/env python3 # -*- coding:utf-8 -*- #类似 其他语言的for循环,但是比for抽象程度更高 # f ...

  5. Python3学习笔记(urllib模块的使用)转http://www.cnblogs.com/Lands-ljk/p/5447127.html

    Python3学习笔记(urllib模块的使用)   1.基本方法 urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None,  ...

  6. Python3学习笔记 - 准备环境

    前言 最近乘着项目不忙想赶一波时髦学习一下Python3.由于正好学习了Docker,并深深迷上了Docker,所以必须趁热打铁的用它来创建我们的Python3的开发测试环境.Python3的中文教程 ...

  7. python3学习笔记(7)_listComprehensions-列表生成式

    #python3 学习笔记17/07/11 # !/usr/bin/env python3 # -*- conding:utf-8 -*- #通过列表生成式可以生成格式各样的list,这种list 一 ...

  8. python3学习笔记(5)_slice

    #python3 学习笔记17/07/10 # !/usr/bin/env python3 # -*- coding:utf-8 -*- #切片slice 大大简化 对于指定索引的操作 fruits ...

  9. python3.4学习笔记(一) 基本语法 python3不向下兼容,有些语法跟python2.x不一样

    python3.4学习笔记(一) 基本语法 python3不向下兼容,有些语法跟python2.x不一样,IDLE shell编辑器,快捷键:ALT+p,上一个历史输入内容,ALT+n 下一个历史输入 ...

随机推荐

  1. Java-IO之超类OutputStream

    图中我们可以看出,以字节为单位的输出流的公共父类是OutputStream: 从中我们可以看出,以字节为单位的输出流的公共父类是OutputStream: (1)OutputStream是以字节为单位 ...

  2. 一台电脑上同启动两个Tomcat的方式,windows/Linux配置。

     安装两个jdk,一个JDK路径在:C:\ProgramFiles (x86)\Java\jdk1.7.0_25,另外一个JDK的路径在E:\UCMSServer\j2sdk 在环境变量里并设置J ...

  3. DB 查询分析器 方便地创建DB2自定义函数

    DB 查询分析器 方便地创建DB2自定义函数                           马根峰            (广东联合电子服务股份有限公司, 广州 510300) 摘要       ...

  4. 小强的HTML5移动开发之路(20)——HTML5 Web SQL Database

    来自:http://blog.csdn.net/dawanganban/article/details/18220761 一.Web Database介绍 WebSQL数据库API实际上不是HTML5 ...

  5. 【shell脚本练习】判断用户存在和用户类型

    题目 写一个脚本 1. 传递一个参数给脚本,此参数为用户名: 2. 如果用户存在,则执行如下任务 * 如果用户的id号小于500,显示其为管理员或系统用户: * 否则,显示其为普通用户: 3. 如果用 ...

  6. Python代码运行助手

    将下述demo文件保存下来,比如存为learning.py 然后运行,如果出现: Ready for Python code on port 39093... 则说明成功了. demo #!/usr/ ...

  7. C++对象模型(一):The Semantics of Constructors The Default Constructor (默认构造函数什么时候会被创建出来)

    本文是 Inside The C++ Object Model, Chapter 2的部分读书笔记. C++ Annotated Reference Manual中明确告诉我们: default co ...

  8. sed命令 linux

    sed 实用工具是一个"编辑器",但它与其它大多数编辑器不同.除了不面向屏幕之外,它还是非交互式的.这意味着您必须将要对数据执行的命令插入到命令行或要处 理的脚本中.当显示它时,请 ...

  9. 开源视频会议系统:OpenMeetings 安装方法

    OpenMeetings是一个多语言可定制的视频会议和协作系统.说到OpenMeetings就应该提一下red5因为OpenMeetings 的视频服务是加载red5上面的.Red5是一款基于JAVA ...

  10. 01_MUI之Boilerplate中:HTML5示例,动态组件,自定义字体示例,自定义字体示例,图标字体示例

     1安装HBuilder5.0.0,安装后的界面截图如下: 2 按照https://www.muicss.com/docs/v1/css-js/boilerplate-html中的说明,创建上图的 ...