Вместо того, чтобы применить перестановку к списку?(обратная сортировка по ключу) - PullRequest
9 голосов
/ 23 мая 2011

Вот пример того, что я хочу сделать

spam_list = ["We", "are", "the", "knights", "who", "say", "Ni"]
spam_order = [0,1,2,4,5,6,3]
spam_list.magical_sort(spam_order)
print(spam_list)

["We", "are", "the", "who", "say", "Ni", "knights"]

Я могу сделать это с помощью enumerate, list и так далее, но я бы хотел напрямую повлиять на spam_list, например list.sort() и не копировать его как sorted()

Редактировать : выдвинул пример строки, чтобы избежать путаницы между индексами и значениями spam_list

Редактировать : оказалось, что это дубликат параллельных массивов сортировки Python на месте? .Ну, я не могу удалить столько усилий для ТАК аргументов согласованности.

Ответы [ 7 ]

21 голосов
/ 23 мая 2011

Вы можете попробовать:

spam_list = [spam_list[i] for i in spam_order]
11 голосов
/ 23 мая 2011

Вы можете задать специальный key для функции сортировки:

order = dict(zip(spam_list, spam_order))
spam_list.sort(key=order.get)

Редактировать: Как @ninjagecko указывает в его ответ , этоне очень эффективно, так как копирует оба списка для создания словаря для поиска.Однако в модифицированном примере, представленном OP, это единственный способ, потому что нужно построить какой-то индекс.Положительным моментом является то, что, по крайней мере для строк, значения не будут скопированы, поэтому накладные расходы относятся только к самому словарю.

5 голосов
/ 23 мая 2011

но я бы хотел напрямую воздействовать на spam_list, например list.sort (), а не копировать его, как sorted ()

ВСЕГО ONE SOLUTION,это именно то, что вы просите. Каждое другое решение неявно делает копию одного или обоих списков (или превращает его в диктовку и т. Д.). То, что вы запрашиваете, - это метод, который сортирует два списка на месте, используя O(1) дополнительный пробел , используя один список в качестве ключей другого.Лично я бы смирился с дополнительным пространством, но если бы вы действительно этого хотели, вы могли бы сделать это:

(правка: возможно, это тот случай, когда оригинальный постер на самом деле не заботится о .sort, потому чтоон эффективен, но скорее потому, что он изменяет состояние, в общем, опасно хотеть, и языки не низкого уровня пытаются этого избежать и даже запретить, но решения, использующие назначение срезов, достигнут семантики «на месте»)

  • Создать пользовательский подкласс словаря (фактически класс Zip), который поддерживается обоими сортируемыми списками.
  • Индексация myZip[i] -> приводит к кортежу (list1[i],list2[i])
  • Назначение myZip[i]=(x1,x2) -> отправляет в list1[i]=x1, list2[i]=x2.
  • Используйте , что , чтобы сделать myZip(spam_list,spam_order).sort(), и теперь оба spam_list и spam_orderсортируются по месту

Пример:

#!/usr/bin/python3

class LiveZip(list):
    def __init__(self, list1, list2):
        self.list1 = list1
        self.list2 = list2

    def __len__(self):
        return len(self.list1)

    def __getitem__(self, i):
        return (self.list1[i], self.list2[i])

    def __setitem__(self, i, tuple):
        x1,x2 = tuple
        self.list1[i] = x1
        self.list2[i] = x2

spam_list = ["We", "are", "the", "knights", "who", "say", "Ni"]
spam_order = [0,1,2,4,5,6,3]

#spam_list.magical_sort(spam_order)
proxy = LiveZip(spam_order, spam_list)

Теперь посмотрим, работает ли он ...

#proxy.sort()
#fail --> oops, the internal implementation is not meant to be subclassed! lame
# It turns out that the python [].sort method does NOT work without passing in
# a list to the constructor (i.e. the internal implementation does not use the
# public interface), so you HAVE to implement your own sort if you want to not
# use any extra space. This kind of dumb. But the approach above means you can 
# just use any standard textbook in-place sorting algorithm:
def myInPlaceSort(x):
    # [replace with in-place textbook sorting algorithm]

СЕЙЧАС это работает:

myInPlaceSort(proxy)

print(spam_list)

К сожалению нет способа просто отсортировать один список в O(1) spтуз без сортировки другой ;если вы не хотите сортировать оба списка, вы можете также использовать свой оригинальный подход, который создает фиктивный список.

Однако вы можете сделать следующее:

spam_list.sort(key=lambda x:x)

, но если функция key или cmp делает какие-либо ссылки на какую-либо коллекцию (например, если вы передаете dict.__getitem__ изречения, который вы должны были создать), это не лучше, чем ваш первоначальный O(N) -пространственный подход, если только выуже случалось иметь такой словарь.

Оказывается, это дублирующий вопрос Имеются ли параллельные массивы сортировки Python? , но на этот вопрос также не было правильных ответов, кроме этот , который эквивалентен моему, но без примера кода.Если вы не оптимизированный или специализированный код, я бы просто использовал ваше оригинальное решение, которое по пространственной сложности эквивалентно другим решениям.

edit2: как указал отправитель, OP не хочет сортировкивообще, но скорее желает, я думаю, применить перестановку .Чтобы достичь этого, вы можете и ДОЛЖНЫ использовать простое индексирование, которое другие ответы предлагают [spam_list[i] for i in spam_order], но необходимо сделать явную или неявную копию, поскольку вам все еще нужны промежуточные данные.(Не относится и к записи, применение обратной перестановки - это, я думаю, обратная параллельная сортировка с тождеством, и вы можете использовать одну для получения другой, хотя сортировка менее эффективна по времени. _,spam_order_inverse = parallelSort(spam_order, range(N)), затем сортировка по spam_order_inverse. Я оставляю рассмотренное выше обсуждение сортировки по записи.)

edit3:

Однако можно достичь перестановки на месте в O(#cycles) места, но с ужасной эффективностью времени.Каждая перестановка может быть разложена на непересекающиеся перестановки, применяемые параллельно на подмножествах.Эти подмножества называются циклами или орбитами.Период равен их размеру.Таким образом, вы делаете прыжок веры и делаете следующее:

Create a temp variable.

For index i=0...N:
    Put x_i into temp, assign NULL to x_i
    Swap temp with x_p(i)
    Swap temp with x_p(p(i))
    ...
    Swap temp with x_p(..p(i)..), which is x_i
    Put a "do not repeat" marker on the smallest element you visited larger than i
    Whenever you encounter a "do not repeat" marker, perform the loop again but
      without swapping, moving the marker to the smallest element larger than i    
    To avoid having to perform the loop again, use a bloom filter

Это будет выполняться за O (N ^ 2) время и O (#cycles) без фильтра Блума или ~ O (N)время и пространство O (#cycle + bloomfilter_space), если вы их используете

3 голосов
/ 23 мая 2011

Если проблема заключается именно в на месте , а не в использовании памяти как таковой - если вы хотите, чтобы это имело побочные эффекты, другими словами - тогда вы могли бы использовать назначение срезов.Кража у Питер Коллингридж :

other_spam_list = spam_list
spam_list[:] = [spam_list[i] for i in spam_order]
assert other_spam_list == spam_list

Кажется, вы могли бы даже сделать это с помощью выражения генератора!Но я подозреваю, что это все еще неявно создает новую последовательность некоторого вида - вероятно, кортеж.Если это не так, я думаю, что это проявит неправильное поведение;но я проверял это, и его поведение казалось правильным.

spam_list[:] = (spam_list[i] for i in spam_order)

Ага!Посмотрите на этот превосходный ответ по неподражаемому Свену Марнаху - назначение среза генератора действительно генерирует неявный кортеж.Это означает, что это безопасно, но не так эффективно, как вы думаете.Тем не менее, кортежи более эффективны по памяти, чем списки, поэтому выражение генератора предпочтительнее с этой точки зрения.

2 голосов
/ 23 мая 2011

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

def modifyList(toModify, newList):
    toModify[:] = newList

def permuteAndUpdate(toPermute, permutation):
    modifyList(toPermute, [toPermute[i] for i in permutation])

permuteAndUpdate(spam_list, spam_order)

print(spam_list)
# ['We', 'are', 'the', 'Ni', 'knights', 'who', 'say']

Кредит отправляется отправителю за то, что он признал, что это именно то, за чем на самом деле может быть ОП;он должен смело копировать этот ответ в свой.Не следует принимать этот ответ, если вы действительно не предпочитаете его.

2 голосов
/ 23 мая 2011
map(lambda x:spam_list[x], spam_order)
0 голосов
/ 08 июля 2019

Вы можете использовать numpy.

import numpy as np
spam_list = list(np.array(spam_list)[spam_order])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...