C # Distinct () 메서드는 시퀀스의 원래 순서를 그대로 유지합니까?
목록에서 고유 한 요소의 순서를 변경하지 않고 목록에서 중복 항목을 제거하고 싶습니다.
Jon Skeet 및 다른 사람들은 다음을 사용하도록 제안했습니다.
list = list.Distinct().ToList();
고유 요소의 순서가 이전과 동일하다는 것이 보장됩니까? 그렇다면 문서에서 아무것도 찾을 수 없으므로 이것을 확인하는 참조를 제공하십시오.
보장되지는 않지만 가장 확실한 구현입니다. 순서대로 반환 하지 않고 스트리밍 방식으로 구현하기 (즉, 가능한 한 빨리 결과를 반환하고 가능한 한 적게 읽음)으로 구현하기가 어려울 것입니다.
Distinct () 의 Edulinq 구현 에 대한 내 블로그 게시물을 읽고 싶을 수 있습니다 .
LINQ to Objects (개인적으로 는 그래야 한다고 생각합니다 )에 대해 이것이 보장되었다고해도 LINQ to SQL과 같은 다른 LINQ 공급자에게는 아무런 의미가 없습니다.
LINQ to Objects 내에서 제공되는 보증 수준은 때때로 IMO라는 약간 일치하지 않습니다. 일부 최적화는 문서화되고 나머지는 문서화되지 않았습니다. 도대체 일부 문서가 잘못되었습니다 .
예, 원래 목록에서 처음 나타나는 순서대로. 그것은됩니다 보장 닷넷 프레임 워크 3.5
Reflector로 약간의 조사를했습니다. System.Core.dll, Version = 3.5.0.0을 분해하면 Distinct ()가 다음과 같은 확장 메서드임을 알 수 있습니다.
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);
}
}
여기서 흥미로운 것은 IEnumerable과 IEnumerator를 구현하는 DistinctIterator입니다. 다음은이 IEnumerator의 단순화 된 (goto 및 lables 제거됨) 구현입니다.
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가 적용된 컬렉션에서 제공하는 동일한 순서 로 요소를 정확히 반환합니다 .
문서 에 따르면 순서는 순서가 없습니다.
예 , Enumerable.Distinct는 순서를 유지합니다. 방법이 게으르다 고 가정하면 "개별 값이 보이는 즉시 산출"이 자동으로 수행됩니다. 생각해보세요.
.NET 참조 소스 확인한다. 각 등가 클래스의 첫 번째 요소 인 하위 시퀀스를 반환합니다.
foreach (TSource element in source)
if (set.Add(element)) yield return element;
.NET 핵심 구현은 비슷합니다.
실망 스럽게도 Enumerable.Distinct에 대한 문서 는이 점에서 혼란 스럽습니다.
결과 시퀀스는 순서가 없습니다.
"결과 시퀀스가 정렬되지 않았습니다."라는 의미 일뿐입니다. 당신은 수 이전에 각 요소를 비교 한 후 미리 정렬하여 고유 구현하지만, 위에서 정의 된이 게으른하지 않을 것입니다.
By default when use Distinct linq operator uses Equals method but you can use your own IEqualityComparer<T> object to specify when two objects are equals with a custom logic implementing GetHashCode and Equals method. Remember that:
GetHashCode should not used heavy cpu comparision ( eg. use only some obvious basic checks ) and its used as first to state if two object are surely different ( if different hash code are returned ) or potentially the same ( same hash code ). In this latest case when two object have the same hashcode the framework will step to check using the Equals method as a final decision about equality of given objects.
After you have MyType and a MyTypeEqualityComparer classes follow code not ensure the sequence maintain its order:
var cmp = new MyTypeEqualityComparer();
var lst = new List<MyType>();
// add some to lst
var q = lst.Distinct(cmp);
In follow sci library I implemented an extension method to ensure Vector3D set maintain the order when use a specific extension method DistinctKeepOrder:
relevant code follows:
/// <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);
}
}
In short Vector3DWithOrder encapsulate the type and an order integer, while Vector3DWithOrderEqualityComparer encapsulates original type comparer.
and this is the method helper to ensure order maintained
/// <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);
}
Note : further research could allow to find a more general ( uses of interfaces ) and optimized way ( without encapsulate the object ).
This highly depends on your linq-provider. On Linq2Objects you can stay on the internal source-code for Distinct, which makes one assume the original order is preserved.
However for other providers that resolve to some kind of SQL for example, that isn´t neccessarily the case, as an ORDER BY-statement usually comes after any aggregation (such as Distinct). So if your code is this:
myArray.OrderBy(x => anothercol).GroupBy(x => y.mycol);
this is translated to something similar to the following in SQL:
SELECT * FROM mytable GROUP BY mycol ORDER BY anothercol;
This obviously first groups your data and sorts it afterwards. Now you´re stuck on the DBMS own logic of how to execute that. On some DBMS this isn´t even allowed. Imagine the following data:
mycol anothercol
1 2
1 1
1 3
2 1
2 3
when executing myArr.OrderBy(x => x.anothercol).GroupBy(x => x.mycol) we assume the following result:
mycol anothercol
1 1
2 1
But the DBMS may aggregate the anothercol-column so, that allways the value of the first row is used, resulting in the following data:
mycol anothercol
1 2
2 1
which after ordering will result in this:
mycol anothercol
2 1
1 2
This is similar to the following:
SELECT mycol, First(anothercol) from mytable group by mycol order by anothercol;
which is the completely reverse order than what you expected.
You see the execution-plan may vary depending on what the underlying provider is. This is why there´s no guarantee about that in the docs.
'Programing' 카테고리의 다른 글
| 자바를 통한 scp (0) | 2020.10.22 |
|---|---|
| 왜 TypeError : 'float'유형의 정수가 아닌 시퀀스를 곱할 수 없습니까? (0) | 2020.10.22 |
| Google 크롬 개발 도구가 표시되지 않는 요소 스타일을 검사합니다. (0) | 2020.10.22 |
| Android 스튜디오 : Gradle 동기화 실패 : '…'HEAD를 보낼 수 없습니다. (0) | 2020.10.22 |
| Borg 패턴이 Python의 Singleton 패턴보다 나은 이유 (0) | 2020.10.22 |