如何以完全相同的方式对两个列表(相互引用)进行排序


139

说我有两个清单:

list1 = [3, 2, 4, 1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

如果我运行list1.sort(),它将进行排序,[1,1,2,3,4]但是还有没有一种list2同步的方法(因此我可以说item 4属于'three')?因此,预期输出为:

list1 = [1, 1, 2, 3, 4]
list2 = ['one', 'one2', 'two', 'three', 'four']

我的问题是我有一个非常复杂的程序,可以很好地处理列表,但是我有点需要开始引用一些数据。我知道这对字典来说是一个完美的情况,但是我在处理过程中尽量避免使用字典,因为我确实需要对键值进行排序(如果必须使用字典,我知道如何使用它们)。

基本上,该程序的性质是,数据按随机顺序排列(如上),我需要对其进行排序,处理然后发送结果(顺序无关紧要,但是用户需要知道哪个结果属于哪个结果)键)。我考虑过先将其放入字典中,然后再对列表进行排序,但是如果不保持顺序(如果将结果传达给用户,可能会产生影响),我将无法区分具有相同值的项。因此,理想情况下,一旦获得列表,我就想出一种将两个列表排序在一起的方法。这可能吗?


我应该指出,您在list2中的变量不指向list1中的int。例如,如果更改诸如list1 [0] = 9之类的值并查看list2,则list2 [0]仍为3。对于python中的整数,它不使用引用/指针,而是复制该值。您最好离开list2 = list1 [:]
罗伯特·金(Robert King)

Answers:


242

解决此问题的一种经典方法是使用“装饰,排序,未装饰”习惯用法,使用python的内置zip函数特别简单:

>>> list1 = [3,2,4,1, 1]
>>> list2 = ['three', 'two', 'four', 'one', 'one2']
>>> list1, list2 = zip(*sorted(zip(list1, list2)))
>>> list1
(1, 1, 2, 3, 4)
>>> list2 
('one', 'one2', 'two', 'three', 'four')

这些当然不再是列表,但是如果需要的话,很容易纠正:

>>> list1, list2 = (list(t) for t in zip(*sorted(zip(list1, list2))))
>>> list1
[1, 1, 2, 3, 4]
>>> list2
['one', 'one2', 'two', 'three', 'four']

值得一提的是,以上可能会为简洁而牺牲速度。就地版本,占用3行,对于我的小型列表来说,在我的机器上快了一点:

>>> %timeit zip(*sorted(zip(list1, list2)))
100000 loops, best of 3: 3.3 us per loop
>>> %timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100000 loops, best of 3: 2.84 us per loop

另一方面,对于较大的列表,单行版本可能会更快:

>>> %timeit zip(*sorted(zip(list1, list2)))
100 loops, best of 3: 8.09 ms per loop
>>> %timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100 loops, best of 3: 8.51 ms per loop

正如Quantum7指出的那样,JSF的建议仍然要快一些,但可能只会快一点,因为Python 内部在所有基于键的排序中使用了完全相同的DSU习惯用法。它发生在离裸机更近的地方。(这表明zip例程的优化程度如何!)

我认为zip基于方法的灵活性更高,可读性更高,所以我更喜欢它。


6
第三行中的星号代表什么?
杰弗里(Jeffrey)2012年

8
为了详细说明上述内容,*操作员会进行参数拆箱
senderle 2012年

1
对于我来说,JF Sebastian建议的排序索引/映射范例比任何一个zip解决方案快大约10%(使用10000个随机整数的列表):%timeit index = range(len(l1)); index.sort(key = l1 .__ getitem__); map(l1 .__ getitem__,index); map(l2 .__ getitem__,index)100个循环,最好为3:每个循环8.04毫秒(vs 9.17毫秒,senderle的时间为9.07毫秒)
Quantum7 2013年

1
list1的第一个和第二个zip,list2 = zip(* sorted(zip(list1,list2)))做这些不同的事情。*发挥了所有作用。
阿舒

1
@ashu在某种意义上是!但换句话说,它们几乎没有什么不同。zip(*x)具有有趣的属性,它是它自己的逆: l = [(1, 2), (3, 4)]; list(zip(*zip(*l))) == lreturn True。它实际上是一个换位运算符。zip()它本身就是相同的运算符,但是假定您已手动解压缩输入序列。
senderle '18

30

您可以使用值作为键对索引进行排序:

indexes = range(len(list1))
indexes.sort(key=list1.__getitem__)

要获得给定排序索引的排序列表:

sorted_list1 = map(list1.__getitem__, indexes)
sorted_list2 = map(list2.__getitem__, indexes)

在您的情况下,您不应有list1list2而应有一个单对列表:

data = [(3, 'three'), (2, 'two'), (4, 'four'), (1, 'one'), (1, 'one2')]

易于创建;在Python中很容易排序:

data.sort() # sort using a pair as a key

仅按第一个值排序:

data.sort(key=lambda pair: pair[0])

对此很酷的事情是,我可以保留索引并稍后对其他内容进行排序,以防list1是影响其他几个数组的重要坐标。
EL_DON

3
python 3的
indexs

@DonQuiKong还需要list() 各地map(),如果你想在Python 3使用此代码
JFS

或者,而不是sorted_list1 = list(map(list1.__getitem__, indexes))一个人可以做sorted_list1 = [list1[i] for i in indexes]
内森

20

我一直使用senderle给出的答案,直到发现为止np.argsort。下面是它的工作原理。

# idx works on np.array and not lists.
list1 = np.array([3,2,4,1])
list2 = np.array(["three","two","four","one"])
idx   = np.argsort(list1)

list1 = np.array(list1)[idx]
list2 = np.array(list2)[idx]

我发现此解决方案更加直观,并且效果很好。性能:

def sorting(l1, l2):
    # l1 and l2 has to be numpy arrays
    idx = np.argsort(l1)
    return l1[idx], l2[idx]

# list1 and list2 are np.arrays here...
%timeit sorting(list1, list2)
100000 loops, best of 3: 3.53 us per loop

# This works best when the lists are NOT np.array
%timeit zip(*sorted(zip(list1, list2)))
100000 loops, best of 3: 2.41 us per loop

# 0.01us better for np.array (I think this is negligible)
%timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100000 loops, best for 3 loops: 1.96 us per loop

尽管np.argsort不是最快的,但我发现它更易于使用。


1
运行您的示例时出现错误:TypeError: only integer arrays with one element can be converted to an index(Python 2.7.6,numpy 1.8.2)。要修复它,必须将list1和list2声明为numpy数组。
BenB 2015年

谢谢。这不是我在函数注释中写的吗?无论如何,我认为np.argsort不尝试np.array内部转换是很愚蠢的。
Daniel Thaagaard Andreasen 2015年

我指的是第一个代码段,因为它没有按书写的
顺序

当列表分配给numpy数组时,我通过转换列表进行了更正。感谢您的评论:)
Daniel Thaagaard Andreasen

现在它们两次被转换为Numpy数组;)
BenB 2015年

13

施瓦兹变换。内置的Python排序是稳定的,因此这两个1不会引起问题。

>>> l1 = [3, 2, 4, 1, 1]
>>> l2 = ['three', 'two', 'four', 'one', 'second one']
>>> zip(*sorted(zip(l1, l2)))
[(1, 1, 2, 3, 4), ('one', 'second one', 'two', 'three', 'four')]

2
但是,如果发现需要这样做,则应该强烈地重新考虑拥有两个“并行”数据列表,而不是保留一个2元组(对)的列表……甚至可能实际上是创建一个类。
Karl Knechtel 2012年

3

关于什么:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

sortedRes = sorted(zip(list1, list2), key=lambda x: x[0]) # use 0 or 1 depending on what you want to sort
>>> [(1, 'one'), (1, 'one2'), (2, 'two'), (3, 'three'), (4, 'four')]

2

您可以使用zip()sort()函数来完成此操作:

Python 2.6.5 (r265:79063, Jun 12 2010, 17:07:01)
[GCC 4.3.4 20090804 (release) 1] on cygwin
>>> list1 = [3,2,4,1,1]
>>> list2 = ['three', 'two', 'four', 'one', 'one2']
>>> zipped = zip(list1, list2)
>>> zipped.sort()
>>> slist1 = [i for (i, s) in zipped]
>>> slist1
[1, 1, 2, 3, 4]
>>> slist2 = [s for (i, s) in zipped]
>>> slist2
['one', 'one2', 'two', 'three', 'four']

希望这可以帮助


2

除非在list2中有两个相同的值,否则可以在sorted()方法中使用key参数。

代码如下:

sorted(list2, key = lambda x: list1[list2.index(x)]) 

它根据list1中的对应值对list2进行排序,但请确保在使用此列表时,list2中的两个值都不会相等,因为list.index()函数会给出第一个值


尽管排序有效,但在某些情况下有些慢。
tyan

2

一种方法是通过对标识[0,1,2,.. n]进行排序来跟踪每个索引的位置

这适用于任意数量的列表。

然后将每个项目移到其位置。最好使用接头。

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

index = list(range(len(list1)))
print(index)
'[0, 1, 2, 3, 4]'

index.sort(key = list1.__getitem__)
print(index)
'[3, 4, 1, 0, 2]'

list1[:] = [list1[i] for i in index]
list2[:] = [list2[i] for i in index]

print(list1)
print(list2)
'[1, 1, 2, 3, 4]'
"['one', 'one2', 'two', 'three', 'four']"

请注意,我们可以对列表进行迭代而无需对它们进行排序:

list1_iter = (list1[i] for i in index)

1

如果您使用的是numpy,则可以np.argsort用来获取排序的索引,并将这些索引应用于列表。这适用于您要排序的任何数量的列表。

import numpy as np

arr1 = np.array([4,3,1,32,21])
arr2 = arr1 * 10
sorted_idxs = np.argsort(arr1)

print(sorted_idxs)
>>> array([2, 1, 0, 4, 3])

print(arr1[sorted_idxs])
>>> array([ 1,  3,  4, 21, 32])

print(arr2[sorted_idxs])
>>> array([ 10,  30,  40, 210, 320])

0

算法解决方案:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']


lis = [(list1[i], list2[i]) for i in range(len(list1))]
list1.sort()
list2 = [x[1] for i in range(len(list1)) for x in lis if x[0] == i]

输出: -> 输出速度: 0.2s

>>>list1
>>>[1, 1, 2, 3, 4]
>>>list2
>>>['one', 'one2', 'two', 'three', 'four']

0

在对另一个列表进行排序时,保留字符串列表顺序的另一种方法如下:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

# sort on list1 while retaining order of string list
sorted_list1 = [y for _,y in sorted(zip(list1,list2),key=lambda x: x[0])]
sorted_list2 = sorted(list1)

print(sorted_list1)
print(sorted_list2)

输出

['one', 'one2', 'two', 'three', 'four']
[1, 1, 2, 3, 4]

0

我想扩展开放式jfs的答案,这对我的问题非常有用:将两个列表按经过装饰的第三个列表排序

我们可以以任何方式创建装饰列表,但是在这种情况下,我们将根据要排序的两个原始列表之一的元素来创建它:

# say we have the following list and we want to sort both by the algorithms name 
# (if we were to sort by the string_list, it would sort by the numerical 
# value in the strings)
string_list = ["0.123 Algo. XYZ", "0.345 Algo. BCD", "0.987 Algo. ABC"]
dict_list = [{"dict_xyz": "XYZ"}, {"dict_bcd": "BCD"}, {"dict_abc": "ABC"}]

# thus we need to create the decorator list, which we can now use to sort
decorated = [text[6:] for text in string_list]  
# decorated list to sort
>>> decorated
['Algo. XYZ', 'Algo. BCD', 'Algo. ABC']

现在我们可以应用jfs的解决方案将我们的两个列表按第三个排序

# create and sort the list of indices
sorted_indices = list(range(len(string_list)))
sorted_indices.sort(key=decorated.__getitem__)

# map sorted indices to the two, original lists
sorted_stringList = list(map(string_list.__getitem__, sorted_indices))
sorted_dictList = list(map(dict_list.__getitem__, sorted_indices))

# output
>>> sorted_stringList
['0.987 Algo. ABC', '0.345 Algo. BCD', '0.123 Algo. XYZ']
>>> sorted_dictList
[{'dict_abc': 'ABC'}, {'dict_bcd': 'BCD'}, {'dict_xyz': 'XYZ'}]

编辑:大家好,我对此发表了一篇文章,如果您愿意的话请查看 :)🐍🐍🐍


-1
newsource=[];newtarget=[]
for valueT in targetFiles:
    for valueS in sourceFiles:
            l1=len(valueS);l2=len(valueT);
            j=0
            while (j< l1):
                    if (str(valueT) == valueS[j:l1]) :
                            newsource.append(valueS)
                            newtarget.append(valueT)
                    j+=1

2
有几种解释可能会有所帮助
saiedmomen '18

@saiedmomen我将其发布为参考stackoverflow.com/questions/53829160/…在这里,目标字符串是在源字符串上搜索的。
user10340258 '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.