score:11

Accepted answer

What you want is a combination of Aggregate and TakeWhile, so let's write that.

public static IEnumerable<S> AggregatingTakeWhile<S, A>(
  this IEnumerable<S> items,
  A initial,
  Func<A, S, A> accumulator,
  Func<A, S, bool> predicate) 
{
  A current = initial;
  foreach(S item in items)
  {
    current = accumulator(current, item);
    if (!predicate(current, item))
      break;
    yield return item;
  }
}

And so now you can say

var items = myObjList.AggregatingTakeWhile(
  0,
  (a, s) => a + s.MyValue,
  (a, s) => a <= 5);

Note that I have made the decision here to consult the predicate after the accumulator has been updated; depending on your application, you might want to tweak that slightly.

Another solution would be to combine aggregation with enumeration:

public static IEnumerable<(A, S)> RunningAggregate<S, A>(
  this IEnumerable<S> items,
  A initial,
  Func<A, S, A> accumulator) 
{
  A current = initial;
  foreach(S item in items)
  {
    current = accumulator(current, item);
    yield return (current, item);
  }
}

And now your desired operation is

var result = myObjList
  .RunningAggregate(0, (a, s) => a + s.MyValue)
  .TakeWhile( ((a, s)) => a <= 5)
  .Select(((a, s)) => s);

I might have gotten the tuple syntax wrong there; I haven't got Visual Studio handy right now. But you get the idea. The aggregation produces a sequence of (sum, item) tuples, and now we can use normal sequence operators on that thing.

score:5

For an old-school, non-Linq approach, you could write a simple method for this:

static List<MyObj> GetItemsUntilSumEquals(List<MyObj> items, int maxSum)
{    
    if (items == null) return null;

    var result = new List<MyObj>();
    var sum = 0;

    foreach (var item in items)
    {
        if (sum + item.MyValue > maxSum) break;
        sum += item.MyValue;
        result.Add(item);
    }

    return result;
}

Related Articles