根据单个对应元素的比率或基于第三个列表对 Python 中的 2 个列表进行排序

Posted

技术标签:

【中文标题】根据单个对应元素的比率或基于第三个列表对 Python 中的 2 个列表进行排序【英文标题】:Sort 2 lists in Python based on the ratio of individual corresponding elements or based on a third list 【发布时间】:2017-12-25 01:26:16 【问题描述】:

我正在尝试为小数 knapsack problem 编写不同的实现。

为此,我有 2 个数组:

    价值观 权重

元素值[n] 对应于元素权重[n]。所以我们可以将 value_per_unit 计算为:

for I in range(values):
    value_per_unit.append(values[I]/weights[I])
value_per_unit.sort()

我现在需要根据 value_per_unit 数组对 2 个数组(值和权重)进行排序

例如: 如果

值 = [60, 100, 120] 权重 = [20, 50, 30]

然后

values_per_unit = [3.0, 2.0, 4.0]

因此 values_per_unit_sorted 将是 [2.0, 3.0, 4.0]

我需要将值和权重数组变为:

values_sorted = [100,60,120] weights_sorted = [50,20,30]

有没有办法使用简单的 lambda 函数来实现这一点?

我仍然可以这样做,但每次我需要访问元素时似乎效率非常低:

weights[(value_per_unit_sorted.index(max(value_per_unit_sorted)))]

【问题讨论】:

【参考方案1】:

一行:

values, weights = zip(*sorted(zip(values, weights), key=lambda t: t[0]/t[1]))

解释:首先,压缩列表以将它们配对。

pairs = zip(values, weights) 
# [(60, 20), (100, 50), (120, 30)]

然后,按价值与重量的商数排序。

sorted_pairs = sorted(pairs, key=lambda t: t[0]/t[1]) 
# [(100, 50), (60, 20), (120, 30)]

最后,将它们解压缩到单独的列表中。

values, weights = zip(*sorted_pairs)
# (100, 60, 120), (50, 20, 30)

另一种方法是构建显式包含比率作为第一个元素的元组。

ratios, values, weights = zip(*sorted((v/w, v, w) for v, w in zip(values, weights)))

在一些快速测试中,前者似乎稍快一些。如果您正在寻找一个最佳算法,您可能不得不展开一些事情并且解决方案不会那么简洁。

为了解决@TomWyllie 的评论,如果您已经有了比率列表,您可以使用:

ratios, values, weights = zip(*sorted(zip(ratios, values, weights)))

请注意,在两对具有相等比率的情况下,最后两个解与初始解不同。这些解决方案将按值进行第二次排序,而第一个解决方案将使项目保持与原始列表相同的顺序。

【讨论】:

一个很小的问题,但是您正在使用此解决方案重新计算所有比率,OP 现在以粗体突出显示它应该“根据 value_per_unit 数组”进行排序,我相当确定这意味着使用“数组”(列表)而不是重新计算值。不过答案很好:) @Tom 然后可以使用第二种解决方案,并且 OP 可以跳过首先构建比率列表。 我同意这可能是明智的,但 OP 并没有要求跳过构建比率的步骤;无论如何,他完全有可能需要 list 来做其他事情,因此想要一个避免重新计算的解决方案。 @TomWyllie 这让事情变得更简单,我为这种情况添加了一个查找解决方案。【参考方案2】:

一个优雅的方法是创建一个包含值和权重的多维列表:

for i in range(len(values)):
    values_and_weights.append([values[i], weights[i])
# The resulting list is [[60, 20], [100, 50], [120, 30]]

然后,使用排序方法,以值除以权重为键。

values_and_weights.sort(key=(lambda x: x[0]/x[1]))

【讨论】:

第一部分可以使用zip简化,然后简化为我的第二个建议。 @JaredGoguen 伟大的思想都一样!我这样做的原因是它更明确,而且发生的事情也更明显。 我认为使用内置函数 zip 更符合 Python 风格,但每个人都有自己的特点。【参考方案3】:

对于更明确(但可以说不太像 Python)的解决方案,创建一个索引列表,按该索引处的值排序value_per_unit,并相应地重新排序valuesweights .

sorted_indices = [index for index, value in 
                  sorted(enumerate(value_per_unit), key=lambda x: x[1])]
values = [values[i] for i in sorted_indices]
weights = [weights[i] for i in sorted_indices]

print(values, weights)

输出:

([100, 60, 120], [50, 20, 30])

您可以整理一下,使用zip 和生成器表达式消除不必要的额外循环;

values, weights = zip(*((values[i], weights[i]) for i, value in
                  sorted(enumerate(value_per_unit), key=lambda x: x[1])))
print(values)
print(weights)

哪些输出;

(100, 60, 120)
(50, 20, 30)

请注意,这些最终值是 tuples 而不是 lists。如果你真的需要输出是一个列表,一个简单的values, weights = map(list, (values, weights)) 就足够了。您甚至可以将其包装到一个衬里中,尽管到那时可能很难了解正在发生的事情。

【讨论】:

【参考方案4】:

您遇到的问题是通过在每个元素上使用计算字段引起的(元素我将具有计算值values[I]/weights[I])。 为了解决这个问题并使其非常易于理解,您可以将其转换为以下形式的元组:( calculated_value, (value, weight) ),每个元素。

这种方法使其易于阅读和理解。看看下面的解决方案:

values = [60, 100, 120]
weights = [20, 50, 30]
value_per_unit = []

for I in range(len(values)):
    value_per_unit.append( (values[I]/weights[I], (values[I], weights[I])) )
sorted_value_per_unit = sorted(value_per_unit, key=lambda x: x[0])

sorted_values = []
sorted_weights = []
for I in range(len(values)):
    (value, weight) = sorted_value_per_unit[I][1]
    sorted_values.append(value)
    sorted_weights.append(weight)

print(str(sorted_values))
print(str(sorted_weights))

另外,请注意我修改了原始代码中的循环:

range(values) 已更改为 range(len(values))

因为范围需要列表的长度,而不是列表本身。

【讨论】:

以上是关于根据单个对应元素的比率或基于第三个列表对 Python 中的 2 个列表进行排序的主要内容,如果未能解决你的问题,请参考以下文章

根据第三个参数对元组列表进行排序[重复]

根据列表项的索引模数执行不同的操作

覆盖两个列表之间的单个对应元素

WPF将单个文本框绑定到集合对象或数组中的元素

基于共同的第一个元素合并二维列表中的元素

算法与数据结构3