2010-06-04 4 views
8

방금 ​​Except()이 첫 번째 목록에서 두 번째 목록의 모든 요소를 ​​제거하지만 반환 된 결과의 모든 요소를 ​​고유하게 만드는 효과가 있음을 발견했습니다. 내가 사용하고 주위예외는 Distinct와 비슷한 효과가 있습니까?

간단한 방법은 Where(v => !secondList.Contains(v))

입니다

사람이이 동작이며,이 설명 문서에 가능하면 포인트 나이 왜 나에게 설명 할 수 있습니까?

답변

18

Except 기능 상태에 대한 설명서 :

값을 비교하는 기본 같음 비교하여 두 시퀀스의 차 집합.

두 집합의 집합 차이는 두 번째 집합에 나타나지 않는 첫 번째 집합의 구성원으로 정의됩니다.

중요한 단어는 여기 되고, 설정 이는 defined로서 :

... 특정 순서없이 일정한 값을 저장할 수있는 추상적 인 데이터 구조, 및 반복 된 값 ..

Except은 집합 기반 작업으로 문서화되기 때문에 결과 값을 구분하는 효과도 있습니다.

+0

글쎄, 좀 더 이해가됩니다. 내가 수학에 착수 한 지 오래되었습니다. 감사. – Stephan

+2

@Stephan - 문서화 된 동작은'IEnumerable '이 시퀀스가 ​​아니라 집합이므로 주어진 목적을위한 범용 확장 메서드를 사용하면 의미 집합 다소 이상합니다. 그런데 다시, IEnumerable 의'Except' 메쏘드가 시퀀스 의미를 가지고 있고'ISet '가'IEnumerable '를 상속 받으면서'ISet '가 의미를 설정 한 대안이 더 나 빠지고 따라서 확장 메소드가 컴파일러에 의해 바인드 된 방법에 따라 의미가 달라질 수 있습니다. –

+1

@ 그렉, 모노는이 [틀린] 것 같다 (http://anonsvn.mono-project.com/viewvc/trunk/mcs/class/System.Core/System.Linq/Enumerable.cs#l794) (이것은 StackOverflow를 통해 실제로 발견 된 두 번째 Mono Enumerable 버그). 처음부터 여러 번 요소를 반환합니다. 내 질문은, 그것이 설정 차이라면 왜 문서가 Enumerable.Ecept 첫 번째 요소의 순서를 유지 보여줍니다 (그것은 또한 "시퀀스"를 반환 말한다)입니까? 세트는 순서를 보존하지 않습니다. 열거 할 수 있습니다. 보장되는 것을 제외하고? –

0

당신이 쓴 :

간단한 방법으로 주위에 내가 사용하고는이 작업을 수행 할 때, 여전히 중복하지 secondList으로이 이루어집니다 Where(v => !secondList.Contains(v))

입니다. 예를 들어

는 :

var firstStrings = new [] { "1", null, null, null, "3", "3" }; 
var secondStrings = new [] { "1", "1", "1", null, null, "4" }; 
var resultStrings = firstStrings.Where(v => !secondStrings.Contains(v)); // 3, 3 

나는 전혀 구별이없는 것으로 확장 메서드를 만들었습니다. 사용의 Examle는 :

public static IEnumerable<TSource> ExceptAll<TSource>(
    this IEnumerable<TSource> first, 
    IEnumerable<TSource> second) 
{ 
    // Do not call reuse the overload method because that is a slower imlementation 
    if (first == null) { throw new ArgumentNullException("first"); } 
    if (second == null) { throw new ArgumentNullException("second"); } 

    var secondList = second.ToList(); 
    return first.Where(s => !secondList.Remove(s)); 
} 

public static IEnumerable<TSource> ExceptAll<TSource>(
    this IEnumerable<TSource> first, 
    IEnumerable<TSource> second, 
    IEqualityComparer<TSource> comparer) 
{ 
    if (first == null) { throw new ArgumentNullException("first"); } 
    if (second == null) { throw new ArgumentNullException("second"); } 
    var comparerUsed = comparer ?? EqualityComparer<TSource>.Default; 

    var secondList = second.ToList(); 
    foreach (var item in first) 
    { 
     if (secondList.Contains(item, comparerUsed)) 
     { 
      secondList.Remove(item); 
     } 
     else 
     { 
      yield return item; 
     } 
    } 
} 

편집 : 더 빠른 구현, DigEmAll의 의견에 따라이 원인

enter image description here

:

var result2Strings = firstStrings.ExceptAll(secondStrings).ToList(); // null, 3, 3 

이것은 무엇이다

public static IEnumerable<TSource> ExceptAll<TSource>(
     this IEnumerable<TSource> first, 
     IEnumerable<TSource> second) 
{ 
    return ExceptAll(first, second, null); 
} 

public static IEnumerable<TSource> ExceptAll<TSource>(
    this IEnumerable<TSource> first, 
    IEnumerable<TSource> second, 
    IEqualityComparer<TSource> comparer) 
{ 
    if (first == null) { throw new ArgumentNullException("first"); } 
    if (second == null) { throw new ArgumentNullException("second"); } 


    var secondCounts = new Dictionary<TSource, int>(comparer ?? EqualityComparer<TSource>.Default); 
    int count; 
    int nullCount = 0; 

    // Count the values from second 
    foreach (var item in second) 
    { 
     if (item == null) 
     { 
      nullCount++; 
     } 
     else 
     { 
      if (secondCounts.TryGetValue(item, out count)) 
      { 
       secondCounts[item] = count + 1; 
      } 
      else 
      { 
       secondCounts.Add(item, 1); 
      } 
     } 
    } 

    // Yield the values from first 
    foreach (var item in first) 
    { 
     if (item == null) 
     { 
      nullCount--; 
      if (nullCount < 0) 
      { 
       yield return item; 
      } 
     } 
     else 
     { 
      if (secondCounts.TryGetValue(item, out count)) 
      { 
       if (count == 0) 
       { 
        secondCounts.Remove(item); 
        yield return item; 
       } 
       else 
       { 
        secondCounts[item] = count - 1; 
       } 
      } 
      else 
      { 
       yield return item; 
      } 
     } 
    } 
} 

More info 내 블로그 (또는 Intersect 및 Union 변종)

+3

이것은 목록에서 항목을 제거하는 것과 같이 각 항목의 목록을 검색하는 것이 비용이 많이 들기 때문에 대단히 비효율적 인 옵션입니다. 'Distinct'라는 언어는 HashSet을 사용하기 때문에 결과적으로 * 훨씬 * 효율적입니다. 효율성을 떨어 뜨리지 않고 소스에서 반복 된 요소를 산출하기 위해 구현을 쉽게 수정할 수 있습니다. – Servy

+0

@Servy에 동의합니다. Contains와 Remove는 모두 O (_n_) 연산이며 루프로 처리하고 있습니다. – Magnus

+0

@Servy, 나는 그것이 더 느리다는 것을 안다. 그러나 결과는 다르다. 이미지에 표시된 것과 같이 더 빠르고 구현 된 구현이 있다면 평론가가 중요합니다. 나는 더 나은 해결책에 호기심이 많다. 효율성을 떨어 뜨리지 않고 소스에서 반복 된 요소를 산출하기 위해 구현을 어떻게 수정합니까? –

관련 문제