Skyline969
Skyline969

Reputation: 431

Custom sort on a dictionary in Python?

I want to have a custom sort on a dictionary that I generate in a nested loop. I know that five records will always be there, but there could be others based on the data I go through. I essentially want those five records in a specific order, and then the order does not matter for all records after those five.

For example, I want this:

{"Entries": [], "Groups": [], "Admin": [], "Network": [], "XYZ": [], "Subnets": []}

to be sorted to this:

{"Admin": [], "Groups": [], "Network": [], "Subnets": [], "Entries": [], "XYZ": []}

Therefore, Admin, Groups, Network, Subnets, and Entries are the five that I want in that specific order at the beginning of the dictionary, and after that the remaining entries' order does not matter. How can I do this?

Upvotes: 1

Views: 4557

Answers (5)

Aristide
Aristide

Reputation: 3984

I had exactly the same problem and devised a lightweight general solution:

from collections import OrderedDict

def make_custom_sort(orders):
    orders = [{k: -i for (i, k) in enumerate(reversed(order), 1)} for order in orders]
    def process(stuff):
        if isinstance(stuff, dict):
            l = [(k, process(v)) for (k, v) in stuff.items()]
            keys = set(stuff)
            for order in orders:
                if keys.issuperset(order):
                    return OrderedDict(sorted(l, key=lambda x: order.get(x[0], 0)))
            return OrderedDict(sorted(l))
        if isinstance(stuff, list):
            return [process(x) for x in stuff]
        return stuff
    return process

First, you create an instance of a custom-order sorting function:

custom_sort = make_custom_sort([ ["Admin", "Groups", "Network", "Subnets", "Entries"] ])

Now, the actual sorting:

result = custom_sort(my_dataset)

The missing keys are rejected at the end in an unspecified order. Note that this closure is recursive. As indicated by the double brackets, you could specify as many sort orders as the various dictionaries nested in your structure would require.

Project on GitHub: https://github.com/laowantong/customsort

Upvotes: 0

Skyline969
Skyline969

Reputation: 431

I've re-thought out how I approach the entire situation, and found a method that works for me.

With a dataset like the following:

{"Entries": [], "Groups": [], "Admin": [], "Network": [], "XYZ": [], "Subnets": []}

I just do the following, which works fine for me:

for menu in ["Admin", "Groups", "Network", "Subnets", "Entries"]:
    try:
        doWork(my_dataset[menu])
        del my_dataset[menu]
    except KeyError:
        # The user might not have any entries for that menu, so don't worry about it
        pass
for menu in my_dataset.keys():
    doWork(my_dataset[menu])

So basically, do what I need to do with the five items and delete them from the dataset. I'm comfortable with doing this, since I'm not using said dataset after this block of code. I don't delete the remaining ones after I do work on them, because that's unnecessary. Garbage collection will just blow away the whole dataset after the function's been completed, right?

Upvotes: 0

Vajk Hermecz
Vajk Hermecz

Reputation: 5702

You need collections.OrderedDict for that...

So, based on that, the solution is something like:

def custom_order(dct, spec):
    """
    dct - the dictionary
    spec - a list of dedicated keys
    """
    res = collections.OrderedDict()
    dct = dict(dct)
    for key in spec:
        if key in dct:
            res[key] = dct.pop(key)
    res.update(dct.items())
    return res

Upvotes: 3

Abhijit
Abhijit

Reputation: 63727

You first need to

  • create a list of (key, value) pairs by getting the list of items
  • Custom Sort the generated list
  • Create an OrderedDict based on the sorted result

Here is an example where I sort your data based on the length of the key

>>> from collections import OrderedDict
>>> some_dict = {"Entries": [], "Groups": [], "Admin": [], "Network": [], "XYZ": [], "Subnets": []}
>>> some_dict = OrderedDict(sorted(some_dict.items(),key = lambda e:len(e[0])))
>>> some_dict
OrderedDict([('XYZ', []), ('Admin', []), ('Groups', []), ('Subnets', []), ('Network', []), ('Entries', [])])

Upvotes: 4

Katriel
Katriel

Reputation: 123632

Store the data as a list of pairs.

Upvotes: 1

Related Questions