This is an already asked question, but that question is to work with 2 attributes only, I need to work with 3 attributes, So I am copy-pasting most of the text.
Let's suppose if we have a class like
class Person {
internal int PersonID;
internal string car ;
internal string friend ;
}
Now I have a list of this class: List persons;
Now this list can have instances multiple same PersonIDs, for ex.
persons[0] = new Person { PersonID = 1, car = "Ferrari" , friend = "Josh" };
persons[1] = new Person { PersonID = 1, car = "BMW" , friend = "Olof" };
persons[2] = new Person { PersonID = 2, car = "Audi" , friend = "Gustaf" };
Is there a way I can group by PersonID and get the list of all the cars and friends he has? For example the expected result would be:
class Result {
int PersonID;
List<string> cars;
List<string> friends;
}
From what I have done so far:
IEnumerable resultsForDisplay = ResultFromSQL_Query.GroupBy(
p => p.PersonId.ToString(),
p => p.car,
(key, g) => new { PersonId = key, car = g.ToList()});
But now I'm stuck at getting the friend's array in resultsForDisplay
Sure, you can perform LINQ queries on the group g as well, like:
IEnumerable<Result> resultsForDisplay = from q in ResultFromSQL_Query
group q by q.PersonID into g
select new Result {PersonID = g.Key,cars = g.Select(x => x.car).ToList(), friends = g.Select(x => x.friend).ToList()};
Or with lambda expressions:
IEnumerable<Result> results = persons.GroupBy(x => x.PersonID)
.Select(g => new Result { PersonID = g.Key, cars = g.Select(x => x.car).ToList(), friends = g.Select(x => x.friend).ToList()};
So you can perform any LINQ query on a group (which thus behaves as an IEnumerable<> on the elements of that grou), like a .Select(..), but also .Sum(..), .Average(..) and other sub queries, aggregates, etc.
Related
I have the following class called "Person".
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Hobby { get; set; }
}
I created a list with the following data:
List<Person> users = new List<Person>()
{
new Person {Id= 1, Name = "Silva", Hobby = "Football" },
new Person {Id= 2, Name = "Bob", Hobby = "Golf"},
new Person {Id= 2, Name = "Bob", Hobby = "Tennis"},
new Person {Id= 1, Name = "Silva", Hobby = "Sleeping"},
new Person {Id= 3, Name = "Sue", Hobby = "Drinking"}
new Person {Id= 1, Name = "Silva", Hobby = "Handball"},
new Person {Id= 3, Name = "Sue", Hobby = "Football"},
};
Now I need to create a new list called "usersHobbies" that when called returns the following result.
Id Name Hobby
1 Silva Football, Sleeping, Handball
2 Bob Golf, Tennis
3 Sue Drinking, Football
You would use GroupBy() to achieve that. Group by Id, select the Name from the first group, use string.Join() to join the Hobbies into a single string:
var result = users.GroupBy(u => u.Id) // group by Id
.Select(g => new // select values
{
Id = g.Key,
Name = g.First().Name,
Hobbies = string.Join(", ", g.Select(u => u.Hobby))
})
.ToList();
Output:
If you want to return a new Person instead of an anonymous type, just update the Select:
.Select(g => new Person
{
Id = g.Key,
Name = g.First().Name,
Hobby = string.Join(", ", g.Select(u => u.Hobby))
})
Update from your comment:
is it possible instead of string.join() to return a list or array
Yes, it is. Using an anonymous type:
var result = users.GroupBy(u => u.Id) // group by Id
.Select(g => new // select values
{
Id = g.Key,
Name = g.First().Name,
Hobbies = g.Select(u => u.Hobby).ToList()
})
.ToList();
Which returns a List<string> for Hobbies.
.GroupBy() has an override that lets you define a key selector, an element selector and the result selector (docs), which could suit your use case:
var result = users
.GroupBy(
u => new { u.Id, u.Name }, // key selector
u => u.Hobby, // element selector
(user, hobbies) => new Person {
Id = user.Id,
Name = user.Name,
Hobby = string.Join(", ", hobbies) })
.ToList();
Here, (user, hobbies) are referencing the key selector and the element selector, respectively.
What happens is:
the users are grouped into unique users based on the combination of Id and Name (key selector)
the hobbies of each unique user are collected into an IEnumerable<string> (element selector)
for each unique user, a Person object is created and populated with all of the users hobbies.
First question :)
I have a List<Materiau> (where Materiau implements IComparable<Materiau>), and I would like to remove all duplicates and add them together
(if two Materiau is the same (using the comparator), merge it to the first and remove the second from the list)
A Materiau contains an ID and a quantity, when I merge two Materiau using += or +, it keeps the same ID, and the quantity is added
I cannot control the input of the list.
I would like something like this:
List<Materiau> materiaux = getList().mergeDuplicates();
Thank you for your time :)
Check out Linq! Specifically the GroupBy method.
I don't know how familiar you are with sql, but Linq lets you query collections similarly to how sql works.
It's a bit in depth to explain of you are totally unfamiliar, but Code Project has a wonderful example
To sum it up:
Imagine we have this
List<Product> prodList = new List<Product>
{
new Product
{
ID = 1,
Quantity = 1
},
new Product
{
ID = 2,
Quantity = 2
},
new Product
{
ID = 3,
Quantity = 7
},
new Product
{
ID = 4,
Quantity = 3
}
};
and we wanted to group all the duplicate products, and sum their quantities.
We can do this:
var groupedProducts = prodList.GroupBy(item => item.ID)
and then select the values out of the grouping, with the aggregates as needed
var results = groupedProducts.Select( i => new Product
{
ID = i.Key, // this is what we Grouped By above
Quantity = i.Sum(prod => prod.Quantity) // we want to sum up all the quantities in this grouping
});
and boom! we have a list of aggregated products
Lets say you have a class
class Foo
{
public int Id { get; set; }
public int Value { get; set; }
}
and a bunch of them inside a list
var foocollection = new List<Foo> {
new Foo { Id = 1, Value = 1, },
new Foo { Id = 2, Value = 1, },
new Foo { Id = 2, Value = 1, },
};
then you can group them and build the aggregate on each group
var foogrouped = foocollection
.GroupBy( f => f.Id )
.Select( g => new Foo { Id = g.Key, Value = g.Aggregate( 0, ( a, f ) => a + f.Value ) } )
.ToList();
List<Materiau> distinctList = getList().Distinct(EqualityComparer<Materiau>.Default).ToList();
Given the following simple object
public class Foo {
public int PrimaryKey;
public int ForeignKey;
public bool FlagOne;
public bool FlagTwo;
}
Suppose I have received a IQueryable<Foo>. Generally, if I want to do a count operation on each flag I would do this:
IQueryable<Foo> foos = GetFoos();
var total = foos.Count();
var flagOneTotal = foos.Count(p => p.FlagOne);
var flagTwoTotal = foos.Count(p => p.FlagTwo);
In EF, the above would execute 3 queries in the database. I would like to retrieve all these in a single query.
For grouping, I can do this to execute single query:
var q = from foo in foos
group foo by foo.ForeignKey into g
select new {
ForeignKey = g.Key,
Total = g.Count(),
FlagOneTotal = g.Count(p => p.FlagOne),
FlagTwoTotal = g.Count(p => p.FlagTwo)
};
var list = q.ToList();
But how would I do the same if I want to get the totals for all elements regardless of foreign key in a single query and a single anonymous object ?
In other words, how would I tell .net that all elements in foos need to be considered 1 group so I can do Count operations on them.
This should do the job:
var q = from foo in foos
group foo by 1 into g
select new {
Total = g.Count(),
FlagOneTotal = g.Count(p => p.FlagOne),
FlagTwoTotal = g.Count(p => p.FlagTwo)
};
var list = q.ToList();
Cheers
I have an Entity like this:
public class Category
{
public int classid {get;set;}
public int itemid {get;set;}
public string label {get;set;}
}
So a List produces this JSON (three sizes and three colors
[{"classid":1,"itemid":1,"label":"Small"},
{"classid":1,"itemid":2,"label":"Medium"},
{"classid":1,"itemid":3,"label":"Large"},
{"classid":2,"itemid":1,"label":"Blue"},
{"classid":2,"itemid":2,"label":"Green"},
{"classid":2,"itemid":3,"label":"Red"},
{"classid":3,"itemid":1,"label":"Tee"},
{"classid":3,"itemid":2,"label":"Golf"},
{"classid":3,"itemid":3,"label":"Dress"}]
However the JavaScript client needs something like this myarray[][].label:
[[{"itemid":1,"label":"Small"},
{"itemid":2,"label":"Medium"},
{"itemid":3,"label":"Large"}],
[{"itemid":1,"label":"Blue"},
{"itemid":2,"label":"Green"},
{"itemid":3,"label":"Red"}],
[{"itemid":1,"label":"Tee"},
{"itemid":2,"label":"Golf"},
{"itemid":3,"label":"Dress"}]]
And this is smack dab in the middle of my Linq query.
How would I construct the Linq query to assemble the two dimensional array from the one dimensional array within Linq?
EDIT: Existing Query:
...
CATS = (from myP in myProduct.ProductCategories
select new ProductCategory
{
classid = myP.classid,
itemid = myP.itemid,
label = myP.label
}),
...
EDIT: Getting Closer:
CATS = (from myP in myProduct.ProductCategories
group myP by myP.classid into groups
select new resultClass
{ classid = groups.Key,
opts = groups.Select(x =>
new ProductOption
{ itemid = x.itemid,
label = x.label}) }),
I haven't tested this, but it's familiar territory and should work:
IEnumerable<Category> items = ...;
var groups = items.GroupBy(x => x.classid);
var arrays = groups.Select(x =>
x.Select(y => new { itemid = y.itemid, label = y.label }).ToArray()
).ToArray();
I have a Person class, with Name and AreaID properties.
public class Person
{
public string Name;
public int AreaID;
// snip
}
I have a List<Person> with the potential for hundreds of Person objects in the list.
e.g., 100 Persons with AreaID = 1 and 100 Persons with AreaID = 2
I want to return distinct list of AreaID's and how many Persons have that AreaID.
For example,
AreaID = 1 Persons = 100
AreaID = 2 Persons = 100
Use the GroupBy method.
var list = ...list of Persons...
var areas = list.GroupBy( p => p.AreaID )
.Select( g => new {
AreaID = g.Key,
Count = g.Count()
});
Looks like you want to group by area ID then:
var groups = from person in persons
group 1 by person.AreaID into area
select new { AreaID = area.Key, Persons = area.Count() };
I'm using "group 1" to indicate that I really don't care about the data within each group - only the count and the key.
This is inefficient in that it has to buffer all the results for the sake of grouping - you make well be able to use Reactive LINQ in .NET 4.0 to do this more efficiently, or you could certainly use Push LINQ if you wanted to. Then again, for relatively small datasets it probably doesn't matter :)
Surprisingly nobody advised to override Equals and GetHashCode. If you do so you can do folowing:
List<Person> unique = personList.Distinct();
Or even
List<Person> areaGroup = personList.GroupBy(p => p.AreaID);
List<Person> area1Count = personList.Where(p => p.AreaID == 1).Count();
This gives you more flexibility, - no need in useless anonymous class.
return list.GroupBy(p => p.AreaID)
.Select(g => new { AreaID = g.Key, People = g.Count() });
you could use list.GroupBy(x => x.AreaID);
You can try this:
var groups = from person in list
group person by person.AreaID into areaGroup
select new {
AreaID = areaGroup.Key,
Count = areaGroup.Count()
};
var people = new List<Person>();
var q = from p in people
group p by p.AreaId into g
select new { Id = g.Key, Total = g.Count() };
people.Add(new Person { AreaId = 1, Name = "Alex" });
people.Add(new Person { AreaId = 1, Name = "Alex" });
people.Add(new Person { AreaId = 2, Name = "Alex" });
people.Add(new Person { AreaId = 3, Name = "Alex" });
people.Add(new Person { AreaId = 3, Name = "Alex" });
people.Add(new Person { AreaId = 4, Name = "Alex" });
people.Add(new Person { AreaId = 2, Name = "Alex" });
people.Add(new Person { AreaId = 4, Name = "Alex" });
people.Add(new Person { AreaId = 1, Name = "Alex" });
foreach (var item in q)
{
Console.WriteLine("AreaId: {0}, Total: {1}",item.Id,item.Total);
}
Something like this, perhaps ?
List<Person> persons = new List<Person> ();
persons.Add (new Person (1, "test1"));
persons.Add (new Person (1, "test2"));
persons.Add (new Person (2, "test3"));
var results =
persons.GroupBy (p => p.AreaId);
foreach( var r in results )
{
Console.WriteLine (String.Format ("Area Id: {0} - Number of members: {1}", r.Key, r.Count ()));
}
Console.ReadLine ();
Instead of distinct, use GroupBy, or the more succinct LINQ statement:
var results = from p in PersonList
group p by p.AreaID into g
select new { AreaID=g.Key, Count=g.Count() };
foreach(var item in results)
Console.WriteLine("There were {0} items in Area {1}", item.Count, item.AreaID);
ToLookup() will do what you want.