找出A &B,多对多A:B排除

本文关键字:排除 找出 | 更新日期: 2023-09-27 18:07:00

关于SO的所有"不同组合"answers"笛卡尔积"的问题,我确信这个问题有一个名称和规范的解决方案,但我不会把它打开。

更新……这里有一个可能更好的例子:假设一个俱乐部定期举办抽奖活动。每个活动都有许多项目抽奖,会员按项目购买门票。在抽奖之夜,抽奖经理打印出几批名片,A、B、C等。每一件物品都要摇号,他就把这些预先组装好的物品中的一件扔进料斗,混合起来,然后取一个名字。在送出奖品后,这个名字会回到这批物品中,如果其他物品碰巧有同一批参赛者,他就会重复使用这个名字。问:有没有一种无状态的算法,可以将名片的批次集合起来,打印出最少的卡片总数?[如果没有,Chris Shain的HashSet<>示例是我所知道的最有效的有状态替代方案。]

原问题和示例:考虑以下人、三明治和过敏症的列表(相互关联存储;这些数据结构只是为了保持帖子简短,而不是问题或解决方案的内在属性):

var people = { "Pete", "Barb", "Debbie", "Frank", "Ralph", "Sally" };
var sandwiches = { "Peanut Butter", "Egg Salad", "Tuna Salad", "Oven Roasted Chicken", "Gluten-free Twigs" };
var allergies = {
    { "Pete", null }, 
    { "Barb", { "Peanut Butter" } }, 
    { "Debbie", { "Peanut Butter", "Egg Salad", "Tuna Salad" } }, 
    { "Frank", { "Egg Salad", "Tuna Salad" } }, 
    { "Ralph", { "Oven Roasted Chicken" } },
    { "Sally", { "Egg Salad", "Tuna Salad" } } };

为了找到可以吃给定三明治的人,我当然可以很容易地遍历三明治(外部)和人(内部),并检查是否过敏。

但是,我想要的是预先计算并公布最小的非过敏人员列表,该列表将涵盖所有三明治(人们显然属于不止一组),任何三明治都不超过一组人,并最大限度地重复使用,例如,集合[Pete, Barb, Debbie, Frank, Sally]将涵盖无麸质小细条和烤箱烤鸡。

举个例子,假设有一个三明治列表要抽彩。厨师做了一个,然后需要找出谁在抽奖(所有不过敏的人)。我想要最不重复的一堆橡皮带的名片,A包B包C包等等,这样人们就可以有一个三明治列表,每个列表都表明该把哪一束名片扔进三明治的帽子里。想象一下,名片纸真的很贵。(显然,为了示例起见,我已经更改了问题域。)

我现在使用的是person集合的哈希表,然后将指向这些集合的指针填充到一个以sandwich为键的字典中。它工作得很好,但是感觉不太优雅。

感谢任何能说出这个问题并为我指出一个更漂亮(或更教科书)方法的人。

Update:我使用相当于MySQL的GROUP_CONCAT实现了期望的最终结果。这不是理想的,但我添加它是因为它澄清了期望的最终结果。在伪代码:

// SandwichPeople = the sandwich list with a concatenated list of 
// people who can eat it:
SELECT Sandwich.SandwichName, GROUP_CONCAT(Person.FullName SEPARATOR ', ') as MemberNames
FROM Sandwich JOIN Person on [...not allergic...]
// SandwichRoster = distinct People from SandwichPeople with auto id
INSERT IGNORE INTO SandwichRoster (MemberNames) 
 SELECT DISTINCT MemberNames from SandwichPeople
// Match sandwiches with rosters:
SELECT SandwichPeople.SandwichName, SandwichRoster.ID
FROM SandwichPeople 
JOIN SandwichRoster on SandwichPeople.MemberNames = SandwichRoster.MemberNames

找出A &B,多对多A:B排除

创建字符串键和HashSet<string>值的字典。对person->allergy字典迭代一次,对于每个过敏,在字典中获取或创建该过敏的记录:

// A dictionary containing the set of people who are allergic to any given thing
var allergyLookup = new Dictionary<String, HashSet<String>>();
allergies.ForEach(kvp => {
    var allergicSet = allergyLookup.ContainsKey(kvp.Value) ? allergyLookup[kvp.Value] : allergyLookup[kvp.Value] = new HashSet<String>();
    allergicSet.Add(kvp.Key);
}

然后当你需要查找对某一组成分过敏的人时,你可以使用基于快速集合的ExceptWith功能:

var ingredients = { "Tuna", "Peanut Butter" };
var peopleWhoCanEatThis = new HashSet<String>(allPeople);
ingredients.ToList().ForEach(i => peopleWhoCanEatThis.ExceptWith(allergyLookup[i]));

HashSet的ExceptWith()函数比通用的要快得多,因为它是基于集合的,可以做固定时间的查找,而不是线性时间的查找。

编辑:错误地使用了Except函数-快速集减法是ExceptWith: http://msdn.microsoft.com/en-us/library/bb299875.aspx