2

I have a 2-dimensional list of named tuples (let's say that each tuple has N values), and I want to unpack them into N different 2-dimensional lists where each unpacked 2-D list is composed entirely of a single attribute from the original list. For example if I have this 2-D list:

>>> combo = namedtuple('combo', 'i, f, s')
>>> combo_mat = [[combo(i + 3*j, float(i + 3*j), str(i + 3*j)) for i in range(3)]
                 for j in range(3)]
>>> combo_mat
[[combo(i=0, f=0.0, s='0'), combo(i=1, f=1.0, s='1'), combo(i=2, f=2.0, s='2')],
 [combo(i=3, f=3.0, s='3'), combo(i=4, f=4.0, s='4'), combo(i=5, f=5.0, s='5')],
 [combo(i=6, f=6.0, s='6'), combo(i=7, f=7.0, s='7'), combo(i=8, f=8.0, s='8')]]

I'd like the 3 results to be:

[[0, 1, 2],
 [3, 4, 5],
 [6, 7, 8]]

[[0.0, 1.0, 2.0],
 [3.0, 4.0, 5.0],
 [6.0, 7.0, 8.0]]

[['0', '1', '2'],
 ['3', '4', '5'],
 ['6', '7', '8']]

If I just had a 1-dimensional list of tuples I'd use zip(*mylist), like:

>>> zip(*[combo(i=0, f=0.0, s='0'), combo(i=1, f=1.0, s='1'), combo(i=2, f=2.0, s='2')])
[(0, 1, 2), (0.0, 1.0, 2.0), ('0', '1', '2')]

And I can extend this to my situation just by nesting:

>>> zip(*[zip(*combs) for combs in combo_mat])
[((0, 1, 2),
  (3, 4, 5),
  (6, 7, 8)),
 ((0.0, 1.0, 2.0),
  (3.0, 4.0, 5.0),
  (6.0, 7.0, 8.0)),
 (('0', '1', '2'),
  ('3', '4', '5'),
  ('6', '7', '8'))]

But this doesn't give me the lists I wanted, and nested unpacking zip(*) functions isn't that readable. Anyone have any ideas for a more pythonic solution? Bonus points if you can work the names of the tuples' attributes in there somewhere in the end result.

Actually, now that I think of it, it would be ideal if I could have a dict that mapped the name of the tuple attribute to its respective matrix, like:

{'i': [[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]], 
 'f': [[0.0, 1.0, 2.0],
       [3.0, 4.0, 5.0],
       [6.0, 7.0, 8.0]]
 's': [['0', '1', '2'],
       ['3', '4', '5'],
       ['6', '7', '8']]}
machine yearning
  • 9,889
  • 5
  • 38
  • 51

4 Answers4

3

Functional programming to the rescue? It's essentially a cleaner version of nesting the zips:

def fmap_element(f, el):
    return f(el)

def fmap_list(f, l):
    return [fmap_element(f, el) for el in l)]

def fmap_lol(f, lol):
    return [fmap_list(f,l) for l in lol]

def split_nt_lol(nt_lol):
    return dict((name, fmap_lol(lambda nt: getattr(nt, name), nt_lol)) 
                for name in nt_lol[0][0]._fields)

Usage:

>>> split_nt_lol(combo_mat)
{'i': [[0, 1, 2], [3, 4, 5], [6, 7, 8]], 
 's': [['0', '1', '2'], ['3', '4', '5'], ['6', '7', '8']], 
 'f': [[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]]}
Claudiu
  • 224,032
  • 165
  • 485
  • 680
2
matrices = {}

for num, name in enumerate(combo._fields):
    matrix = []
    for i in combo_mat:
        row = []
        for j in i:
            row.append(j[num])
        matrix.append(row)
    matrices[name] = matrix
kindall
  • 178,883
  • 35
  • 278
  • 309
  • Aha good one, I didn't know namedtuples had a `_fields` attribute. – machine yearning Aug 08 '11 at 22:21
  • But is there a more direct way of accessing attribute name, value pairs? Maybe I should be storing in a dictionary. – machine yearning Aug 08 '11 at 22:22
  • It's got to store that somewhere... you should be careful, of course, since they could change the implementation at some point. It might be better to make your own namedtuple subclass that also stores the field names somewhere you have control of them. – kindall Aug 08 '11 at 22:27
  • Again leaning more towards dictionaries after this point. They're never gonna give me up, never gonna let me down! – machine yearning Aug 08 '11 at 22:36
  • In that case perhaps you should use a ricktionary. – kindall Aug 10 '11 at 17:53
1

This isn't any more readable than nested zip(*) functions, but it does the job:

>>> dict((key, [[c._asdict()[key] for c in combos] for combos in combo_mat])
         for key in ['i', 'f', 's'])
{'i': [[0, 1, 2], [3, 4, 5], [6, 7, 8]], 
 's': [['0', '1', '2'], ['3', '4', '5'], ['6', '7', '8']], 
 'f': [[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]]}

I suppose you could make it a bit easier to read like so:

def get_mat(combo_mat, key):
    return [[c._asdict()[key] for c in combos] for combos in combo_mat]

def get_mat_dict(combo_mat, keys):
    return dict((key, get_mat(combo_mat, key)) for key in keys)
senderle
  • 145,869
  • 36
  • 209
  • 233
0

This can be simply achieved by transposing the array, using transpose().

Reference: http://www.astro.ufl.edu/~warner/prog/python.html (search for the word "transpose")