安全检查不可重复的 IEnumerables 是否为空

发布于 2025-01-03 10:54:41 字数 1847 浏览 0 评论 0原文

有时检查不可重复 IEnumerable 来查看它是否为空会很有帮助。 LINQ 的 Any 对此效果不佳,因为它消耗了序列的第一个元素,例如

if(input.Any())
{
    foreach(int i in input)
    {
        // Will miss the first element for non-repeatable sequences!
    }
}

(注意:我知道在这种情况下不需要进行检查 - 它只是一个现实世界的示例是对可能为空的右侧 IEnumerable 执行 Zip,如果它是空的,我希望结果是左侧的。手IEnumerable 按原样。)

我想出了一个可能的解决方案,如下所示:

private static IEnumerable<T> NullifyIfEmptyHelper<T>(IEnumerator<T> e)
{
    using(e)
    {
        do
        {
            yield return e.Current;
        } while (e.MoveNext());
    }
}

public static IEnumerable<T> NullifyIfEmpty<T>(this IEnumerable<T> source)
{
    IEnumerator<T> e = source.GetEnumerator();
    if(e.MoveNext())
    {
        return NullifyIfEmptyHelper(e);
    }
    else
    {
        e.Dispose();
        return null;
    }
}

然后可以按如下方式使用:

input = input.NullifyIfEmpty();
if(input != null)
{
    foreach(int i in input)
    {
        // Will include the first element.
    }
}

我对此有两个问题:

1)这是合理的事情吗?做?从性能的角度来看,这可能存在问题吗? (我猜不会,但值得一问。)

2)是否有更好的方法来实现相同的最终目标?


编辑#1:

这是一个不可重复的 IEnumerable 的示例,以澄清:

private static IEnumerable<int> ReadNumbers()
{
    for(;;)
    {
        int i;
        if (int.TryParse(Console.ReadLine(), out i) && i != -1)
        {
            yield return i;
        }
        else
        {
            yield break;
        }
    }
}

基本上,来自用户输入或流等的东西。

编辑#2:

我需要澄清我是寻找一种保留 IEnumerable惰性本质的解决方案 - 在某些情况下将其转换为列表或数组可能是一个答案,但这不是我想要的米之后 这里。 (现实世界的原因是,在我的例子中,IEnumerable 中的项目数量可能很大,重要的是不要将它们一次全部存储在内存中。)

There are times when it's helpful to check a non-repeatable IEnumerable to see whether or not it's empty. LINQ's Any doesn't work well for this, since it consumes the first element of the sequence, e.g.

if(input.Any())
{
    foreach(int i in input)
    {
        // Will miss the first element for non-repeatable sequences!
    }
}

(Note: I'm aware that there's no need to do the check in this case - it's just an example! The real-world example is performing a Zip against a right-hand IEnumerable that can potentially be empty. If it's empty, I want the result to be the left-hand IEnumerable as-is.)

I've come up with a potential solution that looks like this:

private static IEnumerable<T> NullifyIfEmptyHelper<T>(IEnumerator<T> e)
{
    using(e)
    {
        do
        {
            yield return e.Current;
        } while (e.MoveNext());
    }
}

public static IEnumerable<T> NullifyIfEmpty<T>(this IEnumerable<T> source)
{
    IEnumerator<T> e = source.GetEnumerator();
    if(e.MoveNext())
    {
        return NullifyIfEmptyHelper(e);
    }
    else
    {
        e.Dispose();
        return null;
    }
}

This can then be used as follows:

input = input.NullifyIfEmpty();
if(input != null)
{
    foreach(int i in input)
    {
        // Will include the first element.
    }
}

I have two questions about this:

1) Is this a reasonable thing to do? Is it likely to be problematic from a performance point of view? (I'd guess not, but worth asking.)

2) Is there a better way of achieving the same end goal?


EDIT #1:

Here's an example of a non-repeatable IEnumerable, to clarify:

private static IEnumerable<int> ReadNumbers()
{
    for(;;)
    {
        int i;
        if (int.TryParse(Console.ReadLine(), out i) && i != -1)
        {
            yield return i;
        }
        else
        {
            yield break;
        }
    }
}

Basically, things which come from user input or a stream, etc.

EDIT #2:

I need to clarify that I'm looking for a solution that preserves the lazy nature of the IEnumerable - converting it to a list or an array can be an answer in certain circumstances, but isn't what I'm after here. (The real-world reason is that the number of items in the IEnumerable may be huge in my case, and it's important not to store them all in memory at once.)

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

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

发布评论

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

评论(3

日记撕了你也走了 2025-01-10 10:54:42

您也可以只读取第一个元素,如果它不为空,则将第一个元素与输入的其余部分连接起来:

var input = ReadNumbers();
var first = input.FirstOrDefault();
if (first != default(int)) //Assumes input doesn't contain zeroes
{
    var firstAsArray = new[] {first};
    foreach (int i in firstAsArray.Concat(input))
    {
        // Will include the first element.
        Console.WriteLine(i);
    }
}

对于正常的可枚举,第一个元素将重复两次,但对于不可重复的可枚举,它将起作用,除非不允许迭代两次。另外,如果您有这样的枚举器:

private readonly static List<int?> Source = new List<int?>(){1,2,3,4,5,6};

private static IEnumerable<int?> ReadNumbers()
{
    while (Source.Count > 0) {
        yield return Source.ElementAt(0);
        Source.RemoveAt(0);
    }
}

那么它将打印: 1, 1, 2, 3, 4, 5, 6。原因是第一个元素在返回后被消耗。因此,第一个枚举器在第一个元素处停止,永远没有机会消耗该第一个元素。但这将是一个写得很糟糕的枚举器的情况。如果元素被消耗,则返回...

while (Source.Count > 0) {
    var returnElement = Source.ElementAt(0);
    Source.RemoveAt(0);
    yield return returnElement;
}

...您将得到预期输出:1,2,3,4,5,6。

You could also just read the first element and if it's not null, concatenate this first element with the rest of your input:

var input = ReadNumbers();
var first = input.FirstOrDefault();
if (first != default(int)) //Assumes input doesn't contain zeroes
{
    var firstAsArray = new[] {first};
    foreach (int i in firstAsArray.Concat(input))
    {
        // Will include the first element.
        Console.WriteLine(i);
    }
}

For a normal enumerable, the first element would be repeated twice, but for a non-repeatable enumerable it would work, unless iterating twice is not allowed. Also, if you had such an enumerator:

private readonly static List<int?> Source = new List<int?>(){1,2,3,4,5,6};

private static IEnumerable<int?> ReadNumbers()
{
    while (Source.Count > 0) {
        yield return Source.ElementAt(0);
        Source.RemoveAt(0);
    }
}

Then it would print: 1, 1, 2, 3, 4, 5, 6. The reason being that the first element is consumed AFTER it has been returned. So the first enumerator, stopping at the first element, never has the chance of consuming that first element. But it would be a case of a badly written enumerator, here. If the element is consumed, then returned...

while (Source.Count > 0) {
    var returnElement = Source.ElementAt(0);
    Source.RemoveAt(0);
    yield return returnElement;
}

...you get the expected output of: 1, 2, 3, 4, 5, 6.

柠檬色的秋千 2025-01-10 10:54:42

你不需要把它复杂化。带有一个额外的 bool 变量的常规 foreach 循环就可以解决问题。

如果您

if(input.Any())
{
    A
    foreach(int i in input)
    {
        B
    }
    C
}

不想读取 input 两次,则可以将其更改为

bool seenItem = false;
foreach(int i in input)
{
    if (!seenItem)
    {
        seenItem = true;
        A
    }
    B
}
if (seenItem)
{
    C
}

根据 B 的操作,您也许能够避免 seenItem完全变量。

就您而言,Enumerable.Zip 是一个相当基本的函数,很容易重新实现,并且您的替换函数可以使用与上面类似的函数。

编辑:您可能会考虑

public static class MyEnumerableExtensions
{
    public static IEnumerable<TFirst> NotReallyZip<TFirst, TSecond>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TFirst> resultSelector)
    {
        using (var firstEnumerator = first.GetEnumerator())
        using (var secondEnumerator = second.GetEnumerator())
        {
            if (secondEnumerator.MoveNext())
            {
                if (firstEnumerator.MoveNext())
                {
                    do yield return resultSelector(firstEnumerator.Current, secondEnumerator.Current);
                    while (firstEnumerator.MoveNext() && secondEnumerator.MoveNext());
                }
            }
            else
            {
                while (firstEnumerator.MoveNext())
                    yield return firstEnumerator.Current;
            }
        }
    }
}

You don't need to complicate it. A regular foreach loop with a single extra bool variable will do the trick.

If you have

if(input.Any())
{
    A
    foreach(int i in input)
    {
        B
    }
    C
}

and you don't want to read input twice, you can change this to

bool seenItem = false;
foreach(int i in input)
{
    if (!seenItem)
    {
        seenItem = true;
        A
    }
    B
}
if (seenItem)
{
    C
}

Depending on what B does, you may be able to avoid the seenItem variable entirely.

In your case, Enumerable.Zip is a fairly basic function that is easily reimplemented, and your replacement function can use something similar to the above.

Edit: You might consider

public static class MyEnumerableExtensions
{
    public static IEnumerable<TFirst> NotReallyZip<TFirst, TSecond>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TFirst> resultSelector)
    {
        using (var firstEnumerator = first.GetEnumerator())
        using (var secondEnumerator = second.GetEnumerator())
        {
            if (secondEnumerator.MoveNext())
            {
                if (firstEnumerator.MoveNext())
                {
                    do yield return resultSelector(firstEnumerator.Current, secondEnumerator.Current);
                    while (firstEnumerator.MoveNext() && secondEnumerator.MoveNext());
                }
            }
            else
            {
                while (firstEnumerator.MoveNext())
                    yield return firstEnumerator.Current;
            }
        }
    }
}
简美 2025-01-10 10:54:42

如果枚举很长,这不是一个有效的解决方案,但它是一个简单的解决方案:

var list = input.ToList();
if (list.Count != 0) {
    foreach (var item in list) {
       ...
    }
}

This is not an efficient solution if the enumeration is long, however it is an easy solution:

var list = input.ToList();
if (list.Count != 0) {
    foreach (var item in list) {
       ...
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文