简体   繁体   中英

Python list.sort() and sorted() equivalent

This question is strictly for learning. I doubt I will need to use it since sort() and sorted() exists, but I would like to learn the algorithm/approach used in here.

We have an array like so:

test = [
    [2, 2], 
    [1, 2], 
    [1, 1], 
    [3, 3], 
    [1, 3], 
    [2, 3], 
    [3, 2], 
    [2, 1], 
    [3, 1]
]

I want to sort by the second number, then by the first or vice versa.

Normally I would do it using sorted() :

test = sorted(test, key=lambda x: (x[1], x[0]))

Or in place using list.sort() :

test.sort(key=lambda x: (x[1], x[0]))

How can this be done without using any of the methods above.

The actual implementation is at list_sort_impl .

Here is a pure Python implementation of something similar (though less optimized, etc).

First we need a simple helper function:

def cmp (a, b):
    if a < b:
        return 1
    elif a == b:
        return 0
    else:
        return -1

When comparing two integers, it just does integer comparison. When comparing strings it does string comparison. When comparing lists and tuples, it compares the first slot, then the second slot, then the third, until it finds a non-equal comparison and returns that. (Or finds that they are all equal.)

For demonstration purposes we do not need to implement this since le((1, 2, 3), (1, 3, 2)) already does the right thing. If you want to implement your own class and have it have a different ordering, see https://portingguide.readthedocs.io/en/latest/comparisons.html for how to do that.

And now let's implement a simple sort algorithm. I'll do merge sort and pass in an explicit comparison operator.

def _merge (array1, array2, _le):
    i = 0
    j = 0
    answer = []
    while i < len(array1) and j < len(array2):
        if _le(array1[i], array2[j]):
            answer.append(array1[i])
            i += 1
        else:
            answer.append(array2[j])
            j += 1

    while i < len(array1):
        answer.append(array1[i])
        i += 1

    while j < len(array2):
        answer.append(array2[j])
        j += 1

    return answer

def _sorted (array, _le):
    if len(array) < 2:
        return array.copy()
    else:
        k = len(array) // 2
        return _merge(
                _sorted(array[0:k], _le),
                _sorted(array[k:], _le),
                _le
            )

def mysorted (array, key=None, reversed=False):
    _le = le

    if key is not None:
        # Turn the array into pairs of (to_compare, orig)
        array = [(key(x), x) for x in array]
        # Extract compare first pair
        _le = lambda a, b: le(a[0], b[0])

    if reversed:
        _ge = lambda a, b: a == b or not _le(a, b)
        sorted_array = _sorted(array, _ge)
    else:
        sorted_array = _sorted(array, _le)

    if key is not None:
        # Return the orig values in order.
        return [x[1] for x in sorted_array]
    else:
        # All done
        return sorted_array

If you test it out, mysorted should work just like sorted , only slower.

And you can see, part of the magic is buried in turning the array into to_compare/orig pairs, comparing the part you are supposed to compare, and then extracting the original.

And another part of the magic is in the fact that comparison on tuples cascades from start to finish.

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