C#从没有重复的自定义对象的多个列表中计算组合

时间:2016-11-12 08:44:16

标签: c# list recursion combinations

所以我一直在努力寻找一种方法来创建包含自定义对象的多个列表中不重复的所有组合。当然,还有一些额外的限制使它更具挑战性。

基本上我正在从包含部件信息的.csv文件中解析一堆数据。然后将此数据传递给自定义对象,然后根据其“组”将这些对象添加到列表中。 (见下面的代码)

因此,一旦解析了信息,我现在有6个包含任意数量元素的列表。现在我需要按照这些规则生成这6个列表之间的所有组合:

  1. groupA的一个对象
  2. groupB中的两个对象(无重复)
  3. 来自groupC的三个对象(无重复)
  4. groupD的一个对象
  5. 来自groupE的一个对象
  6. groupF的一个对象
  7. 然后使用这些对象创建ModuleFull对象,我的整体最终结果应该是List<ModuleFull>,其中包含从零件清单生成的所有组合。

    我能够找到一种方法来使用LINQ来做到这一点,虽然我没有使用自定义对象列表进行测试,因为我意识到我的列表都包含不同数量的元素。

    因此,我将非常感谢您提出的使用递归来解决此问题的方法所提供的任何帮助。

    以下是解析数据的代码:

                using (TextFieldParser parser = new TextFieldParser(@"c:\temp\test.csv"))
            {
                parser.TextFieldType = FieldType.Delimited;
                parser.SetDelimiters(",");
    
                while (!parser.EndOfData)
                {  
                    string[] fields = parser.ReadFields();
    
                    Part tempPart = new Part(fields[0], fields[2], fields[1], double.parse(fields[4]), long.parse(fields[3]));
    
                    allParts.Add(tempPart);
    
                    if (tempPart.group == "A")
                    {
                        aParts.Add(tempPart);
                    }
                    else if (tempPart.group == "B")
                    {
                        bParts.Add(tempPart);
                    }
                    else if (tempPart.group == "C")
                    {
                        cParts.Add(tempPart);
                    }
                    else if (tempPart.group == "D")
                    {
                        dParts.Add(tempPart);
                    }
                    else if (tempPart.group == "E")
                    {
                        eParts.Add(tempPart);
                    }
                    else if (tempPart.group == "F")
                    {
                        fParts.Add(tempPart);
                    }
                }
    

    以下是填充列表的对象的两个类:

        public class Part
        {
            public string idNum; //0 locations when being parsed
            public string name; //2
            public string group; //1
            public double tolerance; //4
            public long cost; //3
    
            public Part(string id, string nm, string grp, double tol, long cst)
            {
                idNum = id;
                name = nm;
                group = grp;
                tolerance = tol;
                cost = cst;
            }
        }
    
        public class ModuleFull
        {
            public Part groupA;
            public Part groupBOne;
            public Part groupBTwo;
            public Part groupCOne;
            public Part groupCTwo;
            public Part groupCThree;
            public Part groupD;
            public Part groupE;
            public Part groupF;
    
            public ModuleFull(Part a, Part b1, Part b2, Part c1, Part c2, Part c3, Part d, Part e, Part f)
            {
                groupA = a;
                groupBOne = b1;
                groupBTwo = b2;
                groupCOne = c1;
                groupCTwo = c2;
                groupCThree = c3;
                groupD = d;
                groupE = e;
                groupF = f;
            }
        }
    

2 个答案:

答案 0 :(得分:0)

以下代码使用自定义枚举器来获取唯一组合。非常干净的解决方案。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace IEnumerable_IEnumerator_Recursive
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.csv";
        static void Main(string[] args)
        {
            Parser parser = new Parser(FILENAME);
            int level = 0;
            List<Part> root = new List<Part>();
            Part.Recurse(level, root);
        }
    }
    public class Parser
    {
        public Boolean EndOfData = false;

        public Parser(string filename)
        {
            StreamReader reader = new StreamReader(filename);
            string inputLine = "";
            while ((inputLine = reader.ReadLine()) != null)
            {
                inputLine = inputLine.Trim();
                if (inputLine.Length > 0)
                {
                    string[] fields = inputLine.Split(new char[] { ',' });

                    Part tempPart = new Part(fields[0], fields[1], fields[2], fields[3], fields[4]);

                    Part.allParts.Add(tempPart);
                }
            }
            Part.MakeDictionary();
        }

    }
    public class PartEnumerator : IEnumerator<List<Part>>
    {
        List<Part> parts = null;

        public static SortedDictionary<string, int> maxCount = new SortedDictionary<string, int>() {
            {"A", 1},
            {"B", 2},
            {"C", 3},
            {"D", 1},
            {"E", 1},
            {"F", 1}
        };
        public int size = 0;
        List<int> enumerators = null;

        public PartEnumerator(string name, List<Part> parts)
        {

            this.parts = parts;
            size = maxCount[name];
            enumerators = new List<int>(new int[size]);
            Reset();
        }

        object IEnumerator.Current
        {
            get
            {
                return Current;
            }
        }

        public List<Part> Current
        {
            get
            {
                try
                {
                    List<Part> returnParts = new List<Part>();
                    foreach (int enumerator in enumerators)
                    {
                        returnParts.Add(parts[enumerator]);
                    }

                    return returnParts;
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }

        public void Reset()
        {
            for (int count = 0; count < enumerators.Count; count++)
            {
                enumerators[count] = count;
            }
        }

        public Boolean MoveNext()
        {
            Boolean moved = true;
            int listSize = parts.Count;
            int enumNumbers = enumerators.Count;

            //only use enumerators up to the size of list
            if (listSize < enumNumbers)
            {
                enumNumbers = listSize;
            }

            Boolean ripple = true;
            int enumCounter = enumNumbers;
            if (enumCounter > 0)
            {
                while ((ripple == true) && (--enumCounter >= 0))
                {
                    ripple = false;
                    int maxCount = listSize - (enumNumbers - enumCounter);
                    if (enumerators[enumCounter] >= maxCount)
                    {
                        ripple = true;

                    }
                    else
                    {
                        for (int i = enumCounter; i < enumNumbers; i++)
                        {
                            if (i == enumCounter)
                            {
                                enumerators[i] += 1;
                            }
                            else
                            {
                                enumerators[i] = enumerators[i - 1] + 1;
                            }
                        }
                    }
                }
                if ((enumCounter <= 0) && (ripple == true))
                {
                    moved = false;
                }

            }

            return moved;
        }
        public void Dispose()
        {
        }

    }
    public class Part
    {

        public static List<Part> allParts = new List<Part>();
        public static Dictionary<string, PartEnumerator> partDict = new Dictionary<string, PartEnumerator>();


        public string idNum; //0 locations when being parsed
        public string name; //2
        public string group; //1
        public double tolerance; //4
        public long cost; //3

        public Part()
        {
        }
        public Part(string id, string nm, string grp, string tol, string cst)
        {
            idNum = id;
            name = nm;
            group = grp;
            tolerance = double.Parse(tol);
            cost = long.Parse(cst);
        }

        public static void MakeDictionary()
        {
            var listPartEnum = Part.allParts.GroupBy(x => x.name)
                .Select(x => new { Key = x.Key, List = new PartEnumerator(x.Key, x.ToList()) });

            foreach (var partEnum in listPartEnum)
            {
                partDict.Add(partEnum.Key, partEnum.List);
            }
        }

        public static string[] NAMES = { "A", "B", "C", "D", "E", "F" };
        public static void Recurse(int level, List<Part> results)
        {
            Boolean moved = true;
            if (level < PartEnumerator.maxCount.Keys.Count)
            {
                //level is equivalent to names in the Part Enumerator dictionary A to F
                string name = NAMES[level];
                PartEnumerator enumerator = partDict[name];
                enumerator.Reset();
                while ((enumerator != null) && moved)
                {
                    List<Part> allParts = new List<Part>(results);
                    allParts.AddRange((List<Part>)enumerator.Current);
                    int currentLevel = level + 1;
                    Recurse(currentLevel, allParts);
                    moved = enumerator.MoveNext();
                }
            }
            else
            {
                string message = string.Join(",", results.Select(x => string.Format("[id:{0},name:{1}]", x.name, x.idNum)).ToArray());
                Console.WriteLine(message);
            }
        }
    }

}

我使用了以下输入文件

1,A,X,0,0
2,A,X,0,0
3,A,X,0,0
4,A,X,0,0
5,A,X,0,0
1,B,X,0,0
2,B,X,0,0
3,B,X,0,0
4,B,X,0,0
5,B,X,0,0
1,C,X,0,0
2,C,X,0,0
3,C,X,0,0
4,C,X,0,0
5,C,X,0,0
1,D,X,0,0
2,D,X,0,0
3,D,X,0,0
4,D,X,0,0
5,D,X,0,0
1,E,X,0,0
2,E,X,0,0
3,E,X,0,0
4,E,X,0,0
5,E,X,0,0
1,F,X,0,0
2,F,X,0,0
3,F,X,0,0
4,F,X,0,0
5,F,X,0,0

答案 1 :(得分:0)

这可以通过两种相关方法解决。一种是从列表生成项目的所有组合的方法。这将处理你想要一组中多个的情况,比如B组和C组。另一种方法可以让你通过各种方法组合每个列表中的一个元素,也就是所谓的笛卡儿产品并且在某种程度上是第一种方法的特例。

我最近编写了一个包含这两者的组合函数库,因此我可以与您分享我的实现。我的图书馆is on Github如果您想查看源代码,可以installed from NuGet,如果您愿意的话。 (以下示例略微简化以适合您的情况;在我的更全面的版本中,组合方法具有不同的模式,允许您指定输出项目的顺序是否重要,以及是否允许多次使用源项目这里都不需要,所以它们被省略了。)

所以,这些方法中的第一个看起来像这样:

public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> source, int combinationSize)
{
    if (combinationSize > source.Count())
    {
        return new List<IEnumerable<T>>();
    }

    if (source.Count() == 1)
    {
        return new[] { source };
    }

    var indexedSource = source
        .Select((x, i) => new
        {
            Item = x,
            Index = i
        })
        .ToList();

    return indexedSource
        .SelectMany(x => indexedSource
            .OrderBy(y => x.Index != y.Index)
            .Skip(1)
            .OrderBy(y => y.Index)
            .Skip(x.Index)
            .Combinations(combinationSize - 1)
            .Select(y => new[] { x }.Concat(y).Select(z => z.Item))
        );
}

第二种方法来自blog post by Eric Lippert(实际上受到另一个StackOverflow问题的启发),看起来像这样:

public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
    if (sequences == null)
    {
        throw new ArgumentNullException(nameof(sequences));
    }

    IEnumerable<IEnumerable<T>> emptyProduct = new IEnumerable<T>[] { Enumerable.Empty<T>() };
    return sequences.Aggregate(
        emptyProduct,
        (accumulator, sequence) =>
            from accseq in accumulator
            from item in sequence
            select accseq.Concat(new[] { item }))
            .Where(x => x.Any());
}

这两种方法可以这样组合:

var groupA = new[] { "a", "aa", "aaa", "aaaa", "aaaaa" };
var groupB = new[] { "b", "bb", "bbb", "bbbb", "bbbbb" };
var groupC = new[] { "c", "cc", "ccc", "cccc", "ccccc" };
var groupD = new[] { "d", "dd", "ddd", "dddd", "ddddd" };
var groupE = new[] { "e", "ee", "eee", "eeee", "eeeee" };
var groupF = new[] { "f", "ff", "fff", "ffff", "fffff" };

var options = new[]
{
    groupA.Combinations(1), // One object from groupA
    groupB.Combinations(2), // Two objects from groupB (no repetition)
    groupC.Combinations(3), // Three objects from groupC (no repetition)
    groupD.Combinations(1), // One object from groupD
    groupE.Combinations(1), // One object from groupE
    groupF.Combinations(1)  // One object from groupF
};

return options.CartesianProduct();

因此,我们首先生成满足每个子条件的各种方法:一个来自该组,两个来自该组,等等。然后,我们研究将这些组合在一起形成一组子组的所有方法。结果是IEnumerable<IEnumerable<T>>,其中T是您开始使用的类型 - 在本例中为string,但对于您而言,它可能是其他内容。然后,您可以对此进行迭代,并使用每个集合来构建结果类型。

请注意,与许多组合问题一样,这可以很快扩展。例如,使用我的测试数据,这将返回62,500种可能的组合。