How to Specify My Explicit Type Comparator Inline

Can I specify my explicit type comparator inline?

My MiscUtil library contains a ProjectionComparer to build an IComparer<T> from a projection delegate. It would be the work of 10 minutes to make a ProjectionEqualityComparer to do the same thing.

EDIT: Here's the code for ProjectionEqualityComparer:

using System;
using System.Collections.Generic;

/// <summary>
/// Non-generic class to produce instances of the generic class,
/// optionally using type inference.
/// </summary>
public static class ProjectionEqualityComparer
{
/// <summary>
/// Creates an instance of ProjectionEqualityComparer using the specified projection.
/// </summary>
/// <typeparam name="TSource">Type parameter for the elements to be compared</typeparam>
/// <typeparam name="TKey">Type parameter for the keys to be compared,
/// after being projected from the elements</typeparam>
/// <param name="projection">Projection to use when determining the key of an element</param>
/// <returns>A comparer which will compare elements by projecting
/// each element to its key, and comparing keys</returns>
public static ProjectionEqualityComparer<TSource, TKey> Create<TSource, TKey>(Func<TSource, TKey> projection)
{
return new ProjectionEqualityComparer<TSource, TKey>(projection);
}

/// <summary>
/// Creates an instance of ProjectionEqualityComparer using the specified projection.
/// The ignored parameter is solely present to aid type inference.
/// </summary>
/// <typeparam name="TSource">Type parameter for the elements to be compared</typeparam>
/// <typeparam name="TKey">Type parameter for the keys to be compared,
/// after being projected from the elements</typeparam>
/// <param name="ignored">Value is ignored - type may be used by type inference</param>
/// <param name="projection">Projection to use when determining the key of an element</param>
/// <returns>A comparer which will compare elements by projecting
/// each element to its key, and comparing keys</returns>
public static ProjectionEqualityComparer<TSource, TKey> Create<TSource, TKey>
(TSource ignored,
Func<TSource, TKey> projection)
{
return new ProjectionEqualityComparer<TSource, TKey>(projection);
}

}

/// <summary>
/// Class generic in the source only to produce instances of the
/// doubly generic class, optionally using type inference.
/// </summary>
public static class ProjectionEqualityComparer<TSource>
{
/// <summary>
/// Creates an instance of ProjectionEqualityComparer using the specified projection.
/// </summary>
/// <typeparam name="TKey">Type parameter for the keys to be compared,
/// after being projected from the elements</typeparam>
/// <param name="projection">Projection to use when determining the key of an element</param>
/// <returns>A comparer which will compare elements by projecting each element to its key,
/// and comparing keys</returns>
public static ProjectionEqualityComparer<TSource, TKey> Create<TKey>(Func<TSource, TKey> projection)
{
return new ProjectionEqualityComparer<TSource, TKey>(projection);
}
}

/// <summary>
/// Comparer which projects each element of the comparison to a key, and then compares
/// those keys using the specified (or default) comparer for the key type.
/// </summary>
/// <typeparam name="TSource">Type of elements which this comparer
/// will be asked to compare</typeparam>
/// <typeparam name="TKey">Type of the key projected
/// from the element</typeparam>
public class ProjectionEqualityComparer<TSource, TKey> : IEqualityComparer<TSource>
{
readonly Func<TSource, TKey> projection;
readonly IEqualityComparer<TKey> comparer;

/// <summary>
/// Creates a new instance using the specified projection, which must not be null.
/// The default comparer for the projected type is used.
/// </summary>
/// <param name="projection">Projection to use during comparisons</param>
public ProjectionEqualityComparer(Func<TSource, TKey> projection)
: this(projection, null)
{
}

/// <summary>
/// Creates a new instance using the specified projection, which must not be null.
/// </summary>
/// <param name="projection">Projection to use during comparisons</param>
/// <param name="comparer">The comparer to use on the keys. May be null, in
/// which case the default comparer will be used.</param>
public ProjectionEqualityComparer(Func<TSource, TKey> projection, IEqualityComparer<TKey> comparer)
{
if (projection == null)
{
throw new ArgumentNullException("projection");
}
this.comparer = comparer ?? EqualityComparer<TKey>.Default;
this.projection = projection;
}

/// <summary>
/// Compares the two specified values for equality by applying the projection
/// to each value and then using the equality comparer on the resulting keys. Null
/// references are never passed to the projection.
/// </summary>
public bool Equals(TSource x, TSource y)
{
if (x == null && y == null)
{
return true;
}
if (x == null || y == null)
{
return false;
}
return comparer.Equals(projection(x), projection(y));
}

/// <summary>
/// Produces a hash code for the given value by projecting it and
/// then asking the equality comparer to find the hash code of
/// the resulting key.
/// </summary>
public int GetHashCode(TSource obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
return comparer.GetHashCode(projection(obj));
}
}

And here's a sample use:

var f3 = f1.Except(f2, ProjectionEqualityComparer<Foo>.Create(a => a.key));

Inline Comparer

In the case you don't want duplicate Person objects, and want to operate on that collection as a set, you can use a HashSet<Person> instead which when calling its Add method will do the check if such a person already exists. For that to work, you can implement IEquatable<Person> in your class. It would look roughly like this:

public class Person : IEquatable<Person>
{
public Person(string name)
{
Name = name;
}

public string Name { get; private set; }

public bool Equals(Person other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase);
}

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Person) obj);
}

public override int GetHashCode()
{
return (Name != null ? Name.GetHashCode() : 0);
}

public static bool operator ==(Person left, Person right)
{
return Equals(left, right);
}

public static bool operator !=(Person left, Person right)
{
return !Equals(left, right);
}
}

And now you can use it in your HashSet<Person> like this:

void Main()
{
var firstPerson = new Person { Name = "Yuval" };
var secondPerson = new Person { Name = "yuval" };
var personSet = new HashSet<Person> { firstPerson };
Console.WriteLine(personSet.Add(secondPerson)); // Will print false.
}

Note this won't give you the flexibility of multiple comparers, but this way you won't have to create a new class implementing IEqualityComparer<T>.

Is there a concise/inline way to create Set values without explicitly naming their type?

In the big picture I think this is something you might just have to get used to. The notation is a little heavier than some other languages, but OCaml modules are also much more expressive than those of other languages.

That said, you can avoid giving a name to the module using let open:

# let numbers = let open Set.Make(Int) in empty;;
val numbers : Set.Make(Int).t = <abstr>
# let morenumbers = let open Set.Make(Int) in add 14 numbers;;
val morenumbers : Set.Make(Int).t = <abstr>

At least, this works for me.

The essence is that you need to use a place in the syntax where a module name can go. In the expression syntax Set.Make(Int).t looks like a value constructor. (Or anyway this is how I interpret the behavior.)

Update

As a commenter far wiser than I has pointed out, it's good to keep in mind that this is not good style in OCaml. As I said, in the big picture you probably just want to get used to giving names to modules.

Enumerable.Except does not use my custom comparer

Similar to Marc I just tested this, everything is being called just fine, my guess is that you are caught by the LINQ deferred execution, notice the ToArray in my code.

Note, when tracing through this I noticed GetHashCode is never called on null objects in the comparer.

Keep in mind, MiscUtil has an awesome way for you to do this stuff inline, see: Can I specify my explicit type comparator inline?

Or you could adapt this to Except: Distinct list of objects based on an arbitrary key in LINQ

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1 {

public class BusinessObject {
public Guid Guid { get; set; }
}

public class BusinessObjectGuidEqualityComparer<T> : IEqualityComparer<T> where T : BusinessObject {
#region IEqualityComparer<T> Members

public bool Equals(T x, T y) {
return (x == null && y == null) || (x != null && y != null && x.Guid.Equals(y.Guid));
}

/// </exception>
public int GetHashCode(T obj) {
if (obj == null) {
throw new ArgumentNullException("obj");
}

return obj.GetHashCode();
}

#endregion
}

class Program {
static void Main(string[] args) {

var comparer = new BusinessObjectGuidEqualityComparer<BusinessObject>();

List<BusinessObject> list1 = new List<BusinessObject>() {
new BusinessObject() {Guid = Guid.NewGuid()},
new BusinessObject() {Guid = Guid.NewGuid()}
};

List<BusinessObject> list2 = new List<BusinessObject>() {
new BusinessObject() {Guid = Guid.NewGuid()},
new BusinessObject() {Guid = Guid.NewGuid()},
null,
null,
list1[0]
};

var toRemove = list1.Except(list2, comparer).ToArray();
var toAdd = list2.Except(list1, comparer).ToArray();

// toRemove.Length == 1
// toAdd.Length == 2
Console.ReadKey();
}
}
}

Overload custom comparator to std::map

 std::map<std::string, stockDictType::iterator, Comparator> stocksTicker(Comparator(stocksDict));

this defines a member function named stocksTicker that takes a stocksDict argument of type Comparator and returns a std::map.

std::map<std::string, stockDictType::iterator, Comparator> stocksTicker{Comparator{stocksDict}};

This defines a member variable stocksTicker which, by default, is initialized with a Comparator, which in turn was initialized with the member variable stocksDict.

I assume you want the second.

Your syntax was partway between the two. Whatever compiler you had got confused by this.

Live example

You should StockTicker(StockTicker &&)=delete and StockTicker& operator=(StockTicker &&)=delete, as maps containing references to their containing class are not safe to move or copy.

Generating an efficient move here is tricky. I suspect C++17 node splicing might make it possible to do it. You might have to embed a std::shared_ptr<stocksDict*> (yes, a shared pointer to a pointer), and use .key_comp to reseat the stocksDict in the target.

Using custom std::set comparator

1. Modern C++20 solution

auto cmp = [](int a, int b) { return ... };
std::set<int, decltype(cmp)> s;

We use lambda function as comparator. As usual, comparator should return boolean value, indicating whether the element passed as first argument is considered to go before the second in the specific strict weak ordering it defines.

Online demo

2. Modern C++11 solution

auto cmp = [](int a, int b) { return ... };
std::set<int, decltype(cmp)> s(cmp);

Before C++20 we need to pass lambda as argument to set constructor

Online demo

3. Similar to first solution, but with function instead of lambda

Make comparator as usual boolean function

bool cmp(int a, int b) {
return ...;
}

Then use it, either this way:

std::set<int, decltype(cmp)*> s(cmp);

Online demo

or this way:

std::set<int, decltype(&cmp)> s(&cmp);

Online demo

4. Old solution using struct with () operator

struct cmp {
bool operator() (int a, int b) const {
return ...
}
};

// ...
// later
std::set<int, cmp> s;

Online demo

5. Alternative solution: create struct from boolean function

Take boolean function

bool cmp(int a, int b) {
return ...;
}

And make struct from it using std::integral_constant

#include <type_traits>
using Cmp = std::integral_constant<decltype(&cmp), &cmp>;

Finally, use the struct as comparator

std::set<X, Cmp> set;

Online demo

LINQ GroupBy on multiple ref-type fields; Custom EqualityComparer

The problem here is that your key type is anonymous, which means you can't declare a class that implements IEqualityComparer<T> for that key type. While it would be possible to write a comparator which compared anonymous types for equality in a custom manner (via a generic method, delegates and type inference), it wouldn't be terribly pleasant.

The two simplest options are probably:

  • Make the anonymous type "just work" by overriding Equals/GetHashCode in PeriodEndDto and ComponentDto. If there's a natural equality you'd want to use everywhere, this is probably the sanest option. I'd recommend implementing IEquatable<T> as well
  • Don't use an anonymous type for grouping - use a named type, and then you can either override GetHashCode and Equals on that, or you could write a custom equality comparer in the normal way.

EDIT: ProjectionEqualityComparer wouldn't really work. It would be feasible to write something similar though - a sort of CompositeEqualityComparer which allowed you create an equality comparer from several "projection + comparer" pairs. It would be pretty ugly compared with the anonymous type though.

Any explanation why java 8 lambda expression works using Function variable and not works inline

With the variable typed to Function<Book, Integer> the compiler knows the exact generic types. This information is missing in the inline version. Specifying explicit type parameters come to help then as follows:

.sorted(
Comparator.<Book, Integer> comparing( ...

See also this answer.



Related Topics



Leave a reply



Submit