1

Assuming I have an object class MyObject with the following properties:

class MyObject {
   public int MyProperty1 { get; set; }
   public int MyProperty2 { get; set; }
   public int MyProperty3 { get; set; }
}

And I have an array of MyObject[] with the following elements:

MyObject[] myObjects => new MyObject[] { myObject1, myObject2, myObject3 };

How do I create a new instance myObject such that its MyProperty1, MyProperty2, and MyProperty3 are the sums of the respective properties for every such object in the array?

Currently, my implementation is as follows

MyObject MyObjectSummed => new MyObject()
{
   MyProperty1 = myObjects.Sum(x => x.MyProperty1);
   MyProperty2 = myObjects.Sum(x => x.MyProperty2);
   MyProperty3 = myObjects.Sum(x => x.MyProperty3);
}

but I vaguely remember seeing a more efficient way of doing this using LINQ, using a single line of code.

Is this possible and can someone please point me in the right direction?

Thanks!

Thomas Lu
  • 13
  • 3

3 Answers3

2

You need to update three properties, so having "one-liner" will make code very unreadable.

If asking about different LINQ approach instead of summarising three values, then Aggregate is your choice, check @Andy's answer.

If you wrap logic with the method then you can use any amount of lines inside the implementation, but keep it one-liner for the consumers.

Alternative approach can be an extension method for enumerable

public static MyObject CalculateSum(this IEnumerable<MyObject> objects)
{
    var total = new MyObject();
    foreach (var obj in objects)
    {
        total.MyProperty1 += obj.MyProperty1;
        total.MyProperty2 += obj.MyProperty2;
        total.MyProperty3 += obj.MyProperty3;
    }

    return total;
}

Usage is "one-liner" :)

var objects = new MyObject[] { myObject1, myObject2, myObject3 };

var sum = objects.CalculateSum();

Notice that all LINQ methods are extension methods, so you kinda using your own domain specific LINQ "one-liner" ;)

Fabio
  • 31,528
  • 4
  • 33
  • 72
1

So if you do not want to mutate the original array, this is what you can do:

    var result = myObjects.Aggregate(new MyObject(), (accumulate, current) => {
           accumulate.MyProperty1 += current.MyProperty1;
           accumulate.MyProperty2 += current.MyProperty2;
           accumulate.MyProperty3 += current.MyProperty3;
           return accumulate;
    });

If you do not care you can just do this:

By doing this way you are mutating the first element within the array.

    var result = myObjects.Aggregate((accumulate, current) => {
           accumulate.MyProperty1 += current.MyProperty1;
           accumulate.MyProperty2 += current.MyProperty2;
           accumulate.MyProperty3 += current.MyProperty3;
           return accumulate;
    });
Andy Song
  • 4,404
  • 1
  • 11
  • 29
  • Thanks for update and no worries. Nup, looks fine to me :) +1 –  Aug 14 '20 at 07:47
  • 1
    You should provider an initialiser value `myObjects.Aggregate(new MyObject(), (accumulate, current)...`, and you should be updating/returning `accumulate`, not `current`. The current implementation is modifying the original list of values which I suspect is an unindented side effect. – Eamonn McEvoy Aug 14 '20 at 07:49
  • check the property values of `myObjects[1]` before and after running this aggregation, they have changed – Eamonn McEvoy Aug 14 '20 at 07:52
  • This is have same efficiency level as OP's original solution O(n) and amount of lines is even more then original ;) – Fabio Aug 14 '20 at 08:09
  • This also mutates the array, it is updating the first item this time. You need to provide an initialiser value because you are aggregating a list of objects (passed by ref). – Eamonn McEvoy Aug 14 '20 at 08:10
  • @Fabio Its slightly better no? OP's solution iterates the list 3 times whereas Aggregate does only once. You could even say the original is O(n*m) where n is length of the list and m is the number of properties :p I think a simple for loop would be better than using linq aggregate in this case. – Eamonn McEvoy Aug 14 '20 at 08:14
  • @AndySong my mistake, i didn't that you added the initialiser. You are correct. – Eamonn McEvoy Aug 14 '20 at 08:23
  • @EamonnMcEvoy, calling `Sum` three times executes `n` * `1` operations, where `n` is amount of iterations and `1` is constant amount of operations inside one iteration. When talking about big O notation constants (`1` in our case) are usually ignored, so for Sum we have 3 * 1 = 3 operations. Now with `Aggregate` we have one iteration with `3` operations, which makes amount of operation is same as with Sum 1 * 3 = 3 – Fabio Aug 14 '20 at 08:25
  • @EamonnMcEvoy thanks for your help, just one thing if I do not provide initialiser, does it matter if I updating/returning `accumulate` or `current`? I know that if I do not want to mutate the `array`, I have to add initialiser and update/return `accumulate`, but if I am ok with mutating the `array` does it matter which one I am updating/returning? – Andy Song Aug 14 '20 at 08:28
  • @AndySong, notice that without initialiser `Aggregate` will throw an exception for empty array. – Fabio Aug 14 '20 at 08:31
  • @Fabio but still, you should not loop through an array 3 times when you can only need do it once. – Eamonn McEvoy Aug 14 '20 at 08:31
0

If performance is not an issue, you can use reflections. Then you can add and remove integer properties to your object without having to modify the code of adding. If you convert the return value of GetProperties() to a list, you can use the ForEach() method of List<T>, which even reduces your line count further.

MyObject myObjectSummed = new MyObject();
foreach(var prop in myObjectSummed.GetType().GetProperties().Where(p => p.PropertyType == typeof(int)))
{
    prop.SetValue(myObjectSummed, myObjects.Sum(x => (int)prop.GetValue(x)));
}

But i recommend Fabio's answer: From my point of view, it's clearest way to write this logic. Having as few lines of code as possible is not always the best approach.

SomeBody
  • 7,515
  • 2
  • 17
  • 33
  • Thanks for the response. Given that my actual object has more than just 3 properties, I was hoping there was a way to aggregate them all without having to loop through the array for each property as in my previous implementation. Fabio's answer does appear to be the best. – Thomas Lu Aug 14 '20 at 14:45