C# 检索两个列表的子列表的每个可能组合的算法

C# 检索两个列表的子列表的每个可能组合的算法,c#,algorithm,C#,Algorithm,假设我有两个列表,如何迭代每个子列表的每个可能组合,使每个项只出现一次 我想一个例子是,如果你有员工和工作,你想把他们分成几个团队,每个员工只能在一个团队中,每个工作只能在一个团队中。乙二醇 List<string> employees = new List<string>() { "Adam", "Bob"} ; List<string> jobs = new List<string>() { "1", "2", "3"}; 我试着

假设我有两个列表,如何迭代每个子列表的每个可能组合,使每个项只出现一次

我想一个例子是,如果你有员工和工作,你想把他们分成几个团队,每个员工只能在一个团队中,每个工作只能在一个团队中。乙二醇

List<string> employees = new List<string>() { "Adam", "Bob"}  ;
List<string> jobs      = new List<string>() { "1", "2", "3"};
我试着用stackoverflow问题的答案生成一个包含所有可能的员工组合和所有可能的工作组合的列表,然后从每个列表中选择一个项目,但这就是我所能做到的

我不知道名单的最大规模,但肯定会少于100人,而且可能还有其他限制因素(例如每个团队最多只能有5名员工)

更新

//Initialize our sets
        var employees = new [] { "Adam", "Bob" };
        var jobs = new[] {  "1", "2", "3" };

        //iterate to max number of partitions (Sum)
        for (int i = 1; i <= Math.Min(employees.Length, jobs.Length); i++)
        {
            Debug.WriteLine("Partition to " + i + " parts:");

            //Get all possible partitions of set "employees" (Stirling Set)
            var aparts = employees.AllPartitions().Where(y => y.Count == i);
            //Get all possible partitions of set "jobs" (Stirling Set)
            var bparts = jobs.AllPartitions().Where(y => y.Count == i);

            //Get cartesian product of all partitions 
            var partsProduct = from employeesPartition in aparts
                      from jobsPartition in bparts
                               select new {  employeesPartition,  jobsPartition };

            var idx = 0;
            //for every product of partitions
            foreach (var productItem in partsProduct)
            {
                //loop through the permutations of jobPartition (N!)
                foreach (var permutationOfJobs in productItem.jobsPartition.Permute())
                {
                    Debug.WriteLine("Combination: "+ ++idx);

                    for (int j = 0; j < i; j++)
                    {
                        Debug.WriteLine(productItem.employeesPartition[j].ToArrayString() + " : " + permutationOfJobs.ToArray()[j].ToArrayString());
                    }
                }
            }
        }
不确定这是否可以整理和/或简化,但这就是我到目前为止的结论

它使用Yorye提供的组算法(见下面的答案),但我删除了我不需要的顺序,如果密钥不可比较,就会导致问题

var employees = new List<string>() { "Adam", "Bob"  } ;
var jobs      = new List<string>() { "1", "2", "3"  };

int c= 0;
foreach (int noOfTeams in Enumerable.Range(1, employees.Count))
{   
    var hs = new HashSet<string>();

    foreach( var grouping in Group(Enumerable.Range(1, noOfTeams).ToList(), employees))
    {   
        // Generate a unique key for each group to detect duplicates.   
        var key = string.Join(":" , 
                      grouping.Select(sub => string.Join(",", sub)));           

        if (!hs.Add(key))
            continue;

        List<List<string>> teams = (from r in grouping select r.ToList()).ToList();

        foreach (var group in Group(teams, jobs))
        {
            foreach (var sub in group)
            {               
                Console.WriteLine(String.Join(", " , sub.Key )   + " : " + string.Join(", ", sub));
            }
            Console.WriteLine();
            c++;
        }
    }

}           
Console.WriteLine(String.Format("{0:n0} combinations for {1} employees and {2} jobs" , c , employees.Count, jobs.Count));  
var employees=newlist(){“Adam”,“Bob”};
var jobs=newlist(){“1”、“2”、“3”};
int c=0;
foreach(可枚举范围内的整数nooftams(1,employees.Count))
{   
var hs=新的HashSet();
foreach(组中的var分组(Enumerable.Range(1,nooftams.ToList(),employees))
{   
//为每个组生成唯一密钥以检测重复项。
var key=string.Join(“:”,
选择(sub=>string.Join(“,”,sub));
如果(!hs.添加(键))
继续;
列出团队=(从分组中的r中选择r.ToList()).ToList();
foreach(组中的var组(团队、作业))
{
foreach(集团中的var子公司)
{               
Console.WriteLine(String.Join(“,”,sub.Key)+“:“+String.Join(“,”,sub));
}
Console.WriteLine();
C++;
}
}
}           
WriteLine(String.Format({1}个雇员和{2}个工作的{0:n0}组合),c,employees.Count,jobs.Count);

因为我不担心结果的顺序,这似乎给了我所需要的。

使用其他库可以吗?是一个用于组合的通用库(您显然想要一个没有重复的库)。然后你只需要在你的员工列表上做一个foreach,然后运行两者的组合

我不认为从大O的角度来看,你在帮自己什么忙,效率优先吗

这是来自hip,但这应该是获得所需的代码(使用该库):

组合=新组合(作业,2);
foreach(组合中的IList c){
Console.WriteLine(String.Format(“{{{0}{1}}}”,c[0],c[1]);
}

然后,这需要应用到每个员工身上

假设我们有两套,A和B。
A={a1,a2,a3};B={b1,b2,b3}

首先,让我们得到一个由元组组成的集合,元组包含一个子集及其补码:

{a1} {a2 a3} 
{a2} {a1 a3}
{a3} {a1 a2}
{a2, a3} {a1}
...
为此,您可以使用上面的Rikon库,或者编写自己的代码。您需要执行以下操作:

  • 获取一组所有子集(称为幂集)
  • 对于每个子集,从较大的集合中获取该子集的补码或剩余元素
  • 以元组或成对的形式连接这些元素;e、 g.(子集,补码)
  • 在这里安排事务<代码>{a1}{a2a3}和{a2a3}{a1}毕竟是不同的元组

    然后我们得到B的一个相似集合。然后,我们在两个集合之间执行交叉连接,得到:

    {a1} {a2 a3} | {b1} {b2 b3}
    
    {a2} {a1 a3} | {b2} {b1 b3}
    ...
    
    这与上面的描述非常吻合。只要把{Bob,Adam}看作一组,把{1,2,3}看作另一组。
    根据您的需求,您必须转储一些空集(因为电源集也包括空子集)。不过,据我所知,这是总的要点。抱歉缺少代码;我要睡觉了:P

    忽略其他约束(例如团队的最大规模),您要做的是创建集合P的分区和集合Q的分区,并使用相同数量的子集,然后查找其中一个集合的所有组合,以将第一个分区映射到第二个分区

    MichaelOrlov的论文提供了一种简单的生成分区的算法,其中每次迭代都使用常量空间。他还提供了一个算法来列出给定大小的分区

    从p={A,B}和Q={1,2,3}开始,然后大小为1的分区是[p]和[Q],因此唯一的配对是([p],[Q])

    对于大小为2的分区,p只有两个元素,因此只有一个大小为2[{A},{B}],Q有三个大小为2[{1},{2,3}],{1,2},{3}],{1,3},{2}的分区

    由于Q的每个分区包含两个子集,因此每个分区有2个组合,这将提供六对:

    ( [ { A }, { B } ], [ { 1 }, { 2, 3 } ] )
    ( [ { A }, { B } ], [ { 2, 3 }, { 1 } ] )
    ( [ { A }, { B } ], [ { 1, 2 }, { 3 } ] )
    ( [ { A }, { B } ], [ { 3 }, { 1, 2 } ] )
    ( [ { A }, { B } ], [ { 1, 3 }, { 2 } ] ) 
    ( [ { A }, { B } ], [ { 2 }, { 1, 3 } ] ) 
    
    由于其中一个原始集合的大小为2,因此没有大小为3的分区,因此我们停止。

    在我的回答中,我将忽略您得到的最后一个结果:
    Adam,Bob:1,2,3
    ,因为这在逻辑上是一个异常。跳到末尾以查看我的输出

    说明:

    public static IEnumerable<ILookup<TValue, TKey>> Group<TKey, TValue>
        (List<TValue> keys,
         List<TKey> values,
         bool allowEmptyGroups = false)
    {
        var indices = new int[values.Count];
        var maxIndex = values.Count - 1;
        var nextIndex = maxIndex;
        indices[maxIndex] = -1;
    
        while (nextIndex >= 0)
        {
            indices[nextIndex]++;
    
            if (indices[nextIndex] == keys.Count)
            {
                indices[nextIndex] = 0;
                nextIndex--;
                continue;
            }
    
            nextIndex = maxIndex;
    
            if (!allowEmptyGroups && indices.Distinct().Count() != keys.Count)
            {
                continue;
            }
    
            yield return indices.Select((keyIndex, valueIndex) =>
                                        new
                                            {
                                                Key = keys[keyIndex],
                                                Value = values[valueIndex]
                                            })
                .OrderBy(x => x.Key)
                .ToLookup(x => x.Key, x => x.Value);
        }
    }
    
    var employees = new List<string>() { "Adam", "Bob"};
    var jobs      = new List<string>() { "1", "2", "3"};
    var c = 0;
    
    foreach (var group in CombinationsEx.Group(employees, jobs))
    {
        foreach (var sub in group)
        {
            Console.WriteLine(sub.Key + ": " + string.Join(", ", sub));
        }
    
        c++;
        Console.WriteLine();
    }
    
    Console.WriteLine(c + " combinations.");
    
    Adam: 1, 2
    Bob: 3
    
    Adam: 1, 3
    Bob: 2
    
    Adam: 1
    Bob: 2, 3
    
    Adam: 2, 3
    Bob: 1
    
    Adam: 2
    Bob: 1, 3
    
    Adam: 3
    Bob: 1, 2
    
    6 combinations.
    
    Adam, Bob, James: 1, 2
    
    Adam, Bob: 1
    James: 2
    
    James: 1
    Adam, Bob: 2
    
    Adam, James: 1
    Bob: 2
    
    Bob: 1
    Adam, James: 2
    
    Adam: 1
    Bob, James: 2
    
    Bob, James: 1
    Adam: 2
    
    7 combinations.
    6 duplicates.
    
    Partition to 1 parts:
    Combination: 1
    { Adam , Bob } : { 1 , 2 , 3 }
    Partition to 2 parts:
    Combination: 1
    { Bob } : { 2 , 3 }
    { Adam } : { 1 }
    Combination: 2
    { Bob } : { 1 }
    { Adam } : { 2 , 3 }
    Combination: 3
    { Bob } : { 1 , 3 }
    { Adam } : { 2 }
    Combination: 4
    { Bob } : { 2 }
    { Adam } : { 1 , 3 }
    Combination: 5
    { Bob } : { 3 }
    { Adam } : { 1 , 2 }
    Combination: 6
    { Bob } : { 1 , 2 }
    { Adam } : { 3 }
    
    其思想是迭代“元素将属于哪个组”的组合

    假设您有元素“a,b,c”,并且您有组“1,2”,让我们有一个大小为3的数组,作为元素数,它将包含组“1,2”的所有可能组合,并重复:

    {1, 1, 1} {1, 1, 2} {1, 2, 1} {1, 2, 2}
    {2, 1, 1} {2, 1, 2} {2, 2, 1} {2, 2, 2}
    
    现在,我们将对每个组进行排序,并根据以下逻辑对其进行键值收集:

    元素组[i]
    将是
    comb[i]
    的值

    Example with {1, 2, 1}:
    a: group 1
    b: group 2
    c: group 1
    
    And in a different view, the way you wanted it:
    group 1: a, c
    group 2: b
    
    在所有这些之后,您只需过滤不包含所有组的所有组合,因为您希望所有组至少有一个值

    所以
    Adam: 1, 2
    Bob: 3
    
    Adam: 1, 3
    Bob: 2
    
    Adam: 1
    Bob: 2, 3
    
    Adam: 2, 3
    Bob: 1
    
    Adam: 2
    Bob: 1, 3
    
    Adam: 3
    Bob: 1, 2
    
    6 combinations.
    
    public static IEnumerable<ILookup<TKey[], TValue>> GroupCombined<TKey, TValue>
        (List<TKey> keys,
         List<TValue> values)
    {
        // foreach (int i in Enumerable.Range(1, keys.Count))
        for (var i = 1; i <= keys.Count; i++)
        {
            foreach (var lookup in Group(Enumerable.Range(0, i).ToList(), keys))
            {
                foreach (var lookup1 in
                         Group(lookup.Select(keysComb => keysComb.ToArray()).ToList(),
                               values))
                {
                    yield return lookup1;
                }
            }
        }
    
        /*
        Same functionality:
    
        return from i in Enumerable.Range(1, keys.Count)
               from lookup in Group(Enumerable.Range(0, i).ToList(), keys)
               from lookup1 in Group(lookup.Select(keysComb =>
                                         keysComb.ToArray()).ToList(),
                                     values)
               select lookup1;
        */
    }
    
    var c = 0;
    var d = 0;
    
    var employees = new List<string> { "Adam", "Bob", "James" };
    var jobs = new List<string> {"1", "2"};
    
    var prevStrs = new List<string>();
    
    foreach (var group in CombinationsEx.GroupCombined(employees, jobs))
    {
        var currStr = string.Join(Environment.NewLine,
                                  group.Select(sub =>
                                               string.Format("{0}: {1}",
                                                   string.Join(", ", sub.Key),
                                                   string.Join(", ", sub))));
    
        if (prevStrs.Contains(currStr))
        {
            d++;
            continue;
        }
    
        prevStrs.Add(currStr);
    
        Console.WriteLine(currStr);
        Console.WriteLine();
        c++;
    }
    
    Console.WriteLine(c + " combinations.");
    Console.WriteLine(d + " duplicates.");
    
    Adam, Bob, James: 1, 2
    
    Adam, Bob: 1
    James: 2
    
    James: 1
    Adam, Bob: 2
    
    Adam, James: 1
    Bob: 2
    
    Bob: 1
    Adam, James: 2
    
    Adam: 1
    Bob, James: 2
    
    Bob, James: 1
    Adam: 2
    
    7 combinations.
    6 duplicates.
    
    for (var i = 1; i <= keys.Count; i++)
    
    for (var i = 1; i < keys.Count; i++)
    
    public static IEnumerable<ILookup<TKey[], TValue>> GroupCombined<TKey, TValue>
        (List<TKey> keys,
         List<TValue> values)
    {
        for (var i = 1; i <= keys.Count; i++)
        {
            var prevs = new List<TKey[][]>();
    
            foreach (var lookup in Group(Enumerable.Range(0, i).ToList(), keys))
            {
                var found = false;
                var curr = lookup.Select(sub => sub.OrderBy(k => k).ToArray())
                    .OrderBy(arr => arr.FirstOrDefault()).ToArray();
    
                foreach (var prev in prevs.Where(prev => prev.Length == curr.Length))
                {
                    var isDuplicate = true;
    
                    for (var x = 0; x < prev.Length; x++)
                    {
                        var currSub = curr[x];
                        var prevSub = prev[x];
    
                        if (currSub.Length != prevSub.Length ||
                            !currSub.SequenceEqual(prevSub))
                        {
                            isDuplicate = false;
                            break;
                        }
                    }
    
                    if (isDuplicate)
                    {
                        found = true;
                        break;
                    }
                }
    
                if (found)
                {
                    continue;
                }
    
                prevs.Add(curr);
    
                foreach (var group in
                         Group(lookup.Select(keysComb => keysComb.ToArray()).ToList(),
                               values))
                {
                    yield return group;
                }
            }
        }
    }
    
     public static IEnumerable<IEnumerable<T>> Permute<T>(this IEnumerable<T> list)
        {
            if (list.Count() == 1)
                return new List<IEnumerable<T>> { list };
    
            return list.Select((a, i1) => Permute(list.Where((b, i2) => i2 != i1)).Select(b => (new List<T> { a }).Union(b)))
                       .SelectMany(c => c);
        }
    
    public static List<List<List<T>>> AllPartitions<T>(this IEnumerable<T> set)
        {
            var retList = new List<List<List<T>>>();
    
            if (set == null || !set.Any())
            {
                retList.Add(new List<List<T>>());
                return retList;
            }
            else
            {
                for (int i = 0; i < Math.Pow(2, set.Count()) / 2; i++)
                {
                    var j = i;
    
                    var parts = new [] { new List<T>(), new List<T>() };
    
    
                    foreach (var item in set)
                    {
                        parts[j & 1].Add(item);
                        j >>= 1;
                    }
    
                    foreach (var b in AllPartitions(parts[1]))
                    {
                        var x = new List<List<T>>();
    
                        x.Add(parts[0]);
    
                        if (b.Any())
                            x.AddRange(b);
    
                        retList.Add(x);
                    }
                }
            }
            return retList;
        }
    
    //Initialize our sets
            var employees = new [] { "Adam", "Bob" };
            var jobs = new[] {  "1", "2", "3" };
    
            //iterate to max number of partitions (Sum)
            for (int i = 1; i <= Math.Min(employees.Length, jobs.Length); i++)
            {
                Debug.WriteLine("Partition to " + i + " parts:");
    
                //Get all possible partitions of set "employees" (Stirling Set)
                var aparts = employees.AllPartitions().Where(y => y.Count == i);
                //Get all possible partitions of set "jobs" (Stirling Set)
                var bparts = jobs.AllPartitions().Where(y => y.Count == i);
    
                //Get cartesian product of all partitions 
                var partsProduct = from employeesPartition in aparts
                          from jobsPartition in bparts
                                   select new {  employeesPartition,  jobsPartition };
    
                var idx = 0;
                //for every product of partitions
                foreach (var productItem in partsProduct)
                {
                    //loop through the permutations of jobPartition (N!)
                    foreach (var permutationOfJobs in productItem.jobsPartition.Permute())
                    {
                        Debug.WriteLine("Combination: "+ ++idx);
    
                        for (int j = 0; j < i; j++)
                        {
                            Debug.WriteLine(productItem.employeesPartition[j].ToArrayString() + " : " + permutationOfJobs.ToArray()[j].ToArrayString());
                        }
                    }
                }
            }
    
    Partition to 1 parts:
    Combination: 1
    { Adam , Bob } : { 1 , 2 , 3 }
    Partition to 2 parts:
    Combination: 1
    { Bob } : { 2 , 3 }
    { Adam } : { 1 }
    Combination: 2
    { Bob } : { 1 }
    { Adam } : { 2 , 3 }
    Combination: 3
    { Bob } : { 1 , 3 }
    { Adam } : { 2 }
    Combination: 4
    { Bob } : { 2 }
    { Adam } : { 1 , 3 }
    Combination: 5
    { Bob } : { 3 }
    { Adam } : { 1 , 2 }
    Combination: 6
    { Bob } : { 1 , 2 }
    { Adam } : { 3 }
    
    public static String ToArrayString<T>(this IEnumerable<T> arr)
        {
            string str = "{ ";
    
            foreach (var item in arr)
            {
                str += item + " , ";
            }
    
            str = str.Trim().TrimEnd(',');
            str += "}";
    
            return str;
        }
    
    public static IEnumerable<IEnumerable<T>> QuickPerm<T>(this IEnumerable<T> set)
        {
            int N = set.Count();
            int[] a = new int[N];
            int[] p = new int[N];
    
            var yieldRet = new T[N];
    
            var list = set.ToList();
    
            int i, j, tmp ;// Upper Index i; Lower Index j
            T tmp2;
    
            for (i = 0; i < N; i++)
            {
                // initialize arrays; a[N] can be any type
                a[i] = i + 1; // a[i] value is not revealed and can be arbitrary
                p[i] = 0; // p[i] == i controls iteration and index boundaries for i
            }
            yield return list;
            //display(a, 0, 0);   // remove comment to display array a[]
            i = 1; // setup first swap points to be 1 and 0 respectively (i & j)
            while (i < N)
            {
                if (p[i] < i)
                {
                    j = i%2*p[i]; // IF i is odd then j = p[i] otherwise j = 0
    
                    tmp2 = list[a[j]-1];
                    list[a[j]-1] = list[a[i]-1];
                    list[a[i]-1] = tmp2;
    
                    tmp = a[j]; // swap(a[j], a[i])
                    a[j] = a[i];
                    a[i] = tmp;
    
                    //MAIN!
    
                   // for (int x = 0; x < N; x++)
                    //{
                    //    yieldRet[x] = list[a[x]-1];
                    //}
                    yield return list;
                    //display(a, j, i); // remove comment to display target array a[]
    
                    // MAIN!
    
                    p[i]++; // increase index "weight" for i by one
                    i = 1; // reset index i to 1 (assumed)
                }
                else
                {
                    // otherwise p[i] == i
                    p[i] = 0; // reset p[i] to zero
                    i++; // set new index value for i (increase by one)
                } // if (p[i] < i)
            } // while(i < N)
        }
    
                 results.Add(productItem.employeesPartition[j].Aggregate((a, b) => a + "," + b) + " : " + permutationOfJobs.ToArray()[j].Aggregate((a, b) => a + "," + b));