我想从列表中删除重复项,但不改变列表中独特元素的顺序。
Jon Skeet和其他人建议使用以下方法:
list = list.Distinct().ToList();
参考:
是否保证唯一元素的顺序与之前相同?如果是,请提供确认此点的参考资料,因为我在文档中找不到任何关于它的内容。
我想从列表中删除重复项,但不改变列表中独特元素的顺序。
Jon Skeet和其他人建议使用以下方法:
list = list.Distinct().ToList();
参考:
是否保证唯一元素的顺序与之前相同?如果是,请提供确认此点的参考资料,因为我在文档中找不到任何关于它的内容。
虽然不能保证,但这是最明显的实现方法。如果以流式方式实现(即只读取尽可能少的内容,并在能够时立即返回结果),那么很难不按顺序返回结果。
您可能需要阅读我的博客文章,关于Edulinq实现Distinct()方法的内容。
请注意,即使对于LINQ to Objects,这种保证也不是一定的 (个人认为应该有这种保证),而对于其他LINQ提供程序(如LINQ to SQL),更是如此。
在我看来,LINQ to Objects提供的保证水平有时有点不一致。一些优化已记录在案,而其他一些则没有。有些文档甚至是错误的。
.Distinct()
可以以流方式操作(通过简单地跳过当前值,如果它与上一个值相同)。对于任何有限的、内存中的 IReadOnlyCollection<T>
,这种方法也可以起作用,只需迭代一次即可检查它是否已经排序。我不相信 .Distinct()
检查 IOrderedEnumerable<T>
,真是气死人了。尽管 Linq 已经存在了 13 年,但我很惊讶它没有更聪明地应用这些优化。 - DaiList<T>
有一个bool IsSorted
属性,当List<T>.Sort()
运行后为true
,并且如果发生任何重新排序,则变为false
,那将是很好的 - 这样.Distinct()
可以进行优化。哦,好吧... - DaiList<T>
的一部分。 - Jon SkeetDistinct()
方法的CIL代码显示其元素顺序得到保留 - 然而这不是官方文档记录的行为。public static class Emunmerable
{
public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
throw new ArgumentNullException("source");
return DistinctIterator<TSource>(source, null);
}
}
这里有一个有趣的东西,叫做DistinctIterator,它实现了IEnumerable和IEnumerator。这是它的IEnumerator的简化实现(去掉了goto和labels):
private sealed class DistinctIterator<TSource> : IEnumerable<TSource>, IEnumerable, IEnumerator<TSource>, IEnumerator, IDisposable
{
private bool _enumeratingStarted;
private IEnumerator<TSource> _sourceListEnumerator;
public IEnumerable<TSource> _source;
private HashSet<TSource> _hashSet;
private TSource _current;
private bool MoveNext()
{
if (!_enumeratingStarted)
{
_sourceListEnumerator = _source.GetEnumerator();
_hashSet = new HashSet<TSource>();
_enumeratingStarted = true;
}
while(_sourceListEnumerator.MoveNext())
{
TSource element = _sourceListEnumerator.Current;
if (!_hashSet.Add(element))
continue;
_current = element;
return true;
}
return false;
}
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
TSource IEnumerator<TSource>.Current
{
get { return _current; }
}
object IEnumerator.Current
{
get { return _current; }
}
}
正如您所看到的-枚举将按照源可枚举对象(我们调用“Distinct”的列表)提供的顺序进行。 Hashset
仅用于确定我们是否已经返回这样的元素。 如果没有,则我们将返回它,否则-继续在源上枚举。
因此,可以保证Distinct()
将以与应用了 Distinct 的集合提供的相同顺序返回元素。
foreach (TSource element in source)
if (set.Add(element)) yield return element;
.NET Core实现类似。
令人沮丧的是,Enumerable.Distinct的文档对此一点很困惑:
结果序列是无序的。
我只能想象他们的意思是“结果序列没有排序”。您可以通过预先排序然后将每个元素与前一个元素进行比较来实现Distinct,但这不符合以上定义中的惰性求值。
dbQuery.OrderBy(...).Distinct().ToList()
不按照 order by 谓词指定的顺序返回列表 - 移除 Distinct(恰巧是多余的)在我的情况下修复了该错误。 - Rowland Shaw虽然有点晚,但我认为没有人真正发布了最佳完整代码来完成这个任务,所以让我提供这个(本质上与.NET Framework使用Distinct()方法完全相同)*:
public static IEnumerable<T> DistinctOrdered<T>(this IEnumerable<T> items)
{
HashSet<T> returnedItems = new HashSet<T>();
foreach (var item in items)
{
if (returnedItems.Add(item))
yield return item;
}
}
这样做可以保证原始顺序,而不依赖于未记录或假定的行为。我认为这比使用多个LINQ方法更有效,但我也愿意在这里接受纠正。
(*) .NET Framework源代码使用内部的Set
类,该类似乎与HashSet
实现基本相同。
Distinct
的内部源代码,这使人们认为原始顺序得以保留。ORDER BY
语句通常出现在任何聚合之后(例如Distinct
)。所以如果您的代码是这样的:myArray.OrderBy(x => anothercol).GroupBy(x => y.mycol);
SELECT * FROM mytable GROUP BY mycol ORDER BY anothercol;
mycol anothercol
1 2
1 1
1 3
2 1
2 3
myArr.OrderBy(x => x.anothercol).GroupBy(x => x.mycol)
时,我们假设以下结果:mycol anothercol
1 1
2 1
mycol anothercol
1 2
2 1
mycol anothercol
2 1
1 2
SELECT mycol, First(anothercol) from mytable group by mycol order by anothercol;
这与您预期的完全相反。
您会发现执行计划可能因底层提供程序而异。这就是为什么文档中没有关于此的保证。
var cmp = new MyTypeEqualityComparer();
var lst = new List<MyType>();
// add some to lst
var q = lst.Distinct(cmp);
DistinctKeepOrder
时保持顺序:/// <summary>
/// support class for DistinctKeepOrder extension
/// </summary>
public class Vector3DWithOrder
{
public int Order { get; private set; }
public Vector3D Vector { get; private set; }
public Vector3DWithOrder(Vector3D v, int order)
{
Vector = v;
Order = order;
}
}
public class Vector3DWithOrderEqualityComparer : IEqualityComparer<Vector3DWithOrder>
{
Vector3DEqualityComparer cmp;
public Vector3DWithOrderEqualityComparer(Vector3DEqualityComparer _cmp)
{
cmp = _cmp;
}
public bool Equals(Vector3DWithOrder x, Vector3DWithOrder y)
{
return cmp.Equals(x.Vector, y.Vector);
}
public int GetHashCode(Vector3DWithOrder obj)
{
return cmp.GetHashCode(obj.Vector);
}
}
Vector3DWithOrder
封装了类型和一个顺序整数,而 Vector3DWithOrderEqualityComparer
封装了原始类型比较器。/// <summary>
/// retrieve distinct of given vector set ensuring to maintain given order
/// </summary>
public static IEnumerable<Vector3D> DistinctKeepOrder(this IEnumerable<Vector3D> vectors, Vector3DEqualityComparer cmp)
{
var ocmp = new Vector3DWithOrderEqualityComparer(cmp);
return vectors
.Select((w, i) => new Vector3DWithOrder(w, i))
.Distinct(ocmp)
.OrderBy(w => w.Order)
.Select(w => w.Vector);
}