C# 联合与包含,用于连续数据的列表
本文关键字:连续 数据 列表 用于 包含 | 更新日期: 2023-09-27 18:24:27
我正在处理的某些代码的问题的答案时遇到了一些麻烦,而且我似乎找不到一些关于 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
}
让联盟做所有工作更有效率吗?还是使用包含来比较每个元素更好?
如果两者都为否,则使用尽可能少的处理时间填充唯一列表的最佳方法是什么?
效率是关键。此外,这不是家庭作业,或任何与工作相关的事情,只是与学习有关。
这些列表在运行时是连续的,最终会被擦除干净并重新填充。列表中的更改用于根据是否使用与此示例类似的最终结果列表来生成最终列表,如果该列表为空,则为失败条件,如果该列表不为空,则为成功条件。
下面是创建的列表之一的相关代码片段:
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;
}
参考源中找到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
,而只是直接将项目添加到集合中,那就更好了——如果这可能适用于您的用例。
如果您查看 LINQ 扩展的引用源(搜索 UnionIterator
(,您将看到Union
通过使用Set<T>
来跟踪已枚举的项,从而在内部工作。不幸的是,Set<T>
是库的内部类,因此您无法直接使用它。但是,您可以使用一个名为HashSet<T>
的类似集合。
实现中的主要效率低下之一可能是您在外部循环的每次迭代中为objList2
创建一个新列表。这将每次触发迭代和内存分配。由于您是通过嵌套循环构建列表的,因此我建议您执行以下操作之一:
- 将所有内容添加到
List<T>
,并在完成后使用.Distinct
过滤掉重复项。此方法还使用Set<T>
内部类,但与链接多个调用以Union
不同,它只会使用单个Set
来生成唯一列表。 - 使用
HashSet<T>
生成始终具有唯一项列表的集合。