I have a List of my class Edge:
public class Edge
{
public Vector3 a;
public Vector3 b;
public Edge(Vector3 a, Vector3 b)
{
this.a = a;
this.b = b;
}
}
I want ALL my duplicates to be removed from my list, an element is considered duplicated if
(edge1.a == edge2.a && edge1.b == edge2.b)
or
(edge1.a == edge2.b && edge1.b == edge2.a)
I've tried to use the Disctinct() method with an equality comparer and some other LINQ variants, they work as they are supposed to, but that is not the result I want. All the solutions I've found always leave one of the duplicate elements inside the list, when I need every duplicate to be removed from it.
public class SomeClass: MonoBehaviour
{
[SerializeField] private List<Edge> edges;
private void Start()
{
edges = edges
.Distinct(new EdgeComparer())
.ToList();
// This removes the duplicates except for one,
// I need that one to be removed as well.
}
}
public class EdgeComparer : IEqualityComparer<Edge>
{
public bool Equals(Edge x, Edge y)
{
return
(x.a.Equals(y.a) && x.b.Equals(y.b)) ||
(x.a.Equals(y.b) && x.b.Equals(y.a));
}
public int GetHashCode(Edge obj)
{
int a = obj.a.GetHashCode();
int b = obj.b.GetHashCode();
return a ^ b;
}
}
I think I got the question: we should remove all items which appear two or more times in the list; e.g.
(a:3, b:4), (a:5, b:6), (a:0, b:8) (a:3, a:4), (a:6, b:5) => (a:0, b:8)
If it's your problem the code that does it in place can be
var unique = new HashSet<edge>(new EdgeComparer());
var remove = new HashSet<edge>(new EdgeComparer());
// Collect what items to remove
foreach (var edge in edges)
if (!unique.Add(edge))
remove.Add(edge);
// remove items
if (remove.Count > 0) {
int writeIndex = 0;
for (int readIndex = 0; readIndex < edges.Count; ++readIndex)
if (!remove.Contains(edges[readIndex]))
edges[writeIndex++] = edges[readIndex];
edges.RemoveRange(writeIndex, edges.Count - writeIndex);
}
Linq solution is based on GroupBy, not Distinct:
edges = edges
.GroupBy(edge => edge, new EdgeComparer())
.Where(group => group.Count() == 1) // items that appear just once
.Select(group => group.First())
.ToList();
Side note: EdgeComparer with a better GetHashCode can be
public sealed class EdgeComparer : IEqualityComparer<Edge> {
public bool Equals(Edge x, Edge y) =>
ReferenceEquals(x, y) ? true
: x is null || y is null ? false
: x.a.Equals(y.a) && x.b.Equals(y.b) || x.a.Equals(y.b) && x.b.Equals(y.a)
public int GetHashCode(Edge obj) => obj is null
? -1
: a.GetHashCode() < b.GetHashCode()
? HashCode.Combine(a, b)
: HashCode.Combine(b, a);
}
Related
I want to sort the list of classes and interfaces. So my logic is
if the class is not implemented interface it's higher than the interface, otherwise, it's lower
I am using the IComparer interface to sort my list. My model looks like this:
My Comparer class (if returns 1 it means y>x, 0 is x==y -1 is x>y):
public class SortedTypeComparer : IComparer<Type>
{
/// <summary>
/// Compares types
/// </summary>
public int Compare(Type x, Type y)
{
public int Compare(Type x, Type y)
{
if (y.IsAssignableFrom(x))
{
return 1;
}
else if (x.IsAssignableFrom(y))
{
return -1;
}
else if (!y.IsAssignableFrom(x) && !x.IsAssignableFrom(y) && x.IsInterface && y.IsClass)
{
return 1;
}
else if (!y.IsAssignableFrom(x) && !x.IsAssignableFrom(y) && x.IsClass && y.IsInterface)
{
return -1;
}
else
{
return 0;
}
}
}
}
I am expecting when I sort the list it should be:
IAnimal
IRunnable
Animal
ICat
Cat
Or:
IRunnable
IAnimal
Animal
ICat
Cat
Because IRunnable and IAnimal are 'equal'. Here is my usage:
var list = new List<Type>();
list.Add(typeof(IAnimal));
list.Add(typeof(IRunnable));
list.Add(typeof(ICat));
list.Add(typeof(Animal));
list.Add(typeof(Cat));
list.Sort(new SortedTypeComparer());
In this case, it is working as expected. But when I change the order of adding to list for example(put IRunnable to the end):
var list = new List<Type>();
list.Add(typeof(IAnimal));
list.Add(typeof(ICat));
list.Add(typeof(Animal));
list.Add(typeof(Cat));
list.Add(typeof(IRunnable));
list.Sort(new SortedTypeComparer());
The order is
IAnimal
Animal
ICat
IRunnable
Cat
It is not my expectation because of IRunnable>Animal. It seems when it compare Animal and ICat Animal is higher, then when it compares ICat and IRunnable it's saying "ICat == IRunnable, so Animal should be > IRunnable". How can I write the logic in the Compare method to sort my list as expected?
I do not think this is possible with a IComparer. From CompareTo
For objects A, B, and C, the following must be true:
If A.CompareTo(B) returns zero and B.CompareTo(C) returns zero, then A.CompareTo(C) is required to return zero.
So if A inherits from C and B does not inherit anything, then according to your rules compareTo should return:
A.CompareTo(B) -> 0
B.CompareTo(C) -> 0
A.CompareTo(C) -> 1
This violates the requirements of CompareTo.
An alternative would be to build an Directed acyclic graph of the hierarchy. Then you should be able to use Topological sorting to sort the graph.
Maybe answer of #JonasH more correct, but I did by fixing My comparer class. I added the following condition in my method and it is correct for most scenarios:
public class SortedTypeComparer : IComparer<Type>
{
public List<Type> AllTypes { get; set; }
public SortedTypeComparer(List<Type> types)
{
AllTypes = types;
}
/// <summary>
/// Compares types
/// </summary>
public int Compare(Type x, Type y)
{
var result = CompareIsHigherOrLower(x, y);
if (result == 0)
{
var subEntitiesOfX = AllTypes.Where(a => x.IsAssignableFrom(a) && a != x);
foreach (var subTypeOfX in subEntitiesOfX)
{
result = CompareIsHigherOrLower(subTypeOfX, y);
if (result == -1)
{
return -1;//It means SubEntity of X is higher then Y and X should be > Y
}
}
var subEntitiesOfY = AllTypes.Where(a => y.IsAssignableFrom(a) && a != y);
foreach (var subType in subEntitiesOfY)
{
result = CompareIsHigherOrLower(subType, x);
if (result == -1)
{
return 1;//It means SubEntity of Y is higher then X and Y should be > X
}
}
}
return result;
}
int CompareIsHigherOrLower(Type x, Type y)
{
if (y.IsAssignableFrom(x))
{
return 1;
}
else if (x.IsAssignableFrom(y))
{
return -1;
}
else if (!y.IsAssignableFrom(x) && !x.IsAssignableFrom(y) && x.IsInterface && y.IsClass)
{
return 1;
}
else if (!y.IsAssignableFrom(x) && !x.IsAssignableFrom(y) && x.IsClass && y.IsInterface)
{
return -1;
}
else
{
return 0;
}
}
}
I have a list of long type array.
List<ulong[]> TestList = new List<ulong[]>();
and list has following items.
{1,2,3,4,5,6},
{2,3,4,5,6,7},
{3,4,5,6,7,8},
{1,2,3,4,5,6}
and expected distinct result is
{1,2,3,4,5,6},
{2,3,4,5,6,7},
{3,4,5,6,7,8}
So I try as following, but useless.
TestList = TestList.Distinct().ToList();
Am I need something special comparer for getting distinct list?
Distinct() uses the default equality check, which for arrays is reference equality. It does not check the contents of the array for equality.
If you want to do that, you'll need the overload of Distinct() that takes an IEqualityComparer<T>. This allows you to customize the behaviour to determine if two items are equal or not.
For comparing arrays, IStructuralEquatable and friends already do the heavy lifting. You can wrap it simply, like so:
sealed class StructuralComparer<T> : IEqualityComparer<T>
{
public static IEqualityComparer<T> Instance { get; } = new StructuralComparer<T>();
public bool Equals(T x, T y)
=> StructuralComparisons.StructuralEqualityComparer.Equals(x, y);
public int GetHashCode(T obj)
=> StructuralComparisons.StructuralEqualityComparer.GetHashCode(obj);
}
Then, use it in the Distinct() call like this:
TestList = TestList.Distinct(StructuralComparer<ulong[]>.Instance).ToList();
You need to provide an equality comparer, default implementation does not know how to compare arrays of long (it uses reference equality):
class LongArrayComparer : EqualityComparer<long[]>
{
public override bool Equals(long[] a1, long[] a2)
{
if (a1 == null && a2 == null)
return true;
else if (a1 == null || a2 == null)
return false;
return a1.SequenceEqual(a2);
}
public override int GetHashCode(long[] arr)
{
long hCode = arr.Aggregate(0, (acc, it) => acc ^ it);
return hCode.GetHashCode();
}
}
Then use it:
TestList = TestList.Distinct(new LongArrayComparer()).ToList();
List<ulong[]> TestList = new List<ulong[]>() {
new ulong[]{ 1,2,3,4,5,6},
new ulong[]{ 2,3,4,5,6,7},
new ulong[]{ 3,4,5,6,7,8},
new ulong[]{ 1,2,3,4,5,6}
};
var result = TestList.GroupBy(x => String.Join(",", x))
.Select(x => x.First().ToArray())
.ToList();
You can implement an IEqualityComparer
public class IntArrayComparer : IEqualityComparer<string[]>
{
public bool Equals(int[] x, int[] y)
{
var shared = x.Intersect(y);
return x.Length == y.Length && shared.Count() == x.Length;;
}
public int GetHashCode(int[] obj)
{
int hashCode=obj.Length;
for(int i=0;i<obj.Length;++i)
{
hashCode=unchecked(hashCode*314159 +obj[i]);
}
return hashCode;
}
}
Then can implement it:
TestList = TestList.Distinct(new IntArrayComparer()).ToList();
I've got a List<Card>, and I want to sort these cards
So, I'm looking for a method to sort them with different criterias, like their ID, their Name ...
public class Card : IComparer
{
public string ID;
public string Name;
public int CompareId(object firstCard, object secondCard)
{
Card c1 = (Card)firstCard;
Card c2 = (Card)secondCard;
return c1.Id.CompareTo(c2.Id);
}
}
But then, visual studio sent me an error :
'Card' does not implement interface member 'IComparer<Card>.Compare(Card, Card)'
You, probably, want to have your class Comparable not a Comparator
public class Card : IComparable<Card>
{
public string ID;
public string Name;
public int CompareTo(Card other)
{
if (null == other)
return 1;
// string.Compare is safe when Id is null
return string.Compare(this.Id, other.Id);
}
}
then
List<Card> myList = ...
myList.Sort();
Edit: If you want to have several criteria to choose from, you have to implement several Comparers as separated classes, e.g.
public sealed class CardByIdComparer : IComparer<Card>
{
public int Compare(Card x, Card y)
{
if (object.ReferenceEquals(x, y))
return 0;
else if (null == x)
return -1;
else if (null == y)
return 1;
else
return string.Compare(x.Id, y.Id);
}
}
and when sorting provide the required:
List<Card> myList = ...
myList.Sort(new CardByIdComparer());
Edit 2: (inspired by spender's library). If you want to combine several comparers into one (i.e. use comparer1, on tie - comparer2 etc.)
public sealed class ComparerCombined<T> : IComparer<T> {
private IComparer<T>[] m_Comparers;
public ComparerCombined(params IComparer<T>[] comparers) {
if (null == comparers)
throw new ArgumentNullException(nameof(comparers));
m_Comparers = comparers
.Select(item => item == null ? Comparer<T>.Default : item)
.Where(item => item != null)
.Distinct()
.ToArray();
}
public int Compare(T x, T y) {
if (object.ReferenceEquals(x, y))
return 0;
else if (null == x)
return -1;
else if (null == y)
return 1;
foreach (var comparer in m_Comparers) {
int result = comparer.Compare(x, y);
if (result != 0)
return result;
}
return 0;
}
}
usage:
myList.Sort(new ComparerCombined(
new CardByIdComparer(), // Sort By Id
new CardByNameComparer() // On tie (equal Id's) sort by name
));
The easiest way You can use Linq:
List<Card> objSortedList = objListObject.OrderBy(o=>o.ID).ToList();
or
List<Card> objSortedList = objListObject.OrderByDescending(o=>o.ID).ToList();
Good examples for demonstrate the concept of
List<T>.Sort(IComparer <T>) method check the link please.
IComparer<T> in this example compare method used for strings IComparer<T>
but you can use this for ID(int) too.
using System;
using System.Collections.Generic;
class GFG : IComparer<string>
{
public int Compare(string x, string y)
{
if (x == null || y == null)
{
return 0;
}
// "CompareTo()" method
return x.CompareTo(y);
}
}
public class geek
{
public static void Main()
{
List<string> list1 = new List<string>();
// list elements
list1.Add("C++");
list1.Add("Java");
list1.Add("C");
list1.Add("Python");
list1.Add("HTML");
list1.Add("CSS");
list1.Add("Scala");
list1.Add("Ruby");
list1.Add("Perl");
int range = 4;
GFG gg = new GFG();
Console.WriteLine("\nSort a range with comparer:");
// sort the list within a
// range of index 1 to 4
// where range = 4
list1.Sort(1, range, gg);
Console.WriteLine("\nBinarySearch and Insert Dart");
// Binary Search and storing
// index value to "index"
int index = list1.BinarySearch(0, range,
"Dart", gg);
if (index < 0)
{
list1.Insert(~index, "Dart");
range++;
}
}
}
You need to implement IComparer
public int Compare(Card card1, Card card2)
{
if (card1.ID > card2.ID)
return 1; //move card1 up
if (card2.ID < card1.ID)
return -1; //move card2 up
return 0; //do nothing
}
I have text boxes and I get and set its input to the structure which is as follow.
public struct mappingData
{
public string a;
public string b;
public int c;
}
mappingData mappingFileData;
public List<mappingData> mappingDatabase = new List<mappingData>();`
once the button is clicked I store it into List
private void btnAddMapData_Click(object sender, EventArgs e)
{
mappingFileData.a = addressPrefixMbDataType[cbMbDataType.SelectedIndex];
mappingFileData.b = addressPrefixMbValue[cbMbValue.SelectedIndex];
mappingFileData.c = Int32.Parse(tbAddress.Text);
// Add new entry to the linked list each time when 'btnAddMapData' is clicked
mappingDatabase.Add(mappingFileData);
}
Now my database can be follows
a , x , 1
a , x , 2
b , x , 1
b , x , 2
but it should not be like as follows
a , x , 1
a , x , 1 > duplicate beacause already "1" is available previously
b , x , 2
b , x , 2 > duplicate beacause already "2" is available previously
b , x , 1 > not a duplicate because 1st parameter is different that is 'b' so "a" and "b" both can hold 1 since both are different and but if there is two "a" in the list then there should be only one "1".
Someone, please suggest me an idea
If you want a surefire way to check for duplicates, you'll have to first modify your struct to override the == operator and the associated methods:
public struct mappingData
{
public string mbDataType;
public string mbValue;
public string daliAddrType;
public int mbAddress;
public string daliCmdNo;
public int daliDevId;
public override bool Equals(Object obj)
{
return obj is mappingData && this == (mappingData)obj;
}
public override int GetHashCode()
{
return (mbDataType.GetHashCode() + mbValue.GetHashCode() + daliAddrType.GetHashCode() + daliCmdNo.GetHashCode()) * mbAddress * 807403 * daliDevId;
}
public static bool operator ==(mappingData x, mappingData y)
{
return x.mbDataType == y.mbDataType && x.mbValue == y.mbValue && x.daliAddrType == y.daliAddrType && x.mbAddress == y.mbAddress && x.daliCmdNo == y.daliCmdNo && x.daliDevId == y.daliDevId;
}
public static bool operator !=(mappingData x, mappingData y)
{
return !(x == y);
}
}
Now, you can use LINQ or similar methods and compare identical instances of mappingData:
if (!mappingDatabase.Contains(mappingFileData))
{
mappingDatabase.Add(mappingFileData);
}
EDIT:
If you only want to check for the same mbAddress and mbDataType, then you don't need to modify your struct at all. You can do this right away:
if (!mappingDatabase.Any(m => m.mbAddress == mappingFileData.mbAddress && m.mbDataType == mappingFileData.mbDataType))
{
mappingDatabase.Add(mappingFileData);
}
If you use a struct the default implementation takes all fields into consideration to generate the resulting hash code. So by simply putting your struct into a HashSet<> should solve your problem:
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var items = Enumerable.Range(1, 100)
.Select(value => value % 10)
.Select(value => new mappingData{ a = "a" + value, b = "b" + value, c = value });
var hashSet = new HashSet<mappingData>(items);
// Outputs only first ten elements
foreach(var item in hashSet)
{
Console.WriteLine(item);
}
}
public struct mappingData
{
public string a;
public string b;
public int c;
public override string ToString()
{
return $"{a} {b} {c}";
}
}
}
List has several extension methods which can help you find data in the list. In your case I would suggest to use the Count method. It should also work on a struct.
int found = mappingDatabase.Count(delegate (mappingData obj) { return obj.a.Equals("a") && obj.b.Equals("x"); });
You can then set your own rules for searching and determining if it is a duplicate or not.
Don't forget to check that mappingDatabase is not null before calling Count.
My problem is that I always want to order a collection of objects in a certain fashion.
For example:
class foo{
public string name {get;set;}
public DateTime date {get;set;}
public int counter {get;set;}
}
...
IEnumerable<foo> dosomething(foo[] bar){
return bar.OrderBy(a=>a.name).ThenBy(a=>a.date).ThenBy(a=>a.counter);
}
The issue I have is its quite longwinded tacking-on the sort order all the time. A neat solution appears to just create a class that implements IComparer<foo>, meaning I can do:
IEnumerable<foo> dosomething(foo[] bar){
return bar.OrderBy(a=>a, new fooIComparer())
}
.
The problem is, the order method this implements is as follows
...
public int Compare(foo x, foo y){ }
Meaning it compares on a very granular basis.
The currently implementation (which will probably work, although im writing pseudocode)
public int Compare(foo x, foo y){
if (x==y)
return 0;
var order = new []{x,y}.OrderBy(a=>a.name).ThenBy(a=>a.date).ThenBy(a=>a.counter);
return (order[0] == x) ? -1 : -1;//if x is first in array it is less than y, else it is greater
}
This is not exactly efficient, can another offer a neater solution? Ideally without a Compare(x,y) method altogether?
Option 1 - The Comparer
As you're ordering by multiple conditions, you'll to check them individually within each case; for example, if x.name and y.name are equal, then you would check x.date and y.date, and so on.
public class FooComparer : IComparer<Foo>
{
public int Compare(Foo x, Foo y)
{
// nasty null checks!
if (x == null || y == null)
{
return x == y ? 0
: x == null ? -1
: 1;
}
// if the names are different, compare by name
if (!string.Equals(x.Name, y.Name))
{
return string.Compare(x.Name, y.Name);
}
// if the dates are different, compare by date
if (!DateTime.Equals(x.Date, y.Date))
{
return DateTime.Compare(x.Date, y.Date);
}
// finally compare by the counter
return x.Counter.CompareTo(y.Counter);
}
}
Option 2 - The extension method
An alternative, not so appealing approach, could be an extension method. Sadly as the TKey for each ThenBy can be different, we lose the power of generics, but can safely replace it with the type object in this case.
public static IOrderedEnumerable<T> OrderByThen<T>(this IEnumerable<T> source, Func<T, object> selector, params Func<T, object>[] thenBySelectors)
{
IOrderedEnumerable<T> ordered = source.OrderBy(selector);
foreach (Func<T, object> thenBy in thenBySelectors)
{
ordered = ordered.ThenBy(thenBy);
}
return ordered;
}
You have to implement IComparable<foo> and compare all properties:
class foo: IComparable<foo>, IComparer<foo>
{
public string name { get; set; }
public DateTime date { get; set; }
public int counter { get; set; }
public int Compare(foo x, foo y)
{
if (x == null || y == null) return int.MinValue;
if (x.name != y.name)
return StringComparer.CurrentCulture.Compare(x.name, y.name);
else if (x.date != y.date)
return x.date.CompareTo(y.date);
else if (x.counter != y.counter)
return x.counter.CompareTo(y.counter);
else
return 0;
}
public int CompareTo(foo other)
{
return Compare(this, other);
}
}
Then you can use OrderBy in this way:
var ordered = foos.OrderBy(f => f).ToList();
what's wrong with an extension method?
Why wont you simply compare your values:
int Compare(foo x, foo y)
{
if (x== null && y == null)
return 0;
else if (x == null)
return -1;
else if (y == null)
return 1;
var nameComparision = string.Compare(x.name,y.name);
if (nameComparision != 0)
return nameComparision;
var dateComparision = x.date.CompareTo(y.date);
if (dateComparision != 0)
return dateComparision;
var counterComparision = x.counter.CompareTo(y.counter);
return counterComparision;
}