问题描述
我正在尝试创建一个程序,该程序采用给定的总和和给定范围的允许加数,并输出这些加数的独特配置,这些加法器加起来为和。
用例是确定不同规模的多成员区域的可能组合,以将立法机关的成员分为几类。
在一个简单的示例中,假设有15名立法者,并且每个区最少有3个席位,最多只有5个席位,则可能的组合是:
- [3,3,3,3,3]
- [4,4,4,3]
- [5,4,3,3]
- [5,5,5]
我最初的想法是从嵌套数组中可能的最大的最小尺寸分区组开始,然后通过复制和修改前一个条目来添加更多条目。我不知道如何实现该方法,但是我也不确定这是否是解决此问题的正确方法,我正在寻找建议。
def multi_member_districts
reps = 19
min = 3
max = 6
quomin,modmin = reps.divmod(min)
quomax,modmax = reps.divmod(max)
groups = Array.new(1) {Array.new}
(quomin - 1).times do groups[0].push(min) end
groups[0].unshift(min + modmin)
# PSEUDOCODE
# copy groups[i],insert copy at groups[i+1]
# remove the smallest element of groups[i+1] and spread it out across the other
# numbers in groups[i+1] in all configurations in which no element exceeds max
# check that there are no duplicate configurations
# repeat
puts "\nThe possible groups of districts are as follows:"
groups.each_index do |i|
(min..max).each do |j|
unless groups[i].count(j) == 0
puts ">> #{groups[i].count(j)} #{j}-member districts"
end
end
puts
puts "o-o-o-o-o-o-o-o-o-o-o-o-o-o"
end
end
multi_member_districts
EDIT_1: 一个不那么琐碎的例子:19名议员,每个地区3-6个席位-
- [4,3,3,3,3,3]
- [4,4,4,4,3]
- [5、5、5、4]
- [5,4,4,3,3]
- [5、5、3、3、3]
- [6,5,5,3]
- [6、4、3、3、3]
- [6、5、4、4]
- [6,6,4,3]
EDIT_2:澄清了我的问题,减少了代码,希望更合适
解决方法
让我们首先计算组合,其中每个组合对应于数组arr
,其中arr[i]
等于分配给区i
的立法者人数。例如,如果有15名议员,并且每个区必须分配3至5名议员,则[3,3,4,5]
和[5,3]
将是不同的组合。我们可以使用递归来解决该问题。
def doit(nbr,rng)
return nil if nbr < rng.begin
recurse(nbr,rng)
end
def recurse(nbr,rng)
(rng.begin..[rng.end,nbr].min).each_with_object([]) do |n,arr|
if n == nbr
arr << [n]
elsif nbr-n >= rng.begin
recurse(nbr-n,rng).each { |a| arr << a.unshift(n) }
end
end
end
doit(15,3..5)
#=> [[3,3],[3,5],5,4],# [3,[4,# [4,# [5,[5,5]]
doit(19,3..6)
#=> [[3,6],6,# ...
# [6,[6,3]]
doit(19,3..6).size
#=> 111
但是,该问题与特定区域的分配无关。为了获得感兴趣的组合,我们可以编写以下内容。
require 'set'
def really_doit(nbr,rng)
doit(nbr,rng).map(&:tally).uniq.map do |h|
h.flat_map { |k,v| [k]*v }.sort.reverse
end
end
really_doit(15,5]]
really_doit(19,3..6)
#=> [[4,# [6,4]]
Enumerable#tally在Ruby v2.7中首次亮相。要支持早期版本,请将map(&:tally)
替换为map { |a| a.each_with_object(Hash.new(0)) { |n,h| h[n] += 1 }
。
请注意,返回的doit(nbr,rng).map(&:tally).uniq
[{3=>5},{3=>2,4=>1,5=>1},{3=>1,4=>3},{5=>3}]
在really_doit(15,3..5)
和
[{3=>5,4=>1},{3=>3,6=>1},5=>2},4=>2,4=>4},6=>2},5=>2,{4=>2,5=>1,{4=>1,5=>3}]
在really_doit(19,3..6)
中。
我们可以通过在recurse
中构造哈希集(而不是数组的数组)来对此进行改进:
require 'set'
def doit(nbr,rng).map { |h| h.flat_map { |k,v| [k]*v }.sort.reverse }
end
def recurse(nbr,nbr].min).each_with_object(Set.new) do |n,st|
if n == nbr
st << { n=>1 }
elsif nbr-n >= rng.begin
recurse(nbr-n,rng).each { |h| st << h.merge(n=>h[n].to_i+1 ) }
end
end
end
doit(15,5]]
doit(19,3..6)
#=> [[4,4]]
请注意,recurse(nbr,rng)
中的doit
返回:
#<Set: {{3=>5},{5=>1,3=>2},{4=>3,3=>1},{5=>3}}>
在doit(19,3..6)
中执行recurse(nbr,rng)
doit
时返回:
#<Set: {{4=>1,3=>5},{6=>1,3=>3},{5=>2,# {5=>1,{4=>4,{6=>2,# {6=>1,4=>2},{5=>3,4=>1}}>