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

Posted

技术标签:

【中文标题】如何以完全相同的方式对两个列表(相互引用)进行排序【英文标题】:How to sort two lists (which reference each other) in the exact same way 【发布时间】:2012-04-03 14:12:08 【问题描述】:

假设我有两个列表:

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

如果我运行list1.sort(),它会将其排序为[1,1,2,3,4],但有没有办法让list2 也同步(所以我可以说项目4 属于'three')?因此,预期的输出将是:

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

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

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

【问题讨论】:

我应该指出,您在 list2 中的变量并不指向 list1 中的整数。例如。如果更改诸如 list1[0]=9 之类的值并查看 list2,则 list2[0] 仍将为 3。在 python 中使用整数时,它不使用引用/指针,它会复制该值。你最好去 list2 = list1[:] 【参考方案1】:

解决这个问题的一个经典方法是使用“decorate, sort, undecorate”习语,使用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's suggestion 仍然快一点,但它可能只会快一点,因为 Python 使用 very same DSU idiom internally 进行所有基于键的排序。它只是发生在更接近裸机的地方。 (这显示了zip 例程的优化程度!)

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


请注意,当list1 的元素相等时,这种方法最终会比较list2 的元素。如果list2 的元素不支持比较,或者比较时不产生布尔值(例如,如果list2 是NumPy 数组的列表),这将失败,并且如果list2 的元素非常比较昂贵,最好还是避免比较。

在这种情况下,您可以按照 jfs 的回答中的建议对索引进行排序,或者您可以为排序提供一个避免比较 list2 的元素的关键函数:

result1, result2 = zip(*sorted(zip(list1, list2), key=lambda x: x[0]))

此外,当输入为空时,使用zip(*...) 作为转置失败。如果您的输入可能为空,您将不得不单独处理这种情况。

【讨论】:

第三行的星号代表什么? 为了详细说明上面,*操作符做了argument unpacking, J.F. Sebastian 建议的排序索引/地图范例比我的任何一种 zip 解决方案(使用 10000 个随机整数的列表)快约 10%:%timeit index = range(len(l1)); index.sort(key=l1.__getitem__);地图(l1.__getitem__,索引); map(l2.__getitem__, index) 100 个循环,最好的 3 个:每个循环 8.04 毫秒(相对于 senderle 的 9.17 毫秒,9.07 毫秒) list1 中的第一个和第二个 zip,list2 = zip(*sorted(zip(list1, list2))) 做了不同的事情。 * 决定一切。 @ashu,从某种意义上说,是的!但在另一种意义上,它们几乎没有什么不同。 zip(*x) 有一个有趣的属性,它是它自己的逆:l = [(1, 2), (3, 4)]; list(zip(*zip(*l))) == l 返回True。它实际上是一个转置运算符。 zip() 本身就是同一个运算符,但假定您已手动解压缩输入序列。【参考方案2】:

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

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

在给定排序索引的情况下获取排序列表:

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

在你的情况下,你不应该有 list1, list2 而是一个单一的对列表:

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 是影响其他几个数组的重要坐标。 indexes = list(range(len(list1))) for python 3 @DonQuiKong 如果您想在 Python 3 中使用此代码,您还需要 list() around map() 或者,可以用sorted_list1 = [list1[i] for i in indexes]代替sorted_list1 = list(map(list1.__getitem__, indexes))【参考方案3】:

我用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 不是最快的,但我发现它更易于使用。

【讨论】:

运行您的示例时出现错误:TypeError: only integer arrays with one element can be converted to an index (Python 2.7.6, numpy 1.8.2)。要修复它,必须将 list1 和 list2 声明为 numpy 数组。 谢谢。这不是我在函数的注释中写的吗?无论如何,我认为 np.argsort 不要尝试在内部转换为 np.array 是愚蠢的。 我指的是第一个代码 sn-p 因为它没有按书面形式运行 :) 我通过在将列表分配给 numpy 数组时转换列表来纠正它。感谢您的评论:) 现在它们被转换为 Numpy 数组两次 ;)【参考方案4】:

Schwartzian transform。内置的 Python 排序是稳定的,所以两个1s 不会造成问题。

>>> 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 元组(对)的列表。 . 或者甚至可能实际创建一个类。【参考方案5】:

一种方法是通过对标识 [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)

【讨论】:

【参考方案6】:

您可以使用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']

希望对你有帮助

【讨论】:

是否有其他人收到错误“AttributeError: 'zip' object has no attribute 'sort'”?我想知道这个响应是否适用于早期版本的 Python,但不适用于当前版本。【参考方案7】:

怎么样:

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

【讨论】:

【参考方案8】:

如果您使用 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])

【讨论】:

【参考方案9】:

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

代码如下:

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

它根据 list1 中的对应值对 list2 进行排序,但要确保在使用它时,list2 中没有两个值相等,因为 list.index() 函数给出了第一个值

【讨论】:

sorted 在某些情况下有点慢,尽管它可以工作。【参考方案10】:

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

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]

【讨论】:

【参考方案11】:

如果您需要对超过 2 个列表进行同步排序,我想提出一个解决方案:

def SortAndSyncList_Multi(ListToSort, *ListsToSync):
    y = sorted(zip(ListToSort, zip(*ListsToSync)))
    w = [n for n in zip(*y)]
    return list(w[0]), tuple(list(a) for a in zip(*w[1]))

【讨论】:

【参考方案12】:

我想扩展打开 jfs's answer,这对我的问题很有用:将两个列表按第三个装饰列表排序

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

# 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's solution 来按第三个排序我们的两个列表

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

【讨论】:

【参考方案13】:

算法解决方案:

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

【讨论】:

【参考方案14】:
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

【讨论】:

几行解释会很有帮助 @saiedmomen 我发布它是为了参考***.com/questions/53829160/… 这里目标字符串是在源字符串上搜索的。

以上是关于如何以完全相同的方式对两个列表(相互引用)进行排序的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 lambda 表达式对相同的列表进行排序?

如何在 jQuery UI 中将多个可排序列表相互连接?

如何对彼此相关的两个数组进行排序?

如何以交替顺序对两个不同对象列表的 XML 数组项进行排序?

使用与数组相同的算法对 ArrayList 进行排序

两个列表之间的差异