Индекс за пределами диапазона при использовании лямбда

Я немного запутался в лямбда-операторах на Python прямо сейчас. Следующий (рабочий) код сортирует список кортежей после количества вхождений первого элемента кортежей над первыми элементами всех кортежей:

tuples = [(2, 1, 8, 4), (3, 4, 8, 1), (3, 8, 1, 4), (4, 1, 8, 3),
              (4, 8, 1, 3), (8, 8, 3, 1), (8, 1, 3, 4), (8, 4, 1, 3),
              (8, 4, 3, 1)]

temp = list(zip(*tuples))    
tuples.sort(key=lambda x: temp[0].count(x[0])
                ,reverse=True)

print(tuples)

Однако, если я сейчас попытаюсь пропустить создание "temp", то напишите это:

tuples = [(2, 1, 8, 4), (3, 4, 8, 1), (3, 8, 1, 4), (4, 1, 8, 3),
              (4, 8, 1, 3), (8, 8, 3, 1), (8, 1, 3, 4), (8, 4, 1, 3),
              (8, 4, 3, 1)]

tuples.sort(key=lambda x: list(zip(*tuples))[0].count(x[0])
                ,reverse=True)

print(tuples)

Выдает ошибку:

Traceback (most recent call last):
  File "E:\Python-Programms\Sorting", line 6, in <module>
    ,reverse=True)
  File "E:\Python-Programms\Sorting", line 5, in <lambda>
    tuples.sort(key=lambda x: list(zip(*tuples)) [0].count(x[0])
IndexError: list index out of range

Почему возникает эта ошибка?

Ответ 1

Если вы использовали функцию ванили и распечатали список во время сортировки, вы увидите, что список очищается во время операции сортировки (AFAIK это относится к CPython). Для пустого списка нет нулевого индекса:

def f(x):
  print (tuples)
  return ...

tuples.sort(key=f ,reverse=True)

[]
[]
[]
[]
[]
[]
[]
[]
[]

Заглядывание в источник CPython оставляет нам полезный комментарий, объясняющий это поведение:

static PyObject *
list_sort_impl(PyListObject *self, PyObject *keyfunc, int reverse)
{
    ...
    /* The list is temporarily made empty, so that mutations performed
     * by comparison functions can't affect the slice of memory we're
     * sorting (allowing mutations during sorting is a core-dump
     * factory, since ob_item may change).
     */
    ...
}

В свою первоначальную проблему, вместо того, чтобы повторно называть list.count, что очень неэффективно, вы можете создать счетчик, а затем использовать его для сортировки:

from collections import Counter

c = Counter([x[0] for x in tuples])
tuples.sort(key=lambda x: c[x[0]], reverse=True)

Ответ 2

Список

list(zip(*tuples))

в вашей функции lambda не является константой - она ​​оценивается снова и снова на каждом этапе сортировки - каждый раз, когда вы вызываете функцию lambda.

1 st шаг сортировки в порядке - функция lambda - это именно то, что вы хотели. Но затем возникает проблема.

Список tuples во время сортировки в нестабильном состоянии, может быть, пуст, может быть, что-то еще - алгоритм сортировки имеет свободу в нем. Его единственная обязанность заключается в том, чтобы отсортированный список был в правильном состоянии после выполнения полного сортировки.

2 nd шаг сортировки оценивает значение вашей функции lambda на основе этого неустойчивого списка - кто знает его текущее значение?

Таким образом, использование отсортированного списка в функции key не очень радует.