跨人有效分配选项

时间:2019-06-06 03:22:09

标签: ruby-on-rails ruby

(很抱歉,这个问题不是重复的问题,重复的问题是一个错误的帖子,已被删除)

比方说,我有人们选择的数据:

data = {"choices"=> {
  "jaime"=>["apple", "banana"], 
  "taylor"=>["apple", "pear"], 
  "pat"=>["apple", "pear","banana"]
}}

数据意味着jaime不在乎获取苹果或香蕉。现在,我要进行公平分配,以便每个人都可以得到一个在其偏好设置范围内的水果,但是哪个水果都没关系。

还有其他条件:

  • 如果选择太多(如果水果多于人),那么某个人仍然可以选择其他东西(请参见下面的第一个示例),而其他每个人都会得到一件事。
  • 如果选择太多(如果水果少于人,那么某人将一无所获)。
  • 为简单起见,假设幸运的人是谁,还是空手而归的人都无所谓,所以让我们假设它总是落到数据列表上的最后一个人 在这些条件下,请考虑以下示例。

data = {"choices"=> {
  "jaime"=>["apple", "banana"], 
  "taylor"=>["apple", "pear"], 
  "pat"=>["apple", "pear","banana","orange"]
}}
outcome = {
  "jaime"=>["apple"], 
  "taylor"=>["pear"], 
  "pat"=>["banana","orange"]
}

data = {"choices"=> {
  "jaime"=>["apple", "banana"], 
  "taylor"=>["apple", "pear"], 
  "pat"=>["apple", "banana"]
}}
outcome = {
  "jaime"=>["apple"], 
  "taylor"=>["pear"], 
  "pat"=>["banana"]
}

data = {"choices"=> {
  "jaime"=>["apple", "banana"], 
  "taylor"=>["apple", "banana"], 
  "pat"=>["apple", "banana"]
}}
outcome = {
  "jaime"=>["apple"], 
  "taylor"=>["banana"], 
  "pat"=>[]
}

我开始集思广益一些代码

data = {"choices"=> {
  "jaime"=>["apple", "banana"], 
  "taylor"=>["apple", "pear"], 
  "pat"=>["apple", "pear","banana"]
}}
fruit_avail = ["apple","banana","pear"]
result = {"allocation"=>{},"will_not_get_anything"=>[]}

# helper array, contains people that are "done" being allocated

finished = []

fruit_avail.each do |fruit|
  unfinished = data["choices"].reject { |person,options| finished.include? person }
  # helper hash, contains people who have yet to be allocated (opposite of finished)

  first_person_who_has_fruit_choice = unfinished.first { |person,options| v.include? fruit }[0]
  # this is the "someone". since i use .first, this will bias toward the first person with the fruit preference in the choices data getting it. In other words, in the absense of enough fruit, the last person will be empty handed, in the presence of too much fruit, the last person will also have the extra choice

  unfinished.each do |person, options|
    if first_person_who_has_fruit_choice == person
      result["allocation"][person] = [fruit]
      finished << person
    else
      updated_options = result["allocation"][person].present? result["allocation"][person] : options
      # helper variable, gets the latest options for this person (which may have been trimmed due to earlier allocations
      remaining_options =  updated_options - [fruit]
      result["allocation"][person] = remaining_options
      result["will_not_get_anything"] << person if remaining_options.blank?
    end
  end
end

但是上面的方法并没有捕获数据如下的情况:

data = {"choices"=> {
  "jaime"=>["apple", "banana"], 
  "taylor"=>["apple"], 
  "pat"=>["apple", "pear"]
}}

由于该代码仅在列表中起作用,因此将产生以下结果:

outcome = {
  "jaime"=>["apple"], 
  "taylor"=>[], 
  "pat"=>["pear"]
}

实际正确的结果应该是:

outcome = {
  "jaime"=>["banana"], 
  "taylor"=>["apple"], 
  "pat"=>["pear"]
 }

任何想法或建议都会受到赞赏。

3 个答案:

答案 0 :(得分:0)

让我们逐步进行。

choices = {"choices"=> {
  "jaime"=>["apple", "banana"],
  "taylor"=>["apple"],
  "pat"=>["apple", "pear"]
}}['choices']

现在让我们建立水果=>人们的反向哈希:

fruits_to_ppl =
  choices.each_with_object(Hash.new { |h, k| h[k] = [] }) do |(k, vs), acc|
    vs.each { |v| acc[v] << k }
  end.sort_by { |_, v| v.size }.to_h
#⇒ {"banana"=>["jaime"], "pear"=>["pat"], "apple"=>["jaime", "taylor", "pat"]}

此外,让我们按偏好列表的大小对ppl进行排序:

sorted = choices.sort_by { |_, v| v.size }.map(&:first)

好的,我们准备好了。

distribution =
  fruits_to_ppl.each_with_object(Hash.new { |h, k| h[k] = [] }) do |(f, ppl), acc|
    who = sorted.detect { |s| ppl.detect(&s.method(:==)) && acc[s].empty? }
    acc[who] = f if who
  end
#⇒ {"jaime"=>"banana", "pat"=>"pear", "taylor"=>"apple"}

该解决方案是不完整的,可能需要对桶中剩余的物品进行明确处理,但这是完成任务的良好起点。

答案 1 :(得分:0)

这是解决我认为是NP完全问题的蛮力方法。这样可以确保分配给首选水果的人数最大化。

代码

def assign_fruit(preferences, basket)
  prefs = preferences.values
  best = [nil, nil, nil]
  best_count = 0
  basket.permutation(prefs.size) do |perm|
    arr = prefs.zip(perm).map { |pref, p|
      pref.include?(p) ? p : nil }
    sz = arr.compact.size
    if sz > best_count
      best = arr
      break if sz == prefs.size
      best_count = sz
    end
  end
  preferences.transform_values { best.shift }
end

示例

preferences = {
  "jaime"=>["apple", "banana"], 
  "taylor"=>["apple", "pear"], 
  "pat"=>["apple", "pear", "banana", "orange"]
}

assign_fruit(preferences, ["pear", "banana", "plum"])
  #=> {"jaime"=>"banana", "taylor"=>"pear", "pat"=>nil} 
assign_fruit(preferences, ["pear", "banana", "apple", "plum"])
  #=> {"jaime"=>"banana", "taylor"=>"pear", "pat"=>"apple"}
assign_fruit(preferences, ["pear", "banana", "orange"])
  #=> {"jaime"=>"banana", "taylor"=>"pear", "pat"=>"orange"} 
assign_fruit(preferences, ["orange", "orange", "orange"]) 
  #=> {"jaime"=>nil, "taylor"=>nil, "pat"=>"orange"} 

此方法允许水果篮中的水果计数超过每个水果中的一个。

说明

如果prefences如示例中所述,并且

basket = ["pear", "banana", "plum", "fig"]

然后

prefs = preferences.values
  #=> [["apple", "banana"], ["apple", "pear"],
  #    ["apple", "pear", "banana", "orange"]] 
enum = basket.permutation(prefs.size)
  #=> #<Enumerator: ["pear", "banana", "plum",
  #                  "fig"]:permutation(3)> 
enum.size
  #=> 24 

我们可以看到enum元素将通过将enum转换为数组而生成并传递给块:

enum.to_a
  #=> [["pear", "banana", "plum"], ["pear", "banana", "fig"],
  #    ["pear", "plum", "banana"], ["pear", "plum", "fig"],
  #    ["pear", "fig", "banana"], ["pear", "fig", "plum"],
  #    ...
  #    ["fig", "plum", "pear"], ["fig", "plum", "banana"]] 

生成并传递到块的第一个元素是:

perm = enum.next
  #=> ["pear", "banana", "plum"]

然后我们计算:

pairs = prefs.zip(perm)
  #=> [[["apple", "banana"], "pear"],
  #    [["apple", "pear"], "banana"],
  #    [["apple", "pear", "banana", "orange"], "plum"]]

然后将其映射到:

pairs.map { |pref, p| pref.include?(p) ? p : nil }
  #=> [nil, nil, nil]

表明没有人喜欢吃水果。

考虑第二个例子。时间:

perm = ["pear", "apple", "banana"]

我们获得:

pairs = prefs.zip(perm)
  #=> [[["apple", "banana"], "pear"],
  #    [["apple", "pear"], "apple"],
  #    [["apple", "pear", "banana", "orange"], "banana"]] 
pairs.map { |pref, p| pref.include?(p) ? p : nil }
  #=> [nil, "apple", "banana"] 

表明两个人应该感到高兴。对于考虑的每个排列,将获得首选果实的人数与迄今为止获得的最佳果实进行比较。如果该数字大于到目前为止的最佳分配,则该分配将成为迄今为止的最佳分配。如果每个人都能得到喜欢的水果,我们就可以摆脱困境。

答案 2 :(得分:0)

这是一个冗长的解决方案,不仅为了展示基本思想而进行了优化。

我希望代码能自我解释,或者稍后再添加解释。

基本思想是跟踪一个人收到多少水果,并对其进行每次排序,以优先于拥有较少水果的人。

data = {"choices"=> {
  "jaime"=>["apple", "banana", "kiwi"], 
  "taylor"=>["apple", "pear","peach"], 
  "pat"=>["apple", "pear","banana","peach"]
}}
fruit_avail = ["apple","banana","pear","peach","melon"]

# setup
people_fruits_request = data['choices'].transform_values { |v| v & fruit_avail }.sort_by{ |_, v| v.size }.to_h
people_fruits_allocation = people_fruits_request.transform_values { |v| v = [] }
people_fruits_count = people_fruits_request.transform_values { |v| v = 0 }
fruit_avail_requested = fruit_avail & people_fruits_request.values.flatten.uniq

# calculation
fruit_avail_requested.each do |fruit|
  people_requesting = people_fruits_request.select { |_, fruits| fruits.include? fruit }.keys
  person = (people_fruits_count.sort_by { |k,v| v}.map(&:first) & people_requesting).first
  people_fruits_allocation[person] << fruit
  people_fruits_count[person] += 1
end

people_fruits_allocation
#=> {"jaime"=>["apple"], "taylor"=>["pear", "peach"], "pat"=>["banana"]}

fruits_left_in_the_basket = fruit_avail - fruit_avail_requested
#=> ["melon"]