c# list comparer use two compare elements - c#

private class CompAdvertisements : IComparer<Advertisements>
{
private string OrderBy { get; set; }
public CompAdvertisements(string orderBy)
{
OrderBy = orderBy;
}
#region IComparer<Advertisements> Members
public int Compare(Advertisements x, Advertisements y)
{
return x.Country.Name.CompareTo(y.Country.Name);
Can i also user x.Name.CompareTo(y.Name); in comparer that i will compare with two elements lik order by something and order by something2

Yes. If the outer comparison indicates that your two elements (Country.Name) are the same, then you instead return the result of an inner comparison (somethingElse). You can do that for an arbitrary depth of comparisons.
outerCompare = x.Country.Name.CompareTo(y.Country.Name);
if (outerCompare != 0)
{
return outerCompare;
}
else
{
return (x.Name.CompareTo(y.Name));
}

Eric J. is right. You may also want to have a look at this stackoverflow question. The answers there give several ways you can sort a List, and they also go into detail about using an IComparer object to perform a similar task to what you're doing.

Related

remove duplicate from list in object c#

I'm desperately trying to delete all the items with a list of the same value inside.
Here's the code:
private void Button_deleteDouble_MouseDown(object sender, EventArgs e)
{
boardGenerate.Add(new BoardInformation(146, new List<string> { "test" }));
boardGenerate.Add(new BoardInformation(545, new List<string> { "test" }));
boardGenerate = boardGenerate.DistinctBy(x => x.positionQueen).ToList();
}
Normally, since the two lists inside the object are the same, the .DistinctBy() command should remove one of the two objects.
But no, my object list still has the same two objects with the same list
.positionQueen is the name of the variable containing the list
Could somebody help me?
Edit :
The DistinctBy() method comes from MoreLinq.
And this is my BoardInformation class:
public class BoardInformation
{
public BoardInformation(int nbQueen, List<string> positionQueen)
{
this.nbQueen = nbQueen;
this.positionQueen = positionQueen;
}
public int nbQueen { get; set; }
public List<string> positionQueen { get; set; }
}
Set-based operations like Distinct and DistinctBy need a way of determining whether two values are the same. You're using DistinctBy, so you're already asking MoreLINQ to compare the "inner lists" for equality - but you're not saying how to do that.
List<T> doesn't override Equals or GetHashCode, which means it inherits the reference equality behaviour from System.Object. In other words, if you create two separate List<T> objects, they won't compare as equal, even if they have the same content. For example:
List<int> list1 = new List<int>();
List<int> list2 = new List<int>();
Console.WriteLine(list1.Equals(list2)); // False
You need to tell DistinctBy how you want to compare the two lists, using an IEqualityComparer<T> - where T in this case is List<string> (because that's the type of BoardInformation.positionQueen.
Here's an example of a generic ListEqualityComparer you could use:
using System;
using System.Collections.Generic;
using System.Linq;
public sealed class ListEqualityComparer<T> : IEqualityComparer<List<T>>
{
private readonly IEqualityComparer<T> elementComparer;
public ListEqualityComparer(IEqualityComparer<T> elementComparer) =>
this.elementComparer = elementComparer;
public ListEqualityComparer() : this(EqualityComparer<T>.Default)
{
}
public bool Equals(List<T> x, List<T> y) =>
ReferenceEquals(x, y) ? true
: x is null || y is null ? false
// Delegate to LINQ's SequenceEqual method
: x.SequenceEqual(y, elementComparer);
public int GetHashCode(List<T> obj)
{
if (obj is null)
{
return 0;
}
// Just a very simple hash implementation
int hash = 23;
foreach (var item in obj)
{
hash = hash * 31 +
(item is null ? 0
: elementComparer.GetHashCode(item));
}
return hash;
}
}
You'd then pass that to DistinctBy, like this:
// We're fine to use the default *element* comparer (string.Equals etc)
var comparer = new ListEqualityComparer<string>();
boardGenerate = boardGenerate.DistinctBy(x => x.positionQueen, comparer).ToList();
Now DistinctBy will call into the comparer, passing in the lists, and will consider your two BoardInformation objects are equal - so only the first will be yielded by DistinctBy, and you'll end up with a list containing a single item.
It comes down to whether a equality check is using referential equality or value equality...you want value equality based on a specific property and that has to be done by hand.
When there is no IEqualityComparer provided which can used to compare individual objects (which is need by the Distinct call), the system determines the equality from each item's references by using their derived object low level service method call of GetHashCode from each reference; hence a reference difference is done and all your values in the list are unique (not equal) regardless of similar property values.
What you are looking for is to have value equality checked specifically for the nbQueenProperty.
To fully utilize Distinct one must create a IEqualityComparer and modify the GetHashCode. By specifing the hash value which can make objects equal...you can weed out the same positionQueen (or other properties) instances out.
Example
public class MyClass
{
public string Name { get; set; }
public int nbQueen { get; set; }
}
Equality comparer to weed out all nbQueen similarities:
class ContactEmailComparer : IEqualityComparer < MyClass >
{
public bool Equals(MyClass x, MyClass y)
{
return x.nbQueen.Equals(y.nbQueen); // Compares by calling each `GetHashCode`
}
public int GetHashCode(MyClass obj)
{
return obj.nbQueen.GetHashCode(); // Add or remove other properties as needed.
}
}
Test code
var original = new List<MyClass>()
{
new MyClass() { nbQueen = 1, Name="Alpha" },
new MyClass() { nbQueen = 1, Name="Omega" },
new MyClass() { nbQueen = 3, Name="Delta" }
};
IEqualityComparer<MyClass> comparer = new ContactEmailComparer();
var newOne = original.Distinct( comparer ).ToList();
Result of the value of newOne :
To be clear...
... .DistinctBy() command should remove one of the two objects.
Does not remove anything. It returns a reference to a new list that should be distinct via the equality operation. The original list (the reference to it) does not change.
LINQ solution
because you have another List inside your class you can not use District or DistrictBy, alternatively, you can use LINQ to filter the list.
boardGenerate = (from b in boardGenerate
from l in b.positionQueen
group new { l,b } by l into g
select g.First().b
).ToList();
// this returns just first duplicate item like district

CollectionViewSource sort algorithm

How to change the sort algorithm of CollectionViewSource? In fact, i found that the sort algorithm of CollectionViewSource is not stable. And i want to use a stable algorithm on the CollectionViewSource. How can i do that?
I've managed to get a stable sorting using a custom Comparer, but it kinda feels like a big hack...
Just like Benjamin suggested, I get the ListCollectionView from the collection and set its CustomSort property with my custom Comparer. The only difference is that I pass the collection to the Comparer when instantiating it.
private void Sorting(IEnumerable collection)
{
var view = CollectionViewSource.GetDefaultView(collection) as ListCollectionView;
if (view != null)
{
view.CustomSort = new StableComparer(collection);
}
}
Then, in my custom Comparer, I use the collection in the Compare method just to fallback to the items indexes when the comparison returns a zero (they are the same or have the same value).
public class StableComparer : IComparer
{
public IEnumerable Collection { get; set; }
public StableComparer(IEnumerable collection)
{
Collection = collection;
}
public int Compare(object x, object y)
{
IComparable x_Comparable = x as IComparable;
IComparable y_Comparable = y as IComparable;
if (x_Comparable != null && y_Comparable != null)
{
var comparison = x_Comparable.CompareTo(y_Comparable);
// A zero value means x and y are equivalent for sorting, and they could
// be rearranged by an unstable sorting algorithm
if (comparison == 0 && Collection != null)
{
// IndexOf is an extension method for IEnumerable (not included)
var x_Index = Collection.IndexOf(x);
var y_Index = Collection.IndexOf(y);
// By comparing their indexes in the original collection, we get to
// preserve their relative order
if (x_Index != -1 && y_Index != -1)
comparison = x_Index.CompareTo(y_Index);
}
return comparison;
}
return 0;
}
}
I'm still testing this, so I can't guarantee this would work all the time... One problem would be keeping the Collection property inside the Comparer updated, for instance. Or supporting two sort directions.
But I think the idea is clear, though hacky, as I said.
You might want to check out how to implement your custom sorting logic.
In short, set your comparer like this:
private void Sort(object sender, RoutedEventArgs args)
{
BlogPosts posts = (BlogPosts)(this.Resources["posts"]);
ListCollectionView lcv = (ListCollectionView)(CollectionViewSource.GetDefaultView(posts));
lcv.CustomSort = new SortPosts();
}
And implement it like this:
public class SortPosts : IComparer
{
public int Compare(object x, object y)
{
(…)
}
}

Compare generic Collection?

I have a
ObservableCollection<BasicClass> allCollection;
ObservableCollection<BasicClass> selectedCollection;
where
BasicClass
{
public Name {get;set;}
public Age {get;set;}
}
Now I added many BasicClass items to allCollection and only selected BasicClass to selectedCollection
SomeWhere I want to add items in selectedCollection which are not there in allCollection.
I tried this
foreach(var a in allCollection)
{
foreach(var s in selectedCollection)
if(a.Name!=s.Name)
//selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
But the problem is that this code is adding new BasicClass for each and every unmatched name,
but my actuall requirement is, for each Name of allCollection compare all selectedCollection items. If it is not there then add else move for next Item.
LINQ solution could help this? Actually I achieved this by more if and flags but That looks ver hectic.
My traditional solution
foreach(var a in allCollection)
{
bool same = false;
foreach(var s in selectedCollection)
if(a.Name==s.Name)
same=true;
}
if(same==false)
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
And I hate this..
EDIT:
I don't want compare collection to collection.
I want to compare collection1 value to collection2 all values, and if it not there then I want to add
Are you sure you don't just need this?
foreach(var a in allCollection)
{
if (!selectedCollection.Contains(a))
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
EDIT
I've just seen your comment below about matching on name only, so the above is not really what you want:). Try this approach instead:
foreach(var a in allCollection)
{
if (!selectedCollection.Any(s => a.Name == s.Name))
{
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
}
EDIT
As Chris suggested you could also use "Except" to create a collection. I'm not sure this gains much, it may be quicker but it involves writing the comparer code and creates a new temporary collection. However, it is pretty succinct E.g. Once you had the comparaer written you would just need this to add your missing items to the collection:
selectedCollection.Concat(allCollection.Except(selectedCollection));
So basically you need a 'where-not-in'? Linq->Except is the way to go, to filter on BasicClass.name only implement the IEqualityComparer for Except.
I'm not sure I understood your requirements correctly, so i may be missing the point...
Your BasicClass should implement the IEquatable<BasicClass> interface, so that two instances of BasicClass can be compared for equality:
class BasicClass : IEquatable<BasicClass>
{
public Name {get;set;}
public Age {get;set;}
public bool Equals(BasicClass other)
{
if (other == null)
return false;
return string.Equals(this.Name, other.Name);
}
public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode();
}
}
Now you can use the Except method to find items that are in allCollection but not in selectedCollection:
BasicClass[] notSelected = allCollection.Except(selectedCollection).ToArray();
foreach(BasicClass item in notSelected)
{
selectedCollection.Add(item);
}
Alternatively, you can implement a IEqualityComparer<BasicClass> and pass it to Except (instead of implementing IEquatable<BasicClass> in BasicClass)
You're right, this is more easily accomplished with Linq:
var itemsToAdd = allCollection.Except(selectedCollection);
foreach (var item in itemsToAdd)
selectedCollection.Add(item);
On the other hand, this is just going to make both lists contain the exact same items. Sure this is what you want?
If BasicItem overrides 'Equals' and 'GetHashCode' based off of Name, then this is all you need. If it doesn't, then you will also need to implement an IEqualityComparer:
//Allows us to compare BasicItems as if Name is the key
class NameComparer: IEqualityComparer<BasicItem>
{
public bool Equals(BasicItem first, BasicItem second)
{
return first.Name == second.Name;
}
public int GetHashCode(BasicItem value)
{
return value.Name.GetHashCode;
}
}
You now pass an instance of this class to Except:
var itemsToAdd = allCollections.Except(selectedCollection, new NameComparer());

How to get a distinct list from a List of objects?

I have a List<MyClass> someList.
class MyClass
{
public int Prop1...
public int Prop2...
public int Prop3...
}
I would like to know how to get a new distinct List<MyClass> distinctList from List<MyClass> someList, but only comparing it to Prop2.
You can emulate the effect of DistinctBy using GroupBy and then just using the first entry in each group. Might be a bit slower that the other implementations though.
someList.GroupBy(elem=>elem.Prop2).Select(group=>group.First());
Unfortunately there's no really easy built-in support for this in the framework - but you can use the DistinctBy implementation I have in MoreLINQ.
You'd use:
var distinctList = someList.DistinctBy(x => x.Prop2).ToList();
(You can take just the DistinctBy implementation. If you'd rather use a Microsoft implementation, I believe there's something similar in the System.Interactive assembly of Reactive Extensions.)
you need to use .Distinct(..); extension method.
Here's a quick sample:
public class Comparer : IEqualityComparer<Point>
{
public bool Equals(Point x, Point y)
{
return x.X == y.X;
}
public int GetHashCode(Point obj)
{
return (int)obj.X;
}
}
Do not forget about GetHashCode.
Usage:
List<Point> p = new List<Point>();
// add items
p.Distinct(new Comparer());
Override Equals(object obj) and GetHashCode() methods:
class MyClass
{
public int Prop1 { get; set; }
public int Prop2 { get; set; }
public int Prop3 { get; set; }
public override bool Equals(object obj)
{
return ((MyClass)obj).Prop2 == Prop2;
}
public override int GetHashCode()
{
return Prop2.GetHashCode();
}
}
and then just call:
List<MyClass> distinctList = someList.Distinct().ToList();
Since the introduction of value tuples, if you want a LINQ equivalent to SQL's DISTINCT
items.GroupBy(item => (item.prop1, item.prop2, ...)).Select(group => group.First())
If you would like to Distinct your list by multiple fields, You have to create an instance of IEqualityComparer interface:
public class MyComparer : IEqualityComparer<MyModel>
{
public bool Equals(MyModel x, MyModel y)
{
// compare multiple fields
return
x.Field1 == y.Field1 &&
x.Field2 == y.Field2 &&
x.Field3 == y.Field3 ;
}
public int GetHashCode(MyModel obj)
{
return
obj.Field1.GetHashCode() +
obj.Field2.GetHashCode() +
obj.Field3.GetHashCode();
}
}
Then use the comparer to distinct your list:
var distinctedList = myList.Distinct(new MyComparer()).ToList();
I know it's been a while, but I needed the simplest answer and at this time (with .NET 4.5.1) I found the following to be the most straight-forward answer I could get to:
IEnumerable<long> allIds = waitingFiles.Values.Select(wf => wf.groupId).Distinct();
My situation is that I have a ConcurrentDictionary that looks something like:
ConcurrentDictionary<long, FileModel>
The ConcurrentDictionary Values property is basically my List<FileModel>.
*FileModel has a groupId that isn't necessarily unique (though, obviously the key (long) that I use to add the FileModel object into the dictionary is unique to the FileModel).
*Named for clarity in the example.
The point is that I have a large number of FileModels (imagine 100) in the ConcurrentDictionary and within those 100 FileModels there are 5 different groupIds.
At this point I just need a list of the distinct groupId.
So, again if I just had a list of FileModel the code would look like the following:
IEnumerable <long> allIds = allFileModel.Select(fm => fm.groupId).Distinct();

How to sort on the Value of an member of a List<> within a parent List<>

I have a List sort question. I am using c# 3.0 and a generic List structure like this:
public class myObject
{
public int ID { get; set; }
public List<mySetting> setting { get; set; }
}
public class mySetting
{
public int ID { get; set; }
public string Name { get; set; }
public string Value { get; set; } // sort on this!
}
with this structure, I am filling a List of myObject with a LINQ query.
List<myObject> lmo = new List<myObject>();
lmo.SomeFillOperation():
What I want to do now is sort the entire List<myObject> on the individual <mySetting>[].Value values. EDIT: ( So this would be sorting on one keyed index of , for example mySetting[3].Value). I realize I could possibly do it in my SomeFillOperation(), but I want to do it after the List is formed.
Is there a recommended or easy way to do this? Is there a good example you have seen? Thanks in advance!
Well, List<T> already has a Sort method if you want to sort it in place - or you could use LINQ's OrderBy method. OrderBy is slightly easier than Sort:
var sorted = lmo.OrderBy(x => x.Value);
but even Sort isn't too bad:
lmo.Sort((x, y) => x.Value.CompareTo(y.Value));
EDIT: Having read the comment to the question, I no longer understand the question! Leaving this answer here as a potentially useful placeholder while I have dinner...
int MyObjectComparison(MyObject x, MyObject y)
{
return x.setting[0].Value.CompareTo(y.setting[0].Value);
}
lmo.Sort(MyObjectComparison);
Of course, this assumes that you want to use the Value of the first element in setting (and that setting is guarunteed to have at least one element). Solution with less assumption will be forthcoming when more info is given.
Since you are using 3.0, use LINQ:
var newList = lmo.OrderBy(i => i.Value);
and
var newList = lmo.OrderByDescending(i => i.Value);

Categories