字典(dict)的groupby和groupby的区别

6
我有一个这样的列表
[u'201003', u'200403', u'200803', u'200503', u'201303',
 u'200903', u'200603', u'201203', u'200303', u'200703', u'201103']

我们把这个列表称为“年份列表”。

当我按年份分组时,

group_by_yrs_list = groupby(years_list, key = lambda year_month: year_month[:-2]) 
for k,v in group_by_yrs_list:
  print k, list(v)

我得到了期望的输出:
2010 [u'201003']
2004 [u'200403']
2008 [u'200803']
2005 [u'200503']
2013 [u'201303']
2009 [u'200903']
2006 [u'200603']
2012 [u'201203']
2003 [u'200303']
2007 [u'200703']
2011 [u'201103']

然后,我稍微修改了我的实现方式,像这样:
  group_by_yrs_list = dict(groupby(years_list, key = lambda year_month: year_month[:-2]))
  for k,v in group_by_yrs_list.items():
    print k, list(v)

我刚刚添加了一个字典,但输出结果不同。
2003 []
2006 []
2007 []
2004 []
2005 []
2008 []
2009 []
2011 [u'201103']
2010 []
2013 []
2012 []

我不知道为什么。请帮助我找出字典实际在做什么。
(Python 2.7)

请记住,字典没有顺序。但是为什么只有一个列表不为空,这很奇怪。 - TerryA
1
@Haidro:falstru的回答应该会让你豁然开朗。 - justhalf
@justhalf 当然没问题! - TerryA
@TerryA,该注释已经过时,自3.7版本开始,字典是按插入顺序排序的。 - alancalvitti
4个回答

9
groupby函数返回(key, group-iterator)的组合。如果你正在迭代第二个组合,那么第一个组合的group-iterator已经被消耗掉了,所以你会得到空列表。
尝试以下代码:
group_by_yrs_list = {year:list(grp) for year, grp in groupby(years_list, key=lambda year_month: year_month[:-2])}
for k, v in group_by_yrs_list.items():
    print k, v

1
这意味着所有分组的值都指向单个迭代器。我说得对吗? - John Prawyn
3
@JohnPrawyn,是的。itertools._grouper对象共享一个迭代器(gbo->it)。 - falsetru

6
问题在于groupby会按顺序依次产生每个键和一个子迭代器:
>>> for k, v in groupby(years_list, key = lambda year_month: year_month[:-2]):
...    print k, v
2010 <itertools._grouper object at 0x801c68950>
2004 <itertools._grouper object at 0x801bb3a90>
2008 <itertools._grouper object at 0x801c68950>
2005 <itertools._grouper object at 0x801bb3a90>
2013 <itertools._grouper object at 0x801c68950>
2009 <itertools._grouper object at 0x801bb3a90>
2006 <itertools._grouper object at 0x801c68950>
2012 <itertools._grouper object at 0x801bb3a90>
2003 <itertools._grouper object at 0x801c68950>
2007 <itertools._grouper object at 0x801bb3a90>
2011 <itertools._grouper object at 0x801c68950>

在存储之前,需要将每个<itertools._grouper object ...>转换为实际列表,因为groupby的下一个迭代会重置迭代器。如果您不这样做,那么仅剩下一个有用的迭代器,因此在打印字典内容时,您将得到一个非空列表(使用了迭代器)。第二次打印它时,您将得到所有空列表。
关键是在迭代器仍然有效时将其转换为列表(我看到其他几个人比我更早提供了示例代码,我喜欢falsetru的变体)。

2
docs:由于源是共享的,当groupby()对象被推进时,先前的组将不再可见。因此,如果稍后需要该数据,则应将其存储为列表。 - Ashwini Chaudhary
是的,基本上是相同的语句,但更短,更精确(我没有明确说明为什么旧的<itertools._grouper object ...>会变得无用),不过我怀疑那个版本可能会超出主贴作者的理解范围 :-) - torek
你的回答已经很清楚了,我只是想补充一些文档参考。 - Ashwini Chaudhary

2

尝试使用toolz中的非流式groupby操作。

$ pip install toolz
$ ipython

In [1]: from toolz import groupby

In [2]: years_list = [u'201003', u'200403', u'200803', u'200503', u'201303',
   ...:  u'200903', u'200603', u'201203', u'200303', u'200703', u'201103']

In [3]: get_year = lambda year_month: year_month[:-2]

In [4]: groupby(get_year, years_list)
Out[4]: 
{u'2003': [u'200303'],
 u'2004': [u'200403'],
 u'2005': [u'200503'],
 u'2006': [u'200603'],
 u'2007': [u'200703'],
 u'2008': [u'200803'],
 u'2009': [u'200903'],
 u'2010': [u'201003'],
 u'2011': [u'201103'],
 u'2012': [u'201203'],
 u'2013': [u'201303']}

1
根据这个答案,您可以这样做将其转换为dict:
group_by_yrs_list = dict((k,list(v)) for k,v in groupby(years_list, key=lambda x: x[:4]))

这是因为groupby的输出是一个itertools.groupby对象,它是一种生成器,显然不能直接用作dict构造函数的参数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接