-3

Given a nested list of tuples (this is the output of an itertools.groupby operation on the 0th element:

l = [[(95, 'studentD')], [(97, 'studentB')], [(98, 'studentA'), (98, 'studentC')]]

Is there an easy way to obtain this?

{ 95 : 'studentD', 97 : 'studentB', 98: ['studentA', 'studentC']}

Note single elements are not placed within a list. Also, I can guarantee that all tuples in an inner list have the same x[0].

This is my solution:

In [203]: d = {}

In [204]: for x in l:
     ...:     d.update({x[0][0] : ([y for _, y in x] if len(x) > 1 else x[0][1]) })
     ...:     

In [205]: d
Out[205]: {95: 'studentD', 97: 'studentB', 98: ['studentA', 'studentC']}

Are there more elegant options that I am missing?

cs95
  • 379,657
  • 97
  • 704
  • 746
  • Why not go for a consistent structure? – Ashwini Chaudhary Aug 02 '17 at 17:34
  • Actually using this dictionary would be a real pain. I was perhaps too charitable in my assumption that this came from a legitimate use case. :-) I'm not sure there's a good reason to close the question, so I'm just going to downvote. – user94559 Aug 02 '17 at 17:40
  • @smarx It is. I am trying to see if there is a better alternative to my current solution to use [here](https://stackoverflow.com/a/45467300/4909087). What makes you think this is not a legitimate use case? – cs95 Aug 02 '17 at 17:41
  • As I said, using such a dictionary would be really difficult. The highest rated solution in the question you linked to creates a much more useful data structure (a dictionary of lists). – user94559 Aug 02 '17 at 17:43
  • @smarx Sure, there are better alternatives for that particular problem, but just because it isn't useful to you doesn't mean it's useless to everyone. Thanks for answering. – cs95 Aug 02 '17 at 18:01
  • One way in `pandas` would be `pd.DataFrame([y for x in l for y in x]).groupby(0)[1].apply(list).apply(lambda x: x if len(x)>1 else x[0]).to_dict()` – Zero Aug 05 '17 at 17:22

5 Answers5

2

Better to do the grouping directly from the results of itertools.groupby, and not try to reprocess the entire result into a dict.

Starting from a copy of the original problem:

from itertools import groupby

l = [(95, 'studentD'), (97, 'studentB'), (98, 'studentA'), (98, 'studentC')]

d = {}
for k, g in groupby(l, lambda x: x[0]):
    g = [x for _, x in g]
    d[k] = g[0] if len(g) == 1 else g

print d
# {97: 'studentB', 98: ['studentA', 'studentC'], 95: 'studentD'}
Moses Koledoye
  • 77,341
  • 8
  • 133
  • 139
  • 1
    This solution works because list `l` is already sorted -- that's not the case in the *original* problem: `l = [(98, 'studentA'), (97, 'studentB'), (98, 'studentC'), (95,'studentD')]` Unsorted, this solution produces the wrong result. – cdlane Aug 02 '17 at 20:27
1

If you must reprocess, and are looking for elegant, if not Pythonic, how about:

>>> l = [[(95, 'studentD')], [(97, 'studentB')], [(98, 'studentA'), (98, 'studentC')]]
>>> 
>>> d = {}
>>> 
>>> for x in l:
...     grades, students = zip(*x)
...     d[grades[0]] = students if len(students) > 1 else students[0]
... 
>>> print(d)
{95: 'studentD', 97: 'studentB', 98: ('studentA', 'studentC')}
>>> ^D

But if we go back to the groupby() step, we could do something nasty like:

from itertools import groupby

l = [(98, 'studentA'), (97, 'studentB'), (98, 'studentC'), (95,'studentD')]

d = {k: [g] + [x for _, x in rest] if rest else g for k, ((_, g), *rest) in groupby(sorted(l), lambda x: x[0])}

print(d)

Using assignment to structure to simplify things a touch.

cdlane
  • 40,441
  • 5
  • 32
  • 81
0

Here is another idea whitout using groupby

import collections
l = [(95, 'studentD'), (97, 'studentB'), (98, 'studentA'), (98, 'studentC')]
result_dict = collections.defaultdict(list)
for x, y in l:
    result_dict[x].append(y)
Omar Ham
  • 130
  • 7
0

If you can accept that all items are in lists: Flatten + defaultdict

from collections import defaultdict

l = [[(95, 'studentD')], [(97, 'studentB')], [(98, 'studentA'), (98, 'studentC')]]
flat = tuple(item for sublist in l for item in sublist)

d = defaultdict(list)

for k,v in flat:
    d[k].append(v)

Gives:

defaultdict(list,
            {95: ['studentD'], 97: ['studentB'], 98: ['studentA', 'studentC']})

Can remove those with:

for k,v in d.items():
    if len(v) == 1:
        d[k] = v[0]
Anton vBR
  • 18,287
  • 5
  • 40
  • 46
-5
L = [[(95, 'studentD')], [(97, 'studentB')], [(98, 'studentA'), (98, 'studentC')]]

answer = {}
for subl in L:
    for grade, student in subl:
        if grade not in answer:
            answer[grade] = []
        answer[grad].append(student)
inspectorG4dget
  • 110,290
  • 27
  • 149
  • 241
  • Not the dver, but this retains all elements as lists. I would prefer to keep single elements as they are. Alternatively you could use a default dict for this, considerably shortening your code. – cs95 Aug 02 '17 at 17:38