除了与Distinct有类似的效果?

时间:2010-06-04 16:20:32

标签: c# linq

我刚刚发现Except()将从第一个列表中删除第二个列表中的所有元素,但它也会使得返回结果中的所有元素都不同。

我正在使用的简单方法是Where(v => !secondList.Contains(v))

任何人都可以向我解释为什么这是行为,并且如果可能的话,请指出解释此问题的文档?

3 个答案:

答案 0 :(得分:24)

Except函数的文档说明:

  

使用默认的相等比较器来比较值,生成两个序列的集合差异。

     

两组的设定差异被定义为第一组中未出现在第二组中的成员。

这里的重要词是设置defined为:

  

...一个抽象的数据结构,可以存储某些值,没有任何特定的顺序,也没有重复的值......

由于Except被记录为基于集合的操作,因此它还具有使结果值不同的效果。

答案 1 :(得分:2)

给出A = [1, 2, 2, 3, 3, 3]B = [3]

  • A.Except(B);返回[1, 2],如his response的Greg Beech所述
  • 来自Alex Siepman response
  • A.ExceptAll(B);,返回[1, 2, 2, 3, 3](我发现名字不明确)。
  • A.Where(v => !B.Contains(v))来自OP的变通回报[1, 2, 2]

我认为OP变通是您想要的行为,而这一行为不予理treated。

OP解决的主要问题是List<T>.Contains(T)O(n),而Where也是O(n)使得解决方案O(n²)及时(对于A和大小相等的B)和O(1)在内存中。

通过使用哈希集,我们可以使其在时间O(n)和在存储器中O(n)

// I accept any better name for this method
public static IEnumerable<TSource> ExceptFrom<TSource>(
    IEnumerable<TSource> first,
    IEnumerable<TSource> second,
    IEqualityComparer<TSource> comparer)
{
    if (first == null)
        throw new ArgumentNullException(nameof(first));

    if (second == null)
        throw new ArgumentNullException(nameof(second));

    var secondSet = second as HashSet<TSource> ?? // this trick ignore the comparer
                    second.ToHashSet(comparer ?? EqualityComparer<TSource>.Default);

    // Contains is O(1) for HashSet.
    return first.Where(v => !secondSet.Contains(v));
}

答案 2 :(得分:0)

您写道:

  

我正在使用的简单方法是Where(v => !secondList.Contains(v))

执行此操作时,仍然会使用secondList完成Distict。

例如:

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  

我创建了一个没有任何区别的扩展方法。用法考试:

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

这就是它的作用:

enter image description here

这是来源:

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评论的更快的实现

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的变体)