如何返回总和小于最大值的键的哈希对?
Posted
技术标签:
【中文标题】如何返回总和小于最大值的键的哈希对?【英文标题】:How can I return hash pairs of keys that sum up to less than a maximum value? 【发布时间】:2022-01-15 21:45:07 【问题描述】:鉴于此哈希:
numsHash = 5=>10, 3=>9, 4=>7, 2=>5, 20=>4
如果该哈希的键之和小于或等于10
等最大值,我该如何返回此哈希的键值对?
预期的结果是这样的:
newHash = 5=>10, 3=>9, 2=>5
因为这些键的总和等于 10。
我已经为此沉迷了好几个小时,但找不到任何可以解决问题的方法。
【问题讨论】:
这听起来像是Knapsack Problem 的变体。这意味着首先您需要解决背包问题以选择哪些键,然后在第二步中您可以使用Hash#slice 使用您在第一步中计算的键生成结果哈希。 “当其键的总和小于或等于 max_number 时,我如何返回此哈希的键值对”没有意义,因为您可以只选择键-具有最小键的值对,此处为 2=>5
,前提是键不大于“max_number”。我怀疑问题是,“给定一个散列和一个整数,找到总和 等于 给定整数的散列键的集合,并返回带有这些键的散列切片。如果返回 nil
不存在这样的集合。如果我是正确的,那么问题是一个等重的背包问题,正如@spickermann 所怀疑的那样。
对于 OP:像 numHash 这样的变量的使用是一个很好的指标,表明某人是 Ruby 新手,其中蛇形(例如 num_hash)是更地道。我们希望更多人使用 Ruby,因此请将其视为温和的建议而非批评。另外,我怀疑投反对票是因为您没有“展示您的工作”。虽然我在上面解释了为什么我认为在这种特殊情况下这不是坏事,但作为 SO 上的新用户,您可以通过提供有关您已经尝试过的更多细节以及避免逐项列出的错误来避免将来的负面投票/标志在idownvotedbecau.se。
@Todd,你可能是对的,反对票和投票结束可能是因为没有证明解决问题的努力(一些成员要求这样做),但也可能是由于这个问题没有任何意义(参见我上面评论中的第一句话),并且 OP 没有澄清这个问题。这是我觉得令人费解的赞成票。
【参考方案1】:
总结
-
在第一部分中,我提供了一些上下文和一个注释良好的工作示例,说明如何使用一点蛮力和一些 Ruby 核心类在几微秒内解决定义的背包问题。
在第二部分中,我重构并扩展了代码,以演示如何将背包解决方案转换为与您想要的输出相似,尽管(如以下答案中所解释和演示的那样)有多个结果时的正确输出必须是 Hash 对象的集合而不是单个 Hash,除非您的原始帖子中未包含其他选择标准。
请注意,此答案使用 Ruby 3.0 的语法和类,并针对 Ruby 3.0.3 进行了专门测试。虽然它应该在 Ruby 2.7.3+ 上工作而无需更改,并且对于当前支持的大多数 Ruby 2.x 版本进行了一些小的重构,您的里程可能会有所不同。
使用 Ruby 核心方法解决背包问题
这似乎是knapsack problem 的变体,您正在尝试优化填充给定大小的容器。这实际上是一个复杂的 NP 完全问题,因此这种类型的实际应用程序将有许多不同的解决方案和可能的算法方法。
我并不声称以下解决方案是最佳解决方案或适用于此类问题的通用解决方案。但是,鉴于您原始帖子中提供的输入数据,它的运行速度非常快。
它的适用性主要基于这样一个事实,即您拥有相当少的 Hash 键,并且内置的 Ruby 3.0.3 核心方法 Hash#permutation 和 Enumerable#sum 足够快,可以解决这个特殊问题在我的特定机器上,任何时间都在 44-189 微秒之间。对于当前定义的问题,这似乎足够快,但您的里程和实际目标可能会有所不同。
# This is the size of your knapsack.
MAX_VALUE = 10
# It's unclear why you need a Hash or what you plan to do with the values of the
# Hash, but that's irrelevant to the problem. For now, just grab the keys.
#
# NB: You have to use hash rockets or the parser complains about using an
# Integer as a Symbol using the colon notation and raises SyntaxError.
nums_hash = 5 => 10, 3 => 9, 4 => 7, 2 => 5, 20 => 4
keys = nums_hash.keys
# Any individual element above MAX_VALUE won't fit in the knapsack anyway, so
# discard it before permutation.
keys.reject! _1 > MAX_VALUE
# Brute force it by evaluating all possible permutations of your array, dropping
# elements from the end of each sub-array until all remaining elements fit.
keys.permutation.map do |permuted_array|
loop permuted_array.sum > MAX_VALUE ? permuted_array.pop : break
permuted_array
end
返回匹配的哈希数组
上面的代码只返回适合您背包的键列表,但根据您的原始帖子,您希望返回匹配键/值对的哈希。这里的问题是您实际上有多个符合条件的 Hash 对象,因此您的集合实际上应该是一个 Array 而不是单个 Hash。只返回一个 Hash 基本上会返回原始 Hash 减去任何超过您的 MAX_VALUE 的键,这不太可能是预期的结果。
相反,既然您有一个适合您的背包的键列表,您可以遍历您的原始哈希并使用Hash#select 来返回一个具有适当键的唯一哈希对象数组/值对。一种方法是使用 Enumerable#reduce 在子数组中的每个 Hash 元素上调用 Hash#merge 以将最终结果转换为 Hash 对象数组。接下来,您应该调用Enumerable#unique 来删除任何等效的哈希,除了其内部排序。
例如,考虑这个重新设计的代码:
MAX_VALUE = 10
def possible_knapsack_contents hash
hash.keys.reject! _1 > MAX_VALUE .permutation.map do |a|
loop a.sum > MAX_VALUE ? a.pop : break ; a
end.sort
end
def matching_elements_from hash
possible_knapsack_contents(hash).map do |subarray|
subarray.map |i| hash.select |k, _| k == i .
reduce() _1.merge _2
end.uniq
end
hash = 5 => 10, 3 => 9, 4 => 7, 2 => 5, 20 => 4
matching_elements_from hash
根据定义的输入,如果您不解决唯一性问题,这将产生 24 个哈希值。但是,通过在最终的 Hash 对象数组上调用 #uniq,这将正确生成 7 个 unique 符合您定义的标准的哈希,如果不一定是您似乎期望的单个哈希:
[2=>5, 3=>9, 4=>7,
2=>5, 3=>9, 5=>10,
2=>5, 4=>7,
2=>5, 5=>10,
3=>9, 4=>7,
3=>9, 5=>10,
4=>7, 5=>10]
【讨论】:
您是否希望使用keys.combination
而不是keys.permutation
?
@Chris #combination 返回一个 n 元组。您必须重构其他代码才能使其工作,而且我看不出迭代元组大小比从一组排列的数组中删除元素更好。如果您有使用#combination 的更快或更有效的解决方案,我鼓励您将其作为单独的答案发布。以上是关于如何返回总和小于最大值的键的哈希对?的主要内容,如果未能解决你的问题,请参考以下文章
如何根据枚举哈希对数组进行排序并返回 Ruby 中的最大值?
将 json 格式的键值对转换为以符号为键的 ruby 哈希的最佳方法是啥?