我在找到一些问题的答案时遇到了一些问题,我已经找到了一些特定于我正在处理的代码的代码,而且我似乎无法找到关于Union如何在其核心工作的文档C#中的机制。所以问题是这个。
我有一组与此示例类似的数据:
object[] someMainTypeArray = new object [n];
List<object> objList2 = new List<object>();
foreach ( object obj in someMainTypeArray ) {
List<object> objList1 = new List<object>() { "1","2","3" };
//each obj has a property that will generate a list of data
//objList1 is the result of the data specific to obj
//some of this data could be duplicates
//Which is better, this:
foreach ( object test in objList1 ) {
if ( !objList2.Contains( test ) ) {
objList2.Add( test );
}
}
//or this:
objList2 = objList2.Union( objList1 ).ToList();
//Also, assume this has to happen anywhere from 0 to 60 times per second
}
让联盟做所有工作更有效率吗?或者使用Contains比较每个元素是否更好?
如果两者都为“否”,那么使用最少的处理时间填充唯一列表的最佳方法是什么?
效率是关键。此外,这不是家庭作业,或任何与工作有关的事情,只是学习相关。
列表在运行时是连续的,最终会被清理干净并重新填充。列表中的更改用于根据是否使用与此示例类似的最终结果列表来确定最终列表,如果该列表为空,则表示失败,并且该清单不是空的,是一个成功的条件。
以下是其中一个创建列表的代码片段:
Player.ClearMoves();
List<Pair<BoardLocation, BoardLocation>> attacking = new List<Pair<BoardLocation, BoardLocation>>();
foreach ( ChessPiece p in Board[this.Player.Opponent] ) {
if ( p.TheoryMove( this.Location ) ) {
foreach ( Pair<BoardLocation , BoardLocation> l in Utility.GetLocations( p.Location , this.Location ) ) {
if ( !attacking.Contains( l ) ) {
attacking.Add( l );
}
}
}
}
if ( attacking.Count < 1 ) {
return false;
}
答案 0 :(得分:9)
您可以在reference source中找到Enumerable.Union
实施。
这是它的工作原理:
public static IEnumerable<TSource> Union<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second) {
if (first == null) throw Error.ArgumentNull("first");
if (second == null) throw Error.ArgumentNull("second");
return UnionIterator<TSource>(first, second, null);
}
static IEnumerable<TSource> UnionIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
Set<TSource> set = new Set<TSource>(comparer);
foreach (TSource element in first)
if (set.Add(element)) yield return element;
foreach (TSource element in second)
if (set.Add(element)) yield return element;
}
如您所见,Union
将遍历两个枚举并从这些源中生成对象。像所有Linq方法一样,它不会创建列表,而是作为生成器函数。只有在您致电.ToList()
时才会创建该列表。
为了避免重复,它将使用Set
并尝试在产生元素之前添加元素。如果对集合的添加成功,那么元素就不在那里,因此可以产生。
请注意,集合非常有效,可以查找元素是否存在。它们以摊销的常数时间提供项目查找。所以这肯定比你的objList2.Contains
更有效,它需要反复遍历列表以确定每个元素是否存在于其中。
另请注意,Union
用于维护输入枚举的顺序。如果您不需要,那么您可以完全跳过此操作,并首先使用Set
。如果您计划在重新使用结构时始终将新项目添加到同一目标集,那么这一点尤其有用:
HashSet<object> set = new HashSet<object>();
foreach (…)
{
List<object> objList1 = …
// expand the set with the items from `objList1`
set.UnionWith(objList1);
}
如果你首先避免创建objList1
并且直接将你的项目添加到集合中,那将会更好 - 如果你的用例可能的话。
答案 1 :(得分:3)
如果您查看reference source for the LINQ extensions(搜索UnionIterator
),您会看到Union
在内部使用Set<T>
来跟踪已枚举的项目。不幸的是,Set<T>
是图书馆的内部课程,因此您无法直接使用它。但是你可以使用一个名为HashSet<T>
的类似集合。
您实施的主要效率低下可能是您在外部循环的每次迭代中为objList2
创建新列表。这将每次触发迭代和内存分配。由于您通过嵌套循环构建列表,我建议您执行以下操作之一:
List<T>
并在您完成后使用.Distinct
来过滤掉重复项。此方法也使用Set<T>
内部类,但与将Union
的多个调用链接在一起时,它只会使用一个Set
来构建唯一列表。HashSet<T>
构建一个始终包含唯一项目列表的集合。