找出IEnumerable<>的最佳方法有独特的价值观

时间:2011-03-22 12:32:58

标签: c# .net linq

我有很多代码可以用来做这样的事情

bool GetIsUnique(IEnumerable<T> values)
{
    return values.Count() == values.Distinct().Count;
}

有更好的更快更好的方法吗?

7 个答案:

答案 0 :(得分:21)

我会把它作为一个很好的扩展方法

public static bool IsUnique<T>(this IEnumerable<T> list)
{
    var hs = new HashSet<T>();
    return list.All(hs.Add);  
}

检查是否可以将所有项目添加到HashSet。

答案 1 :(得分:19)

您的方法需要迭代序列两次,但有一些潜在的缺点:

  1. 对于任何显着大小的序列,迭代两次将比迭代一次慢。
  2. 如果您尝试多次迭代它们,某些序列将抛出异常;其他人可能会为后续迭代返回不同的结果。
  3. 您的方法使用Count,每次都需要迭代整个序列。一旦你知道重复价值,就没有理由不提前爆发。
  4. 以下方法只需要遍历序列一次,并且一旦遇到任何重复值就会提前中断:

    bool GetIsUnique<T>(IEnumerable<T> values)
    {
        var set = new HashSet<T>();
    
        foreach (T item in values)
        {
            if (!set.Add(item))
                return false;
        }
        return true;
    }
    

答案 2 :(得分:5)

我认为如果有非唯一值,这取决于你想要做什么。 @Jamiec's Or @ LukeH的答案是很好的答案,可能最适合纯粹的速度,但它无法告诉你问题在哪里。

您可能还会考虑类似

的内容
var group = values.GroupBy(x => x);
return group.Any(g => g.Count() > 1);

它本身比HashSet实施更糟糕。但如果你保持这个群体,你可以找到哪些元素是重复的。

var group = values.GroupBy(x => x);
return group.Where(g => g.Count() > 1);

或者

var group = values.GroupBy(x => x);
return group.Where(g => g.Count() > 1).Select(g => g.Key);

使用GroupBy进行思考可让您保持选择权,以便下一步做什么。但如果你关心的只是知道所有的值是否都是唯一的,我会选择HashSet

答案 3 :(得分:1)

你将通过以上数据进行两次循环 - 一次获得计数,一次获得非常计数。如果前两个项目相同则特别糟糕!尝试这样的事情:

bool GetIsUnique<T>(IEnumerable<T> values)
{
    HashSet<T> hashSet = new HashSet<T>();
    foreach(var value in values)
    {
        if (hashSet.Contains(value))
        {
            return false;
        }
        hashSet.Add(value);
    }
    return true;
}

这一个会在找到重复后立即完成。显然它在哈希查找的速度上,但鉴于Distinct在内部使用了一个集合,我仍然期望它更快。

答案 4 :(得分:0)

两个基本规则:

  1. 最简单的阅读和理解方式几乎总是最好的编码方式。这段代码很容易阅读,所以我说你很高兴。
  2. 性能(“更快”)应该只是一个问题,如果你可以证明这个是减慢程序速度的方法,或者你正在构建一个其他人可以访问的库无法访问源代码。
  3. 其他方法会更快(当找到重复值时它们会短路,返回false),但是,如果它是我的代码,我仍然坚持你的版本。

答案 5 :(得分:0)

找到第一个副本的快速方法(如果存在)是:

public static bool TryFindFirstDuplicate<T>(this IEnumerable<T> source, out T duplicate)
{
    var set = new HashSet<T>();
    foreach (var item in source)
    {
        if (!set.Add(item))
        {
            duplicate = item;
            return true;
        }
    }
    duplicate = default(T);
    return false;
}

答案 6 :(得分:0)

我很惊讶还没有人对此进行过测试:

将问题中的Gluip版本与JamieC,LukeK和MrKWatkins进行比较,这三个答案都比问问者版本更好。

在三个答案中,它们都是相当可比的,但在大多数情况下,JamieC的速度稍快一些。

当数据没有重复项或IEnumerable末尾有重复项时,大小或算法没有重大差异。

当数据在中间附近或开始时有重复项时,原始问题中的Gluip版本显示出其慢于其他三个。

检查集合的时间似乎与集合的大小呈线性比例关系,这意味着对于大或小的集合,最好不要使用任何算法。

这是一个测试程序,可以输出CSV,您可以将CSV加载到电子表格程序中,并根据需要进行排序和绘制图形:

测试程序:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AreUniqueTest
{
class Program
{
    const int Iterations = 1000;

    enum DupeLocation
    {
        None,
        Early,
        Center,
        Late,
    }

    enum SetSize
    {
        Tiny= 10,
        Small = 100,
        Medium = 1000,
        Large = 10000,
        Huge = 100000,
    }

    static void Main()
    {
        Dictionary<string, Func<IEnumerable<int>, bool>> functions = new Dictionary<string, Func<IEnumerable<int>, bool>>
        {
            {"Gluip", GetIsUniqueGluip},
            {"LukeH", GetIsUniqueLukeH },
            {"Jamiec", GetIsUniqueJamiec },
            {"MrKWatkins", GetIsUniqueMrKWatkins }
        };

        var output = new StringBuilder();

        Console.WriteLine("Function,SetSize,DupeLocation,TotalTicks,AverageTicks");
        output.AppendLine("Function,SetSize,DupeLocation,TotalTicks,AverageTicks");

        foreach (SetSize size in Enum.GetValues(typeof(SetSize)))
        {
            var sizevalue = (int) size;
            foreach (DupeLocation location in Enum.GetValues(typeof(DupeLocation)))
            {
                var data = CreateTestData((int)size, location);
                foreach (string functionKey in functions.Keys)
                {
                    var ticks = RunSet(functions[functionKey], data, Iterations);
                    var avg = ticks / Iterations;
                    Console.WriteLine($"{functionKey},{sizevalue},{location},{ticks},{avg}");
                    output.AppendLine($"{functionKey},{sizevalue},{location},{ticks},{avg}");
                }
            }
        }

        File.WriteAllText("output.csv", output.ToString());
        Process.Start("output.csv");
    }

    static long RunSet<T>(Func<IEnumerable<T>, bool> getIsUnique, IEnumerable<T> values, int iterations)
    {
        var stopwatch = new Stopwatch();
        stopwatch.Start();
        for (var i = 0; i < iterations; i++)
        {
            getIsUnique.Invoke(values);
        }
        stopwatch.Stop();
        return stopwatch.ElapsedTicks;
    }

    static bool GetIsUniqueLukeH<T>(IEnumerable<T> values)
    {
        var set = new HashSet<T>();

        foreach (T item in values)
        {
            if (!set.Add(item))
                return false;
        }
        return true;
    }

    static bool GetIsUniqueGluip<T>(IEnumerable<T> values)
    {
        return values.Count() == values.Distinct().Count();
    }

    static bool GetIsUniqueJamiec<T>(IEnumerable<T> list)
    {
        var hs = new HashSet<T>();
        return list.All(hs.Add);
    }

    static bool GetIsUniqueMrKWatkins<T>(IEnumerable<T> values)
    {
        HashSet<T> hashSet = new HashSet<T>();
        foreach (var value in values)
        {
            if (hashSet.Contains(value))
            {
                return false;
            }
            hashSet.Add(value);
        }
        return true;
    }

    static int[] CreateTestData(int size, DupeLocation location)
    {
        var result = new int[size];
        Parallel.For(0, size, i => { result[i] = i; });
        return SetDupe(result, location);
    }

    static int[] SetDupe(int[] values, DupeLocation location)
    {
        switch (location)
        {
            case DupeLocation.Early:
                values[1] = values[0];
                break;
            case DupeLocation.Center:
                var midpoint = values.Length / 2;
                values[midpoint] = values[midpoint + 1];
                break;
            case DupeLocation.Late:
                values[values.Length - 1] = values[values.Length - 2];
                break;
            // case DupeLocation.None: // do nothing.
        }
        return values;
    }
}
}
相关问题