【问题标题】:Sort tuple list with another list使用另一个列表对元组列表进行排序
【发布时间】:2018-05-14 03:00:59
【问题描述】:

我有一个元组列表to_order 如:

to_order = [(0, 1), (1, 3), (2, 2), (3,2)]

还有一个列表,它给出了应用于to_order 的每个元组的第二个元素的顺序:

order = [2, 1, 3]

所以我正在寻找一种方法来获得这个输出:

ordered_list = [(2, 2), (3,2), (0, 1), (1, 3)]

有什么想法吗?

【问题讨论】:

  • tie-braker 政策?
  • 默认决胜局 = 稳定排序?
  • 这个问题不是重复的,至少不是建议的问题。链接的问题具有相同长度的列表。在上述问题中,一个order 索引有多个to_order 对。在假设的副本中提到的有效方法(带有mapping)在这种情况下不起作用。
  • 这个问题不清楚。它没有指定要排序的列表大小的估计值或在边缘情况下要做什么。它还缺乏研究,因为有大量关于按另一个列表排序和对元组列表排序的问题。为什么这些问题中的任何答案都不适用?
  • @jpmc26:我找不到任何完全相同的副本。如果你找到了,请随时发表评论。一些提议的副本要么不适用,要么效率低下。我同意这个问题可以显示更多的研究。

标签: python list sorting tuples


【解决方案1】:

您可以提供一个key,它将检查order 中的(第二个元素的)索引并根据它进行排序:

to_order = [(0, 1), (1, 3), (2, 2), (3,2)]
order = [2, 1, 3]
print(sorted(to_order, key=lambda item: order.index(item[1]))) # [(2, 2), (3, 2), (0, 1), (1, 3)]

编辑

既然开始了关于时间复杂性的讨论……来吧,下面的算法在O(n+m) 中运行,使用 Eric 的输入示例:

N = 5
to_order = [(randrange(N), randrange(N)) for _ in range(10*N)]
order = list(set(pair[1] for pair in to_order))
shuffle(order)


def eric_sort(to_order, order):
    bins = {}

    for pair in to_order:
        bins.setdefault(pair[1], []).append(pair)

    return [pair for i in order for pair in bins[i]]


def alfasin_new_sort(to_order, order):
    arr = [[] for i in range(len(order))]
    d = {k:v for v, k in enumerate(order)}
    for item in to_order:
        arr[d[item[1]]].append(item) 
    return [item for sublist in arr for item in sublist]


from timeit import timeit
print("eric_sort", timeit("eric_sort(to_order, order)", setup=setup, number=1000))
print("alfasin_new_sort", timeit("alfasin_new_sort(to_order, order)", setup=setup, number=1000))

输出:

eric_sort 59.282021682999584
alfasin_new_sort 44.28244407700004

【讨论】:

【解决方案2】:

算法

您可以根据第二个元素将元组分布在列表的字典中,并遍历order 索引以获得排序列表:

from collections import defaultdict
to_order = [(0, 1), (1, 3), (2, 2), (3, 2)]
order = [2, 1, 3]

bins = defaultdict(list)

for pair in to_order:
    bins[pair[1]].append(pair)

print(bins)
# defaultdict(<class 'list'>, {1: [(0, 1)], 3: [(1, 3)], 2: [(2, 2), (3, 2)]})

print([pair for i in order for pair in bins[i]])
# [(2, 2), (3, 2), (0, 1), (1, 3)]

不需要sortindex,输出稳定。

该算法类似于假定的duplicate 中提到的mapping。此链接答案仅在 to_orderorder 具有相同长度时才有效,而 OP 的问题并非如此。

性能

此算法对to_order 的每个元素进行两次迭代。复杂度为O(n)。 @alfasin 的第一个算法要慢得多(O(n * m * log n)),但他的第二个算法也是O(n)

这是一个列表,在 01000 之间有 10000 个随机对。我们提取唯一的第二个元素并打乱它们以定义order

from random import randrange, shuffle
from collections import defaultdict
from timeit import timeit
from itertools import chain

N = 1000
to_order = [(randrange(N), randrange(N)) for _ in range(10*N)]
order = list(set(pair[1] for pair in to_order))
shuffle(order)


def eric(to_order, order):
    bins = defaultdict(list)
    for pair in to_order:
        bins[pair[1]].append(pair)
    return list(chain.from_iterable(bins[i] for i in order))


def alfasin1(to_order, order):
    arr = [[] for i in range(len(order))]
    d = {k:v for v, k in enumerate(order)}
    for item in to_order:
        arr[d[item[1]]].append(item) 
    return [item for sublist in arr for item in sublist]

def alfasin2(to_order, order):
    return sorted(to_order, key=lambda item: order.index(item[1]))

print(eric(to_order, order) == alfasin1(to_order, order))
# True
print(eric(to_order, order) == alfasin2(to_order, order))
# True

print("eric", timeit("eric(to_order, order)", globals=globals(), number=100))
# eric 0.3117517130003762
print("alfasin1", timeit("alfasin1(to_order, order)", globals=globals(), number=100))
# alfasin1 0.36100843100030033
print("alfasin2", timeit("alfasin2(to_order, order)", globals=globals(), number=100))
# alfasin2 15.031453827000405

【讨论】:

  • 这依赖于 to_order 被第一个键预排序,否则你会得到不同的输出,例如to_order = [(0, 1), (3, 2), (2, 2), (1, 3)] 将输出 [(3, 2), (2, 2), (0, 1), (1 , 3)]
  • @Matt:我不确定我是否理解你的观点。此算法按order 排序。如果对具有相同的第二个元素,则它们以与输入中相同的顺序返回。它与描述以及此处的所有其他答案一致。如果要对每个子列表进行排序,可以在bins.values() 中进行。
  • 谢谢,我也选择了使用dict方式
  • @Eric 是的,对不起,我写的评论咖啡不足。我的意思是值得注意的是,比赛可能不会按预期排序
  • @Matt 没问题,总是欢迎建设性的批评。那么您的预期订单是什么?
【解决方案3】:

另一种解决方案: [item for key in order for item in filter(lambda x: x[1] == key, to_order)]

此解决方案首先使用order,为order 中的每个key 过滤to_order

等效:

ordered = []
for key in order:
    for item in filter(lambda x: x[1] == key, to_order):
        ordered.append(item)

更短,但我不知道如何通过列表理解来做到这一点:

ordered = []
for key in order:
    ordered.extend(filter(lambda x: x[1] == key, to_order))

注意:如果to_order 包含元组x,其中x[1] 不在order 中,则不会抛出ValueError

【讨论】:

  • Ev kounis 用列表理解写了一个类似的解决方案:[x for y in order for x in to_order if x[1] == y]。但是,它非常慢,甚至比公认的答案还要慢。
【解决方案4】:

我个人更喜欢list 对象sort 函数而不是内置的sort,它生成一个新列表而不是更改列表。

to_order = [(0, 1), (1, 3), (2, 2), (3,2)]
order = [2, 1, 3]
to_order.sort(key=lambda x: order.index(x[1]))
print(to_order)
>[(2, 2), (3, 2), (0, 1), (1, 3)]

顺便解释一下:sort方法的key参数基本上preprocesses列表和ranks都是基于一个度量的值。在我们的例子中,order.index() 查看当前处理项目的第一次出现并返回其位置。

x = [1,2,3,4,5,3,3,5]
print x.index(5)
>4

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-08
    • 2022-01-18
    • 2012-06-01
    • 1970-01-01
    • 2015-04-05
    • 2023-04-01
    • 2021-11-02
    相关资源
    最近更新 更多