I have class ObjectA with property called Value.
I have also class ObjectB with property called Sum.
Then I have List<ObjectA> listA.
How to return List<ObjectB> listB from C# Enumerable.Aggregate method, where property Sum of every following ObjectB is the (rising) aggregated sum of properties Value from List<ObjectA>?
EXPECTED BEHAVIOUR:
IN: List of ObjectA with following properties Value {1,2,3,4,5,6}
OUT: List of ObjectB with following properties Sum {1,3,6,10,15,21}
CodePudding user response:
Short answer: Don't. Use Select or a loop instead.
Long answer:
Aggregate
is intended for cases where you start with a list of values, but want to end up with a single value. You're trying to end up with another collection, with one value for each input.
That's not to say it can't be done. Just for academic purposes, let's look at some of the patterns you could use. I'm going to simplify the problem to just use integers instead of ObjectA
and ObjectB
.
This is probably the most "pure" approach, in the sense that the delegate you pass to Aggregate has no side-effects.
var values = new[]{1,2,3,4,5,6};
var sums = values.Aggregate(Enumerable.Empty<int>(), (previous, next) => previous.Append(previous.LastOrDefault() next)).ToList();
However, this probably has O(n²)
complexity because you're calling LastOrDefault()
on IEnumerable<>
s constructed by chaining Append
calls together. It might be preferable to accept a little impurity by closing over a running total variable.
var values = new[]{1,2,3,4,5,6};
int runningTotal = 0;
var sums = values.Aggregate(Enumerable.Empty<int>(), (previous, next) => previous.Append(runningTotal = next)).ToList();
But if we're willing to track state that way, things could be much simpler by just using Select
instead of Aggregate
:
int runningTotal = 0;
var sums = values.Select(next => runningTotal = next).ToList();
And of course, purists would say that functional syntax like LINQ statements shouldn't have side-effects. It might be clearer to just build out a new list with a more imperative-style foreach loop.
var values = new[]{1,2,3,4,5,6};
var sums = new List<int>();
int runningTotal = 0;
foreach(var next in values)
{
runningTotal = next;
sums.Add(runningTotal);
}
CodePudding user response:
You can do the Aggregate
in the following way:
var objectBs = objectAs
.Aggregate<ObjectA, IEnumerable<ObjectB>>(
Enumerable.Empty<ObjectB>(),
(objectBs, objectA) => objectBs.Append(new ObjectB
{
Sum = (objectBs.LastOrDefault()?.Sum ?? 0) objectA.Value
}));
But this hard to read for people (I personally do). Alternatively, write it with a for loop:
var listOfObjectBs = new List<ObjectB>
{
new ObjectB
{
Sum = objectAs.First().Value
}
};
for (var i = 1; i < objectAs.Count; i )
{
listOfObjectBs.Add(new ObjectB
{
Sum = listOfObjectBs[i - 1].Sum objectAs[i].Value
});
}