【问题标题】:Pack a list of dicts in python在 python 中打包字典列表
【发布时间】:2014-09-23 17:03:33
【问题描述】:

我有一个这样结构的字典列表:

[
    {'state': '1', 'city': 'a'},
    {'state': '1', 'city': 'b'},
    {'state': '2', 'city': 'c'},
    {'state': '2', 'city': 'd'},
    {'state': '3', 'city': 'e'}
]

我想这样打包:

[
    {'state': '1', 'cities': ['a', 'b']},
    {'state': '2', 'cities': ['c', 'd']},
    {'state': '3', 'cities': ['e']}
]

我有一个可行但非常慢的两步方法(我的列表长度超过 10000 项,而且我的字典很复杂):

def pack(iterable):

    # step 1: lists -> super slow ! contains duplicates
    listed = [{'state': i['state'],
              'cities': [c['city'] for c in iterable if c['state']==i['state']]}
              for i in iterable]

    # step 2: remove duplicates
    packed = [l for n, l in enumerate(listed) if not l in listed[n+1:]]

    return packed

有什么优化建议吗?

Ps:欢迎对帖子标题提出建议。

2014/09/26 编辑:我刚刚发现 pandas 非标准库在这种情况下很有帮助。

下面我的自我回答中有更多示例。

【问题讨论】:

  • 这不是一个“线程”,它是一个问题。为了改进工作代码,您可能需要codereview.stackexchange.com
  • 不知道 codereview.stackexchange.com。感谢您的建议!

标签: python list dictionary pandas group-by


【解决方案1】:
state_merged = {}
for s in states:
    state_merged.setdefault(s['state'], []).append(s['city'])

states = [{'state':k, 'cities':v} for k, v in state_merged.iteritems()]

如果您使用的是 python 3.0,请使用 state_merged.items() 而不是 state_merged.iteritems()

【讨论】:

    【解决方案2】:

    以下内容不需要预先排序的迭代并在O(n) 时间运行,但是它假设状态和其他字典键之间存在不对称(根据您的示例,这似乎是一个正确的假设)。

    import collections
    def pack(iterable):
        out = collections.defaultdict(list) #or use defaultdict(set)
        for d in iterable:
            out[d['state']].append(d['city'])
        return out
    
    it = [
        {'state': '1', 'city': 'a'},
        {'state': '1', 'city': 'b'},
        {'state': '2', 'city': 'c'},
        {'state': '2', 'city': 'd'},
        {'state': '3', 'city': 'e'}
    ]
    
    pack(it) == {'1': ['a', 'b'],
                 '2': ['c', 'd'],
                 '3': ['e']}
    

    如果您需要返回与请求格式相同的可迭代对象,您可以将out 转换为list

    def convert(out):
        final = []
        for state, city in out.iteritems(): #Python 3.0+ use .items()
            final.append({'state': state, 'city': city})
        return final
    
    convert(pack(it)) == [
        {'state': '1', 'city': ['a', 'b']},
        {'state': '2', 'city': ['c', 'd']},
        {'state': '3', 'city': ['e']}
    ]
    

    如果您的输入中不止 2 个键,则需要进行以下更改:

    it = [{'state': 'WA', 'city': 'Seattle', 'zipcode': 98101, 'city_population': 9426},
          {'state': 'OR', 'city': 'Portland', 'zipcode': 97225, 'city_population': 24749},
          {'state': 'WA', 'city': 'Spokane', 'zipcode': 99201, 'city_population': 12523}]
    
    
    def citydata():
        return {'city': [], 'zipcode': [], 'state_population': 0} #or use a namedtuple('Location', 'city zipcode state_population')
    
    def pack(iterable):
        out = defaultdict(citydata)
        for d in iterable:
            out[d['state']]['city'].append(d['city'])
            out[d['state']]['zipcode'].append(d['zipcode'])
            out[d['state']]['state_population'] += d['city_population']
        return out
    
    pack(it) == {
       'WA':
           {'city': ['Seattle', 'Spokane'], 'zipcode': [98101, 99201], 'state_population': 21949},
       'OR':
           {'city': ['Portland'], 'zipcode': [97225], 'state_population': 24749}
    }
    

    convert 函数需要相应调整。

    convert(pack(it)) == [
           {'state': 'WA', 'city': ['Seattle', 'Spokane'], 'zipcode': [98101, 99201], 'state_population': 21949},
           {'state': 'OR', 'city': ['Portland'], 'zipcode': [97225], 'state_population': 24749}
    ]
    

    要保持原始迭代的顺序,请使用OrderedDefaultdict 而不是默认字典。

    【讨论】:

    • 我没有提到多键问题,但当然这正是我想去的地方。
    • 顺便说一句,在我的情况下,原始订单不是问题。
    【解决方案3】:

    这是一种功能更强大、速度更快的方法:

    import itertools
    def pack(original):
        return [
            {'state': state, 'cities': [element['city'] for element in group]} 
            for state, group 
            in itertools.groupby(original, lambda e: e['state'])
        ]
    

    这假设您的每个州都有其所有成员在原始列表中连续列出。

    您当前的方法慢得多的原因是它必须为找到的每个状态 id 遍历整个列表。这被称为O(n^2) 方法。这种方法只需要遍历源列表一次,所以它是O(n)

    【讨论】:

    • 如果 iterable 没有预先排序,则需要花费 O(n log n)sorted_iterable = sorted(unsorted_iterable, key=operator.itemgetter('state')) 进行排序
    【解决方案4】:

    在我的 windows python 2.6.5 (exe here http://www.lfd.uci.edu/~gohlke/pythonlibs/#pandas) 上安装 pandas lib(这是非标准的)时遇到了一些麻烦,我才发现它。

    网址:http://pandas.pydata.org/pandas-docs/stable/

    一般介绍:

    pandas 是一个 Python 包,它提供快速、灵活和富有表现力的数据结构,旨在使处理“关系”或“标记”数据变得既简单又直观。它旨在成为在 Python 中进行实用的、真实世界数据分析的基本高级构建块。

    那些已经使用 numpy 和 R 的人会熟悉 Pandas。

    这是解决我的熊猫问题的方法:

    >>> import pandas as pd
    
    >>> raw = [{'state': '1', 'city': 'a'},
               {'state': '1', 'city': 'b'},
               {'state': '2', 'city': 'c'},
               {'state': '2', 'city': 'd'},
               {'state': '3', 'city': 'e'}]
    
    >>> df = pd.DataFrame(raw) # magic !
    
    >>> df
      city state
    0    a     1
    1    b     1
    2    c     2
    3    d     2
    4    e     3
    
    >>> grouped = df.groupby('state')['city']
    >>> grouped
    <pandas.core.groupby.SeriesGroupBy object at 0x05F22110>
    
    >>> listed = grouped.apply(list)
    >>> listed
    state
    1        [a, b]
    2        [c, d]
    3           [e]
    Name: city, dtype: object
    
    >>> listed.to_dict() # magic again !
    {'1': ['a', 'b'], '3': ['e'], '2': ['c', 'd']}
    

    更复杂的例子包括grouped.apply(custom_fct)这里:

    Pandas groupby: How to get a union of strings

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-13
      • 1970-01-01
      • 1970-01-01
      • 2010-12-25
      • 2013-02-16
      相关资源
      最近更新 更多