Collapse a List by Sum (against a Property) using Lambda
-
jeudi 12 avril 2012 19:09
Hello,
I'm new to Lamda expressions and I have the following scenario. Say I have a list with 2 properties (I'd like to avoid using the Key thingy), and there will be unique values in the first property (a Decimal), but potentially equal values in the second property (a Date).
What I'd like to do is to take the list, Sum all the values of Decimal in the first property, where their Dates from the second property are equal. I'd like to end up with the same type of List as I started with (i.e., not a "var", etc.).
For Example - starting List:
Decimal Date
5 1/1/2012
10 2/1/2012
7 2/1/2012
3 3/1/2012
The ending List should be:
5 1/1/2012
17 2/1/2012
3 3/1/2012
Thanks!
Toutes les réponses
-
jeudi 12 avril 2012 19:13
list = list.GroupBy(item => item.Date) .Select(group => new MyItem(group.Sum(item => item.Number), group.Key)) .ToList();
I just made up variable/Type names since you didn't define any.
For the record, there is no such thing as a 'var' type (unless you do something like "class var {...}"). 'var', as a keyword, just means, "I was too lazy to type in what this is actually supposed to be, you figure it out for me". If the compiler can't figure it out it's a compile time error.
Most of the Linq methods, such as the select/group by used here, return IEnumerable<T>. If you're really sure you want a list, the conversion is trivial, but an IEnumerable is sufficient more often than you might think.
- Proposé comme réponse Idea Hat jeudi 12 avril 2012 19:45
- Modifié servy42Microsoft Community Contributor jeudi 12 avril 2012 19:51
-
jeudi 12 avril 2012 19:22
Thanks.
"MyItem" is the base object that the list is made up of, correct?
What does the "group" key word stand for?
I've tried this and I cannot get "Number" to show up as valid for "group.Number.Sum()".
- Modifié sdfsda jeudi 12 avril 2012 19:24
-
jeudi 12 avril 2012 19:38
It just occurs to me the base object I have does not have a "new" constructor that would seem to support this.
Is there a way to use the actual list and just "collapse" it without having to create a new one?
-
jeudi 12 avril 2012 19:38
I think its supposed to be group.Sum(item=>item.Decimal)
Continuing the var discussion, IEnumerable<T> is also lazily calculated, which can be less of a resource hog.
- Modifié Idea Hat jeudi 12 avril 2012 19:39
-
jeudi 12 avril 2012 19:43
Idea Hat is right. Try this code.
public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { List<Temp> temp = new List<Temp>(); temp.Add(new Temp(5,"1/1/2012")); temp.Add(new Temp(10, "2/1/2012")); temp.Add(new Temp(7, "2/1/2012")); temp.Add(new Temp(3, "3/1/2012")); var temp1 = temp.GroupBy(p => p.Date).Select(group => new Temp(group.Sum(item => item.Decimal),group.Key)).ToList(); Grid1.DataSource = temp1; Grid1.DataBind(); } } public class Temp { private int _decimal; private string _date; public Temp(int _decimal,string _date) { Decimal = _decimal; Date = _date; } public int Decimal { get; set; } public string Date { get; set; } }Output:
Decimal Date 5 1/1/2012 17 2/1/2012 3 3/1/2012 Thanks,
Parth
- Modifié ParthPatel jeudi 12 avril 2012 19:52
- Proposé comme réponse ParthPatel jeudi 12 avril 2012 19:55
- Marqué comme réponse sdfsda vendredi 13 avril 2012 12:33
-
jeudi 12 avril 2012 19:47
Can you expand on this: "group.Sum(item=>item.Decimal"?
Where is this supposed to be at?
-
jeudi 12 avril 2012 19:48
""MyItem" is the base object that the list is made up of, correct?"
Correct. Again, if you told us what it was I wouldn't need to make stuff up. The definition of the class would be helpful too (what are the properties, constructors, etc.).
"What does the "group" key word stand for?"
It's not a keyword, it's just the name that I chose for the parameter of that lambda function. You could make up whatever you want, but it's a variable representing a group, so I called it "group". The coloring function on this site seems to like to color it differently, but just ignore that. In VS it looks normal.
"I've tried this and I cannot get "Number" to show up as valid for "group.Number.Sum()"."
That's because you didn't give me the definition of the unnamed class that you're using. You say that you have a class with a date and number. Not knowing what they might actually be called, I just called them "Date" and "Number". I figured you could figure out the rest. If you can't, you'll need to provide the definition of the class.
I also was slightly off in my syntax, which is what Idea Hat was saying. I've updated the code accordingly.
"It just occurs to me the base object I have does not have a "new" constructor that would seem to support this."
So then how does one create a new object of that type? Is there a relevant method? Is there a parameter-less constructor? If so, use that and then just set the properties on the new item.
"Is there a way to use the actual list and just "collapse" it without having to create a new one?"
You can but it would be quite a bit more work. It would be easy enough to just remove all of the items and then add a bunch of new ones, but it wouldn't perform as well. If lots of other places are holding onto references to this List you could do that. If you just feel bad about recreating it, don't. It's logically a new list, it makes sense to make a new one. If you're concerned about performance, again, you'll likely end up doing more work to try to re-use the list, so only do that if there is a real compelling need.
- Modifié servy42Microsoft Community Contributor jeudi 12 avril 2012 19:52
-
jeudi 12 avril 2012 19:54@Parth the private fields in your class are entirely unused; you can just delete them since you're using auto-generated properties.
-
jeudi 12 avril 2012 19:55
Of course there's a way, but not with LINQ.
And even that's not true. You could do it with LINQ but the solution would be awful and would violate every principle of good programming and specifically good usage of LINQ. LINQ queries must not cause side effects. A side effect is changing the source from which the query is being generated.
If your object has mutable properties - meaning you can change Decimal and DateTime - but you can't create a new object - then the object is designed poorly.
-
jeudi 12 avril 2012 19:57
servy42:
To answer the question of the constructor, the basic list I'm using is an inheritance from the CSLA framework:
public class PaymentViews : ReadOnlyListBase<PaymentViews, PaymentView>
None of the "constructors" follow the "new(var 1, Key)" format.
As I said, I'm new to Lamda and find it quite confusing and obscure.
- Modifié sdfsda jeudi 12 avril 2012 19:59
-
jeudi 12 avril 2012 19:59
Evan,
I agree, but I did not create these classes.
I'm not familiar enough with how Lamda works to figure out if any type of expression will work with these classes.
-
jeudi 12 avril 2012 20:01I see that Idea Hat's fix should work, I just need to get the correct constructor arguments for my particular class into it. Not sure if the class' definition can handle that.
-
jeudi 12 avril 2012 20:06
"Is there a way to use the actual list and just "collapse" it without having to create a new one?"
To expand Servy's comment it would also be slowwwwwwww. Removing an object from a middle of a List<T> is not a fast operation (you have to move all the rest of the objects around).
You could, instead, use a Dictionary<Date,Decimal>, but you'll have to use Keys for that.
-
jeudi 12 avril 2012 20:07Since I'm working with an implementation of the CSLA framework, I notice the book has a section on LINQ to CSLA - guess I'll have to read up on that to make sure I can do this at all.....
-
jeudi 12 avril 2012 20:08
you can use the following syntax to create the new object on multiple lines. (This is not .Linq per se, but more Anon. Methods)
(group=>
{
Temp t = new Temp();
//fill your data in here
return t;
})
- Modifié Idea Hat jeudi 12 avril 2012 20:09
-
jeudi 12 avril 2012 20:12
@Evan:
"Of course there's a way, but not with LINQ.
And even that's not true. You could do it with LINQ but the solution would be awful and would violate every principle of good programming and specifically good usage of LINQ. LINQ queries must not cause side effects. A side effect is changing the source from which the query is being generated. "
It wouldn't actually be that bad. If it had to be done I'd just call list.Clear and then pass the query used above into the AddRange() function. It would perform worse than just making a new list, but it wouldn't really violate any major prinicpals other than re-using the list for no good reason. If there was a good reason (the list was referenced elsewhere) then it would not be a bad idea at all.
"If your object has mutable properties - meaning you can change Decimal and DateTime - but you can't create a new object - then the object is designed poorly."
Agreed. My guess is this isn't quite the case here though. I'd say the OP just doesn't realize how to use the class, not that it's poorly designed.
- Modifié servy42Microsoft Community Contributor jeudi 12 avril 2012 20:17
-
jeudi 12 avril 2012 20:16
"To answer the question of the constructor, the basic list I'm using is an inheritance from the CSLA framework:
public class PaymentViews : ReadOnlyListBase<PaymentViews, PaymentView>
None of the "constructors" follow the "new(var 1, Key)" format.
As I said, I'm new to Lamda and find it quite confusing and obscure."
If you don't actually tell us what the details of the class are you make it much harder for us to help you. You're continuing to force us to guess and guess while you tell us what works and what doesn't. If you answered the requests I've made you would eliminate these steps.
As for Lambdas, Google is an okay research. A Lambda is a mathematical concept, and it's basically a function. In C#, it's essentially a shorter syntax that lets you define an anonymous function. Adding in C# to the search terms will give you more details in a C# context.
-
jeudi 12 avril 2012 20:21
"I see that Idea Hat's fix should work, I just need to get the correct constructor arguments for my particular class into it. Not sure if the class' definition can handle that."
If it's not your class but some other class then you don't want to try 'injecting' a constructor. That's way overcomplicating things. You can create your own helper function outside of the class to accomplish a comparable goal:
public static MyClass createMyClass(decimal number, DateTime date) { MyClass item = new MyClass(); item.Number = number; item.Date = date; return item; }
Then just use that method in the LINQ 'Select' statement rather than the constructor. You could also use a multi-line select, or an object initializer block, but this is simpler and more readable in your case.
-
jeudi 12 avril 2012 20:23
"Since I'm working with an implementation of the CSLA framework, I notice the book has a section on LINQ to CSLA - guess I'll have to read up on that to make sure I can do this at all....."
Assuming CSLA is some sort of database or external service, that would be using LINQ to interact with it directly. This code was written to be Linq to object, and just manipulate things in memory. Without knowing anything about what CSLA is (since you haven't even told us) I couldn't comment about it's Linq provider.
-
jeudi 12 avril 2012 21:25
Here is just a bit different example, where the custom class does not take any parameters in the constructor:
class Program { static void Main(string[] args) { List<MyClass> list = new List<MyClass>(); list.Add(new MyClass { MyNum = 5, MyDate = new DateTime(2012, 1, 1) }); list.Add(new MyClass { MyNum = 10, MyDate = new DateTime(2012, 1, 2) }); list.Add(new MyClass { MyNum = 7, MyDate = new DateTime(2012, 1, 2) }); list.Add(new MyClass { MyNum = 3, MyDate = new DateTime(2012, 1, 3) }); List<MyClass> query = list.GroupBy(g => g.MyDate).Select(s => new MyClass { MyDate = s.Key, MyNum = s.Sum(i => i.MyNum) }).ToList(); } } public class MyClass { public int MyNum { get; set; } public DateTime MyDate { get; set; } }
Mitja
- Marqué comme réponse sdfsda vendredi 13 avril 2012 12:31
-
vendredi 13 avril 2012 15:56
Okay, so I did figure out how to get what I wanted, despite the class having no sufficient constructor (I could have added one, but not really what I needed).
Basically, all I need is a properly formatted list with a handful (4) of values to use for a Recursion function. So I made a simple Struct to use, and after looking up more blogs on how to use Linq/Lambda, I figured out that the thing I wasn't understanding was how to setup a "return" object from the "from" portion of the query. I thought at first that "Key" was a single-value only, but figured out I could add to it whatever I wanted to pull any data value from the original list into my new resultant list. So this is what I ended up with:
List<InterestBalancePortion> _list = (from p in Payments group p by new {p.ProcessDate, p.PaymentTypeID } into l select new InterestBalancePortion { AccrualBalance = l.Sum(p => p.Amount), PaymentType = (TPS_Enum.PaymentType)l.Key.PaymentTypeID, StartDate = l.Key.ProcessDate, EndDate = this.AsOfDate } ).ToList();So, starting with my above example values, I did get my correctly "collapsed" list result.
Finally, a point on forum etiquette:
Mitja Bonca, Idea Hat, Evan and ParthPatel - Thanks very much for your constructive comments. I know in a forum it is hard to convey exactly all that is going on, short of a code-dump, which is not practical. You all did the best you could with what I was able to provide, and it was enough to help me along.
However, some forum users can express frustrated and snide remarks that do not add to the discussion nor do they invite "newbies" to bring forward their questions. I am an experienced programmer (but far from expert), but (perhaps sadly) this experience does not cover Linq/Lamda and so I am quite ignorant here. Yes, the class I had to work with in this example was poorly designed for use in this case, and although I could have added the necessary corrections, the least-intrusive refactoring, I felt, was to use a localized struct for my needs. It is not the case that I simply don't realize how to use the class.
I don't care how many forum points or gold medals one has, a basic politeness and patience is called for in these forums. Many thanks to all of you who did so.
-
lundi 16 avril 2012 05:27
Hi,
Thanks everyone I got the solution from this post.
-
lundi 16 avril 2012 12:02
I'd just call list.Clear and then pass the query used above into the AddRange() function.
You'd need to store the result of the query into a temporary list. Running the query after the list is cleared would return nothing.

