IEqualityComparer fails using two properties - c#

When I use this comparer in Distinct() it always returns false. Can't see a reason why.
public class IdEqualityComparer : IEqualityComparer<Relationship>
{
public bool Equals(Relationship x, Relationship y)
{
if (x == null && y == null)
return true;
else if (x == null || y == null)
return false;
else if (x.ID == y.ID && x.RelatedID == y.RelatedID)
return true;
else
return false;
}
public int GetHashCode(Relationship obj)
{
unchecked
{
int hash = (obj.ID ?? "").GetHashCode() ^ (obj.RelatedID ?? "").GetHashCode();
return hash;
}
}
}
The hash seems correct to me, but the ID and RelatedID comparison never returns true.
It fails, as I can check the result afterward and the output is not distinct using those two properties.

Apologies to all! It is working fine.
I was comparing objects defined similarly to Marc's answer. But, I did this:
var relators = relationships.Distinct(new RelationshipEqualityComparer());
and then sent relationships, rather than realtors, to the next method where the items were reviewed. Sometimes it takes another pair of eyes!
Thanks!

Seems to work fine here;
static void Main()
{
var objs = new[]
{
new Relationship { ID = "a", RelatedID = "b" }, // <----\
new Relationship { ID = "a", RelatedID = "c" }, // |
new Relationship { ID = "a", RelatedID = "b" }, // dup--/
new Relationship { ID = "d", RelatedID = "b" }, // <------\
new Relationship { ID = "d", RelatedID = "c" }, // |
new Relationship { ID = "d", RelatedID = "b" }, // dup ---/
new Relationship { ID = "b", RelatedID = "c" }, //
};
var count = objs.Distinct(new IdEqualityComparer()).Count();
System.Console.WriteLine(count);
}
gives 5, not 7 (which we would expect if it always returned false). Tested with:
public class Relationship
{
public string ID { get; set; }
public string RelatedID { get; set; }
}
To illustrate this more clearly:
var a = new Relationship { Id = "x", RelatedID = "foo" };
var b = new Relationship { Id = "y", RelatedID = "foo" };
var c = new Relationship { Id = "x", RelatedID = "foo" };
we can now demonstrate that the comparer returns true and false appropriately:
var comparer = new IdEqualityComparer();
Console.WriteLine(comparer.Equals(a, b)); // False
Console.WriteLine(comparer.Equals(a, c)); // True
we can also show that the hash-code is working appropriately:
Console.WriteLine(comparer.GetHashCode(a));
Console.WriteLine(comparer.GetHashCode(b));
Console.WriteLine(comparer.GetHashCode(c));
note that the numbers will change, but for me on this run this gives:
-789327704
1132350395
-789327704
The numbers don't matter - what matters is that the first and last are equal, and (ideally) different from the middle one.
So: the comparer is working fine, and the premise of the question is incorrect. You need to identify in your code what is different, and fix it.

Related

C# Remove duplicated objects from list and increase first

I have an list of objects => class Example { int quantity; string name; string comment; } and I want to remove all duplicates and increase the quantity by the number of duplicates that have the same name and comment.
Example:
[
{quantity: 1, name: "Hello", comment: "Hello there"},
{quantity: 2, name: "Bye", comment: "Good bye"},
{quantity: 1, name: "Hi", comment: "Hi there"},
{quantity: 1, name: "Hello", comment: "Hello there"},
{quantity: 1, name: "Bye", comment: "Good bye"},
]
and the result it should be:
[
{quantity: 2, name: "Hello", comment: "Hello there"},
{quantity: 3, name: "Bye", comment: "Good bye"},
{quantity: 1, name: "Hi", comment: "Hi there"}
]
I want to remove all duplicates
You haven't defined when two example objects are "duplicates". I guess, that you mean to say, that if two Examples objects have the same values for properties Name and Comment, then they are duplicates.
Normally you can use one of the overloads of Enumerable.GroupBy to find Duplicates. Use the overload with a parameter resultSelector to define exactly what you want as result.
IEnumerable<Example> examples = ...
var result = examples.GroupBy(
// key: Name-Comment
example => new
{
Name = example.Name,
Comment = example.Comment,
}
// parameter resultSelector: for every Name/Comment combination and all
// Examples with this Name/Comment combination make one new example:
(nameCommentCombination, examplesWithThisNameCommentCombination) => new Example
{
Name = nameCommentCombination.Name,
Comment = nameCommentCombination.Comment,
// Quantity is the sum of all Quantities of all Examples with this
// Name/Comment combination
Quantity = examplesWithThisNameCommentCombination
.Select(example => example.Quantity)
.Sum(),
});
This will only work if you want exact string equality. Are "Hello" and "HELLO" equal? And how about "Déjà vu" and "Deja vu"? Do you want case insensitivity for Name and Comment? And how about diacritical characters?
If you want more than just plain string equality, consider to create an ExampleComparer class.
class ExampleComparer : EqualityComparer<Example>
{
... // TODO: implement
}
Usage would be:
IEnumerable<Example> examples = ...
IEqualityComparer<Example> comparer = ...
var result = examples.GroupBy(example => example, // key
// resultSelector:
(key, examplesWithThisKey) => new Example
{
Name = key.Name,
Comment = key.Comment,
Quantity = examplesWithThiskey.Sum(example => example.Quantity),
},
comparer);
Implement the ExampleComparer
class ExampleComparer : EqualityComparer<Example>
{
public static IEqualityComparer<Example> ByNameComment {get;} = new ExampleComparer;
private static IEqualityComparer<string> NameComparer => StringComparer.CurrentCultureIgnoreCase;
private static IEqualityComparer<string> CommentComparer => StringComparer.CurrentCultureIgnoreCase;
I chose for two separate string comparers, so if later you decide different comparison, for instance Name has to match exactly, then you only have to change it here.
public override bool Equals (Example x, Example y)
{
// almost every Equals method starts with the following three lines
if (x == null) return y == null; // true if both null
if (y == null) return false; // false, because x not null
if (Object.ReferenceEquals(x, y)) return true; // same object
// return true if both examples are considered equal:
return NameComparer.Equals(x.Name, y.Name)
&& CommentComparer.Equals(x.Comment, y.Comment);
}
public override int GetHashCode(Example x)
{
if (x == null) return 5447125; // just a number
return NameComparer.GetHashCode(x.Name)
^ CommentComparer.GetHashCode(x.Comment);
}
Note: this will also work if Name or Comment are null or empty!
I used operator ^ (XOR), because that gives a fairly good hash if there are only two fields to consider. If you think that the vast majority of Examples have unique names, consider to check only property Name:
return NameComparer.GetHashCode(x.Name);
Because method Equals uses the NameComparer and CommentComparer to check for equality, make sure you use the same Comparers to calculate the HashCode.
Here's what I'd do:
Example[] before = new Example[]
{
new Example { Quantity = 1, Name = "Hello", Comment = "Hello there" },
new Example { Quantity = 2, Name = "Bye", Comment = "Good bye" },
new Example { Quantity = 1, Name = "Hi", Comment = "Hi there" },
new Example { Quantity = 1, Name = "Hello", Comment = "Hello there" },
new Example { Quantity = 1, Name = "Bye", Comment = "Good bye" },
};
Example[] after =
before
.GroupBy(x => new { x.Name, x.Comment }, x => x.Quantity)
.Select(x => new Example { Quantity = x.Sum(), Name = x.Key.Name, Comment = x.Key.Comment })
.ToArray();
That gives:
Perhaps this version is a little clearer:
Example[] after =
before
.GroupBy(
x => new { x.Name, x.Comment },
(k, xs) => new Example
{
Quantity = xs.Sum(x => x.Quantity),
Name = k.Name,
Comment = k.Comment
})
.ToArray();
Or perhaps this:
Example[] after =
(
from x in before
group x.Quantity by new { x.Name, x.Comment } into xs
select new Example
{
Quantity = xs.Sum(x => x),
Name = xs.Key.Name,
Comment = xs.Key.Comment
}
).ToArray();
Here's a simple solution, which gives you the answer in the List tata but you can do .ToArray() if you wish.
public class Example
{
public int quantity;
public string name;
public string comment;
}
Example[] toto = new Example[]
{
new Example
{
quantity = 1,
name = "Hello",
comment = "Hello there"
},
new Example
{
quantity = 2,
name = "Bye",
comment = "Good bye"
},
new Example
{
quantity = 1,
name = "Hi",
comment = "Hi there"
},
new Example
{
quantity = 1,
name = "Hello",
comment = "Hello there"
},
new Example
{
quantity = 1,
name = "Bye",
comment = "Good bye"
}
};
List<Example> tata = new List<Example>();
foreach (Example exa in toto)
{
bool found = false;
foreach (Example exb in tata)
{
if (exb.name == exa.name && exb.comment == exa.comment)
{
exb.quantity += exa.quantity;
found = true;
break;
}
}
if (!found)
{
tata.Add(exa);
}
}
A good exercise would be to LINQ that!

Using Linq Select to create a list that contains more items than the original list

I have a list of items. For example (although the list could be any length):
var inputList = new List<Input1>()
{
new Input1() { Test = "a" },
new Input1() { Test = "b" }
};
What I want to do is create a new list of:
a1, a2, b8, b9
That is the value of Test (i.e. a) with a suffix based on the value of Test.
In that order. Obviously, this is a minimum workable example, not the actual problem. So I'd like to use something like the .Select to split the data - something like this:
var outputList = inputList.Select(x =>
{
if (x.Test == "a")
{
return new Input1() { Test = "a1" };
//return new Input1() { Test = "a2" };
}
else if (x.Test == "b")
{
return new Input1() { Test = "b8" };
//return new Input1() { Test = "b9" };
}
else
{
return x;
}
});
Input1 for completeness:
class Input1
{
public string Test { get; set; }
}
That is, to return a list that contains items that were not in the original list.
I realise I can use a foreach, but I'm interested if there's a better / more concise way.
Suppose you have a method that transforms your single input into multiple inputs:
public static Input1[] Transform(Input1 x)
{
if (x.Test == "a") return new[] {new Input1("a1"), new Input1("a2")};
if (x.Test == "b") return new[] {new Input1("b8"), new Input1("b9")};
return new[] {x};
}
(This is just from your toy example - I guess you actually need a transformation that is more meaningful.)
Then you can just use SelectMany to get your desired result in the correct order:
inputList
.SelectMany(Transform);
If you're using C# 8.0 or above, you may use switch expression as follows:
var outputList =
inputList.SelectMany(x => x.Test switch
{
"a" => new[] { new Input1() { Test = "a1" }, new Input1() { Test = "a2" } },
"b" => new[] { new Input1() { Test = "b8" }, new Input1() { Test = "b9" } },
_ => new[] { x }
})
.ToList();

c#: Move element whose ID is in array to top of list

In C#,I have List of Employee object. Employee class is
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
}
In List objected are sorted based on Employee.ID. I have an array of int which is basically Employee.ID which I want on top of the list and in list,order must remain same as in array.
If I hava input like this
List:
[
{ID:1,Name:A},
{ID:2,Name:B},
{ID:3,Name:AA},
{ID:4,Name:C},
{ID:5,Name:CD},
.
.
{ID:100,Name:Z}
]
and Array: {2,3,1}
Then I want Output List:
[
{ID:2,Name:B},
{ID:3,Name:AA},
{ID:1,Name:A},
{ID:4,Name:C},
{ID:5,Name:CD},
.
.
{ID:100,Name:Z}
]
And I have done this
foreach (int i in a)
{
list = list.OrderBy(x => x.ID != i).ToList();
}
//a is array
//list is List
Any better Solution.Thanks in advance.
After you got your list sorted based on the ID just iterate the array and move the elements. In order to do this you need to first remove and then insert the item at the correct position.
for(int i = 0; i < myArray.Length; i++)
{
var e = myList.Single(x => x.Id == myArray[i]);
myList.Remove(e);
myList.Insert(i, e);
}
You may also want to use SingleOrDefault instead of Single to verify that myList even contains the element with the current id, e.g. when your array contains [2, 3, 101]
To add another version to the mix. The complete sorting can be done in one go:
list = list.OrderBy(e=> {int i =Array.IndexOf(a, e.ID); return i == -1 ? int.MaxValue : i; }).ToList();
where list is the EmployeeList and a the indices array. (NB, the for loop is not needed, the above should do both sortings).
Inside the OrderBy callback, if the id is not inside a, int.MaxValue is returned to place it after the ones inside the array (a.Length would work as well). OrderBy should maintain the original order of the enumeration (list) for those elements that return the same value.
PS, if you want to sort first by index inside a and the rest on the ids (not necessarily the original order), you can use the following (as long as a.Length + largest ID < int.MaxValue) : list = list.OrderBy(e=> {int i =Array.IndexOf(a, e.ID); return i == -1 ? a.Length + e.ID : i; }).ToList();
Here's a way to do it in pure LINQ, without changing the original sequence.
Broken into steps to see what's going on.
public static void Main()
{
var employeeList = new List<Employee>()
{
new Employee(){ ID= 1,Name= "A"},
new Employee() { ID= 2,Name= "B"},
new Employee() { ID= 3,Name= "AA"},
new Employee() { ID= 4,Name= "C"},
new Employee() { ID= 5,Name= "CD"},
new Employee() { ID= 100,Name= "Z"}
};
var orderByArray = new int[] { 2, 3, 1, 100, 5, 4 };
var sortPos = orderByArray.Select((i, index) => new { ID = i, SortPos = index });
var joinedList = employeeList.Join(sortPos, e => e.ID, sp => sp.ID, (e, sp) => new { ID = e.ID, Name = e.Name, SortPos = sp.SortPos });
var sortedEmployees = joinedList.OrderBy(e => e.SortPos).Select(e => new Employee { ID = e.ID, Name = e.Name });
}
Try this using LINQ:
List<Employee> employees = ...
int[] ids = ...
var orderEmployees = ids.Select(id => employees.Single(employee => employee.ID == id))
.Concat(employees.Where(employee => !ids.Contains(employee.ID)).ToList();
Foreach id in ids array we will grab the matching employee and we will concat to it all the employees that their id does not exist in ids array.
I like to use a special Comparer for that, it seems clearer to me, though a bit more code. It hides the complexity of the sort in the comparer class, and then you can just call it with :
theList.OrderBy(x => x.id, new ListOrderBasedComparer(sortList));
It will sort according to any list passed to the comparer when instantiating, and will put elements not in the "known sort list" at the end.
You can of course adapt it to your special needs.
public class ListOrderBasedComparer: Comparer<int>
{
private List<int> sortList;
public ListOrderBasedComparer(List<int> sortList)
{
// if you want you can make constructor accept arrays and convert it
// (if you find that more convenient)
this.sortList = sortList;
}
public override int Compare(int x, int y)
{
var indexOfX = sortList.FindIndex(a => a == x);
var indexOfY = sortList.FindIndex(a => a == y);
// handle elements not in sortArray : if not in sort array always assume they should be "less than the others" and "equal between them".
if (indexOfX == -1 && indexOfY == -1) return 0;
if (indexOfY == -1) return -1;
if (indexOfX == -1) return 1;
// if elements are in sortArray (FindIndex returned other than -1), use usual comparison of index values
return indexOfX.CompareTo(indexOfY);
}
}
Example on how to use it, with Linq :
public class TestCompare
{
public void test ()
{
var myArray = new MyClass[]
{
new MyClass { id = 1, name = "A" },
new MyClass { id = 2, name = "B" },
new MyClass { id = 3, name = "C" },
new MyClass { id = 4, name = "D" },
new MyClass { id = 5, name = "E" },
new MyClass { id = 6, name = "F" },
};
var myArray2 = new MyClass[]
{
new MyClass { id = 1, name = "A" },
new MyClass { id = 2, name = "B" },
new MyClass { id = 0, name = "X" },
new MyClass { id = 3, name = "C" },
new MyClass { id = 4, name = "D" },
new MyClass { id = 23, name = "Z"},
new MyClass { id = 5, name = "E" },
new MyClass { id = 6, name = "F" },
};
var sortList = new List<int> { 2, 3, 1, 4, 5, 6 };
// good order
var mySortedArray = myArray.OrderBy(x => x.id, new ListOrderBasedComparer(sortList)).ToList();
// good order with elem id 0 and 23 at the end
var mySortedArray2 = myArray2.OrderBy(x => x.id, new ListOrderBasedComparer(sortList)).ToList();
}
}
public class MyClass
{
public int id;
public string name;
}

Auto sorted list c# with duplicate keys

I have a series of objects and a function
double P(Object a, Object b){...}
Now, for a fixed Object a, I would like to store inside a list L all the other objects in this way:
Objects a,b,c,d with P(a,b)=1, P(a,c)=2, P(a,d)=1 should have
L[0] = b or d, L[1] = b or d, L[2] = c
Note that I only need to access (not modify, delete ecc..) the items stored in L, if L could be a SortedList then IndexOfValue would be perfect but it doesn't support duplicate keys.
Is there an easy way to solve this problem?
From the c# interactive shell
// making up a class, since there aren't any details.
// make it have some kind of value, and a human friendly name
public class Thing { public int Val {get; set;} public string Name { get; set; } }
// since P isn't given, make something up. How about adding two numbers?
Func<Thing, Thing, double> P = (a, b) => { return a.Val + b.Val; };
// give starting values to match example function output
var a = new Thing() { Val = 0, Name = "a" };
var b = new Thing() { Val = 1, Name = "b" };
var c = new Thing() { Val = 2, Name = "c" };
var d = new Thing() { Val = 1, Name = "d" };
// others is the list of values, sorted by the output from the function "P",
// compared against the first Thing ("a" in this case")
var others = (new List<Thing>() { b,c,d }).OrderBy(x => P(a, x));
// interactive shell out gives:
. others.Select(x => x.Name)
Enumerable.WhereSelectEnumerableIterator<Submission#0.Thing, string> { "b", "d", "c" }

Group by linq for nested objects

I am making a group by linq statement where i convert a single list of data into an list with a nested list. Here is my code so far:
[TestMethod]
public void LinqTestNestedSelect2()
{
// initialization
List<combi> listToLinq = new List<combi>() {
new combi{ id = 1, desc = "a", name = "A", count = 1 },
new combi{ id = 1, desc = "b", name = "A", count = 2 },
new combi{ id = 2, desc = "c", name = "B", count = 3 },
new combi{id = 2, desc = "d", name = "B", count = 4 },
};
// linq group by
var result = (from row in listToLinq
group new { des = row.desc, count = row.count } by new { name = row.name, id = row.id } into obj
select new A { name = obj.Key.name, id = obj.Key.id, descriptions = (from r in obj select new B() { des = r.des, count = r.count }).ToList() }).ToList();
// validation of the results
Assert.AreEqual(2, result.Count);
Assert.AreEqual(2, result[0].descriptions.Count);
Assert.AreEqual(2, result[0].descriptions.Count);
Assert.AreEqual(2, result[1].descriptions.Count);
Assert.AreEqual(2, result[1].descriptions.Count);
}
public class A
{
public int id;
public string name;
public List<B> descriptions;
}
public class B
{
public int count;
public string des;
}
public class combi
{
public int id;
public string name;
public int count;
public string desc;
}
This is fine if the objects are small like the example. However I will implement this for objects with a lot more properties. How can I efficiently write this statement so I don't have to write field names twice in my linq statement?
I would like to return the objects in the statement and I want something like:
// not working wishfull thinking code
var result = (from row in listToLinq
group new { des = row.desc, count = row.count } by new { name = row.name, id = row.id } into obj
select new (A){ this = obj.key , descriptions = obj.ToList<B>()}).ToList();
Background: I am re writing a web api that retrieves objects with nested objects in a single database call for the sake of db performance. It's basically a big query with a join that retrieves a crap load of data which I need to sort out into objects.
probably important: the ID is unique.
EDIT:
based on the answers so far I have made a solution which sort of works for me, but is still a bit ugly, and I would want it to be better looking.
{
// start part
return (from row in reader.AsEnumerable()
group row by row.id into grouping
select CreateA(grouping)).ToList();
}
private static A CreateA(IGrouping<object, listToLinq> grouping)
{
A retVal = StaticCreateAFunction(grouping.First());
retVal.descriptions = grouping.Select(item => StaticCreateBFunction(item)).ToList();
return ret;
}
I hope the StaticCreateAFunction is obvious enough for what it does. In this scenario I only have to write out each property once, which is what I really wanted. But I hope there is a more clever or linq-ish way to write this.
var result = (from row in listToLinq
group new B { des = row.desc, count = row.count } by new A { name = row.name, id = row.id } into obj
select new A { name = obj.Key.name, id = obj.Key.id, descriptions = obj.ToList() }).ToList();
You can add to each of the A and B classes a constructor that receives a combi and then it takes from it only what it needs. For example for a:
public class A
{
public A(combi c)
{
id = c.id;
name = c.name;
}
}
public class B
{
public B(combi c)
{
count = c.count;
des = c.desc;
}
}
Then your query can look like:
var result = (from row in listToLinq
group row by new { row.id, row.name } into grouping
select new A(grouping.First())
{
descriptions = grouping.Select(item => new B(item)).ToList()
}).ToList();
If you don't like the grouping.First() you can then override Equals and GetHashCode and then in the group by do by a new a with the relevant fields (which will be those in the Equals) and then add a copy constructor from a
Another way, in which you decouple the A/B classes from the combi is to extract the convert logic to a collection of static methods.

Categories

Resources