Sorting a list of dicts in python, using another list as a "filter"?
b = [{'id': 'a'}, {'id': 'c'}, {'id': 'b'}, {'id': 'e'}]
I need this to become:
b = [{'id': 'a'}, {'id': 'c'}, {'id': 'e'}, {'id': 'b'}]
This new order is defined by another list.
my_filter = ['a', 'c', 'e', 'b']
...as you can see, the list with dicts now have a sequence of ids that were presented on the my_filter
variable.
I'm capable of reordering this but using a bunch of fors and it's not going to be efficient. Do you know a better way? I already know how to sort a list of dictionaries by values of the dictionary, but I need this order to be defined by another list.
EDIT: my_filter
was named filter
, I changed after Dave Kirby's recommendation since it's a builtin. Since some answers sti开发者_高级运维ll have filter
in them, this edit section is to avoid confusion if you see filter
in some of them.
>>> sorted(b, key=lambda x: filter.index(x['id']))
[{'id': 'a'}, {'id': 'c'}, {'id': 'e'}, {'id': 'b'}]
Keep in mind Ignacio's solution is equivalent to doing the nested for loops you wanted to avoid. i.e. it's n^2. A more efficient solution is as follows:
>>> filterdict = dict((k,i) for i,k in enumerate(filter))
>>> sorted(b, key=lambda x: filterdict[x['id']])
[{'id': 'a'}, {'id': 'c'}, {'id': 'e'}, {'id': 'b'}]
or:
>>> b.sort(key=lambda x: filterdict[x['id']])
>>> b
[{'id': 'a'}, {'id': 'c'}, {'id': 'e'}, {'id': 'b'}]
to sort in-place.
Edit: antonakos' solution is the best (2n) if your ids are unique (which is probably a safe assumption, but you didn't specify so I didn't want to assume). Here's a slightly shorter way of writing what he wrote, in case it helps make it clearer:
>>> d = dict((i['id'], i) for i in b)
>>> [d[key] for key in filter]
[{'id': 'a'}, {'id': 'c'}, {'id': 'e'}, {'id': 'b'}]
order = dict((v, i) for (i,v) in enumerate(filter))
sorted(b, key=lambda x:order[x['id']])
This should be somewhat more efficient that Ignacio's answer since a dictionary lookup is O(1) while index(x) is O(n).
BTW filter is the name of a built-in function, so you should not use that as a variable name.
Build a reverse dictionary and look up the values of the filter sequence:
def order_by_values_for_key(key, dictionaries, values):
rev = {}
for d in dictionaries:
val = d[key]
rev[val] = d
return [ rev[val] for val in values ]
b = [{'id': 'a'}, {'id': 'c'}, {'id': 'b'}, {'id': 'e'}]
filter = ['a', 'c', 'e', 'b']
print order_by_values_for_key('id', b, filter)
Using the dictionary comprehensions of Python3 you can write:
def order_by_values_for_key(key, dictionaries, values):
rev = { d[key] : d for d in dictionaries }
return [ rev[val] for val in values ]
To add to antonakos and Keither's answers (I lack the rep to comment directly), dictionary comprehensions are also available in 2.7, and were about 1/3 faster for this example:
python -mtimeit "{k: i for i,k in enumerate(range(1000))}"
10000 loops, best of 3: 93.5 usec per loop
python -mtimeit "dict((k,i) for i,k in enumerate(range(1000)))"
10000 loops, best of 3: 158 usec per loop
精彩评论