C# 使用O(1)内存、O(n)运行时复杂性和无双重枚举实现LINQ“按谓词块”

C# 使用O(1)内存、O(n)运行时复杂性和无双重枚举实现LINQ“按谓词块”,c#,.net,algorithm,linq,data-structures,C#,.net,Algorithm,Linq,Data Structures,示例:假设谓词为i==0 然后 [1] -> [1] [0] -> [] [1, 0] -> [1] [0, 1] -> [1] [0, 0] -> [] [1, 1, 0] -> [1, 1] [1, 0, 1] -> [1, 1] [1, 1, 0, 0, 1, 0, 1, 1, 1] -> [1, 1, 1, 1, 1, 1] 基本上,返回谓词为false的连续子段 我想这会管用的 internal static IEnumerable<IEnumerable<T>>

示例:假设谓词为i==0

然后

[1] -> [1] [0] -> [] [1, 0] -> [1] [0, 1] -> [1] [0, 0] -> [] [1, 1, 0] -> [1, 1] [1, 0, 1] -> [1, 1] [1, 1, 0, 0, 1, 0, 1, 1, 1] -> [1, 1, 1, 1, 1, 1] 基本上,返回谓词为false的连续子段

我想这会管用的

internal static IEnumerable<IEnumerable<T>> PartitionBy<T>(this IEnumerable<T> source, Func<T, bool> condition)
{
    IEnumerator<T> mover = source.GetEnumerator();

    for (; mover.MoveNext() ; )
    {
        var chunk = mover.MoveUntil(condition);

        if (chunk.Any())
        {
            yield return chunk;
        }
    }          
}

private static IEnumerable<T> MoveUntil<T>(this IEnumerator<T> mover, Func<T, bool> condition)
{
    bool hitCondition = false;

    do
    {
        if (condition(mover.Current))
        {
            hitCondition = true;
        }
        else
        {
            yield return mover.Current;
        }
    }
    while (!hitCondition && mover.MoveNext());
}

有mover.moveuntlcondition.ToList;但如果可能的话,我不想在内存中保存任何子片段

首先,我认为您希望增加内存复杂性,因为您的输出长度与输入成线性比例。作为函数式编程的忠实粉丝,我选择使用一个对应于C中LINQ函数聚合的折叠

基本上,我们从集合的空集合和一个标志开始,该标志指示下一次迭代是否必须创建新的子集合,我们只知道谓词匹配时,即在上一次迭代中。我使用包含这两个元素的元组作为累加器。为了清晰起见,我在一个单独的函数中提取了聚合的逻辑

static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> a, Func<T, bool> predicate)
{
    // The accumulator is a tuple defined as: (collection, mustCreateNewList)
    return a.Aggregate((new List<List<T>>(), true), (acc, e) => ForEachElement(acc, e, predicate)).Item1;
}

static (List<List<T>>, bool) ForEachElement<T>((List<List<T>>, bool) acc, T e, Func<T, bool> predicate)
{
    var (collection, mustCreateNewList) = acc;

    // The predicate matches, continue to iterate!
    if (predicate(e)) return (collection, true);

    // The previous iteration requests to create a new list
    if(mustCreateNewList) collection.Add(new List<T>());

    // Add the current element to the last list
    collection[collection.Count - 1].Add(e);

    return (collection, false);
}

可以使用LINQ调用流式传输结果。执行情况如下:

不创建临时列表以减少内存消耗,我认为对于内存来说是O1,因为一次只处理一个子段。 不会有双重枚举,每个记录只调用一次谓词。 它将在运行时启用,因为与此类似,GroupBy操作应该启用,而其他LINQ调用是单次传递操作,因此也应该启用。 公共静态IEnumerable分区通过此IEnumerable a,Func谓词 { int groupNumber=0; Func getGroupNumber=跳过=> { 如果跳过 { //准备下一组,我们不在乎我们是否增加了不止一次 //我们只想分组 groupNumber++; //null,以便能够筛选出组分隔符 返回null; } 返回组号; }; 归还 .Selectx=>new{Value=x,GroupNumber=getGroupNumberpredicatex} .Wherex=>x.GroupNumber!=null .GroupByx=>x.GroupNumber .Selectg=>g.Selectx=>x.Value; }
我不明白你的预期结果,尤其是最后一个。此外,如果每个数字不是1或0,则更容易理解。使用一些2,3等@mjwills我在上一个有点o型。刚刚修好了。为了解释,如果你有[ 1, 1, 0,0, 1, 0,1, 1, 1 ],谓词是i=0,那么你想要所有的子片段被一个或多个0分隔,所以那些将是前2个数字1, 1,中间1的1,然后最后3 1, 1, 1。即,通过存储每个内孔的起始位置enumerable@AlexeiLevenkov最好不要。这就是我所说的标题中没有双重枚举的意思。如果每个数字不是1或0,则更容易理解。使用一些2、3等。了解groupnexting是否是一个选项很重要。我想我的意思是在枚举过程中的任何给定时间都有O1内存对不起,但这句话对我来说没有意义。以全局方式,您请求输出的大小在0到n之间,其中n是谓词中不匹配元素的数目。这意味着输出大小绑定到输入大小,因此n。我提供的代码在每次迭代中只分配一个列表/元素。我不知道在枚举期间的任何给定时间是否有您所指的内容?
static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> a, Func<T, bool> predicate)
{
    // The accumulator is a tuple defined as: (collection, mustCreateNewList)
    return a.Aggregate((new List<List<T>>(), true), (acc, e) => ForEachElement(acc, e, predicate)).Item1;
}

static (List<List<T>>, bool) ForEachElement<T>((List<List<T>>, bool) acc, T e, Func<T, bool> predicate)
{
    var (collection, mustCreateNewList) = acc;

    // The predicate matches, continue to iterate!
    if (predicate(e)) return (collection, true);

    // The previous iteration requests to create a new list
    if(mustCreateNewList) collection.Add(new List<T>());

    // Add the current element to the last list
    collection[collection.Count - 1].Add(e);

    return (collection, false);
}
var array = new int[] { 1, 1, 0, 0, 1, 0, 1, 1, 1 };
var result = array.Partition(i => i == 0);