C# 检查IEnumerable是否可用的快速方法<;T>;不包含重复项(=不同)

C# 检查IEnumerable是否可用的快速方法<;T>;不包含重复项(=不同),c#,collections,C#,Collections,是否有一种快速的内置方法来检查IEnumerable是否只包含不同的字符串 起初,我从以下几点开始: var enumAsArray = enum.ToArray(); if (enumAsArray.Length != enumAsArray.Distinct().Count()) throw ... 然而,这看起来像是O(2n)-是吗ToArray()可能是O(1) 这看起来更快: var set = new HashSet<string>(); foreach (va

是否有一种快速的内置方法来检查
IEnumerable
是否只包含不同的字符串

起初,我从以下几点开始:

var enumAsArray = enum.ToArray();
if (enumAsArray.Length != enumAsArray.Distinct().Count())
    throw ...
然而,这看起来像是O(2n)-是吗<代码>ToArray()可能是O(1)

这看起来更快:

var set = new HashSet<string>();
foreach (var str in enum)
{
    if (!set.Add(str))
        throw ...
}
var set=newhashset();
foreach(枚举中的var str)
{
如果(!set.Add(str))
扔。。。
}
这应该是O(n),但是,是否也有内置的方式

编辑:可能Distinct()在内部使用此选项


解决方案: 在考虑了所有的评论和答案后,我为我的第二个解决方案编写了一个扩展方法,因为这似乎是最快的版本,也是最可读的:

public static bool ContainsDuplicates<T>(this IEnumerable<T> e)
{
    var set = new HashSet<T>();
    // ReSharper disable LoopCanBeConvertedToQuery
    foreach (var item in e)
    // ReSharper restore LoopCanBeConvertedToQuery
    {
        if (!set.Add(item))
            return true;
    }
    return false;
}
公共静态bool包含两个副本(此IEnumerable e)
{
var set=新的HashSet();
//重拾器禁用循环可以转换为查询
foreach(e中的var项)
//重竖琴还原循环可以转换为查询
{
如果(!set.Add(项目))
返回true;
}
返回false;
}

您的第二个代码示例简短、简单、明显有效,如果不是完全完美的理想解决方案,也显然非常接近它。对于你的特殊问题,这似乎是一个完全可以接受的解决方案

除非在您注意到问题并完成性能测试后,您对该特定解决方案的使用会导致性能问题,否则我将保持原样。总的来说,考虑到我能看到的改善空间是如此之小,这似乎不太可能。这不是一个足够长或复杂的解决方案,试图找到一些“更短”或更简洁的东西是值得你花费时间和精力的

简言之,几乎可以肯定的是,在代码中有更好的地方可以花时间;你已经拥有的一切都很好

要回答您的具体问题:

  • 然而,这看起来像是O(2n)-是吗

    是的

  • ToArray()
    可能是O(1)

    不,不是

  • 也许
    Distinct()
    会在内部使用它

    它确实使用了一个
    HashSet
    ,看起来非常相似,但它只是忽略了重复的项;它不会向调用者提供它刚刚传递了重复项的任何指示。因此,您需要迭代整个序列两次,以查看它是否删除了任何内容,而不是在遇到第一个重复时停止。这就是总是重复完整序列两次的东西和可能重复完整序列一次的东西之间的区别,但是一旦确定了答案就会短路并停止

  • 也有内置的方式吗

    好吧,你展示了一个,只是效率不高。我认为没有一个完整的基于LINQ的解决方案像您所展示的那样高效。我能想到的最好的方法是:
    data.Except(data).Any()
    。与常规计数相比,这比distinct好一点,因为第二次迭代可能会短路(但不是第一次),但它也会对序列进行两次迭代,并且仍然比非LINQ解决方案更差,因此仍然不值得使用


  • 以下是对OP答案的一种可能的改进:

    public static IEnumerable<T> Duplicates<T>(this IEnumerable<T> e)
    {
        var set = new HashSet<T>();
        // ReSharper disable LoopCanBeConvertedToQuery
        foreach (var item in e)
        // ReSharper restore LoopCanBeConvertedToQuery
        {
            if (!set.Add(item))
                yield return item;
        }
    }
    

    只是对现有解决方案的补充:

    public static bool ContainsDuplicates<T>(this IEnumerable<T> items)
    {
        return ContainsDuplicates(items, EqualityComparer<T>.Default);
    }
    
    public static bool ContainsDuplicates<T>(this IEnumerable<T> items, IEqualityComparer<T> equalityComparer)
    {
        var set = new HashSet<T>(equalityComparer);
    
        foreach (var item in items)
        {
            if (!set.Add(item))
                return true;
        }
    
        return false;
    }
    
    public static bool包含两个副本(此IEnumerable items)
    {
    return包含两个副本(items,EqualityComparer.Default);
    }
    公共静态bool包含两个副本(此IEnumerable items,IEqualityComparer equalityComparer)
    {
    var集合=新的哈希集合(equalityComparer);
    foreach(项目中的var项目)
    {
    如果(!set.Add(项目))
    返回true;
    }
    返回false;
    }
    
    此版本允许您选择一个相等比较器,如果您希望基于非默认规则比较项目,这可能会很有用


    例如,要对一组字符串大小写进行不敏感的比较,只需将其传递给
    StringComparer.OrdinalIgnoreCase

    即可,因为
    set.Add
    不是O(1)(如文档中所述,它可能需要重新分配)。但是,
    newhashset(enum)
    是O(n),之后可以直接读取
    Count
    。但是,平均而言,使用第二种方法应该更快,因为它在第一次重复时会中断?为什么没有新的HashSet(enum.Count())构造函数?@D.R.这是一个非常小的优化,以换取使用语义不恰当的数据结构。总的来说,这似乎不值得。我不知道,如果您关心性能,那么您可能正在处理大型数据集,而且我不知道Dictionary如何比HashSet使用更少的内存。如果不了解最终将使用的数据类型,性能问题很难有效地回答。@D.R.如果您想分享稍微改进的方法,您刚刚编辑了问题中的代码;这并不能真正改变问题本身。事实上,编辑别人的答案是不合适的。未来的访问者在问题中找到答案而不是答案,这难道不令人困惑吗?@D.R.但这不是一个解决方案。它基本上与您的原始代码完全相同。@D.R.顺便说一句,您添加到此答案的
    包含重复的
    方法不能保证有效。它依赖于
    Any
    的特定实现,该实现不会对同一项多次计算谓词。我无法想象为什么一个实现可能会对它进行两次评估,但这是允许的,而且没有必要做出不必要的假设,因为您已经有了一个保证工作的
    foreach
    循环。@D.R.
    Any
    的库实现不会这样做,如果实现了这个方法,那将是一个非常糟糕的实现,只不过你依赖的是公共API在技术上不能保证的东西,即使它在技术上是正确的。
    public static bool ContainsDuplicates<T>(this IEnumerable<T> items)
    {
        return ContainsDuplicates(items, EqualityComparer<T>.Default);
    }
    
    public static bool ContainsDuplicates<T>(this IEnumerable<T> items, IEqualityComparer<T> equalityComparer)
    {
        var set = new HashSet<T>(equalityComparer);
    
        foreach (var item in items)
        {
            if (!set.Add(item))
                return true;
        }
    
        return false;
    }