简体   繁体   English

C#:通过 SortedSet 中的多个属性比较两个对象

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM