sort a python list while maintaining its element's indices

2.9k Views Asked by At

I need to sort a python list but the sort should not alter the index the elements had before the sort. There's a function called asort for this in php, but how to do it in python? Is there such a method? If not, how to do it in fewer line?

For instance, take following example:

$a = [1, 5, 2, 4];
print_r($a);
asort($a);
print_r($a);

Output will be:

Array
(
    [0] => 1
    [1] => 5
    [2] => 2
    [3] => 4
)
Array
(
    [0] => 1
    [2] => 2
    [3] => 4
    [1] => 5
)

Here 5 still has index 1 after asort but it has been sorted to the end.

I do have my own solution, which is: Create the indices as a list of integers. Then use [List Length] and [Series] to create this list. Then, sort both numeric values and indices synchronously, i.e. using the same Sort component. Although, this is not exactly what asort() does, but pretty close.

Is there a better way to do it? That too in fewer lines? Maybe a built-in method? An efficient way can be preferred over fewer lines.

Thanks

2

There are 2 best solutions below

2
On BEST ANSWER

The primary difference between PHP and Python is that PHP arrays are both associative and ordered, while in Python you can either have an ordered list or an associative dict, but not both in one data structure.*

* There's OrderedDict and dict is starting to become ordered itself, but let's stick with the primitives.

So fundamentally you'll need to think of different data structures. The typical way to do this is to have a list of tuples, with one tuple value representing your former index and the other value the former value:

[(0, 'foo'), (1, 'bar'), ...]

You can get to that from a normal list using enumerate:

l = list(enumerate(my_list))  # ['foo', 'bar'] → [(0, 'foo'), (1, 'bar')]

And you can sort it in the process:

l = sorted(enumerate(my_list), key=lambda i: i[1])  # → [(1, 'bar'), (0, 'foo')]

Here lambda i: i[1] simply sorts by the second tuple value, i.e. your former value. You can replace that with itemgetter(1) from the operator module for brevity, or adjust as necessary for your sorting condition.

If you want to turn that into an associative data structure again, use OrderedDict:

from collections import OrderedDict
from operator import itemgetter

l = OrderedDict(sorted(enumerate(my_list), key=itemgetter(1)))
0
On

First you connect the original values to its index list. Then you sort it and the original indexes are attched to the new sorted values. Finally unzip them to get both lists.

i_xs                 = [(x, i) for (i, x) in enumerate(xs)]
s                    = sorted(i_xs)
sorted_xs, index_lst = unzip(s)

Using this

def unzip(ls):
    if isinstance(ls, list):
        if not ls:
            return [], []
        else:
            xs, ys = zip(*ls)

        return list(xs), list(ys)
    else:
        raise TypeError

Example. In:

[34, 23424, 1212, -2324, 34353]

Out:

[-2324, 34, 1212, 23424, 34353]

[3, 0, 2, 1, 4]