1 问题
你有一个元素均为字典或其他类型的序列,你希望根据每个元素中的同一个字段(例如:日期)对序列中的所有元素进行分组迭代。
2. 解决方案
itertools 模块中的类 groupby 可以容易地实现上述需求。例如,假设你有类似下列格式的列表:
rows = [
{'address': '5412 N CLARK', 'date': '07/01/2012'},
{'address': '5148 N CLARK', 'date': '07/04/2012'},
{'address': '5800 E 58TH', 'date': '07/02/2012'},
{'address': '2122 N CLARK', 'date': '07/03/2012'},
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
{'address': '1060 W ADDISON', 'date': '07/02/2012'},
{'address': '4801 N BROADWAY', 'date': '07/01/2012'},
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'},
]
假定你希望按照日期对上述数据进行分组。对此,你可以先将上述数据按照每个字典的 date 域进行排序,然后使用 itertools.groupby 类创建一个迭代器:
>>> from operator import itemgetter
>>> from itertools import groupby
>>>
>>> rows.sort(key=itemgetter('date'))
>>>
>>> for date, items in groupby(rows, key=itemgetter('date')):
... print(date)
... for item in items:
... print('\t', item)
上述代码的输出结果如下:
07/01/2012
{'address': '5412 N CLARK', 'date': '07/01/2012'}
{'address': '4801 N BROADWAY', 'date': '07/01/2012'}
07/02/2012
{'address': '5800 E 58TH', 'date': '07/02/2012'}
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}
{'address': '1060 W ADDISON', 'date': '07/02/2012'}
07/03/2012
{'address': '2122 N CLARK', 'date': '07/03/2012'}
07/04/2012
{'address': '5148 N CLARK', 'date': '07/04/2012'}
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'}
需要说明的是,关于 itemgetter 的使用方法和源码分析可以参考【Python 每日一技】针对元素为字典的列表按照共同的键进行排序。
3. 讨论
在使用 itertools.groupby 创建实例的过程中,该实例会扫描可迭代对象也即给定的序列,同时查找具有某相同数据域(该数据域由 key 接受的函数指定)的元素(上述案例中每个元素都是字典)。
最终使用该类创建的对象是一个迭代器,在每对该迭代器进行一次迭代时,都会返回一个元组,元组有两个值:
- 第一个值是某数据域(在上述案例中是日期);
- 第二个值又是一个迭代器,在对此迭代器迭代时会生成若干个具有相同某数据域的元素(在上述案例中每个元素都是字典)。
需要注意的是,如果希望得到期望的结果,那么第一步需要将待分组的序列按照元素的某公共数据域进行排序,因为 groupby 在创建对象过程中只会检查相邻的元素是否具有相同的某公共数据域。
如果你的目的仅仅是为了将数据进行分组用于后续的随机访问,可能你使用 collections 模块中的 defaultdict 类创建一个多值字典更加合适。例如:
>>> from collections import defaultdict
>>> rows_by_date = defaultdict(list)
>>> for row in rows:
... rows_by_date[row['date']].append(row)
>>> rows_by_date
defaultdict(<class 'list'>, {'07/01/2012': [{'address': '5412 N CLARK', 'date': '07/01/2012'}, {'address': '4801 N BROADWAY', 'date': '07/01/2012'}], '07/02/2012': [{'address': '5800 E 58TH', 'date': '07/02/2012'}, {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}, {'address': '1060 W ADDISON', 'date': '07/02/2012'}], '07/03/2012': [{'address': '2122 N CLARK', 'date': '07/03/2012'}], '07/04/2012': [{'address': '5148 N CLARK', 'date': '07/04/2012'}, {'address': '1039 W GRANVILLE', 'date': '07/04/2012'}]})
>>> for date in rows_by_date:
... print(date)
... for row in rows_by_date[date]:
... print('\t', row)
07/01/2012
{'address': '5412 N CLARK', 'date': '07/01/2012'}
{'address': '4801 N BROADWAY', 'date': '07/01/2012'}
07/02/2012
{'address': '5800 E 58TH', 'date': '07/02/2012'}
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}
{'address': '1060 W ADDISON', 'date': '07/02/2012'}
07/03/2012
{'address': '2122 N CLARK', 'date': '07/03/2012'}
07/04/2012
{'address': '5148 N CLARK', 'date': '07/04/2012'}
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'}
如果使用 collections.defaultdict 则不需要先对原始数据进行排序,因此如果内存允许那么使用这种方式则速度更快。
|