목록에서 고유 요소의 순서를 변경하지 않고 목록에서 중복을 제거하고 싶습니다.
Jon Skeet 및 다른 사람들은 다음을 사용하도록 제안했습니다.
list = list.Distinct().ToList();
고유 요소의 순서가 이전과 동일하다는 것이 보장됩니까? 그렇다면 문서에서 아무것도 찾을 수 없으므로 이것을 확인하는 참조를 제공하십시오.
목록에서 고유 요소의 순서를 변경하지 않고 목록에서 중복을 제거하고 싶습니다.
Jon Skeet 및 다른 사람들은 다음을 사용하도록 제안했습니다.
list = list.Distinct().ToList();
고유 요소의 순서가 이전과 동일하다는 것이 보장됩니까? 그렇다면 문서에서 아무것도 찾을 수 없으므로 이것을 확인하는 참조를 제공하십시오.
답변:
보장되지는 않지만 가장 확실한 구현입니다. 순서대로 반환 하지 않고 는 스트리밍 방식으로 구현하기 (즉, 가능한 한 빨리 결과를 반환하고 가능한 한 적게 읽은 상태로) 구현하기가 어려울 것입니다.
Distinct () 의 Edulinq 구현 에 대한 내 블로그 게시물을 읽고 싶을 수 있습니다 .
LINQ to Objects (개인적으로 는 그래야 한다고 생각합니다 )에 대해 이것이 보장되었다고해도 LINQ to SQL과 같은 다른 LINQ 공급자에게는 아무런 의미가 없습니다.
LINQ to Objects 내에서 제공되는 보증 수준은 때때로 IMO라는 약간 일치하지 않습니다. 일부 최적화는 문서화되고 나머지는 문서화되지 않았습니다. 도대체 문서 중 일부가 잘못되었습니다 .
.NET Framework 3.5에서 Linq-to-Objects 구현의 CIL을 디스 어셈블하면 Distinct()
요소의 순서가 유지되지만 이는 문서화 된 동작이 아닙니다.
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에 대한 문서 는이 점에서 혼란 스럽습니다.
결과 시퀀스는 순서가 없습니다.
"결과 시퀀스가 정렬되지 않았습니다"라는 의미 일뿐입니다. 당신은 수 이전에 각 요소를 비교 한 후 미리 정렬하여 고유 구현하지만, 위에서 정의 된이 게으른하지 않을 것입니다.
dbQuery.OrderBy(...).Distinct().ToList()
이 술어에 의해 지정된 순서대로 목록을 반환하지 않는 경우-Distinct를 제거하면 (중복이었던) 버그가 수정되었습니다
기본적으로 Distinct linq 연산자를 사용할 때 Equals 메서드를 사용하지만 IEqualityComparer<T>
사용자 지정 논리 구현 GetHashCode
및 Equals
메서드를 사용하여 두 개체가 같은 경우 고유 한 개체를 사용하여 지정할 수 있습니다 . 기억:
GetHashCode
무거운 CPU 비교 (예 : 명백한 기본 검사 만 사용)를 사용해서는 안되며 두 객체가 확실히 다른지 (다른 해시 코드가 반환되는 경우) 또는 잠재적으로 동일한 지 (동일한 해시 코드)를 나타내는 첫 번째로 사용됩니다. 이 최근의 경우 두 개체가 동일한 해시 코드를 갖는 경우 프레임 워크는 주어진 개체의 동등성에 대한 최종 결정으로 Equals 메서드를 사용하여 확인하는 단계를 수행합니다.
당신이 가진 후 MyType
와 MyTypeEqualityComparer
클래스는 코드가 순서가 순서를 유지 보장하지 따르십시오 :
var cmp = new MyTypeEqualityComparer();
var lst = new List<MyType>();
// add some to lst
var q = lst.Distinct(cmp);
follow sci 라이브러리 에서 특정 확장 방법을 사용할 때 Vector3D 세트가 순서를 유지하도록 확장 방법을 구현했습니다 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);
}
참고 : 추가 연구를 통해보다 일반적인 (인터페이스 사용) 및 최적화 된 방법 (객체를 캡슐화하지 않고)을 찾을 수 있습니다.
이것은 linq 공급자에 따라 크게 달라집니다. Linq2Objects에서는에 대한 내부 소스 코드를 Distinct
유지할 수 있으므로 원래 순서가 유지된다고 가정합니다.
그러나 예를 들어 어떤 종류의 SQL로 확인되는 다른 공급자의 경우에는- ORDER BY
문이 일반적으로 집계 (예 :) 뒤에 오기 때문에 반드시 필요한 것은 아닙니다 Distinct
. 따라서 코드가 다음과 같다면 :
myArray.OrderBy(x => anothercol).GroupBy(x => y.mycol);
이것은 SQL에서 다음과 유사한 것으로 변환됩니다.
SELECT * FROM mytable GROUP BY mycol ORDER BY anothercol;
이것은 분명히 먼저 데이터를 그룹화하고 나중에 정렬합니다. 이제이를 실행하는 방법에 대한 DBMS 자체 논리에 갇혀 있습니다. 일부 DBMS에서는 허용되지 않습니다. 다음 데이터를 상상해보십시오.
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
그러나 DBMS는 anothercol-column을 집계 할 수 있으므로 항상 첫 번째 행의 값이 사용되어 다음 데이터가 생성됩니다.
mycol anothercol
1 2
2 1
주문 후 결과는 다음과 같습니다.
mycol anothercol
2 1
1 2
다음과 유사합니다.
SELECT mycol, First(anothercol) from mytable group by mycol order by anothercol;
예상했던 것보다 완전히 역순입니다.
실행 계획은 기본 제공자가 무엇인지에 따라 다를 수 있습니다. 이것이 문서에서 그것에 대한 보장이없는 이유입니다.