使用索引列表对元组进行排列

4
我是一名有用的助手,可以为您翻译文本。
我有一个包含3个元素的Python元组,我想使用一个包含3个元素的列表的索引对其进行排序或重新排列,并且我想知道最简洁的方法是什么。
到目前为止,我已经得到了以下代码:
my_tuple = (10, 20, 30)
new_positions = [2, 0, 1]
my_shuffled_tuple = my_tuple[new_positions[0]], my_tuple[new_positions[1]], my_tuple[new_positions[2]]
# outputs: (30, 10, 20)

如果我这样做,也会得到相同的结果:

my_shuffled_tuple = tuple([my_tuple[i] for i in new_positions])

有没有更简洁的方法来创建my_shuffled_tuple
3个回答

6

有一种方法是使用生成器表达式作为tuple的参数,它接受可迭代对象

In [1]: my_tuple = (10, 20, 30)
   ...: new_positions = [2, 0, 1]
   ...: 

In [2]: my_shuffled_tuple = tuple(my_tuple[i] for i in new_positions)

In [3]: my_shuffled_tuple
Out[3]: (30, 10, 20)

如果速度是一个问题,而且你正在处理大量数据,那么你应该考虑使用Numpy。这可以直接使用索引列表或数组进行索引:

In [4]: import numpy as np

In [5]: my_array = np.array([10, 20, 30])

In [6]: new_positions = [2, 0, 1]  # or new_positions = np.array([2, 0, 1])

In [7]: my_shuffled_array = my_array[new_positions]

In [8]: my_shuffled_array
Out[8]: array([30, 10, 20])

我在我的问题中已经使用了 tuple([my_tuple[i] for i in new_positions]) 语法。但感谢 Bas 指出了关于 numpy 的注意点!像 my_array[new_positions] 这样的语法是我最初所希望的 - 我会考虑将我的元组转换为 numpy 数组,尽管速度在我的应用程序中并不是一个问题。 - tomr_stargazer
1
你的语法和我的答案有一个微小但重要的区别,那就是两个方括号!在你的情况下,你首先使用列表推导式构建一个列表,然后迭代该列表以构建元组。我使用了生成器表达式,它不会生成中间列表。请参见我答案中的链接以了解区别。在这种情况下,这并不太重要,但通常它允许进行高效的“惰性”操作而不会生成大型中间列表。例如,max(int(line) for line in big_file)永远不会在内存中超过一行。 - Bas Swinckels
哦!你说得对——我没有注意到,非常感谢澄清!现在我会将你的答案标记为“已接受”,因为这个区别完全会让我忽略——非常感谢。 - tomr_stargazer
请注意,生成器表达式通常比列表推导式慢,特别是对于小型可迭代对象。在像这样的问题中,生成器表达式和列表推导式之间的区别几乎完全无关紧要,在很多情况下,生成器表达式甚至更糟糕(例如,如果您已经像示例中一样创建了数组“new_positions”,并且它很小,则使用生成器表达式进行迭代只会更慢)。 - ely
1
现在,在tuple构造函数内使用生成器表达式可能不会受到这种影响,因为tuple构造函数在C级别上进行了优化。但更重要的是,引入有关生成器表达式与推导式的任何微妙差别都是过度夸大的,通常只是过早或不必要的优化,有时甚至更糟。我曾经看到过一些代码,其中某人费尽心思制作了一个包含两个元素的生成器,并手动调用了两次next。那很糟糕——即使它有一些资源好处,对于新手来说可读性的惩罚也太严重了。 - ely
非常感谢prpl提供的额外信息!我很幸运,在这种情况下不必担心性能问题,主要是在优化简洁性和可读性。我会记住关于生成器和列表推导式的注意事项。 - tomr_stargazer

3
您可以像这样使用 operator.itemgetter:
from operator import itemgetter

my_tuple = (10, 20, 30)
new_positions = [2, 0, 1]

print itemgetter(*new_positions)(my_tuple)

如果您需要经常按照新的顺序访问my_tuple(或其他内容),则可以将此itemgetter保存为帮助函数:

access_at_2_0_1 = itemgetter(*new_positions)

然后access_at_2_0_1(foo)将与tuple(foo[2], foo[0], foo[1])相同。

当您尝试使用类似argsort的操作(需要对许多数组重新访问以按照从排序其他数组得到的排序顺序进行排序)时,这非常有用。一般来说,在这一点上,您应该使用NumPy数组,但仍然是一个方便的方法。

请注意,由于itemgetter依赖于__getitem__协议(笨蛋),如果这很重要,则不能保证适用于所有类型的可迭代对象。


2
tuple()内置函数中使用生成器推导式(它可以接受生成器)。
>>> my_tuple = (10, 20, 30)
>>> new_positions = [2, 0, 1]
>>> tuple(my_tuple[i] for i in new_positions)
(30, 10, 20)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接