Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ, simplifying expression - take while sum of taken does not exceed given value

Tags:

c#

linq

Given a setup like this ..

class Product {
   int Cost;
   // other properties unimportant
}

var products = new List<Product> {
    new Product { Cost = 5 },
    new Product { Cost = 10 },
    new Product { Cost = 15 },
    new Product { Cost = 20 }
};

var credit = 15;

Assume that the list will be sorted in the given order. I wish to basically iterate over each item in the list, keep a summed value of cost, and keep getting products out as long as the total cost does not exceed credit.

I can do this with some loops and stuff, but I was wondering if there is a way to compact it into a simpler LINQ query.

like image 819
Ciel Avatar asked Sep 13 '11 13:09

Ciel


1 Answers

Others have pointed out the captured variable approach, and there are arguably correct viewpoints that this approach is bad because it mutates state. Additionally, the captured variable approaches can only be iterated once, and are dangerous because a. you might forget that fact and try to iterate twice; b. the captured variable does not reflect the sum of the items taken.

To avoid these problems, just create an extension method:

public static IEnumerable<TSource> TakeWhileAggregate<TSource, TAccumulate>(
    this IEnumerable<TSource> source,
    TAccumulate seed,
    Func<TAccumulate, TSource, TAccumulate> func,
    Func<TAccumulate, bool> predicate
) {
    TAccumulate accumulator = seed;
    foreach (TSource item in source) {
        accumulator = func(accumulator, item);
        if (predicate(accumulator)) {
            yield return item;
        }
        else {
            yield break;
        }
    }
}

Usage:

var taken = products.TakeWhileAggregate(
    0, 
    (cost, product) => cost + product.Cost,
    cost => cost <= credit
);

Note that NOW you can iterate twice (although be careful if your TAccumulate is mutable a reference type).

like image 52
jason Avatar answered Oct 19 '22 03:10

jason