[英]C#: Comparing two objects by multiple properties in SortedSet
I'd like to compare two objects by multiple properties.我想通过多个属性比较两个对象。
Let's say I have a class called Student, and every student has name and score.假设我有一个名为 Student 的班级,每个学生都有姓名和分数。 My wish is to create a SortedSet( implement a Comparer here ), so whenever I add a Student into the collection they will be sorted by their score, and if they have the same score they will be sorted by their name alphabetically.
我的愿望是创建一个 SortedSet(在这里实现一个比较器),所以每当我将一个学生添加到集合中时,他们将按他们的分数排序,如果他们有相同的分数,他们将按他们的名字字母顺序排序。
Java 8 equivalent of this would be: Java 8 相当于:
TreeSet<Student> students = new TreeSet<>(
Comparator.comparing(Student::getScore).thenComparing(Student::getName)
);
Is this possible using Comparer<Student>.Create()
or in any other way?这是否可以使用
Comparer<Student>.Create()
或以任何其他方式?
You can use SortedSet
with a custom comparer.您可以将
SortedSet
与自定义比较器一起使用。 Implementation is a little trickier:实现有点棘手:
var s = new SortedSet<Student>(
Comparer<Student>.Create((a, b) => {
// This code implements comparison by score first
var res= a.Score.CompareTo(b.Score);
// Ties are resolved by name in alphabetic order
return res != 0 ? res : a.Name.CompareTo(b.Name);
})
);
Two quick examples I put together, you could use an IComparer<>
of type Student
.我把两个简单的例子放在一起,你可以使用
IComparer<>
类型的Student
。 You would then give it the comparisons in the order required, as such:然后,您将按照所需的顺序对其进行比较,如下所示:
class Program
{
static void Main(string[] args)
{
var set = new SortedSet<Student>(new StudentComparer());
set.Add(new Student {Name = "Test", Score = 10});
set.Add(new Student { Name = "Tom", Score = 5 });
set.Add(new Student { Name = "Adam", Score = 90 });
set.Add(new Student { Name = "Adam", Score = 85 });
foreach (var setItem in set)
{
Debug.WriteLine($@"{setItem.Name} - {setItem.Score}");
}
/* outputs:
Tom - 5
Test - 10
Adam - 85
Adam - 90
*/
}
}
class Student
{
public string Name { get; set; }
public int Score { get; set; }
}
class StudentComparer : IComparer<Student>
{
public int Compare(Student x, Student y)
{
var result = x.Score.CompareTo(y.Score);
if (result == 0)
{
result = x.Name.CompareTo(y.Name);
}
return result;
}
}
You could also use a normal List, and use Linq:您也可以使用普通列表,并使用 Linq:
var students = new List<Student>
{
new Student {Name = "Test", Score = 10},
new Student {Name = "Tom", Score = 5},
new Student {Name = "Adam", Score = 90},
new Student {Name = "Adam", Score = 85}
};
var orderedList = students.OrderByDescending(s => s.Score)
.ThenBy(s => s.Name);
foreach (var student in orderedList)
{
Debug.WriteLine($@"{student.Name} - {student.Score}");
}
/* outputs:
Adam - 90
Adam - 85
Test - 10
Tom - 5
*/
You can use SortedSet
generic and provide it with an IComparer<Student>
that does the comparison based on the order you want, ie, first the scores are compared and if they are the same you compare the names.您可以使用
SortedSet
generic 并为它提供一个IComparer<Student>
,它根据您想要的顺序进行比较,即,首先比较分数,如果它们相同,则比较名称。
class Program
{
static void Main(string[] args)
{
var students = new List<Student>()
{
new Student()
{
Score = 10,
Name = "David"
},
new Student()
{
Score = 4,
Name = "Nik"
},
new Student()
{
Score = 10,
Name = "Randy"
}
};
SortedSet<Student> sortedStudents = new SortedSet<Student>(new StudentMultiCriteria());
foreach (var student in students)
{
sortedStudents.Add(student);
}
foreach (var sortedStudent in sortedStudents)
{
Console.WriteLine(sortedStudent);
}
}
}
class Student
{
public int Score { get; set; }
public string Name { get; set; }
public override string ToString()
{
return $"Score {Score}, Name {Name}";
}
}
class StudentMultiCriteria : IComparer<Student>
{
public int Compare(Student x, Student y)
{
// You do the comparison based on different fields here
return x.Score.CompareTo(y.Score) == 0 ? x.Name.CompareTo(y.Name) : x.Score.CompareTo(y.Score);
}
}
My bad, in my comment I should of said Icomparable<Student>
.我的不好,在我的评论中我应该说
Icomparable<Student>
。 This will allow the sorted collection to use a default comparer.这将允许排序集合使用默认比较器。 Also It seems to me that when sorting like this, generally the scores will be sorted descending but the names ascending.
另外在我看来,当这样排序时,通常分数会降序排序,但名称会升序。 Therefore your class could look like this:
因此,您的课程可能如下所示:
class Student : IComparable<Student>
{
public int score = 0;
public string name = "";
public int CompareTo(Student other)
{
if (score == other.score)
{
return name.CompareTo(other.name);
}
else
{
return other.score.CompareTo(score);
}
}
}
static void Main()
{
SortedSet<Student> students = new SortedSet<Student>();
students.Add(new Student { score = 20, name = "abcd" });
students.Add(new Student { score = 10, name = "bcde" });
students.Add(new Student { score = 10, name = "acde" });
students.Add(new Student { score = 30, name = "cdef" });
students.Add(new Student { score = 10, name = "abce" });
}
The result would look something like this:结果看起来像这样:
30,"cdef" 30,"cdef"
20,"abcd" 20,"abcd"
10,"abce" 10、“abce”
10,"acde" 10、“阿德”
10,"bcde" 10、"bcde"
You could write a generic comparer to mimic that API:您可以编写一个通用比较器来模仿该 API:
public class Comparator<T> : Comparer<T>
{
readonly List<Func<T, T, int>> m_comparisons = new List<Func<T, T, int>>();
Comparator()
{
}
public static Comparator<T> Comparing<TProp>(Func<T, TProp> property)
where TProp : IComparable<TProp>
{
return new Comparator<T>().ThenComparing(property);
}
public static Comparator<T> ComparingDescending<TProp>(Func<T, TProp> property)
where TProp : IComparable<TProp>
{
return new Comparator<T>().ThenComparingDescending(property);
}
public static Comparator<T> Comparing<TProp>(Func<T, TProp> property, IComparer<TProp> propertyComparer)
{
return new Comparator<T>().ThenComparing(property, propertyComparer);
}
public static Comparator<T> ComparingDescending<TProp>(Func<T, TProp> property, IComparer<TProp> propertyComparer)
{
return new Comparator<T>().ThenComparingDescending(property, propertyComparer);
}
public Comparator<T> ThenComparing<TProp>(Func<T, TProp> property)
where TProp : IComparable<TProp>
{
return ThenComparing(property, Comparer<TProp>.Default);
}
public Comparator<T> ThenComparingDescending<TProp>(Func<T, TProp> property)
where TProp : IComparable<TProp>
{
return ThenComparingDescending(property, Comparer<TProp>.Default);
}
public Comparator<T> ThenComparing<TProp>(Func<T, TProp> property, IComparer<TProp> propertyComparer)
{
m_comparisons.Add((t1, t2) => propertyComparer.Compare(property(t1), property(t2)));
return this;
}
public Comparator<T> ThenComparingDescending<TProp>(Func<T, TProp> property, IComparer<TProp> propertyComparer)
{
m_comparisons.Add((t1, t2) => propertyComparer.Compare(property(t2), property(t1)));
return this;
}
public override int Compare(T x, T y)
{
foreach (Func<T, T, int> comparison in m_comparisons)
{
int result = comparison(x, y);
if (result != 0)
{
return result;
}
}
// They are equal.
return 0;
}
}
Usage:用法:
SortedSet<Student> students = new SortedSet<Student>(Comparator
.Comparing(x => Score)
.ThenComparing(x => x.Name)); // etc.
I find this much more readable and safer than building up a big lambda using Comparer<Student>.Create
.我发现这比使用
Comparer<Student>.Create
构建一个大的 lambda 更具可读性和安全Comparer<Student>.Create
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.