Reputation: 920
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
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
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
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
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