2

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?

Abrar Jahin
  • 13,970
  • 24
  • 112
  • 161
  • you cannot sort both your list as well the elements with single lamda expression used in key. You can use listcomprehension to sort each element and then use sort() on the new list to sort the whole list. Of course it could be in one line – buran Jul 18 '20 at 07:30
  • 1
    Why would the sort “get the key” function *change* an entry? It won’t - and shouldn’t as things may break if this is actually done with the sort functions. You might want to *sort each pair as it’s own collection* (see list comprehension) in a the containing collection, and then *sort the collection of (internally sorted) pairs*. – user2864740 Jul 18 '20 at 07:30
  • 1
    What is the purpose of `key = lambda x: (x[1], x[0])`? – Idea O. Jul 20 '20 at 09:57

3 Answers3

2

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
Abrar Jahin
  • 13,970
  • 24
  • 112
  • 161
norok2
  • 25,683
  • 4
  • 73
  • 99
1

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.

Cihan
  • 2,267
  • 8
  • 19
  • 1
    What is the point of having a double lambda expression? The actual job is being done by `x.sort()` anyway. To have the outer list sorting only after the inner list sorting, you just need to call it before `x`. Therefore, you can simplify the code quite a bit and use a single function call. – norok2 Jul 20 '20 at 06:05
0

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]))
  • 1
    that's not what OP asks for. and is overcomplicated. same could be done with `sorted_list = sorted([sorted(item) for item in my_list])` – buran Jul 18 '20 at 07:33
  • Hi, I like to have `in-pace` sorting with no extra space (https://stackoverflow.com/a/16585637/2193439), is that possible? – Abrar Jahin Jul 18 '20 at 07:35
  • @EhteshamSiddiqui, my code will produce `[[1, 2], [1, 2], [3, 4], [5, 6]]` - exactly what OP wants. – buran Jul 18 '20 at 07:43
  • @EhteshamSiddiqui, you are completely confused. with input `[[2, 1], [1, 1]]` the result will be `[[1, 1], [1, 2]]` - as expected. – buran Jul 18 '20 at 08:10