LINQ - grouping by name to Dictionary<string, List<T>> - c#

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

Related

Casting Nhibernate result into IDictionary<string,int>

I am trying to convert the result of the query into IDictionary
Here string will contain orderId and the int will contain the TradedQuantity
The query below should join three objects Order, OrderRevision and OrderEvent.
1 Order can have many orderRevisions
1 OrderRevision can have many orderEvents
What the query is trying to do is to inner join three objects and get all order objects whose order id matches the list of orderids supplied to it. Then it does a group by based on orderId and gets the latest TradedQuantity from orderEvents object. LatestTradedQuantity will be the TradedQuantityFrom latest OrderEvent. For now the latest orderevent can be regarded as the one that has highest OrderEventId value.
OrderRevision revisionAlias = null;
Order orderAlias = null;
var query =
Session.QueryOver<OrderEvent>()
.JoinAlias(oe => oe.OrderRevision,() => revisionAlias)
.JoinAlias(oe => oe.OrderRevision.Order,() => orderAlias)
.Where(x => x.OrderRevision.Order.SourceSystem.Name.ToLower() == sourceSystem.ToLower())
.WhereRestrictionOn(x => x.OrderRevision.Order.Id).IsIn(orderIds.ToList())
.SelectList(list => list.SelectGroup(x => x.OrderRevision.Order.SourceOrderIdentifier)
.SelectMax(x => x.Id).Select(x => x.TradedQuantity))
.Select(x => new KeyValuePair<string, int?>(x.OrderRevision.Order.SourceOrderIdentifier, x.TradedQuantity)
);
As this query does not do what is supposed to. Could you please help and let me know how the result can be cast into IDictionary?
You have tagged your question with linq-to-nhibernate, so I guess using it instead of queryover would suit you. With Linq, use a sub-query for selecting the "max" order events ids for each order, then query them and project them to a dictionary.
using System.Linq;
using NHibernate.Linq;
...
var orderEventsIdsQuery = Session.Query<OrderEvent>()
.Where(oe => orderIds.Contains(oe.OrderRevision.Order.Id))
.GroupBy(oe => oe.OrderRevision.Order.SourceOrderIdentifier,
(soi, oes) => oes.Max(oe => oe.Id));
var result = Session.Query<OrderEvent>()
.Where(oe => orderEventsIdsQuery.Contains(oe.Id))
.ToDictionary(oe => oe.OrderRevision.Order.SourceOrderIdentifier,
oe => oe.TradedQuantity);
This should do the job. I do not use QueryOver and I will not try to give an answer for doing it with QueryOver.

How can I split a List<T> into two lists, one containing all duplicate values and the other containing the remainder?

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));

C# LINQ getting duplicates from a list

I have a list of objects that have a string, and int and another int.
I want to be able to create a list of all the objects that have a duplicate string.
Here is what I have so far:
MyObject duplicates = allMyObjects.GroupBy(a => a.MyString)
.Where(a => a.Count() > 1)
.ToList();
The error I am getting is that I cannot implicitly convert the type System.Collections.Generic.List<string, MyObject> to MyObject
var duplicates = allMyObjects.GroupBy(a => a.MyString)
.Where(a => a.Count() > 1)
.SelectMany(g=>g)
.ToList();
you need to write
List<MyObject> duplicates = allMyObjects.GroupBy(a => a.MyString)
.Where(a => a.Count() > 1)
.ToList();
You could use ToLookup to make a nice data structure with all the info you need
var objectsByString = allMyObjects.ToLookup(o => o.MyString);
This will return a Lookup<string, MyObject>. You can get the duplicate strings using:
var duplicateStrings = objectsByString.Where(l => l.Count()>1).Select(l => l.Key);
which will return a IEnumerable<string> with the duplicate strings. And, for each duplicate you can access the actual objects that have duplicates using something like this:
string duplicateKey = duplicateStrings.First();
var duplicateObjects = objectsByString[duplicateKey]
which returns a IEnumerable<MyObject> with the items that have that string.
There are several problem, the first is a List-of-MyObject cannot be assigned to MyObject, so let's use var to ignore this for a second.
var duplicates = allMyObjects.GroupBy(a => a.MyString)
.Where(a => a.Count() > 1)
.ToList();
Now, the type of duplicates is List<IGrouping<string, MyObject>> (despite the incorrectly reported error message). Whoops, gotta get rid of (or write to code to account for) the groups!
var duplicates = allMyObjects.GroupBy(a => a.MyString)
.Where(a => a.Count() > 1)
.SelectMany(g => g)
.ToList();
Now the type of duplicates is List<MyObject>, after having selected every ("selected many") object from every group with more than one item. Better, but this still isn't an MyObject. Well, that's fine: fix the declared type of the variable (that var was previously automatically doing)..
List<MyObject> duplicates = /* same as before */;
Or leave var to do it's thing and if an IEnumerable<MyObject> is fine, simply omit the ToList:
var duplicates = allMyObjects.GroupBy(a => a.MyString)
.Where(a => a.Count() > 1)
.SelectMany(g => g);
Go forth and iterate thy duplicates!

Remove duplicates of a List, selecting by a property value in C#?

I have a list of objects that I need some duplicates removed from. We consider them duplicates if they have the same Id and prefer the one whose booleanValue is false. Here's what I have so far:
objects.GroupBy(x => x.Id).Select(x => x.Where(y => !y.booleanValue));
I've determined that GroupBy is doing no such grouping, so I don't see if any of the other functions are working. Any ideas on this? Thanks in advance.
You can do this:
var results =
from x in objects
group x by x.Id into g
select g.OrderBy(y => y.booleanValue).First();
For every Id it finds in objects, it will select the first element where booleanValue == false, or the the first one (if none of them have booleanValue == false).
If you prefer fluent syntax:
var results = objects.GroupBy(x => x.Id)
.Select(g => g.OrderBy(y => y.booleanValue).First());
Something like this should work:
var result =
objects.GroupBy(x => x.Id).Select(g =>
g.FirstOrDefault(y => !y.booleanValue) ?? g.First())
This assumes that your objects are of a reference type.
Another possibility might be to use Distinct() with a custom IEqualityComparer<>.
This partially answers the question above, but I justed need a really basic solution:
objects.GroupBy(x => x.Id)
.Select(x => x.First())
.ToArray();
The key to getting the original object from the GroupBy() is the Select() getting the First() and the ToArray() gets you an array of your objects, not a Linq object.

Ordering list by shared value of property

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();

Categories

Resources