How to get distinct List from a custom list? - c#

I am using c# framework 3.5 ..
my class here
public class KonumBilgisi
{
public string Enlem { get; set; }
public string Boylam { get; set; }
public string KonumAdi { get; set; }
public DateTime Tarih { get; set; }
public byte SucTuruId { get; set; }
}
I have a list
List konumlar;
well, I want to get items that equal their enlem and boylam variables each other..
As you see on the photo below
I want to compate enlem and boylam and if its the equal I want to get them to different list..
I can do that with a loop but want to use LINQ but i couldnt do that. I used groupby but it doesnt wrong..
var distinctList = konumlar.GroupBy(x => x.Enlem)
.Select(g => g.First())
.ToList().GroupBy(s=>s.Boylam).Select(g => g.First())
.ToList();
EDIT
Actually I couldnt explain my quesion well..
maybe distinct is not right word.. I want to seperate items which are equals each other..
such as:
I will take pendik items in one list
and others will be in konumlar but pendik items will be removed from konumlar list
EDIT 2
Okay I want to seperate the list like that

You are almost there - rather than using two separate GroupBy calls, use a single one, with a two-part key:
var distinctList = konumlar
.GroupBy(s => new {s.Enlem, s.Boylam})
.Select(g => g.First())
.ToList();
EDIT : To get all items except the ones with duplicates, modify the query as follows:
var noPendiks = konumlar
.GroupBy(s => new {s.Enlem, s.Boylam})
.Where(g => g.Count() == 1)
.Select(g => g.Single()) // You know there's only one
.ToList();
The above will give you all items except the "pendik" ones. To get only the "pendik"s, use the query below:
var pendiks = konumlar
.GroupBy(s => new {s.Enlem, s.Boylam})
.Where(g => g.Count() > 1)
.SelectMany(g => g)
.ToList();

You can use the Distinct() Linq function, however, that does only work identical items. In case you want a DistinctBy() you can create a LinqExtensions class with a DistinctBy() method.
Here's one which I use quite common:
/// <summary>
/// Provides common extension methods on LINQ members.
/// </summary>
public static class LinqExtensions
{
#region Members
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
HashSet<TKey> seenKeys = new HashSet<TKey>();
foreach (TSource element in source)
{
if (seenKeys.Add(keySelector(element)))
{
yield return element;
}
}
}
#endregion Members
}
Use it as:
var distinctList = konumlar.DistinctBy(x => x.property && x.property2 && ...);
Kind regards

I assume this is what you want:
List<KonumBilgisi> distinctList = konumlar
.GroupBy(k => new { k.Enlem, k.Boylam })
.SelectMany(x => x.GroupBy(k => k.Boylam).First())
.ToList();
It select unique items according those two properties, then it select unique items according Boylam.

Related

How to Iterate over grouped items and compare them?

I'm having a hard time with this approach since I am new to Entity Framework. I actually don't know if there is something special related to EF or if the limitation is on me.
I would like to group some records from my database and after that, I'd like to iterate over the groups, then iterate over the elements on each group comparing it with all the other elements in the same group.
I have created two simple classes to illustrate the scenario:
public class MyContext : DbContext
{
public DbSet<MyClass> MyClass { get; set; }
}
And:
public class MyClass
{
public int Id { get; set; }
public int Value { get; set; }
}
What I have so far with a context injected is:
this.MyContext.MyClass
.GroupBy(x => x.Value)
.ToList() // need to materialize here
.ForEach(grp =>
{
// compare each item with all the other
// items in the current group
});
But I don't know how to iterate over the items and then compare with the others in the same group.
With the following code, the quesiton becomes what type is grp?
this.MyContext.MyClass
.GroupBy(x => x.Value)
.ToList() // need to materialize here
.ForEach(grp =>
{
// compare each item with all the other
// items in the current group
});
Well the grp variable is of type IGrouping<TKey, TElement>. That type derives from IEnumerable<TElement> so each grp is a list of TElement so you can foreach or do whatever you want to all the items in the grp.
DotNetFiddle Example.
Your variable grp is an IGrouping<int, MyClass>. You can treat it as an IEnumerable<MyClass>. For instance, you can get the item with the biggest Id like this:
this.MyContext.MyClass
.GroupBy(x => x.Value)
.ToList() // need to materialize here
.ForEach(grp =>
{
MyClass itemWithMaxId = grp.FirstOrDefault();
foreach (MyClass item in grp)
{
if (item.Id > itemWithMaxId.Id)
{
itemWithMaxId = item;
}
}
});
Note, however, that the ForEach method does not return anything, it only performs the specified action on each element of the list. If you want to get something, for instance the item with the biggest Id of each group, I suggest you to use the Select method provided by Linq, like in this example:
var itemsWithMaxIdByGroup = this.MyContext.MyClass
.GroupBy(x => x.Value)
.ToList() // need to materialize here
.Select(grp =>
{
MyClass itemWithMaxId = grp.First();
foreach (MyClass item in grp.Skip(1))
{
if (item.Id > itemWithMaxId.Id)
{
itemWithMaxId = item;
}
}
return itemWithMaxId;
});

Selecting both parent and child items when querying child properties

In my data structures I have the following classes:
public partial class Item
{
// stuff
public int QuoteId { get; set; }
public virtual ItemType ItemType { get; set; }
}
public partial class ItemType
{
//stuff
public virtual ICollection<Item> Items { get; set; }
}
What I want to do is get a list of all the ItemTypes, each of which has its Items collection populated according to a QuoteId.
So, for example if there are three item types, only two of which have items with a quote Id of 50:
ItemType1
Item.QuoteId == 50
ItemType2
ItemType3
Item.QuoteId == 50
I've managed to get something close with this query:
r.ItemTypes.Select(x => x.Items.Where(i => i.QuoteId == CurrentQuote.QuoteId));
But what this gives you (as you might expect, since I'm Selecting on Item) is an IEnumerable<IEnumerable<Item>>. This has the structure that I'm after but doesn't have the ItemType data.
I realise this is a dumb question, but I'm frustrated by my inability to get the answer.
r.ItemTypes.Where(x => x.Items.Any(i => i.QuoteId == CurrentQuote.QuoteId));
If you need to get all ItemTypes and only specific Items for every, you can do this:
r.ItemTypes.Select(x => new
{
x,
FilteredItems = x.Items.Where(i => i.QuoteId == CurrentQuote.QuoteId)
});
After that you need to assign x.Items to FilteredItems for every ItemType
You have to select the Item.ItemType property if you want the all ItemTypes of a given QuoteId. You also have to use SelectMany to flatten the "nested" collections:
IEnumerable<ItemType> types = r.ItemTypes
.SelectMany(x => x.Items.Where(i => i.QuoteId == CurrentQuote.QuoteId)
.Select(i => i.ItemType));
If you are not interested in the nested ItemType(don't know the logic) you can use Backs' approach:
IEnumerable<ItemType> types = r.ItemTypes
.Where(x => x.Items.Any(i => i.QuoteId == CurrentQuote.QuoteId));
var result = from itemTypes in r.ItemTypes
where itemTypes.QuoteId equals CurrentQuote.QuoteId
select new {itemType = itemTypes}

Distinct by product id

Below is my class.
public class MyGroceryListItems
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
}
Now I am fetching from database the values for this class and putting those classes in IList<MyGroceryListItems>. However, there are some duplicate items with same id but different quantity. How can I fetch only distinct entries by id?
I tried list.distinct but that didn't work because not all the entries are same in the record having same product id.
IEnumerable<MyGroceryListItems> items = ...;
var uniqueItemsByProdId =
items.GroupBy(x => x.ProductId).Select(g => g.First());
This will pick off a single (and somewhat arbitrary) item in the case that more than a item shares a ProductId with another.
Alternatively (and slightly faster), you could use a DistinctBy extension:
public static IEnumerable<T>
DistinctBy<T,TKey>(this IEnumerable<T> src, Func<T,TKey> selector)
{
HashSet<TKey> hs = new HashSet<TKey>();
foreach(var item in src)
{
//Add returns false if item is already in set
if(hs.Add(selector(item)))
{
yield return item;
}
}
}
like this:
items.DistinctBy(x => x.ProductId)
More useful, perhaps, is a query that gives the aggregate quantities for each item by ProductId:
items
.GroupBy(x => x.ProductId)
.Select(g => new MyGroceryListItems{
g.Key.ProductId,
g.Key.ProductName,
Quantity = g.Sum(gg => gg.Quantity)
})
You can implement an equality comparer. There is a solid example on msdn: http://msdn.microsoft.com/ru-ru/library/bb338049.aspx
This will give you more control over which items you consider equal. But involves more coding. If you want to select single and somewhat random item from all the items which share an id, then you'll be better off with spender's solution

Linq sorting, grouping, and returning a dictionary

I have an array of custom objects (PONO) called FrameList, I can orderBy and GroupBy to sort and group. But now I want to return a dictionary, and I am getting the error:
(BTW, there is no error is I remove the .ToDictionary() method. )
Error 1 'System.Linq.IGrouping' does
not contain a definition for 'type' and no extension method 'type'
accepting a first argument of type
'System.Linq.IGrouping' could be found
(are you missing a using directive or an assembly
reference?) C:\Users\ysg4206\Documents\Visual Studio
2010\Projects\Watson\CatalogServiceClasses\BuildToby.cs 21 38 Watson
Here is the snippet that sorts and groups:
var dict = request.FrameList.OrderBy(f => f.trueDate)
.GroupBy(f => f.type)
.ToDictionary(f => f.type, f => f);
and here is the definition of the FrameList (which is a simple array of FrameData)
[DataContract]
public class FrameData
{
[DataMember]
public String idx { get; set; }
[DataMember]
public String type { get; set; }
[DataMember]
public String path { get; set; }
[DataMember]
public String date { get; set; }
[DataMember]
public DateTime trueDate { get; set; }
}
It's not clear what you're trying to do.
If you're just looking to create an idiomatic multi-map, try:
var lookup = request.FrameList.ToLookup(f => f.type);
If you're looking to create a multi-map with the Dictionary class, try:
var dict = request.FrameList
.GroupBy(f => f.type)
.ToDictionary(g => g.Key, g => g.ToList());
Do note that in both of these cases, OrderBy before grouping is useless since Lookup and Dictionary are inherently unordered collections. If you're trying to accomplish something with your ordering (perhaps you want items within a group to be ordered by date?), please clarify, and we might be able to provide something more appropriate.
EDIT: Based on your comment, looks like you need:
var dict = request.FrameList
.GroupBy(f => f.type)
.ToDictionary(g => g.Key,
g => g.OrderBy(f => f.trueDate)
.ToList());
Assuming your method signature is IDictionary<string,IList<FrameData>>
Try to use next code snippet:
var dict = request.FrameList
.OrderBy(f => f.trueDate)
.GroupBy(f => f.type)
.ToDictionary(f => f.Key, f => f.ToList());
GroupBy operator returns IGrouping objects. Those objects have Key property. But there is no type property on grouping object (its property of your entity):
var dict = request.FrameList
.OrderBy(f => f.trueDate)
.GroupBy(f => f.type) // this selects IEnumerable<IGrouping>
.ToDictionary(g => g.Key, g => g);
NOTE: If this is a Linq to Entities query, then GroupBy returns IQueryable<IGrouping<TKey, TElement>>

How to get values from IGrouping

I have a question about IGrouping and the Select() method.
Let's say I've got an IEnumerable<IGrouping<int, smth>> in this way:
var groups = list.GroupBy(x => x.ID);
where list is a List<smth>.
And now I need to pass values of each IGrouping to another list in some way:
foreach (var v in structure)
{
v.ListOfSmth = groups.Select(...); // <- ???
}
Can anybody suggest how to get the values (List<smth>) from an IGrouping<int, smth> in such a context?
Since IGrouping<TKey, TElement> implements IEnumerable<TElement>, you can use SelectMany to put all the IEnumerables back into one IEnumerable all together:
List<smth> list = new List<smth>();
IEnumerable<IGrouping<int, smth>> groups = list.GroupBy(x => x.id);
IEnumerable<smth> smths = groups.SelectMany(group => group);
List<smth> newList = smths.ToList();
Here's an example that builds/runs: https://dotnetfiddle.net/DyuaaP
Video commentary of this solution: https://youtu.be/6BsU1n1KTdo
foreach (var v in structure)
{
var group = groups.Single(g => g.Key == v. ??? );
v.ListOfSmth = group.ToList();
}
First you need to select the desired group. Then you can use the ToList method of on the group. The IGrouping is a IEnumerable of the values.
More clarified version of above answers:
IEnumerable<IGrouping<int, ClassA>> groups = list.GroupBy(x => x.PropertyIntOfClassA);
foreach (var groupingByClassA in groups)
{
int propertyIntOfClassA = groupingByClassA.Key;
//iterating through values
foreach (var classA in groupingByClassA)
{
int key = classA.PropertyIntOfClassA;
}
}
From definition of IGrouping :
IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable
you can just iterate through elements like this:
IEnumerable<IGrouping<int, smth>> groups = list.GroupBy(x => x.ID)
foreach(IEnumerable<smth> element in groups)
{
//do something
}
var groups = list.GroupBy(x => x.ID);
Can anybody suggest how to get the values (List) from an IGrouping<int, smth> in such a context?
"IGrouping<int, smth> group" is actually an IEnumerable with a key, so you either:
iterate on the group or
use group.ToList() to convert it to a List
foreach (IGrouping<int, smth> group in groups)
{
var thisIsYourGroupKey = group.Key;
List<smth> list = group.ToList(); // or use directly group.foreach
}
If you have an IGrouping<GroupItem, ListItem>, and you want to access the items of type ListItem of this group without utilizing a foreach loop, it's very simple. The object of type IGrouping<GroupItem, ListItem> is of type IEnumerable<ListItem> as well, as it is defined as:
public interface IGrouping<out TKey, out TElement> : IEnumerable<TElement>, IEnumerable
So you can simply say:
foreach (IGrouping<GroupItem, ListItem> group in list.GroupBy(x => x.ID))
{
IEnumerable<ListItem> itemsInThisGroup = group;
// ...
}
If for some reason, it has to be a List<T> instead of an IEnumerable<T>, you can of course still call itemsInThisGroup.ToList(). But usually it's better not to if you needn't.
Simply do this:
// this will "split" the list into groups
var groups = list.GroupBy(x => x.ID);
// groups is a "collection of lists"
foreach (var sublist in groups)
{
// now the sublist is only a part of the original list
// to get which is the value of ID, you can use sublist.Key
}
You don't need Select().GroupBy(expr) makes "a list of lists", kind of.
Assume that you have MyPayments class like
public class Mypayment
{
public int year { get; set; }
public string month { get; set; }
public string price { get; set; }
public bool ispaid { get; set; }
}
and you have a list of MyPayments
public List<Mypayment> mypayments { get; set; }
and you want group the list by year. You can use linq like this:
List<List<Mypayment>> mypayments = (from IGrouping<int, Mypayment> item in yearGroup
let mypayments1 = (from _payment in UserProjects.mypayments
where _payment.year == item.Key
select _payment).ToList()
select mypayments1).ToList();

Categories