C# 使用O(1)内存、O(n)运行时复杂性和无双重枚举实现LINQ“按谓词块”
示例:假设谓词为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的连续子段 我想这会管用的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>>
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);