jezrael
jezrael

Reputation: 862601

Flatten list of nested namedtuples to list of dictionaries

Sample:

import collections

A = collections.namedtuple('A', 'a b c')
B = collections.namedtuple('B', 'x y z w')

L = [A(a='CODE1',  b=B(x=2, y='u', z='v', w='a'), c=10),
     A(a='CODE2',  b=B(x=4, y='h', z='r', w='b'), c=30)]

I have list of namedtuples and I want to flatten it, replace nested namedtuple B by values of b.

What I try:

First I try convert both namedtuples to dictionaries and merge them, but problem was remove original b values in dict comprehension and also ordering is wrong:

t = [{k: v for k, v in {**x.b._asdict(), **x._asdict()}.items() 
      if k != 'b'} for x in L]
print (t)

[{'x': 2, 'y': 'u', 'z': 'v', 'w': 'a', 'a': 'CODE1', 'c': 10}, 
 {'x': 4, 'y': 'h', 'z': 'r', 'w': 'b', 'a': 'CODE2', 'c': 30}]

Another solution working like need, but it is a bit complicated in my opinion - I prefer list comprehension solution:

t = []
for x in L:
    out = {}
    d = x._asdict()
    for k, v in d.items():
        if isinstance(v, tuple):
            d1 = v._asdict()
            for k1, v1 in d1.items():
                out[k1] = v1
        else:
            out[k] = v
    t.append(out)

print (t)

[{'a': 'CODE1', 'x': 2, 'y': 'u', 'z': 'v', 'w': 'a', 'c': 10}, 
 {'a': 'CODE2', 'x': 4, 'y': 'h', 'z': 'r', 'w': 'b', 'c': 30}]

Upvotes: 4

Views: 1099

Answers (2)

Benjamin schwarz
Benjamin schwarz

Reputation: 109

To my knowledge there is no ordering in a dictionary. If ordering is important for you, you might consider using an OrderedDict from the Collections package (see the manual). Btw, it seems namedtuples are built on OrderedDicts...

Concerning your question, maybe you can create another type of namedtuples in order to force your order:

AB = collections.namedtuple('AB', 'a x y z w c')

and then you can explicitly transcode as desired:

list(map( lambda l: AB(l.a, l.b.x, l.b.y, l.b.z, l.b.w, l.c) , L)) 

and if for some reason you don't want to explicitly write B's field, you can automate a bit with

AB(L[0].a,*[v for v in L[0].b._asdict()],L[0].c)

Edit to embed comments and answers to comments ------------

import collections
from itertools import chain

A = collections.namedtuple('A', 'a b c')
B = collections.namedtuple('B', 'x y z w')

L = [A(a='CODE1',  b=B(x=2, y='u', z='v', w='a'), c=10),
     A(a='CODE2',  b=B(x=4, y='h', z='r', w='b'), c=30)]

AB = collections.namedtuple('AB',
" ".join(chain.from_iterable(
        v._asdict().keys() if isinstance(v, (A, B)) else [k] for k,v in L[0]._asdict().items()) 
    )
)
LL = [AB._make(dict(
    chain.from_iterable( 
        v._asdict().items() if isinstance(v, (A, B)) else [(k, v)] for k, v in nt._asdict().items())
    )) for nt in L] 
print (LL)

That's all I can see for now, hopes this helps.

Upvotes: 2

RomanPerekhrest
RomanPerekhrest

Reputation: 92854

With complex list comprehensions and OrderedDict object:

from collections import namedtuple, OrderedDict
from itertools import chain

...
res = [OrderedDict(chain.from_iterable(
                   v._asdict().items() if isinstance(v, (A, B)) else [(k, v)]
                   for k, v in nt._asdict().items())) for nt in L]
print(res)

The output:

[OrderedDict([('a', 'CODE1'),
              ('x', 2),
              ('y', 'u'),
              ('z', 'v'),
              ('w', 'a'),
              ('c', 10)]),
 OrderedDict([('a', 'CODE2'),
              ('x', 4),
              ('y', 'h'),
              ('z', 'r'),
              ('w', 'b'),
              ('c', 30)])]

The "trick" is to obtain a flat sequence of tuples passed to OrderedDict(...) (top-level tuples is wrapped [(k, v)] to agree on potential nested tuples)

Upvotes: 3

Related Questions