Вот грубый подход к решению проблемы, которая, на мой взгляд, является 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"]
показывает, что два человека должны быть довольны. Для каждой рассматриваемой перестановки число людей, получающих предпочтительный фрукт, сравнивается с лучшим на данный момент. Если число больше, чем лучшее на данный момент, это назначение становится лучшим на данный момент. Если каждый получит предпочтительный фрукт, мы можем вырваться из цикла.