1

What I have:

d1=[{'type':'fruit','name':'apple'},{'type':'fruit','name':'orange'},{'type':'vehicle','name':'car'},{'type':'vehicle','name':'bike'}]

What I did:

res=[{i['type']:i['name']} for i in d1]

What I received:

res=>[{'fruit': 'apple'}, {'fruit': 'orange'}, {'vehicle': 'car'}, {'vehicle': 'bike'}]

But What I need:

res=>{'fruit': ['apple','orange'], 'vehicle': ['car','bike']}

How to achieve this? thanks in advance

UPDATE This will be more readable. But I want one liner instead!

u={}
for i in d1:
    if u.get(i['type']):
        u[i['type']].append(i['name'])
    else:
        u[i['type']]=[i['name']]
deceze
  • 510,633
  • 85
  • 743
  • 889
Vanjith
  • 520
  • 4
  • 23
  • I strongly recommend avoiding one-liners for readability. However, if you really need it, @deceze 's answer is excellent. – CFV Jul 10 '20 at 09:11

3 Answers3

3

As for a one-liner, that would have to be something like this:

from itertools import groupby
from operator import itemgetter

res = {k: list(map(itemgetter('name'), v)) for k, v in groupby(sorted(d1, key=itemgetter('type')), itemgetter('type'))}

Which I think is pretty complicated, but there you go…

deceze
  • 510,633
  • 85
  • 743
  • 889
  • Nice, I was trying something similar and didn't know about itemgetter (learned something!). This actually looks worse than it is when you go through it, but I agree with the general sentiment of every one but OP that the one liner is not really efficient nor readable. – Andrew Holmgren Jul 10 '20 at 08:54
1

I would prefer not to use a oneliner here. Something like this maybe

x = {}
for item in d1:
    x.setdefault(item['type'], []).append(item['name'])

As for me it's a bit more readable

If you really insist on oneliner, you can do something like this. But this would produce en empty list like [None, None], which will be thrown away

x = {}
[x.setdefault(item['type'], []).append(item['name']) for item in d1]
  • Readable code means I can do like this, u={} for i in d1: if u.get(i['type']): u[i['type']].append(i['name']) else: u[i['type']]=[i['name']] But I want efficient one liner – Vanjith Jul 10 '20 at 08:41
  • @Vanjith You can replace the `if..else` with the demonstrated `setdefault`, which makes that much simpler. – deceze Jul 10 '20 at 08:45
  • 2
    The onliner recommended is [very anti-Pythonic](https://stackoverflow.com/a/5753614/4985099) – sushanth Jul 10 '20 at 08:47
1

You could try this oneliner:

d1=[{'type':'vehicle','name':'car'},{'type':'fruit','name':'apple'},{'type':'fruit','name':'orange'},{'type':'vehicle','name':'bike'}]

res = {
    k: [d.get('name') for d in d1 if k in d.values()]
    for k in set(list(map(lambda x: x['type'],d1)))
}

print(res)

Or maybe you could try this "two-liner":

d1=[{'type':'fruit','name':'apple'},{'type':'fruit','name':'orange'},{'type':'vehicle','name':'car'},{'type':'vehicle','name':'bike'}]

temp=[{d['type']:d['name']} for d in d1]
res = {k: list(filter(None,[d.get(k) for d in temp]))
    for k in set().union(*temp)
}
print(res)

Second solution is based on Alex Hall's answer.

Outputs:

{'vehicle': ['car', 'bike'], 'fruit': ['apple', 'orange']}
MrNobody33
  • 6,413
  • 7
  • 19