nest yields to return IEnumerable> with lazy evaluation

前端 未结 4 2072
忘掉有多难
忘掉有多难 2021-02-13 16:40

I wrote a LINQ extension method SplitBetween analogous to String.Split.

> new List(){3,4,2,21,3,2,17,16,1}
> .SplitBet         


        
4条回答
  •  心在旅途
    2021-02-13 17:00

    Here is a solution that I suppose does what you ask for.

    The problem was that you only had one method with yield and you were manually creating the internal collection while the outer IEnumerable was enumerated. The second problem was that your way of "testing" fails even on mine code below. However, as David B pointed out in his comment, you must go through all the elements to define number of elements of outer IEnumerable. But you can defer creation and population of inner IEnumerables.

    public static IEnumerable> SplitBetween(this IEnumerable source,Func separatorSelector, bool includeSeparators=false)
    {
        IList sourceList = source.ToList();
        var indexStart = 0;
        var indexOfLastElement = sourceList.Count - 1;
        for(int i = 0; i <= indexOfLastElement; i++)
            if (separatorSelector(sourceList[i]))
            {
                if(includeSeparators)
                    yield return SplitBetweenInner(sourceList, indexStart, i);
                else
                    yield return SplitBetweenInner(sourceList, indexStart, i - 1);
    
                indexStart = i + 1;
            }
            else if(i == indexOfLastElement)
                yield return SplitBetweenInner(sourceList, indexStart, i);        
    }
    
    private static IEnumerable SplitBetweenInner(IList source, int startIndex, int endIndex)
    {
        //throw new Exception("BOOM");
        for(int i = startIndex; i <= endIndex; i++)
            yield return source[i];
    }
    

    Note that it behaves slightly different as your code (it doesn't create another empty List when the last element satisfies the separator condition - it's up to definition what is correct here, but I find that better as the behavior is the same as if the element appears at the beginning of source list)

    If you test the code, you will see that the inner IEnumerable execution is deferred.

    If the throw exception line is uncommented:

    (new List(){3,4,2,21,3,2,17,16,1}).SplitBetween(x=>x>=10, true).Count();
    

    returns 4

    (new List(){3,4,2,21,3,2,17,16,1}).SplitBetween(x=>x>=10, true).First().Count();
    

    throws BOOM

提交回复
热议问题