c#计算自定义对象的多个列表的组合,没有重复
本文关键字:组合 自定义 计算 对象 列表 | 更新日期: 2023-09-27 18:17:29
所以我一直在努力寻找一种方法来创建所有组合,而不会从包含自定义对象的多个列表中重复。当然,还有一些额外的限制使它更具挑战性。
基本上,我正在从包含部分信息的.csv文件解析一堆数据。然后将此数据传递给自定义对象,然后将这些对象根据其"组"添加到列表中。(见下面的代码)
因此,一旦信息被解析,我现在有6个包含任意数量元素的列表。现在我需要按照以下规则生成这6个列表之间的所有组合:
- 组a中的一个对象
- 来自组b的两个对象(无重复)
- 组pc中的三个对象(无重复)
- 来自groupD的一个对象
- 组 中的一个对象
- 组f中的一个对象
然后使用这些对象来创建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;
}
}
下面的代码使用一个自定义枚举器来获得唯一的组合。非常干净的溶液。
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
这可以用两个相关的方法来解决。一个是从列表中生成所有项目组合的方法。这个方法处理的是你想从一个集合中得到多个元素的情况,比如组B和c。另一个方法是让你从每个列表中得到一个元素的所有组合方法,这也被称为笛卡尔积,在某种程度上是第一种方法的特殊情况。
我最近写了一个组合函数库,其中包含了这两个函数,所以我可以和你分享我的实现。我的库是在Github上,如果你想看看源代码,可以从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))
);
}
第二个方法来自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个可能的组合。