如何确保序列具有一定的长度?

发布于 2024-09-24 21:26:12 字数 245 浏览 7 评论 0原文

我想检查 IEnumerable 是否包含恰好一个元素。此代码片段确实有效:

bool hasOneElement = seq.Count() == 1

但是它的效率不是很高,因为 Count() 将枚举整个列表。显然,知道列表为空或包含超过 1 个元素意味着它不为空。是否有具有这种短路行为的扩展方法?

I want to check that an IEnumerable contains exactly one element. This snippet does work:

bool hasOneElement = seq.Count() == 1

However it's not very efficient, as Count() will enumerate the entire list. Obviously, knowing a list is empty or contains more than 1 element means it's not empty. Is there an extension method that has this short-circuiting behaviour?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

夜灵血窟げ 2024-10-01 21:26:12

这应该可以做到:

public static bool ContainsExactlyOneItem<T>(this IEnumerable<T> source)
{
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        // Check we've got at least one item
        if (!iterator.MoveNext())
        {
            return false;
        }
        // Check we've got no more
        return !iterator.MoveNext();
    }
}

可以进一步省略它,但我不建议您这样做:

public static bool ContainsExactlyOneItem<T>(this IEnumerable<T> source)
{
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        return iterator.MoveNext() && !iterator.MoveNext();
    }
}

这是一种很时髦的技巧,但可能不应该在生产代码中使用。只是还不够清楚。事实上,&& 的 LHS 中的副作用是RHS 正常工作所需的运算符只是令人讨厌......同时很有趣;)

编辑:我刚刚看到你想出了完全相同的东西,但长度是任意的。不过,您的最终返回语句是错误的 - 它应该是 return !en.MoveNext()。这是一个完整的方法,具有更好的名称(IMO),参数检查和优化 ICollection/ICollection

public static bool CountEquals<T>(this IEnumerable<T> source, int count)
{
    if (source == null)
    {
        throw new ArgumentNullException("source");
    }
    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count",
                                              "count must not be negative");
    }
    // We don't rely on the optimizations in LINQ to Objects here, as
    // they have changed between versions.
    ICollection<T> genericCollection = source as ICollection<T>;
    if (genericCollection != null)
    {
        return genericCollection.Count == count;
    }
    ICollection nonGenericCollection = source as ICollection;
    if (nonGenericCollection != null)
    {
        return nonGenericCollection.Count == count;
    }
    // Okay, we're finally ready to do the actual work...
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        for (int i = 0; i < count; i++)
        {
            if (!iterator.MoveNext())
            {
                return false;
            }
        }
        // Check we've got no more
        return !iterator.MoveNext();
    }
}

编辑:现在对于函数式粉丝来说,递归形式CountEquals请不要使用这个,它只是为了咯咯笑):

public static bool CountEquals<T>(this IEnumerable<T> source, int count)
{
    if (source == null)
    {
        throw new ArgumentNullException("source");
    }
    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count", 
                                              "count must not be negative");
    }
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        return IteratorCountEquals(iterator, count);
    }
}

private static bool IteratorCountEquals<T>(IEnumerator<T> iterator, int count)
{
    return count == 0 ? !iterator.MoveNext()
        : iterator.MoveNext() && IteratorCountEquals(iterator, count - 1);
}

编辑:请注意,对于像 LINQ to SQL 这样的东西,您应该使用简单的 Count () 方法 - 因为这将允许它在数据库中完成,而不是在获取实际结果之后完成。

This should do it:

public static bool ContainsExactlyOneItem<T>(this IEnumerable<T> source)
{
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        // Check we've got at least one item
        if (!iterator.MoveNext())
        {
            return false;
        }
        // Check we've got no more
        return !iterator.MoveNext();
    }
}

You could elide this further, but I don't suggest you do so:

public static bool ContainsExactlyOneItem<T>(this IEnumerable<T> source)
{
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        return iterator.MoveNext() && !iterator.MoveNext();
    }
}

It's the sort of trick which is funky, but probably shouldn't be used in production code. It's just not clear enough. The fact that the side-effect in the LHS of the && operator is required for the RHS to work appropriately is just nasty... while a lot of fun ;)

EDIT: I've just seen that you came up with exactly the same thing but for an arbitrary length. Your final return statement is wrong though - it should be return !en.MoveNext(). Here's a complete method with a nicer name (IMO), argument checking and optimization for ICollection/ICollection<T>:

public static bool CountEquals<T>(this IEnumerable<T> source, int count)
{
    if (source == null)
    {
        throw new ArgumentNullException("source");
    }
    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count",
                                              "count must not be negative");
    }
    // We don't rely on the optimizations in LINQ to Objects here, as
    // they have changed between versions.
    ICollection<T> genericCollection = source as ICollection<T>;
    if (genericCollection != null)
    {
        return genericCollection.Count == count;
    }
    ICollection nonGenericCollection = source as ICollection;
    if (nonGenericCollection != null)
    {
        return nonGenericCollection.Count == count;
    }
    // Okay, we're finally ready to do the actual work...
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        for (int i = 0; i < count; i++)
        {
            if (!iterator.MoveNext())
            {
                return false;
            }
        }
        // Check we've got no more
        return !iterator.MoveNext();
    }
}

EDIT: And now for functional fans, a recursive form of CountEquals (please don't use this, it's only here for giggles):

public static bool CountEquals<T>(this IEnumerable<T> source, int count)
{
    if (source == null)
    {
        throw new ArgumentNullException("source");
    }
    if (count < 0)
    {
        throw new ArgumentOutOfRangeException("count", 
                                              "count must not be negative");
    }
    using (IEnumerator<T> iterator = source.GetEnumerator())
    {
        return IteratorCountEquals(iterator, count);
    }
}

private static bool IteratorCountEquals<T>(IEnumerator<T> iterator, int count)
{
    return count == 0 ? !iterator.MoveNext()
        : iterator.MoveNext() && IteratorCountEquals(iterator, count - 1);
}

EDIT: Note that for something like LINQ to SQL, you should use the simple Count() approach - because that'll allow it to be done at the database instead of after fetching actual results.

书信已泛黄 2024-10-01 21:26:12

不,但您可以自己写一个:

 public static bool HasExactly<T>(this IEnumerable<T> source, int count)
 {
   if(source == null)
      throw new ArgumentNullException("source");

   if(count < 0)
      return false;

   return source.Take(count + 1).Count() == count;
 }

编辑:澄清后从至少更改为完全

对于更通用和更有效的解决方案(仅使用 1 个枚举器并检查序列是否实现了 ICollectionICollection 在这种情况下不需要枚举),您可能想看看我的答案此处,可让您指定是否要查找ExactAtLeastAtMost 测试。

No, but you can write one yourself:

 public static bool HasExactly<T>(this IEnumerable<T> source, int count)
 {
   if(source == null)
      throw new ArgumentNullException("source");

   if(count < 0)
      return false;

   return source.Take(count + 1).Count() == count;
 }

EDIT: Changed from atleast to exactly after clarification.

For a more general and efficient solution (which uses only 1 enumerator and checks if the sequence implements ICollection or ICollection<T> in which case enumeration is not necessary), you might want to take a look at my answer here, which lets you specify whether you are looking forExact,AtLeast, orAtMost tests.

⊕婉儿 2024-10-01 21:26:12

seq.Skip(1).Any() 将告诉您列表是否有零个或一个元素。

我认为您所做的编辑是检查长度 n 的最有效方法。但有一个逻辑错误,小于 length 的项将返回 true。看看我对第二个 return 语句做了什么。

    public static bool LengthEquals<T>(this IEnumerable<T> en, int length)
    {
        using (var er = en.GetEnumerator())
        {
            for (int i = 0; i < length; i++)
            {
                if (!er.MoveNext())
                    return false;
            }
            return !er.MoveNext();
        }
    }

seq.Skip(1).Any() will tell you if the list has zero or one elements.

I think the edit you made is about the most efficient way to check the length is n. But there's a logic fault, items less than length long will return true. See what I've done to the second return statement.

    public static bool LengthEquals<T>(this IEnumerable<T> en, int length)
    {
        using (var er = en.GetEnumerator())
        {
            for (int i = 0; i < length; i++)
            {
                if (!er.MoveNext())
                    return false;
            }
            return !er.MoveNext();
        }
    }
沩ん囻菔务 2024-10-01 21:26:12

这个怎么样?

public static bool CountEquals<T>(this IEnumerable<T> source, int count) {
    return source.Take(count + 1).Count() == count;
}

Take() 将确保我们调用 MoveNext 的次数不会超过 count+1 次。

我想指出的是,对于 ICollection 的任何实例,原始实现 source.Count() == count 应该更快,因为 Count() 已优化为仅查看 Count 成员。

How about this?

public static bool CountEquals<T>(this IEnumerable<T> source, int count) {
    return source.Take(count + 1).Count() == count;
}

The Take() will make sure we never call MoveNext more than count+1 times.

I'd like to note that for any instance of ICollection, the original implementation source.Count() == count should be faster because Count() is optimised to just look at the Count member.

爱的故事 2024-10-01 21:26:12

我相信您正在寻找的是.Single()。除 1 之外的任何其他情况都会引发您可以捕获的 InvalidOperationException。

http://msdn.microsoft.com/nb-no/library/bb155325.aspx

I believe what you're looking for is .Single(). Anything other than exactly one will throw InvalidOperationException that you can catch.

http://msdn.microsoft.com/nb-no/library/bb155325.aspx

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文