Лексикографически сортировать глубоко вложенные списки смешанных типов данных в Python 3

#python #list #sorting #python-3.x #comparison

#python #Список #сортировка #python-3.x #сравнение

Вопрос:

В Python 3 list.sort() метод будет выполнять лексикографическую сортировку. Но в Python 3 сравнение списка с a float или int выдает a TypeError , в отличие от Python 2, где вы можете это сделать:

 >>> [0, 1] < 2
False
  

Каков наилучший способ добиться старого поведения Python 2?

Я пробовал подклассы list , но для этого каждый из вложенных списков должен быть приведен к типу подкласса, чтобы все вложенные сравнения использовали переопределенные методы сравнения. Есть ли способ добиться этого, который не прибегает к рекурсивному преобразованию каждого вложенного списка в подкласс?

Я хотел бы иметь возможность сравнивать два списка следующим образом:

 >>> a = [[[0, 1], [2, 3]], [0, 1]]
>>> b = [[0, 1], [2, 3]]
>>> a < b
False
  

Результат должен быть False , потому a[0][0] что является a list и b[0][0] является an int , и в моем случае int s всегда следует считать меньше a list .

Редактировать:

Я хочу реализовать функцию сортировки, идентичную встроенной в Python 3 list.sort , за исключением случаев, когда a list сравнивается с a float или int , и в этом случае list всегда следует считать больше.

Комментарии:

1. Знаете ли вы о том, что сравнения python2 в значительной степени бессмысленны? Все list s больше, чем все int s, потому l что идет после i … вам действительно нужна такая сортировка?

2. Как уже было сказано, сравнения такого рода бессмысленны. Что именно вы пытаетесь сравнить (сумму всех элементов, количество элементов, глубину вложенности)? Другими словами, какое свойство первого списка делает его больше, чем второй список, для ваших целей?

3. @SiHa, немного прояснил ситуацию в моей правке.

4.Хм. Как говорит Дэн, сложность заключается в написании ключевой функции, которая возвращает один и тот же тип независимо от того, вызывается ли она с int помощью , float , или list , а также будет корректно оцениваться list как> int а также по-прежнему работать для «обычных» сравнений (9> 3, b> a)

Ответ №1:

Поскольку, как упоминалось в документах Python 2:

Большинство других объектов встроенных типов сравниваются неравнозначно, если они не являются одним и тем же объектом; выбор, считать ли один объект меньше или больше другого, производится произвольно, но последовательно в течение одного выполнения программы.

сравнение объектов имеет смысл только тогда, когда два объекта имеют один и тот же тип. Полагаться на значение, возвращаемое выражениями, [0, 1] < 2 которые не должны выполняться в программе, и именно поэтому это поведение было удалено из Python 3.

Чтобы объяснить это подробнее, если у вас есть список [[[0, 1], [2, 3]], [0, 1]] , он состоит из двух элементов:
[[0, 1], [2, 3]] and [0, 1] . Для того, чтобы python мог их сортировать, он сравнивает их внутренние значения лексикографически, поскольку оба являются списками со значениями [0, 1] and [2, 3] для первого и 0 and 1 для второго. Но тогда он должен сравнивать [0, 1] with 0 , которые не имеют одного и того же типа, и, таким образом, сравнение дает произвольные результаты.

Итак, эта сортировка нарушена.

Сказав выше, если у вас есть некоторые списки, которые можно сортировать осмысленно, а некоторые — нет (из-за приведенного выше объяснения), простым решением является перехват возможного исключения, а затем возврат False .

 try:
    [0, 1] < 2
except TypeError:
    # return or assign False. True is not actually meaningful.
  

или, для list.sort()

 try:
    x.sort()
except TypeError:
    pass    # Do nothing. Python would produce meaningless results, anyway.
  

Если вы хотите произвести осмысленную сортировку (если это действительно имеет смысл), то вам нужно будет определить ключевую функцию, как уже упоминалось. Однако это может быть довольно сложным. Возможно, будет лучше взглянуть на вашу проблему с другой точки зрения.

Комментарии:

1. Моя проблема возникла в ходе перевода списков в определенный вид «нормальной формы»; для этой цели достаточно рассмотреть один int или float , как всегда, меньше списка, независимо от его содержимого. Объекты разных типов можно сравнивать осмысленно, если сравнение четко определено , что и является отношением, которое я только что описал.

2. Хорошо, я думаю о решении. Есть ли еще какая-либо структура в этой форме, которая могла бы нам помочь? Есть ли ограничение на глубину? Кроме того, вы сами создаете форму? Я думаю о любой возможности взлома во время его создания, которая поможет нам позже.

3. Жесткого ограничения глубины нет, хотя на практике она, вероятно, не превысит 10-15; Я сам создаю форму. Я полагаю, я мог бы отслеживать глубину и использовать это … хотя, если бы этого можно было избежать, было бы лучше.

4. Я подумал о быстром и грязном взломе, но он будет работать только в том случае, если вы создаете свои списки с list помощью встроенного (без понимания списка). Вы создадите свой подкласс list, переопределив метод сравнения, как вы упомянули, а затем временно присвоите подклассу list имя. Таким образом, вы можете быстро и легко создать свой расширенный список, а затем восстановить встроенный список для дальнейшего использования. Как это звучит?

5. Да, это на самом деле то, что я сделал на данный момент. Кажется, это лучший вариант, хотя он кажется таким неправильным…

Ответ №2:

Правильное решение — не создавать подклассы list , а просто использовать key параметр sort метода для определения пользовательской ключевой функции:

 sorted(l, key=custom_key_function)
  

custom_key_function(list_element) следует сгенерировать стандартизированный ключ для этого элемента списка, при этом все ключи относятся к одному классу.

Не зная точно, какие элементы могут содержать ваши списки, я не буду вдаваться в подробности о том, как это реализовать, но я думаю, что справедливо сказать из ваших примеров, что вам может потребоваться рекурсивно сортировать вложенные списки, используя то же custom_key_function самое .

Ответ №3:

Вот медленный способ.

Чтобы добавить порядок между несопоставимыми типами A и B поместить их экземпляры в кортежи:

 a = [[[0, 1], [2, 3]], [0, 1]]
b = [[0, 1], [2, 3]]

def deep_annotate(item):
    if isinstance(item, list):
        return (1, [deep_annotate(i) for i in item])
    else:
        return (0, item)

deep_annotate(a) < deep_annotate(b)
#>>> False

deep_annotate(a) > deep_annotate(b)
#>>> True
  

К сожалению, многое из этого не сокращается, что можно сделать с помощью умного использования cmp_to_key .