4

I am wondering if there is a more elegant/pythonic way to do the following. Suppose I have a nested dictionary:

orders = {'peter': {'food': 'pizza', 'drink': 'soda'}, 'paul': {'food': 'taco', 'drink': 'soda'},'mary': {'food': 'pizza', 'drink': 'water'}}

and I want to obtain a list containing the unique 'food' items for each person, I.e. ['pizza', 'taco']

is this the easiest way to do it?

foodList = []
for i in orders.keys():
    foodList.append(orders[i]['food'])
s = set(foodList)
Rashwan L
  • 38,237
  • 7
  • 103
  • 107
laszlopanaflex
  • 1,836
  • 3
  • 23
  • 34

4 Answers4

8

Use a set comprehension.

>>> {orders[i]['food'] for i in orders}
{'pizza', 'taco'}

If the values for food are a list or a tuple you can use a nested loop in the set comprehension.

>>> orders = {'peter': {'food': ['pizza','fries'], 'drink': 'soda'}, 'paul': {'food': ['taco'], 'drink': 'soda'}}
>>> {j for i in orders for j in orders[i]['food']}
{'pizza', 'taco', 'fries'}

You can even use operator.itemgetter as mentioned by Padraic.

>>> from operator import itemgetter 
>>> set(map(itemgetter("food"), orders.values()))
{'pizza', 'taco'}

Similarly if the values for food are a list, you can use chain.

>>> from itertools import chain
>>> set(chain(*map(itemgetter("food"), orders.values())))
{'pizza', 'taco', 'fries'}
Community
  • 1
  • 1
Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
  • thanks! what if the value were a list though instead of a string? is there such a thing as "unique" lists in python? – laszlopanaflex Dec 05 '15 at 20:39
  • @laszlopanaflex Not sure if I understood your problem. But if you want a list, change the `{` and `}` to `[]`. It is called a list comprehension. The set is one datastructure where the values are unique, lists aren't. Also see [How do you remove duplicates from a list in Python whilst preserving order?](http://stackoverflow.com/q/480214). Hope it helps. – Bhargav Rao Dec 05 '15 at 20:42
  • If you get a list of food instead of a single food: `set(reduce(lambda x,y:x + y, [orders[i]['food'] for i in orders], []))` – Lorenzo Peña Dec 05 '15 at 20:47
  • what i meant was, suppose instead of 'pizza' and 'taco' i instead had ['pizza', 'taco'] and ['pizza', 'taco'], i.e. each person wanted both items. so, what i'm wondering is can you obtain the unique lists if you see what i'm saying – laszlopanaflex Dec 05 '15 at 20:47
  • @laszlopanaflex For that you will need to add another level of integration. There you need to add those to the set too. – Bhargav Rao Dec 05 '15 at 20:49
  • Or `set(map(itemgetter("food"), orders.values()))` if you prefer functional – Padraic Cunningham Dec 05 '15 at 21:50
  • For bonus points if the values are lists or tuples `set(chain(*map(itemgetter("food"), orders.values())))` – Padraic Cunningham Dec 05 '15 at 22:01
  • @PadraicCunningham Done. Now feeling hungry after seeing the food :-( – Bhargav Rao Dec 05 '15 at 22:06
  • Just about to watch a movie and pig out myself, I will have some for you too ;) – Padraic Cunningham Dec 05 '15 at 22:08
  • Hehe @Padraic Enjoy. I just finished my food now. :D – Bhargav Rao Dec 05 '15 at 22:44
0

You can use defaultdict- It addresses the list value.

>>>import collections
>>>from collections import defaultdict
>>>d =defaultdict(list)
>>>orders = {'paul': {'food': ['taco', ['sugar']], 'drink': 'soda'}, 'peter': {'food': 'pizza', 'drink': 'soda'}, 'mary': {'food': 'pizza', 'drink': 'water'}}

>>>for i in orders:
    d['food'].append(orders[i]['food'])
>>>def flatten(l):#borrowed
    for el in l:
        if isinstance(el, collections.Iterable) and not isinstance(el, basestring):
            for sub in flatten(el):
                yield sub
        else:
            yield el
>>>list(set(flatten(d.values())))
>>>['sugar', 'pizza', 'taco']
Learner
  • 5,192
  • 1
  • 24
  • 36
0

If you get a list of food instead of a single food:

set(reduce(lambda x,y:x + y, [orders[i]['food'] for i in orders], []))
Lorenzo Peña
  • 2,225
  • 19
  • 35
0

If the values you're collecting are "immutable", you can use a set comprehension to collect the different ones. @BhargavRao already suggested this, but here's a cleaner solution:

foods = set( val["food"] for val in orders.values() )

You also ask (in a comment) about what to do if the values are lists. If a value is ["pizza", "taco"], I assume you want "pizza" and "taco" in the set, not ["pizza", "taco"] as an element. It's a bit tricky if some elements are lists and some are strings (poor design; it's better to have lists everywhere, even if it's just one element), so I'd do it stepwise, like this:

foods = set()
for val in orders.values():
    eat = val["food"]
    if isinstance(eat, str):
        foods.add(eat)      # one item
    else:
        foods.update(eat)   # list or tuple
alexis
  • 48,685
  • 16
  • 101
  • 161