简体   繁体   中英

python dictionary sorted by list

I have a list

category = ['Toy','Cloth','Food','Auto']

I also have a dictionary (where first A, B, C... are item names, first element in each list is category and the second is the price.

inventory = {'A':['Food', 5], 'B':['Food', 6], 
          'C':['Auto', 5], 'D':['Cloth', 14], 
           'E':['Toy',19], 'F':['Cloth', 13], 'G':['Toy',20], 'H':['Toy',11]}

I would like this to be sorted first by the order of the category in the list, then secondarily, I would like them to be ordered by the price (while category order maintained) such that the result looks like this...

inventory_sorted = {'G':['Toy',20],'E':['Toy',19], 'H':['Toy',11], 'D':['Cloth', 14], 
                    'F':['Cloth', 13], 'B':['Food', 6],'A':['Food', 5],'C':['Auto', 5],}

Could you please offer me two step process where first is about sorting by the list's category and the second is about sorting (inversely) by the price with the category sorting preserved. If you are using Lambda, please offer me a bit of narrative so that I could understand better. I am new to Lamda expressions. Thank you so much

You cannot sort a Python dict object as they are not ordered. At most, you can produce a sorted sequence of (key-value) pairs. You could then feed those pairs to a collections.OrderedDict() object if you want to have a mapping that includes the order.

Convert your category order to a mapping to get an order, then use that in a sort key together with the price. Since you want your prices sorted in descending order, you need to return the negative price:

cat_order = {cat: i for i, cat in enumerate(category)}
inventory_sorted = sorted(inventory.items(),
                          key=lambda i: (cat_order[i[1][0]], -i[1][1]))

The i argument is passed each key-value pair; i[1] is then the value, and i[1][0] the category, i[1][1] the price.

This produces key-value pairs in the specified order:

>>> category = ['Toy','Cloth','Food','Auto']
>>> inventory = {'A':['Food', 5], 'B':['Food', 6], 
...           'C':['Auto', 5], 'D':['Cloth', 14], 
...            'E':['Toy',19], 'F':['Cloth', 13], 'G':['Toy',20], 'H':['Toy',11]}
>>> cat_order = {cat: i for i, cat in enumerate(category)}
>>> sorted(inventory.items(), key=lambda i: (cat_order[i[1][0]], -i[1][1]))
[('G', ['Toy', 20]), ('E', ['Toy', 19]), ('H', ['Toy', 11]), ('D', ['Cloth', 14]), ('F', ['Cloth', 13]), ('B', ['Food', 6]), ('A', ['Food', 5]), ('C', ['Auto', 5])]
>>> from pprint import pprint
>>> pprint(_)
[('G', ['Toy', 20]),
 ('E', ['Toy', 19]),
 ('H', ['Toy', 11]),
 ('D', ['Cloth', 14]),
 ('F', ['Cloth', 13]),
 ('B', ['Food', 6]),
 ('A', ['Food', 5]),
 ('C', ['Auto', 5])]

An OrderedDict() object directly accepts this sequence:

>>> from collections import OrderedDict
>>> OrderedDict(sorted(inventory.items(), key=lambda i: (cat_order[i[1][0]], -i[1][1])))
OrderedDict([('G', ['Toy', 20]), ('E', ['Toy', 19]), ('H', ['Toy', 11]), ('D', ['Cloth', 14]), ('F', ['Cloth', 13]), ('B', ['Food', 6]), ('A', ['Food', 5]), ('C', ['Auto', 5])])

You can kind of get this with the following:

sorted(inventory.items(), key=lambda t: category.index(t[1][0]))

This works because:

  • inventory.items() turns your dict into a list of tuples, which can retain an order
  • The key function orders based on where t[1][0] appears in your category list, and
  • t will be something like ('G', ('Toy', 20)) so t[1] is ('Toy', 20) and t[1][0] is 'Toy' .

But you cannot go back to a standard dict from this (even though it would be very easy) because you would lose your ordering again, rendering the sort pointless. So you will either have to work with the data in this format, or use something like collections.OrderedDict as already mentioned.

Another completely different way of doing this, which is rather powerful, is to

  1. use the python class to make the data structure,
  2. store the data in a list
  3. sort the list with key=attrgetter('variable')

Here's a snippet of example code:

class Item:
    def __init__(self,label,category,number):
        self.label = label
        self.category = category
        self.number = number
    def __repr__(self):
        return "Item(%s,%s,%d)"%(self.label,self.category,self.number)
    def __str__(self):
        return "%s: %s,%d"%(self.label,self.category,self.number)

inventory = []
inventory.append(Item("A","Food",5))
inventory.append(Item("B","Food",6))
inventory.append(Item("C","Auto",5))
inventory.append(Item("D","Cloth",14))
inventory.append(Item("E","Toy",19))
inventory.append(Item("F","Cloth",13))
inventory.append(Item("G","Toy",20))
inventory.append(Item("H","Toy",11))

inventory.sort(key=attrgetter('number'),reverse=True)
inventory.sort(key=attrgetter('category'))

The advantage of this, is that the sort is designed to maintain the order from the previous sort, so calling it twice (as I've done above) allows you to sort it by category primarily, but sort it by number secondarily. You could do this for as many sort keys as you want.

You can also add whatever other information you want to your Items.

categories = ['Toy','Cloth','Food','Auto']

inventory = {'A':['Food', 5], 'B':['Food', 6], 
          'C':['Auto', 5], 'D':['Cloth', 14], 
           'E':['Toy',19], 'F':['Cloth', 13], 'G':['Toy',20], 'H':['Toy',11]}

from collections import OrderedDict

inventory_sorted = OrderedDict()

for category in categories:
    same_category = [(key, num) for key, (cat, num) in inventory.items() if cat == category] 

    for (key, num) in sorted(same_category, key=lambda (_, num): num, reverse=True):
        inventory_sorted[key] = [category, num]

for key, value in inventory_sorted.items():
    print key, value

Since dictionaries are unordered we will be using OrderedDict to achieve your goal.

How it works:

same_category is a simple list comprehension which filters items if they are the same category as the current loop category, it will form a list consisting of tuples of (key, num) pairs.

then we sort this new list using the number, the line which does this is key=lambda (_, num): num , this unpacks the tuple, discards the key using _ , and sort by the num , we reverse it so it shows high numbers first.

then we add each of those [category, num] pairs to to the OrderedDict inventory_sorted in the key .

Result:

G ['Toy', 20]
E ['Toy', 19]
H ['Toy', 11]
D ['Cloth', 14]
F ['Cloth', 13]
B ['Food', 6]
A ['Food', 5]
C ['Auto', 5]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM