DrSammyD
DrSammyD

Reputation: 920

Array distinct lambda comparisons with preference

I have data with the following shape

someArray = [{ Name: "Some Class", TypeEnum: "Default" },
 { Name: "Some Class", TypeEnum: "Other" },
 { Name: "Some Class 2", TypeEnum: "Default" },
 { Name: "Some Class 2", TypeEnum: "Other" },
 { Name: "Some Class 3", TypeEnum: "Default" },
 { Name: "Some Class 4", TypeEnum: "Not Other" }]

Imagine each of those as objects in C#

What I need is an array of distinct versions of that array, with preference given to a selected TypeEnum. For example if I have selected the TypeEnum of other, I still want it to default to Default if it can't find a version of that class with the "Other" TypeEnum

e.g. With "Other" selected as the Type enum, the above data would look like

 [{ Name: "Some Class", TypeEnum: "Other" },
 { Name: "Some Class 2", TypeEnum: "Other" },
 { Name: "Some Class 3", TypeEnum: "Default" }]

What I'm doing now is a lambda comparison from here

TypeEnum myEnum = "Other"
someArray.Distinct((x,y) => x.Name == y.Name && 
                   x.TypeEnum != myEnum && 
                   (y.TypeEnum == myEnum || y.TypeEnum == "Default"));

I'm hoping that Distinct pops out any x from the array that get's a true from that expression.

Am I wrong in how I think Distinct works. If I am, what should I use instead?

Upvotes: 3

Views: 466

Answers (4)

Fung
Fung

Reputation: 3558

You can define a Comparer<T> class to handle your preference for comparison, like this:

public class SomeClassComparer : Comparer<SomeClass>
{
    private TypeEnum _preference;

    public SomeClassComparer(TypeEnum preference)
        : base()
    {
        _preference = preference;
    }

    public override int Compare(SomeClass x, SomeClass y)
    {
        if (x.Name.Equals(y.Name))
        {
            return x.TypeEnum == y.TypeEnum ? 0
                : x.TypeEnum == _preference ? -1
                : y.TypeEnum == _preference ? 1
                : x.TypeEnum == TypeEnum.Default ? -1
                : y.TypeEnum == TypeEnum.Default ? 1
                : x.TypeEnum.CompareTo(y.TypeEnum);
        }
        else
            return x.Name.CompareTo(y.Name);
    }
}

UPDATE: If you're only interested in the elements with your preferred or Default TypeEnum, you could filter out the rest first. Then sort the array according to the Comparer, i.e. giving preferred TypeEnum higher precedence than Default. Finally group the objects by their Name, and take the first one from each group:

var result = someArray.Where(x => x.TypeEnum == TypeEnum.Default || x.TypeEnum == myEnum)
                      .OrderBy(x => x, new SomeClassComparer(myEnum))
                      .GroupBy(x => x.Name)
                      .Select(x => x.First());

Or you can use the following version if you don't want to define a Comparer class:

Comparison<SomeClass> compareByTypeEnum = (x, y) =>
{
    if (x.Name.Equals(y.Name))
    {
        return x.TypeEnum == y.TypeEnum ? 0
            : x.TypeEnum == myEnum ? -1
            : y.TypeEnum == myEnum ? 1
            : x.TypeEnum == TypeEnum.Default ? -1
            : y.TypeEnum == TypeEnum.Default ? 1
            : x.TypeEnum.CompareTo(y.TypeEnum);
    }
    else
        return x.Name.CompareTo(y.Name);
};
Array.Sort(someArray, compareByTypeEnum);
var result = someArray.Where(x => x.TypeEnum == TypeEnum.Default || x.TypeEnum == TypeEnum.Other)
                      .GroupBy(x => x.Name)
                      .Select(x => x.First());

Upvotes: 2

TylerOhlsen
TylerOhlsen

Reputation: 5578

Distinct does not work the way you want and here's why you can probably never get it to work by itself. Distinct is using a hash table to find the unique values. It adds each item to the hash table in order and omits adding any values that are hash equal to any other value that's already in the hash table.

This means that the order of your items matters because the first item in the array wins. We can use this to our advantage though by modifying ordering the list before calling distinct. Modifying @Fung's solution a little bit we get this...

var result = someArray.OrderBy(key => key.TypeEnum, new TypeEnumComparer(myEnum))
    .Distinct(new LambdaEqualityComparer<SomeClass>((x, y) => x.Name == y.Name));

With a modified comparer...

public class TypeEnumComparer : Comparer<TypeEnum>
{
    private TypeEnum _preference;

    public TypeEnumComparer(TypeEnum preference)
        : base()
    {
        _preference = preference;
    }

    public override int Compare(TypeEnum x, TypeEnum y)
    {
        if (x == y)                return 0;
        if (x == _preference)      return -1;
        if (y == _preference)      return 1;
        if (x == TypeEnum.Default) return -1;
        if (y == TypeEnum.Default) return 1;

        return x.CompareTo(y);
    }
}

Upvotes: 1

Prasad Kanaparthi
Prasad Kanaparthi

Reputation: 6563

Try this, use DefaultIfEmpty("Default")

        var someArray = new List<TestClass>
                            {
                                new TestClass {Name = "Some Class", TypeEnum = "Default"},
                                new TestClass {Name = "Some Class", TypeEnum = "Other"},
                                new TestClass {Name = "Some Class 2", TypeEnum = "Default"},
                                new TestClass {Name = "Some Class 2", TypeEnum = "Other"},
                                new TestClass {Name = "Some Class 3", TypeEnum = "Default"}
                            };

        string myEnum = "Other";

        var result = someArray.GroupBy(t => t.Name).
                     Select(t => new TestClass
                       {
                           Name = t.Key,
                           TypeEnum = t.Select(s => s.TypeEnum).Where(p => p == myEnum).DefaultIfEmpty("Default").FirstOrDefault()
                       });

Upvotes: 0

nawfal
nawfal

Reputation: 73183

You can use a GroupBy to get a dictionary to do this kind of work. Probably its much simpler too.

List<Tuple<string, string>> lst = new List<Tuple<string, string>>();
lst.Add(new Tuple<string, string>("Some Class", "Default"));
lst.Add(new Tuple<string, string>("Some Class", "Other"));
lst.Add(new Tuple<string, string>("Some Class 2", "Default"));
lst.Add(new Tuple<string, string>("Some Class 2", "Other"));
lst.Add(new Tuple<string, string>("Some Class 3", "Default"));

var dict = lst.GroupBy(g => g.Item1)
              .ToDictionary(g => g.Key, k => k.Select(s => s.Item2)
                                              .Where(p => p == "Other")
                                              .DefaultIfEmpty("Default")
                                              .First());

Or in your case:

TypeEnum myEnum = "Other"
var dict = lst.GroupBy(g => g.Name)
    .ToDictionary(g => g.Key, k => k.Select(s => s.TypeEnum)
                                .Where(p => p == myEnum)
                                .DefaultIfEmpty("Default")
                                .First());

Upvotes: 1

Related Questions