我有很多代码,其中我会做这样的事情
bool GetIsUnique(IEnumerable<T> values)
{
return values.Count() == values.Distinct().Count;
}
有没有更好、更快、更好的方法来做这件事?
我会将这个变成一个好用的扩展方法
public static bool IsUnique<T>(this IEnumerable<T> list)
{
var hs = new HashSet<T>();
return list.All(hs.Add);
}
检查所有项目是否可以添加到 HashSet 中。
All
加入具有副作用的谓词。 - CodesInChaosAll
的实现不会记忆结果。你真的在考虑 All 的恶意实现吗?天哪。 - JamiecCount
,每次都需要迭代整个序列。只要发现有重复值,就可以立即退出,没有理由不这样做。bool GetIsUnique<T>(IEnumerable<T> values)
{
var set = new HashSet<T>();
foreach (T item in values)
{
if (!set.Add(item))
return false;
}
return true;
}
我认为这取决于你想做什么,如果存在非唯一值。@Jamiec或@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);
如果你只关心所有值是否唯一,我建议使用 HashSet
。然而,如果你想保持对下一步操作的选择性,考虑使用 GroupBy
。
GroupBy
需要在开始测试重复项之前迭代整个序列,这可能在处理较长序列时成为一个问题。(HashSet<T>
的实现只要找到第一个重复项就可以中断。) - LukeHHashSet
始终会更快。我只是想指出,如果你要跟进“有哪些重复项?”或者“有多少个重复项?”这些问题,那么考虑使用GroupBy
是值得的,因为你可以问它所有这些问题。 - Mike TwoGroupBy
并没有任何优势。每次使用它时,查询都将被重新评估,那么为什么不在初始测试中使用Intersect
,然后在需要时使用GroupBy
进行任何后续查询呢? - LukeHGetHashCode
调用。使用一次GroupBy
调用,我们只需要为原始集合循环一次支付这个成本。我们所做的是调用GroupBy
,Select
具有多个元素的组,并在其上调用ToList
。然后我们对具有重复项的组进行大量工作。并不是声称这在所有情况下都是正确的。只是试图提供一种替代方案和考虑它的原因。无论如何,性能都应该针对您实际的情况进行测量。 - Mike Twobool 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内部使用了一个集合,我仍然期望它会更快。
我很惊讶竟然还没有人测试过这个:
将问题中的Gluip版本与JamieC、LukeK和MrKWatkins进行比较,三个答案都比提问者的版本更好。
在这三个答案中,它们都相当可比,但在大多数情况下,JamieC的速度略快。
当数据没有重复项或重复出现在IEnumerable的末尾时,大小或算法没有明显差异。
当数据在中间附近或开头有重复项时,与其他三个版本相比,原始问题中的Gluip版本表现出了它的缓慢。
检查集合的时间似乎随集合大小呈线性增长,这意味着没有任何算法适用于大或小的集合。
以下是一个测试程序,它可以输出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;
}
}
}
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;
}
两个基本规则:
其他方法将更快(当发现重复值并返回false时将会短路),但如果是我的代码,我仍然会坚持使用您的版本。