I have a list of objects with field UserID, Property:
I would like to order the list by most shared property value. So if every user has Property= "Popular", that should come up first. If everyone but one user has Property="Second" that should come up second in list...
even if its only used once for each user.
I would do distinct() on each possible Property and but that doesnt seem efficient with many possible Property.
You can use a grouping on Property, order the groups by the number of counts in each group and then flatten the list again using SelectMany():
var items = myList.GroupBy(x => x.Property)
.OrderByDescending(g => g.Count())
.SelectMany(g => g);
.ToList();
From your question its not quite clear to me whether you want duplicates to show up or not and if you are at all interested in the UserID. If not, you can just select the keys of the groups to give you a List<string> of unique Property values in the desired order:
var props = myList.GroupBy(x => x.Property)
.OrderByDescending(g => g.Count())
.Select(g => g.Key);
.ToList();
Edit:
It seems like this would be more what you are actually are looking for - groups are are ordered by the number of unique users that have a given property.
var props = myList.GroupBy(x => x.Property)
.OrderByDescending(g => g.Select(x=> x.UserID)
.Distinct()
.Count())
.Select(g => g.Key);
.ToList();
Related
I have a basic class for an Account (other properties removed for brevity):
public class Account
{
public string Email { get; set; }
}
I have a List<T> of these accounts.
I can remove duplicates based on the e-mail address easily:
var uniques = list.GroupBy(x => x.Email).Select(x => x.First()).ToList();
The list named 'uniques' now contains only one of each account based on e-mail address, any that were duplicates were discarded.
I want to do something a little different and split the list into two.
One list will contain only 'true' unique values, the other list will contain all duplicates.
For example the following list of Account e-mails:
unique#email.com
dupe#email.com
dupe#email.com
Would be split into two lists:
Unique
unique#email.com
Duplicates
dupe#email.com
dupe#email.com
I have been able to achieve this already by creating a list of unique values using the example at the top. I then use .Except() on the original list to get the differences which are the duplicates. Lastly I can loop over each duplicate to 'pop' it out of the unique list and move it to the duplicate list.
Here is a working example on .NET Fiddle
Can I split the list in a more efficient or syntactically sugary way?
I'd be happy to use a third party library if necessary but I'd rather just stick to pure LINQ.
I'm aware of CodeReview but feel the question also fits here.
var groups = list.GroupBy(x => x.Email)
.GroupBy(g => g.Count() == 1 ? 0 : 1)
.OrderBy(g => g.Key)
.Select(g => g.SelectMany(x => x))
.ToList();
groups[0] will be the unique ones and group[1] will be the non-unique ones.
var duplicates = list.GroupBy(x => x) // or x.Property if you are grouping by some property.
.Where(g => g.Count() > 1)
.SelectMany(g => g);
var uniques = list.GroupBy(x => x) // or x.Property if you are grouping by some property.
.Where(g => g.Count() == 1)
.SelectMany(g => g);
Alternatively, once you get one list, you can get the other one using Except:
var uniques = list.Except(duplicates);
// or
var duplicates = list.Except(uniques);
Another way to do it would be to get uniques, and then for duplicates simply get the elements in the original list that aren't in uniques.
IEnumerable<Account> uniques;
IEnumerable<Account> dupes;
dupes = list.Where(d =>
!(uniques = list.GroupBy(x => x.Email)
.Where(g => g.Count() == 1)
.SelectMany(u => u))
.Contains(d));
I am building a library app. I have a list of Books where some of them have a duplicate name (there are few copies of the same book). I would like to convert the list to Dictionary>, where the string would be the name of a book, and the List would contain all the Book objects with this name.
I've managed to get this far:
var result = queriedBooks
.GroupBy(b => b.Name)
.Where(g => g.Count() >= 1)
.ToDictionary(b => b.Key, /// );
This is where I get stuck. I have no idea what to pass as a value. Intellisense does not help either, as there is no Value property available. I would like to avoid using anonymous objects, as each Book entry has many properties which I use in a view.
Thank you very much!
As an alternative you may want just Lookup<String, Book> instead of combersome Dictionary<String, List<Book>>:
LookUp<String, Book> result = queriedBooks
.ToLookup(book => book.Name);
In case of Dictionary<String, List<Book>>:
var result = queriedBooks
.GroupBy(book => book.Name)
.ToDictionary(chunk => chunk.Key, chunk => chunk.ToList());
Please note that .Where(g => g.Count() >= 1) is redundant;
You should simply use ToList() like this:
.ToDictionary(b => b.Key, b => b.ToList());
Each group has a Key property which is the key. It also (the group) is an IEnumerable<Book> that represents the items in the group which is why ToList() works.
try with ToList with distinct for example In a table, a column may contain many duplicate values; and sometimes you only want to list the different (distinct) values.
var query = queriedBooks
.Distinct()
.GroupBy(b => b.Name)
.ToDictionary(b => b.Key, b.ToList() );
please don't use it .Where(g => g.Count() >= 1) it is redundant
I have the following Linq query which selects a distinct list of attributes from all products:
products
.SelectMany(p => p.Attributes)
.Where(a => a.AttributeGroup.IsProductFilter)
.Distinct()
.ToList();
Each attribute is able to be assigned to each product, so I am only wanting a list of attributes where the number of attributes is less than the number of products (as they are used for filtering and there would be no change if the numbers were equal)
I'm not sure how to go about doing this - I thought I need to use GroupBy but wasn't sure how to get a list of attributes back:
IEnumerable<ProductAttribute> attributes = products.SelectMany(p => p.Attributes).Where(a => a.AttributeGroup.IsProductFilter);
return attributes.GroupBy(a => a.ID)
.Where(g => g.Count() < products.Count) // this is now an ienumarable group object so not sure how to get it back to an ienumarable attribute
Or this seemed a bit better
attributes.GroupBy(a => a)
.Where(g => g.Count() < products.Count)
.Select(g => g.ToList())
.Distinct()
.OrderBy(a => a.AttributeGroup.Order) // this doesn't work as a isn't an attribute
It's probably really simple but I'm not that great with Linq so any help solving this would be appreciated
I'm not sure, but doesn't SelectMany helps here too?
return attributes.GroupBy(a => a.ID)
.Where(g => g.Count() < products.Count)
.SelectMany(g => g); // perhaps Distinct after
I have a collection of textboxes in my winform application.
I need help with LINQ query to get the collection of TextBox (i.e. IEnumerable) which contain duplicate entries.I want to make use of LINQ.
This query I used, is returning just the duplicate entry. But I need all the duplicate entries.
var duplicates = emailAddressList.GroupBy(t => t.Text)
.Where(g => !string.IsNullOrEmpty(g.Key))
.SelectMany(grp => grp.Skip(1))
.ToList();
Can any one help where am I going wrong ?
Regards
This query I used, is returning just the duplicate entry. But I need
all the duplicate entries.
Check if g.Count() > 1 and use SelectMany(g => g) to get all of each duplicate-group instead of only the duplicates (without first).
var duplicates = emailAddressList
.GroupBy(t => t.Text)
.Where(g => !string.IsNullOrEmpty(g.Key) && g.Count() > 1)
.SelectMany(g => g)
.ToList();
I have a table that has the following 3 columns, ID, ShortCode, UploadDate.
I want to use LINQ to group the results by shortcode (and keep all the results) then order those groups and return a list.
I have the following:
rawData.Provider.CreateQuery<PDFDocument>(qb.rootExperession)
.ToList<PDFDocument>().
GroupBy(b=>b.ShortCode)
.SelectMany(b=>b).ToList<PDFDocument>()
I want to return all results, grouped by ShortCode, the items within each group sorted by UploadDate and the groups sorted so the one that has the most recent document in it first.
Does anyone know if this is even possible?
Try
rawData.Provider.CreateQuery<PDFDocument>(qb.rootExperession)
.AsEnumerable()
.OrderByDescending(d => d.UploadDate)
.GroupBy(d => d.ShortCode)
.SelectMany(g => g)
.ToList();
This should
Order the items by upload date (descending so newest first)
Then group them by short code - so within each group the items are still sorted
The groups are still in descending order, so no need to order again
Finally concatenate the results into a single list
If performance is an issue you many be better off doing
rawData.Provider.CreateQuery<PDFDocument>(qb.rootExperession)
.AsEnumerable()
.GroupBy(d => d.ShortCode)
.Select(g => g.OrderByDescending(d => d.UploadDate))
.OrderByDescending(e => e.First().UploadDate)
.SelectMany(e => e)
.ToList();
which sorts the contents of each group separately rather than sorting everything first and then grouping.
In fact, you don't want to group by short code, you want to order by them. So the following query should do the trick:
rawData.Provider.CreateQuery<PDFDocument>(qb.rootExperession)
.ToList()
.OrderBy(b => b.ShortCode)
.ThenBy(b => b.UploadDate)
.ToList()
Edit
If you really want to use a GroupBy, you can do so this way:
rawData.Provider.CreateQuery<PDFDocument>(qb.rootExperession)
.ToList()
.GroupBy(b => b.ShortCode)
.SelectMany(grouping => grouping.OrderBy(b => b.UploadDate))
.ToList()
But I discourage it. There is no point creating groups if you do not want groups in the first place!
Second edit
I did not get you wanted the groups ordered by UpdateTime too. It complicates a little the query:
rawData.Provider.CreateQuery<PDFDocument>(qb.rootExperession)
.ToList()
.GroupBy(b => b.ShortCode)
.Select(grouping => grouping.OrderByDescending(b => b.UploadDate))
.OrderByDescending(grouping => grouping.First().UploadDate)
.SelectMany(grouping => grouping)
.ToList()