有没有办法“偷看”MoveNext(),或者我怎样才能超越这个fencepost问题?

时间:2017-07-18 14:14:56

标签: c# .net algorithm linq

我正在尝试在固定大小的“批次”中编写yield s T s的算法。 (来源可能无限长)。

实施例。

    int[] arr = { 5, 1, 8, 10, 50, 4, 37, 8 };
    int size = 2;
    foreach(var batch in arr.Batches(size))
    {
        Console.WriteLine(string.Join(",", batch)); 
    }

- - - - >

5,1
8,10
50,4
37,8

当然我尝试像

这样的东西
public static class Extensions
{
    public static IEnumerable<IEnumerable<T>> Batches<T>(this IEnumerable<T> source, int batchSize)
    {
        for(var mover = source.GetEnumerator(); ; )
        {
            IEnumerable<T> batch = LimitMoves(mover, batchSize);
            if(!batch.Any())
            {
                yield break;
            }
            yield return batch;
        }
    }

    private static IEnumerable<T> LimitMoves<T>(IEnumerator<T> mover, int limit)
    {
        for(int i = 0; i < limit && mover.MoveNext(); ++i)
        {
            yield return mover.Current;
        }       
    }
}

并获取

1,8
50,4
8

4 个答案:

答案 0 :(得分:2)

谢尔盖很好,除了我讨厌无限循环和其他破坏方法。为什么不使用它们被设计的语言结构:

public static IEnumerable<IEnumerable<T>> Batches<T>(this IEnumerable<T> source, 
                                                     int batchSize)
{
    var mover = source.GetEnumerator();
    while(mover.MoveNext()) 
            yield return LimitMoves(mover, batchSize);
}

答案 1 :(得分:1)

在您输入LimitMoves方法之前,只需转到下一个项目,然后在该方法中生成当前项目而无需额外MoveNext()调用(请参阅下面的注释以了解您当前代码无效的原因以及您的其他问题代码有):

public static IEnumerable<IEnumerable<T>> Batches<T>(
   this IEnumerable<T> source, int batchSize)
{
    for (var mover = source.GetEnumerator(); ;)
    {
        if (!mover.MoveNext()) // there is no items for next batch
            yield break;
        else
            yield return LimitMoves(mover, batchSize);
    }
}

private static IEnumerable<T> LimitMoves<T>(IEnumerator<T> mover, int limit)
{
    // if you are here then there is an item which you can yield
    do
    {
        yield return mover.Current;
    }
    while (--limit > 0 && mover.MoveNext());
}

输出:

5,1
8,10
50,4
37,8

注1 :您的问题出现在batch.Any()来电,其中移动了光标&#39; 之前输入LimitMoves方法的源序列中的下一个项目。然后在for循环条件下,您再次在limit && mover.MoveNext()验证时移动了。因此,当您输入LimitMoves时当前的项目没有产生。

注意2 :你应该总是处理枚举器,并适当地使用循环 - 不要使用for循环进行变量初始化和迭代而不管任何条件 - 这会使你的代码难以理解保持。循环条件应该明确且易于查看。 E.g。

public static IEnumerable<IEnumerable<T>> Batches<T>(
   this IEnumerable<T> source, int batchSize)
{
    using(var mover = source.GetEnumerator())
    {
        while (mover.MoveNext())
           yield return LimitMoves(mover, batchSize);
    }
}

注3 :正如@Rene所说,你应该明白,当你枚举批次时,这种方法要求完全消耗每个批次。类似的解决方案以及替代方案可以在这里找到:Create batches in LINQ

答案 2 :(得分:0)

.Any()方法导致底层枚举器前进,这导致跳过一个步骤。这是一个代码已更正的版本。

public static class Extensions
{
    public static IEnumerable<IEnumerable<T>> Batches<T>(this IEnumerable<T> source, 
                                                              int batchSize)
    {
        using (var enumerator = source.GetEnumerator())
            while (enumerator.MoveNext())
                yield return enumerator.GetPage(batchSize);
    }

    private static IEnumerable<T> GetPage<T>(this IEnumerator<T> source, 
                                                  int batchSize)
    {
        for (var i = 0; i < batchSize; i++)
            if (i == 0 || source.MoveNext())
                yield return source.Current;
            else
                yield break; // not really needed but works as an early exit
    }
}

以下是使用上述代码的示例...

static void Main()
{
    var set = new[] { 5, 1, 8, 10, 50, 4, 37, 8, 1 };
    var batches = set.Batches(2);
    var result = string.Join("\r\n", 
                             batches.Select(batch => string.Join(",", batch)));
    Console.WriteLine(result);
}

......和结果

5,1
8,10
50,4
37,8
1

答案 3 :(得分:0)

SergeyJames之类的答案看起来不错。但他们的问题是这些扩展的用户必须确切知道如何使用结果。

yield告诉编译器生成状态机,这是一种使用新IEnumerable<TSource>方法实现MoveNext的类型,只有在迭代时才会调用该方法序列。因此,当您致电MoveNext时,只会执行外部Batches来电。但内部 MoveNext调用已执行延期

因此,在结果序列上调用ToArray()Count()之类的内容会产生奇怪的结果。即使您只需要第1000批,您也必须明确地逐个遍历批次。

所以只要没有内存问题,我更喜欢这样的解决方案:

public static IEnumerable<IEnumerable<TSource>> Batches<TSource>(this IEnumerable<TSource> source, int size)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size), size, "Value must be greater than zero!");

    return BatchIterator(source, size);
}

private static IEnumerable<IEnumerable<TSource>> BatchIterator<TSource>(IEnumerable<TSource> source, int size)
{
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            TSource[] array = new TSource[size];
            array[0] = enumerator.Current;
            for (int current = 1; current < size; current++)
            {
                if (!enumerator.MoveNext())
                {
                    yield return array.Take(current);
                    yield break;
                }

                array[current] = enumerator.Current;
            }

            yield return array;
        }
    }
}

这可能会在内存使用和速度方面表现更差,正如James和Servy所提到的那样。如果这是一个问题,您可以使用&#34;仅限产量&#34;解决方案,但扩展的每个消费者都必须知道如何正确迭代返回的批次(请参阅谢尔盖的答案和this answer的评论。)