【问题标题】:C# Calculate Combinations from Multiple Lists of Custom Objects with No RepetitionC# 从多个自定义对象列表中计算组合,不重复
【发布时间】:2016-11-12 08:44:16
【问题描述】:

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

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

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

  1. A 组中的一个对象
  2. B 组中的两个对象(不重复)
  3. C 组中的三个对象(不重复)
  4. groupD 中的一个对象
  5. E 组中的一个对象
  6. groupF 中的一个对象

这些对象随后用于创建 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;
        }
    }

【问题讨论】:

    标签: c# list recursion combinations


    【解决方案1】:

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

    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
    

    【讨论】:

      【解决方案2】:

      这可以通过两种相关的方法来解决。一种是从列表中生成所有项目组合的方法。这处理您想要一组中多个元素的情况,例如 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&lt;IEnumerable&lt;T&gt;&gt;,其中T 是您开始使用的类型——在本例中为string,但对您来说可能是别的东西。然后,您可以对其进行迭代并使用每个集合来构建您的结果类型。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-02-26
        • 2018-07-10
        • 2018-07-24
        • 1970-01-01
        相关资源
        最近更新 更多