我需要类似于AggregateWhile
方法的东西。标准System.Linq.Enumerable
类不提供它。到目前为止,我一直能够利用标准的LINQ方法来解决我遇到的每一个问题。所以我想知道在这种情况下是否仍然可行,或者我确实需要使用非标准方法扩展LINQ。
假设的AggregateWhile
方法将迭代序列并应用累加器。一旦谓词返回false,聚合就会完成。结果是元素的聚合,但不是,包括谓词失败的元素。
这是一个例子。我们有一个带有累加器的List { 1, 2, 3, 4, 5 }
,它将两个输入数字加在一起,并且一个谓词表示累积的谓词必须小于12. AggregateWhile
将返回10,因为这是1 + 2 + 3的结果+ 4并加上最后的5将推动总数超过限制。在代码中:
var list = new List<int> { 1, 2, 3, 4, 5 };
int total = list.AggregateWhile( (x, y) => x + y, a => a < 12 ); // returns 10
我需要一个纯粹的功能解决方案,因此关闭临时变量不是一种选择。
答案 0 :(得分:3)
您可以自己编写函数,也可以在累加器中带一个标记:
int total = list.Aggregate(new { value = 0, valid = true },
(acc, v) => acc.value + v < 12 && acc.valid ?
new { value = acc.value + v, valid = true } :
new { value = acc.value, valid = false },
acc => acc.value);
这很难看,所以写一个新的AggregateWhile
会更好:
public static TSource AggregateWhile<TSource>(this IEnumerable<TSource> source,
Func<TSource, TSource, TSource> func,
Func<TSource, bool> predicate)
{
using (IEnumerator<TSource> e = source.GetEnumerator()) {
TSource result = e.Current;
TSource tmp = default(TSource);
while (e.MoveNext() && predicate(tmp = func(result, e.Current)))
result = tmp;
return result;
}
}
(没有错误检查以简洁)
答案 1 :(得分:2)
这不会起作用吗?
int total = list.Aggregate(0, (a, x) => (a + x) > 12 ? a : a + x);
使用Tuple<bool, int>
作为累加器类型,在第一次溢出时中断:
int total = list.Aggregate(new Tuple<bool, int>(false, 0),
(a, x) => a.Item1 || (a.Item2 + x) > 12
? new Tuple<bool, int>(true, a.Item2)
: new Tuple<bool, int>(false, a.Item2 + x)
).Item2;
但不幸的是,它不是那么好。
开始使用F#。 ;)
let list = [ 1; 2; 3; 4; 5; 1 ]
let predicate = fun a -> a > 12
let total = list |> List.fold (fun (aval, astate) x ->
if astate || predicate (aval + x)
then (aval, true)
else (aval + x, false)) (0, false)
Tuple
解包,没有new
臃肿。当你编码它时,类型推断使它变得轻而易举。
答案 2 :(得分:2)
您可以编写自己的扩展方法。这不像普通的Linq方法那么完美,我作弊是因为我已经知道你的要求,使它变得更简单。实际上,您可能需要a
的可选起始值以及T或其他内容的不同输入和输出类型:
public static class Linq
{
public static T AggregateWhile<T>(this IEnumerable<T> sequence, Func<T, T, T> aggregate, Func<T, bool> predicate)
{
T a;
foreach(var value in sequence)
{
T temp = aggregate(a, value);
if(!predicate(temp)) break;
a = temp;
}
return a;
}
}
答案 3 :(得分:1)
我在遇到问题后回答了这个问题,后来我重新考虑了不需要AggregateWhile
的问题。但是现在我遇到了一个稍微不同的问题,无疑需要AggregateWhile
或直接替代它。
@sloth和@rkrahl提出的解决方案很有帮助。但它们不足之处在于聚合逻辑(在这种情况下是加法)重复两次。对于这个问题的琐碎例子,这似乎不是什么大问题。但是对于我真正的问题,计算很复杂,所以写两次是不可接受的。
以下是我更喜欢的解决方案(缺少实际的AggregateWhile
方法):
class Program
{
static void Main( string[] args ) { new Program(); }
public Program()
{
var list = new int[] { 1, 2, 3, 4, 5 };
int total = list
.Aggregate( new Accumulator( 0 ), ( a, i ) => a.Next( i ), a => a.Total );
}
}
class Accumulator
{
public Accumulator( int total )
{
this.total = total;
}
public Accumulator Next( int i )
{
if ( isDone )
return this;
else {
int total = this.total + i;
if ( total < 12 )
return new Accumulator( total );
else {
isDone = true;
return this;
}
}
}
bool isDone;
public int Total
{
get { return total; }
}
readonly int total;
}
理想的解决方案是完全实现并经过测试的AggregateWhile
方法,这些方法对应于三个Aggregate
重载。除此之外,上述模式的优势在于它可以利用.NET框架中已经存在的(有些缺乏的)功能。
答案 4 :(得分:1)
以下是AggregateWhile
seed
:
public static TAccumulate AggregateWhile<TSource, TAccumulate>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> func,
Func<TAccumulate, bool> predicate)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (func == null)
throw new ArgumentNullException(nameof(func));
if (predicate == null)
throw new ArgumentNullException(nameof(predicate));
var accumulate = seed;
foreach (var item in source)
{
var tmp = func(accumulate, item);
if (!predicate(tmp)) break;
accumulate = tmp;
}
return accumulate;
}