I'm trying to unflatten a list of dictionary items to a nested dictionary structure which should contain the following structure
Bus
Message
Fieldname
I currently have a way to get the messages inside the bus but this will only result in a single message being added to each bus because I end up overwriting the dictionary within the bus with the new message. Actual output below
{'BUS_A': {'MSG_1': None}, 'BUS_B': {'MSG_2': None}}
How can I create the below desired input from the input data including the bus items, messages and fields?
A complete, executable example of where I've currently got to so far.
signals = [{"bus":"BUS_A", "message":"MSG_1", "fieldname":"Signal1"},
{"bus":"BUS_A", "message":"MSG_2", "fieldname":"Signal2"},
{"bus":"BUS_B", "message":"MSG_3", "fieldname":"Signal3"},
{"bus":"BUS_B", "message":"MSG_4", "fieldname":"Signal4"}]
desired_output = {"BUS_A":
{"MSG_1":
{"Signal1":None},
"MSG_2":
{"Signal2":None},
},
"BUS_B":
{"MSG_3":
{"Signal3":None},
"MSG_4":
{"Signal4":None},
}
}
def Unflatten(signals):
buses = {item['bus']:{} for item in signals}
for bus in buses:
for item in signals:
if item['bus'] == bus:
buses[bus] = {item['message']:None}
print(buses)
Unflatten(signals)
CodePudding user response:
Using a defaultdict
saves you a lot of hassle having to conditionally set up the structure of the dictionary:
>>> signals = [{"bus":"BUS_A", "message":"MSG_1", "fieldname":"Signal1"},
... {"bus":"BUS_A", "message":"MSG_1", "fieldname":"Signal2"},
... {"bus":"BUS_B", "message":"MSG_2", "fieldname":"Signal3"},
... {"bus":"BUS_B", "message":"MSG_2", "fieldname":"Signal4"}]
>>> from collections import defaultdict
>>> buses = defaultdict(lambda: defaultdict(dict))
>>> for s in signals:
... buses[s["bus"]][s["message"]][s["fieldname"]] = None
...
>>> buses
defaultdict(<function <lambda> at 0x00000265E25451F0>, {'BUS_A': defaultdict(<class 'dict'>, {'MSG_1': {'Signal1': None, 'Signal2': None}}), 'BUS_B': defaultdict(<class 'dict'>, {'MSG_2': {'Signal3': None, 'Signal4': None}})})
Without defaultdict
you need to initialize each value to an empty dictionary as needed:
>>> buses = {}
>>> buses = {}
>>> for s in signals:
... if s["bus"] not in buses:
... buses[s["bus"]] = {}
... if s["message"] not in buses[s["bus"]]:
... buses[s["bus"]][s["message"]] = {}
... buses[s["bus"]][s["message"]][s["fieldname"]] = None
...
>>> buses
{'BUS_A': {'MSG_1': {'Signal1': None, 'Signal2': None}}, 'BUS_B': {'MSG_2': {'Signal3': None, 'Signal4': None}}}
CodePudding user response:
Here is one approach. It slims down some of the property juggling by starting with an object that already has 2 empty busses. This method is probably inferior to defaultdict
, but it illustrates the logic behind unflattening your signals more directly. I can't pretend to know what you are doing, but this all seems like a bad way to do it, and to some degree pointless. What is the real outcome of this method ~ you assigned None
to a signal name. All the data is already there, changing it's form is probably entirely unnecessary. Your 2 Busses should probably just be separate instantiations of a Bus
class which has methods that can parse and organize the data that is fed to it. If you use a dataclass
you could also use asdict
(when/if necessary) to pass it's properties to whatever is expecting the data as a dict
.
def unflatten(signals) -> dict:
busses = {'BUS_A':dict(), 'BUS_B':dict()}
for obj in signals:
bus, msg, sig = obj['bus'], obj['message'], obj['fieldname']
if not msg in busses[bus]:
busses[bus][msg] = dict()
busses[bus][msg][sig] = None
return busses