根据单个对应元素的比率或基于第三个列表对 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
,并相应地重新排序values
和weights
.
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 个列表进行排序的主要内容,如果未能解决你的问题,请参考以下文章