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

Question:

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?

Asked By: Abrar Jahin

||

Answers:

You can’t sort inplace as you need to create a modified list of inner elements first. The best you can do is

[sorted(x) for x in my_list].sort(key=lambda k: (k[0], k[1]))
Answered By: Ehtesham Siddiqui

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
Answered By: norok2

If you really want to do all this in-place and in one-line, you can achieve it with something horrendous like this:

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

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

print(vals)

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

While this does fit your requirements, it is inefficient and impossible to read. I’d strongly suggest doing it properly as noted in other answers.

Answered By: chn