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)
{
(…)
}
}
Related
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
I have a form responsible of creating (and saving) new Patients. On this form I am using an ErrorProvider to show error icons on invalid fields (in this case just "LastName"). So, as usual => errorProvider.DataSource = patient;
Everything works fine when my model uses default GetHashCode(). But when I try to override this method using a custom hash code (I want to use this model with ISet collections) the control does not work properly. Now, I understand that custom hash codes should be used just for immutable objects. But the point is, how can I fill the fields of these objects if the ErrorProvider behaviour relays on GetHashCode to work properly? Is it necessary to implement a Dirty mechanism that switches between default hash code (during object initialization) and custom hash?
Code sample:
public class Patient : IDataErrorInfo, INotifyPropertyChanged
{
public string lastName;
public virtual string LastName
{
get { return lastName; }
set
{
if (lastName == value) return;
lastName = value;
NotifyPropertyChanged("LastName");
}
}
#region IDataErrorInfo Members
string IDataErrorInfo.Error { get { return null; } }
string IDataErrorInfo.this[string propertyName]
{
get { return this.GetValidationError(propertyName); }
}
#endregion // IDataErrorInfo Members
protected string GetValidationError(string propertyName)
{
if (ValidatedProperties.IndexOf(propertyName) < 0)
return null;
string error = null;
switch (propertyName)
{
case "LastName":
if (LastName == null)
error = "null";
break;
default:
break;
}
return error;
}
public virtual event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public override int GetHashCode()
{
unchecked
{
int result = 17;
result = 23 * result + ((LastName != null) ? LastName.GetHashCode() : 0);
return result;
}
}
}
Each field that is used in GetHashCode function must be immutable. I would not recommend to implement two versions of GetHashCode, because it should be persistent and repeatable. I know one possible way how to solve this problem. If you know that some object will be changed, then you can delete it from set before editing operation and add again into set when all modifications are done. In this case you can skip overriding of GetHashCode and use SortedSet with a specified comparer that implements IComparer interface.
Update
Normally, I would recommend to use HashSet if you don't need sorted set as a result. SortedSet employs binary search tree and it seems that it doesn't use GetHashCode function. SortedSet is a bit slower than HashSet. SortedSet performance is about O(log n), because it has to find a space for inserting element in the sorted set. HashSet takes only O(1).
IComparer helps to find whether two objects are equal (there is no need to call Equals) or tells which of them is less than or greater than other. I wrote a bit of code for testing of SortedSet functionality.
Code
public class Foo
{
public Foo(string something)
{
Something = something;
}
public string Something { set; get; }
}
public class BySomething : IComparer<Foo>
{
private readonly CaseInsensitiveComparer _comparer = new CaseInsensitiveComparer();
public int Compare(Foo x, Foo y)
{
return _comparer.Compare(x.Something, y.Something);
}
}
Test
[TestMethod]
public void SortedSetTest()
{
var first = new Foo("Doe");
var second = new Foo("Floyd");
var third = new Foo("Floyd");
var set = new SortedSet<Foo>(new BySomething());
set.Add(first);
set.Add(second);
set.Add(third);
Assert.AreEqual(set.Count, 2);
}
I have a collection of type:
Iesi.Collections.Generic
public ISet<ItemBinding> ItemBindings { get; set; }
where ItemBinding is Domain.Model
I initialize the collection in this way:
ItemBindings = new HashedSet<ItemBinding>();
and I fill the collection with members.
When i want to remove an item from this collection i can't remove it.
private void OnRemove(ItemBinding itemToRemove) {
ItemBindings.Remove(itemToRemove);
}
even the itemToRemove has the same hashCode as the item from the collection.
Also I tried in to find the item in collection, keep it in a variable, and remove it:
private void OnRemove(ItemBinding itemToRemove) {
var foundItem = ItemBindings.Single( x => x.Id == itemToRemove.Id); // always if found
ItemBindings.Remove(foundItem);
}
but this doesn't work.
An workaround which works ok is this:
private void OnRemove(ItemBinding itemToRemove) {
var backUpItems = new List<ItemBinding>(ItemBindings);
backUpItems.Remove(itemToRemove);
ItemBindings.Clear();
ItemBindings.AddAll(backUpItems);
}
but this is an dirty workaround. I'm trying to do this simple Remove in an elegant manner :).
CHANGE the TYPE
If I change the type from ISet in IList it works ok.
public IList<ItemBinding> ItemBindings { get; set; }
ItemBindings = new List<ItemBinding>();
When i want to remove an item from this collection IT IS REMOVED.
private void OnRemove(ItemBinding itemToRemove) {
ItemBindings.Remove(itemToRemove);
}
What i'm missing in the way that i can't remove items from ISet ... ?
Thank you for suggestions, solutions.
This is very simple problem. Just download dotPeek 1.2 and fire up symbol server & you can then check into the ACTUAL implementation of ISet.Remove() and see why it's being picky. As #HansPassant said, it's probably the case of GetHashCode(), or the actual implementation of HashedSet
As for my speculations; take a look at DictionarySet(base class of HashedSet):
https://www.symbolsource.org/Public/Metadata/Default/Project/NHibernate/3.0.0.Alpha2/Release/All/Iesi.Collections/Iesi.Collections/Generic/DictionarySet.cs
As you can see, Remove() uses Contains() to actually test if it should remove element or not. What does Contains() do? Basically it's wrapper around Dictionary.Contains()
http://msdn.microsoft.com/en-us/library/ms182358(v=vs.80).aspx
GetHashCode returns a value based on the current instance that is
suited for hashing algorithms and data structures such as a hash
table. Two objects that are the same type and are equal must return
the same hash code to ensure that instances of
System.Collections.HashTable and System.Collections.Generic.Dictionary
work correctly.
Notice it's important that:
1) Your GetHashCode() can't change. This means that all the fields which are used by GetHashCode() can't change. As soon as you insert element into Dictionary, its GetHashCode() will be called and it's being put to specific bucket. You can't recover it when you have different GetHashCode() later. After the GetHashCode() has passed, your Equals method will be called. Make sure your fields are immutable.
Relevant source from Dictionary:
private int FindEntry(TKey key)
{
if ((object) key == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
if (this.buckets != null)
{
int num = this.comparer.GetHashCode(key) & int.MaxValue;
for (int index = this.buckets[num % this.buckets.Length]; index >= 0; index = this.entries[index].next)
{
if (this.entries[index].hashCode == num && this.comparer.Equals(this.entries[index].key, key))
return index;
}
}
return -1;
}
See this thread how to override Equals() AND GetHashCode():
Why is it important to override GetHashCode when Equals method is overridden?
Notice the answer by #Albic, in that thread.
I cannot reproduce this behaviour.
private class ItemBinding
{
public string ID { get; set; }
}
[TestMethod]
public void TestMethod1()
{
System.Collections.Generic.HashSet<ItemBinding> set = new System.Collections.Generic.HashSet<ItemBinding>();
ItemBinding item1 = new ItemBinding() { ID = "Jaffa" };
set.Add(item1);
Assert.IsTrue(set.Count == 1);
set.Remove(item1);
Assert.IsTrue(set.Count == 0);
ItemBinding item2 = new ItemBinding() { ID = "Moon" };
set.Add(item2);
ItemBinding item3 = new ItemBinding() { ID = "Moon" };
Assert.IsTrue(item2.GetHashCode() != item3.GetHashCode());
Assert.IsTrue(set.Remove(item3) == false);
Assert.IsTrue(set.Count == 1);
}
The above test shows Hashset working as expected. Is is possible you are falling into the trap shown in the second test of comparing two instances of a class that have the same values, but are in fact different class instances (therefore fail the GetHashCode equality test ?).
If you can alter the posted code here to more accurately represent your particular problem, that would be helpful.
You can try using...
private void OnRemove(ItemBinding itemToRemove)
{
ItemBindings.RemoveWhere(x => x.Id == itemToRemove.Id);
}
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());
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.