Сортировка списка кортежей не сортирует их на самом деле

#python #python-3.x #sorting #tuples

#python #python-3.x #сортировка #кортежи

Вопрос:

Допустим, у меня есть такой словарь:

 a = {1:"a", 3:"c", 5:"e", 4:"d" ,2:"b", 6:"f", 7:"g"}
  

И я преобразую его в список кортежей:

 b = list(a.items())
  

Теперь я хочу отсортировать список на основе первого значения кортежа. У меня есть три способа сделать это:

 from operator import itemgetter
b.sort(key=itemgetter(0))

  
 b.sort(key=lambda x: x[::-1])
  
 b = sorted(b, key=lambda t: (t[0]))
  

В любом случае, если я печатаю b , я получаю отсортированный список:

 print(b)

>>> [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e'), (6, 'f'), (7, 'g')]
  

Однако, если я действительно использую b в процессе, оказывается, что он не отсортирован. Я протестировал несколько вещей, но текущая, которая у меня есть, это:

 batch_count = len(b) // 3
batches = [[] for _ in range(batch_count)]
for index, vec in enumerate(b):
    batches[index % batch_count].append(vec)
  

Это группируется b в списки не менее 3. batches Однако, если я печатаю, я получаю исходный порядок из a , за исключением 7 ожидаемого.

 print(batches)

>>> [[(1, 'a'), (3, 'c'), (5, 'e'), (7, 'g')], [(2, 'b'), (4, 'd'), (6, 'f')]]
  

Кто-нибудь знает, почему и как я могу это предотвратить?

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

1. Результат — это именно то, что вы должны получить; тот факт, что ваш оригинал dict имел почти тот же порядок (игнорируя границы пакета), является совпадением.

2. Какой результат вы ожидали? Группировка элементов b в пакеты, естественно, изменит их порядок. Обратите внимание, что каждая группа по-прежнему упорядочена должным образом.

Ответ №1:

Это не тот же порядок. В результате он разделяется на четные / нечетные позиции index % batch_count .

При batch_count значении 2 у вас есть index % 2 , которое будет чередоваться между 0 и 1 :

 >>> [i % 2 for i in range(10)]
[0, 1, 0, 1, 0, 1, 0, 1, 0, 1]
  

Это приводит к тому, что в первой вложенной коллекции остаются четные позиции, а во второй — коэффициенты.

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

1. Я только что протестировал с другим dict, и вы правы! Я потратил на это около часа и не понял! Я тупой! Спасибо!

2. Нет проблем. Если вы сомневаетесь, проверьте свои промежуточные данные. А для группировки ознакомьтесь с разделом рецептов в itertools документации. Там может быть существующее решение, которое вам поможет. Это отличный модуль.

Ответ №2:

Вывод нормальный. Вы можете попробовать это без сортировки вообще, чтобы увидеть разницу.

 batch_count = len(b) // 3
batches = [[] for _ in range(batch_count)]
for index, vec in enumerate(b):
    batches[index % batch_count].append(vec)
  

создает два подсписка из вашего исходного списка в вашем случае, evens и odds. Каждый из них отсортирован.

Ответ №3:

Вы могли бы использовать grouper функцию для разделения списка на список списков размера n .

 from itertools import zip_longest

def grouper(n, iterable):
    args = [iter(iterable)] * n
    return ([e for e in t if e != None] for t in zip_longest(*args))

b = [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e'), (6, 'f'), (7, 'g')]
batches = list(grouper(3, b))
print(batches)
  

Вывод:

 [[(1, 'a'), (2, 'b'), (3, 'c')], [(4, 'd'), (5, 'e'), (6, 'f')], [(7, 'g')]]
  

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

1. Хотя мне нравится ваше мышление, но тот факт, что он создает итерацию только с первой записью, имеющей значение, а затем перебирает ее, создает ненужный перебор. Поэтому цикл for работает быстрее. Хотя и очень незначительно. Тем не менее, очень хорошее решение. Спасибо!

2. @mnsr Если вы достигли того же самого после пересмотра вашего for-цикла, то да, сохраните это. Это решение является более общим и работает для каждого типа списка, который вы хотите сгруппировать по номеру. Накладные расходы незначительны. Спасибо за поддержку, приветствия 🙂