Python 使用嵌套的defaultdict重建数组

Python 使用嵌套的defaultdict重建数组,python,arrays,defaultdict,Python,Arrays,Defaultdict,此问题是前一个问题的延伸: -但不同之处足以引起一个新问题: 我已经为此挣扎了一段时间了。我的数据是来自sql查询的字典数组。数组中的每个元素表示一次装运,并且有基于键的公共值 data = [ {"CustName":"customer1", "PartNum":"part1", "delKey":"0001", "qty":"10", "memo":"blah1"}, {"CustName":"customer1", "PartNum":"part1", "delKey":"

此问题是前一个问题的延伸: -但不同之处足以引起一个新问题:

我已经为此挣扎了一段时间了。我的数据是来自sql查询的字典数组。数组中的每个元素表示一次装运,并且有基于键的公共值

data = [
    {"CustName":"customer1", "PartNum":"part1", "delKey":"0001", "qty":"10", "memo":"blah1"},
    {"CustName":"customer1", "PartNum":"part1", "delKey":"0002", "qty":"10", "memo":"blah2"},
    {"CustName":"customer1", "PartNum":"part1", "delKey":"0003", "qty":"10", "memo":"blah3"},
    {"CustName":"customer2", "PartNum":"part3", "delKey":"0004", "qty":"20", "memo":"blah4"},
    {"CustName":"customer2", "PartNum":"part3", "delKey":"0005", "qty":"20", "memo":"blah5"},
    {"CustName":"customer3", "PartNum":"partXYZ", "delKey":"0006", "qty":"50", "memo":"blah6"},
    {"CustName":"customer3", "PartNum":"partABC", "delKey":"0007", "qty":"100", "memo":"blah7"}]
我想要的输出是根据特定键分组的

dataOut = [
   {"CustName":"customer1", "Parts":[
        {"PartNum":"part1", "deliveries":[
            {"delKey":"0001", "qty":"10", "memo":"blah1"},
            {"delKey":"0002", "qty":"10", "memo":"blah2"},
            {"delKey":"0003", "qty":"10", "memo":"blah3"}]}]},
   {"CustName":"customer2", "Parts":[
        {"PartNum":"part3", "deliveries":[
            {"delKey":"0004", "qty":"20", "memo":"blah4"},
            {"delKey":"0005", "qty":"20", "memo":"blah5"}]}]},
   {"CustName":"customer3", "Parts":[
        {"PartNum":"partXYZ", "deliveries":[
            {"delKey":"0006", "qty":"50", "memo":"blah6"}]},
        {"PartNum":"partABC", "deliveries":[
            {"delKey":"0007", "qty":"100", "memo":"blah7"}]}]}]
我可以使用defaultdict和列表理解(如前一个问题所提供的并稍加修改)以单个级别进行分组

d = defaultdict(list)
for item in data:
    d[item['CustName']].append(item)
print([{'CustName': key, 'parts': value} for key, value in d.items()])
但是我似乎无法获得输出数组中的第二级—分组b和
PartNum
键。通过一些研究,我认为我需要做的是使用
defaultdict
作为外部'defaultdict'的类型,如下所示:

d = defaultdict(defaultdict(list))
它抛出错误,因为defaultdict返回一个函数,所以我需要使用
lambda
(是吗?)

d=defaultdict(lambda:defaultdict(列表))
对于数据中的项目:

d[item['CustName'].append(item)这是我能做的最漂亮的方式。它使用相同的
defaultdict
思想来实现适当的分组,因为python的内置
groupby
函数只对有序数据起作用

请注意,此版本将改变输入数据集中的项,因此结果中的叶项与输入中的dict实例相同,但删除了
“CustName”
“PartNum”

编辑:

为了防止将来有人需要它,这里有一个不会改变原始数据的版本:

from collections import defaultdict

def groupby_getitem(seq, key):
  d = defaultdict(list)
  for item in seq:
    d[item[key]].append(item)
  return d

def your_operation(data):
  return [ {
    'CustName': CustName,
    'Parts': [ { 
      'PartNum': PartNum,
      'deliveries': [ dict(
        (k,v) for k,v in delivery.items() if not k in ['CustName', 'PartNum']
      ) for delivery in deliveries ]
    } for PartNum,deliveries in groupby_getitem(custItems, 'PartNum').items() ]
  } for CustName,custItems in groupby_getitem(data, 'CustName').items() ]

使用@Pynchia建议的
groupby
,并使用@hege_hegedus建议的
sorted
处理无序数据:

from itertools import groupby
dataOut = []
dataSorted = sorted(data, key=lambda x: (x["CustName"], x["PartNum"]))
for cust_name, cust_group in groupby(dataSorted, lambda x: x["CustName"]):
    dataOut.append({
        "CustName": cust_name,
        "Parts": [],
    })
    for part_num, part_group in groupby(cust_group, lambda x: x["PartNum"]):
        dataOut[-1]["Parts"].append({
            "PartNum": part_num,
            "deliveries": [{
                "delKey": delivery["delKey"],
                "memo": delivery["memo"],
                "qty": delivery["qty"],
            } for delivery in part_group]
        })

如果您查看第二个
for
循环,这将有望回答您关于访问循环中的第二级数组的问题。

您可以使用基于
OrderedDefaultdict
的树状数据结构,而不是
defaultdict(list)
。(这个定义来自于我的一个无关的朋友。)

因此,对于问题中显示的
数据
d
将包含:

d = {
    "customer1": {
        "Parts": {
            "part1": {
                "deliveries": {"0001": {"memo": "blah1", "qty": "10"},
                               "0002": {"memo": "blah2", "qty": "10"},
                               "0003": {"memo": "blah3", "qty": "10"}}}}},
    "customer2": {
        "Parts": {
            "part3": {
                "deliveries": {"0004": {"memo": "blah4", "qty": "20"},
                               "0005": {"memo": "blah5", "qty": "20"}}}}},
    "customer3": {
        "Parts": {
            "partXYZ": {
                "deliveries": {"0006": {"memo": "blah6", "qty": "50"}}},
            "partABC": {
                "deliveries": {"0007": {"memo": "blah7", "qty": "100"}}}}}
}
它可以简单地打印出来,因为它现在按您想要的方式分组。

排序“CustName”、“PartNum”、“delKey”
。迭代每个零件、每个客户的交付项目,并累积以匹配您的输出规格

我喜欢使用
操作符。itemgetter
——对我来说,它让事情变得更清楚

import collections, itertools, operator

cust_name = operator.itemgetter('CustName')
part_num = operator.itemgetter('PartNum')
group_sort = operator.itemgetter('CustName', 'PartNum', 'delKey')
del_key = operator.itemgetter('delKey')
qty = operator.itemgetter('qty')
memo = operator.itemgetter('memo')


# sort on the relavent keys
data.sort(key = group_sort)
result = []

# iterate over customers
for custname, group1 in itertools.groupby(data, cust_name):
    cust_dict = {'CustName' : custname, 'Parts': []}
    # iterate over parts for this customer
    for partnum, group2 in itertools.groupby(group1, part_num):
        part_dict = {"PartNum" : partnum, 'deliveries' : []}
        # iterate over delivery items for this part
        for thing in group2:
            part_dict['deliveries'].append({'delKey':del_key(thing),
                                            'qty':qty(thing),
                                            'memo':memo(thing)})
        cust_dict['Parts'].append(part_dict)
    result.append(cust_dict)

这显然会对原始数据中的项进行多次迭代,这可能会对性能造成影响——但我看不出有什么方法可以绕过多次迭代来完成您需要做的事情

对口述进行排序,然后应用。如果可以的话,也可以事先使用SQL进行操作。我现在帮不了你更多的忙,我在手机上……一个
PartNum
是否会有两个具有相同数字/值的
delKey
?您的实际
数据中有多少项
?原始数据集中可能有数万项。您是否关心输出列表中值的顺序?如果没有,您可以很容易地摆脱这些级别,使您的结构只是一组嵌套的字典
Tree=lambda:defaultdict(Tree)
就是这种结构所需的全部设置。对原始数据进行变异会有后果吗?功能
groupby\u mutate
是否通过副作用完成其任务?是的。
groupby\u mutate
函数不是真正可重用的,它是专门为适应这种情况而设计的,我毫不怀疑它可以在其他地方使用。@wwii:它是否有后果取决于上下文。对于直接来自服务的数据,如果原始数据实例没有被软件的另一个组件使用,则应该没有问题。我不想改变数据。我必须添加一些测试来验证数据是否已损坏。我想知道定制对象是否是正确的方法-使其更容易完成。thnx请注意,与@Blckknght一样,如果不需要保留
数据中项目的顺序,则不需要定义
OrderedDefaultdict
,只需使用
Tree=lambda:defaultdict(Tree)
。如果有许多项需要处理,则使用基于词典的数据结构可能比使用基于列表的数据结构更快。这非常接近我所需要的,但是,在每个级别使用
dict
会使进一步的迭代产生问题。对我来说,逻辑上应该是
Parts:[…]
,而不是
Parts:{…}
,因为每个客户都有一组部件。整个混乱的数据被传递到一个有角度的前端,这需要一个数组。这可能会很麻烦,但您可能会让
\uuuu missing\uuu()
方法检查
键的值
,并在
'Parts'
时返回一个空的
列表,而不是调用
self.default\u factory()
。更好地实现它至少需要抽象出特殊键是什么,而不是硬编码。基本上,您需要定义哪个键表示需要一棵树的“叶子”,而不是一个“分支”或节点。这个方法似乎最适合我获得所需的输出。我想使用
方法,但是。我无法获取树中的列表。
from collections import OrderedDict

class OrderedDefaultdict(OrderedDict):
    def __init__(self, *args, **kwargs):
        if not args:
            self.default_factory = None
        else:
            if not (args[0] is None or callable(args[0])):
                raise TypeError('first argument must be callable or None')
            self.default_factory = args[0]
            args = args[1:]
        super(OrderedDefaultdict, self).__init__(*args, **kwargs)

    def __missing__ (self, key):
        if self.default_factory is None:
            raise KeyError(key)
        self[key] = default = self.default_factory()
        return default

Tree = lambda: OrderedDefaultdict(Tree)

d = Tree()
for rec in data:
    custName, partNum, delKey = rec['CustName'], rec['PartNum'], rec['delKey']
    details = {"qty": rec["qty"], "memo": rec["memo"]}
    d[custName]['Parts'][partNum]['deliveries'][delKey] = details
d = {
    "customer1": {
        "Parts": {
            "part1": {
                "deliveries": {"0001": {"memo": "blah1", "qty": "10"},
                               "0002": {"memo": "blah2", "qty": "10"},
                               "0003": {"memo": "blah3", "qty": "10"}}}}},
    "customer2": {
        "Parts": {
            "part3": {
                "deliveries": {"0004": {"memo": "blah4", "qty": "20"},
                               "0005": {"memo": "blah5", "qty": "20"}}}}},
    "customer3": {
        "Parts": {
            "partXYZ": {
                "deliveries": {"0006": {"memo": "blah6", "qty": "50"}}},
            "partABC": {
                "deliveries": {"0007": {"memo": "blah7", "qty": "100"}}}}}
}
import collections, itertools, operator

cust_name = operator.itemgetter('CustName')
part_num = operator.itemgetter('PartNum')
group_sort = operator.itemgetter('CustName', 'PartNum', 'delKey')
del_key = operator.itemgetter('delKey')
qty = operator.itemgetter('qty')
memo = operator.itemgetter('memo')


# sort on the relavent keys
data.sort(key = group_sort)
result = []

# iterate over customers
for custname, group1 in itertools.groupby(data, cust_name):
    cust_dict = {'CustName' : custname, 'Parts': []}
    # iterate over parts for this customer
    for partnum, group2 in itertools.groupby(group1, part_num):
        part_dict = {"PartNum" : partnum, 'deliveries' : []}
        # iterate over delivery items for this part
        for thing in group2:
            part_dict['deliveries'].append({'delKey':del_key(thing),
                                            'qty':qty(thing),
                                            'memo':memo(thing)})
        cust_dict['Parts'].append(part_dict)
    result.append(cust_dict)