Search code examples
pythonarrayslistalgorithmlambda

Custom 2D list sort with lambda expression (in 1 line) in Python


I have a python 2D list like this-

[[3,4],[1,2],[2,1],[6,5]]

I like to have it sorted in both direction, row and column-wise. So, my desired output would be like this-

[[1, 2], [1, 2], [3, 4], [5, 6]]

What I have done is-

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

And what I am getting is-

[[2, 1], [1, 2], [3, 4], [6, 5]]

Can anyone please help to sort it in-place with lambda expression?


Solution

  • The key parameter (and lambda) is not meant to modify the content of the list. Instead, it is meant to be used for sorting according to how each element evaluates when the key function is applied. However, you can use key's side-effects to achieve what you are after by calling .sort() on your key's function's argument. Since .sort() result is just None, you also need to provide the statement itself to be used for the actual sorting:

    l = [[3,4],[1,2],[2,1],[6,5]]
    
    l.sort(key=lambda x: (x.sort(), x))
    
    print(l)
    # [[1, 2], [1, 2], [3, 4], [5, 6]]
    

    This is not considered good programming practice, though.

    A cleaner and much more efficient, but obviously not 1-line, the approach would be:

    l = [[3,4],[1,2],[2,1],[6,5]]
    for x in l:
        x.sort()
    l.sort()
    
    print(l)
    # [[1, 2], [1, 2], [3, 4], [5, 6]]
    

    The key based approach is also significantly less efficient since it tries to sort the inner lists at each key call which we should expect to occur n log n times, against the n times strictly required, thus some of the inner lists is inevitably being sorted more than necessary. Instead, looping through the outer list explicitly sort each internal list once.

    Just to give some ideas of the timings:

    import random
    import copy
    import collections
    
    
    def double_sort_loop(seq):
        for x in seq:
            x.sort()
        seq.sort()
    
    
    def double_sort_deque(seq):
        collections.deque(map(lambda x: x.sort(), seq), maxlen=0)
        seq.sort()
    
    
    def double_sort_key(seq):
        seq.sort(key=lambda x: (x.sort(), x))
    
    
    def gen_input(n, m, v_min=None, v_max=None):
        if v_min is None:
            v_min = 0
        if v_max is None:
            v_max = v_min + (2 * m * n)
        return [[random.randint(v_min, v_max) for _ in range(m)] for _ in range(n)]
    
    
    random.seed(0)
    
    base = gen_input(100000, 10)
    
    %timeit seq = copy.deepcopy(base); double_sort_loop(seq)
    # 1 loop, best of 3: 1.03 s per loop
    %timeit seq = copy.deepcopy(base); double_sort_deque(seq)
    # 1 loop, best of 3: 1.02 s per loop
    %timeit seq = copy.deepcopy(base); double_sort_key(seq)
    # 1 loop, best of 3: 1.19 s per loop