将排列应用于列表的原位方法? (按键排序的反转)

时间:2011-05-23 14:01:41

标签: python list sorting

以下是我想要做的一个例子

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"]

我可以使用enumeratelist等进行此操作,但我希望直接影响spam_list,例如list.sort(),而不是像{{1}那样复制它}}

编辑:推送了一个字符串示例,以避免在sorted()

的索引和值之间产生混淆

修改:结果证明这是Python sort parallel arrays in place?的副本。好吧,我不能删除SO一致性论点的那么多努力。

7 个答案:

答案 0 :(得分:21)

你可以尝试:

spam_list = [spam_list[i] for i in spam_order]

答案 1 :(得分:11)

您可以为排序功能提供特殊的key

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

编辑:正如@ninjagecko在his answer中指出的那样,这并不是很有效,因为它会复制两个列表以创建查找字典。但是,通过OP给出的修改示例,这是唯一的方法,因为必须构建一些索引。好处是,至少对于字符串,值不会被复制,因此开销只是字典本身的开销。

答案 2 :(得分:5)

  

但是我想直接影响spam_list,比如list.sort()而不是像sorted()

那样复制它

只有 ONE 解决方案,这完全符合您的要求。 每个其他解决方案都隐含地制作一个或两个列表的副本(或将其转换为dict等)。您要求的是一种对两个列表进行排序的方法,< strong>使用O(1)额外空格,使用一个列表作为另一个列表的键。我个人会接受额外的空间复杂性,但如果你真的想要,你可以这样做:

(编辑:可能是原始海报并不真正关心.sort,因为它有效,而是因为它改变了状态;一般来说,这是一个危险的想要和非低级别语言试图避免这种情况甚至禁止它,但使用切片赋值的解决方案将实现“就地”语义)

  • 创建一个自定义词典子类(实际上是一个Zip类),它由您正在排序的两个列表支持。
  • 索引myZip[i] - &gt;结果是元组(list1[i],list2[i])
  • 作业myZip[i]=(x1,x2) - &gt;发送到list1[i]=x1, list2[i]=x2
  • 使用 执行myZip(spam_list,spam_order).sort(),现在spam_listspam_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)空间中对一个列表进行排序,而无需对其他列表进行排序;如果你不想对两个列表进行排序,你可以采用构建虚拟列表的原始方法。

但您可以执行以下操作:

spam_list.sort(key=lambda x:x)

但是如果key或cmp函数对任何集合进行任何引用(例如,如果你传入一个必须构建的dict的dict.__getitem__),这并不比你原来的O(N)空间好。方法,除非你已经碰巧有这样的字典。

原来这是Python sort parallel arrays in place?的重复问题,但该问题除了this one之外也没有正确答案,这与我的相同,但没有示例代码。除非你是非常优化或专门的代码,否则我只会使用你原来的解决方案,这在空间复杂性方面与其他解决方案相当。

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 :(得分:3)

如果问题特别是 in-placeness 而不是内存使用本身 - 如果你想让它有副作用,换句话说 - 那么你可以使用切片分配。从Peter Collingridge窃取:

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)

啊哈!通过无法模仿的Sven Marnach - 生成器切片分配确实生成一个隐式元组。这意味着它是安全的,但不像你想象的那样具有内存效率。尽管如此,元组比列表更有内存效率,因此从这个角度来看,生成器表达式更为可取。

答案 4 :(得分:2)

map(lambda x:spam_list[x], spam_order)

答案 5 :(得分:2)

如果你真的根本不关心效率,只想要就地语义(这有点奇怪,因为有完整的编程语言致力于避免就地语义),那么你可以这样做:

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']

感谢发送者认识到这可能是OP之后实际存在的内容;他应该随意将这个答案复制到他自己的答案中。除非你真的更喜欢这个答案,否则不应该接受这个答案。

答案 6 :(得分:0)

您可以使用numpy。

import numpy as np
spam_list = list(np.array(spam_list)[spam_order])