1. 源起:
KV 7.0加入列表管理功能,处理排序问题时,对空列表执行按大小、日期、长度排序时,其中次序会发生改变,令人纳闷。
没天理呀,不应该啊!List.Sort()方法,它为什么?
对此问题,深入去了解,倒发现了有趣的问题:稳固排序与非稳固排序。
2、稳固排序与非稳固排序
在微软官方网站找到此段说明:
Remarks
If comparison is provided, the elements of the List<T> are sorted using the method represented by the delegate.
If comparison is null, an ArgumentNullException is thrown.
This method uses Array.Sort, which applies the introspective sort as follows:
- If the partition size is fewer than 16 elements, it uses an insertion sort algorithm
- If the number of partitions exceeds 2 * LogN, where N is the range of the input array, it uses a Heapsort algorithm.
- Otherwise, it uses a Quicksort algorithm.
This implementation performs an unstable sort; that is, if two elements are equal, their order might not be preserved. In contrast, a stable sort preserves the order of elements that are equal.
On average, this method is an O(n log n) operation, where n is Count; in the worst case it is an O(n ^ 2) operation.
大意是此实现将执行不稳定排序。也就是说,如果两个元素相等,则可能不会保留其顺序。
我们建Demo做验证,用例如下:
var list = new List<string>(); list.AddRange(new string[] { "3", "2", "1", "4" }); list.Sort((x, y) => { return 0; }); foreach (string s in list) Console.WriteLine(s);
其输出为:
验证上述结果,推测它是使用了二分法快速反序,把后半部分置前了。
可八辈的我们宁愿它输入为原始顺序3214,这个合理,但是,可是,它无耻的变了……
3、实际场景
比如,我们需要对特定的对象进行排序,如下摘取网上例子稍加修正,以做示例:
static void Main(string[] args) { var p1 = new Person() { Name = "Abby", Age = 38 }; var p4 = new Person() { Name = "Jason", Age = 23 }; var p2 = new Person() { Name = "Bob", Age = 23 }; var p3 = new Person() { Name = "Charlie", Age = 23 }; var p5 = new Person() { Name = "Danielle", Age = 18 }; var list = new List<Person>(); list.Add(p1); list.Add(p2); list.Add(p3); list.Add(p4); list.Add(p5); list.Sort(); Console.WriteLine("Unstable List Sort:"); foreach (Person p in list) Console.WriteLine(p); Console.ReadLine(); } class Person : IComparable { public string Name { get; set; } public int Age { get; set; } public int CompareTo(object obj) { int result = 1; if (obj != null && obj is Person) { var person = (Person)obj; result = this.Age.CompareTo(person.Age);; } return result; } public override string ToString() { return String.Format("{0} - {1}", this.Name, this.Age); } }
其输出为:
其中间23岁之三项,没有按Jason、Bob、Charlie的次序来,甚至也不是倒序。
我就是想要它加入顺序不变,怎么办?
对此问题,搜出一些文章,有建议用LINQ排序因为它是稳固排序,可我们工程所用.net framework 2.0,不支持,只好做罢。
在stackoverflow上,碰到一群难兄难弟,诸般建议阅过,找到可用方法,加索引!
4、解决方法
且修改Person类,加入SortIndex属性如下,并修正其比较函数:
class Person : IComparable { public string Name { get; set; } public int Age { get; set; } public int SortIndex { get; set; } public int CompareTo(object obj) { int result = 1; if (obj != null && obj is Person) { var person = (Person)obj; result = this.Age.CompareTo(person.Age); if (result == 0) result = this.SortIndex.CompareTo(person.SortIndex); } return result; } ... }
初始化时,加入其索引:
var p1 = new Person() { Name = "Abby", Age = 38, SortIndex = 0 }; var p4 = new Person() { Name = "Jason", Age = 23, SortIndex = 1 }; var p2 = new Person() { Name = "Bob", Age = 23, SortIndex = 2 }; var p3 = new Person() { Name = "Charlie", Age = 23, SortIndex = 3 }; var p5 = new Person() { Name = "Danielle", Age = 18, SortIndex = 4 };
输出顺序:
保留初始顺序,解决问题。
后记:
之前未曾留意,此问题发现倒是很有意思,度娘不给力,解决问题还是要google,在stackoverflow上找到有同困扰之人,得其可参考方案。
此场景,通常用于自定义数据结构比较,如KV项目之空白列表,当其可比较项相等时,应该保留其原始顺序,可是被改变了,导致寻求解决问题的方法。
但官方方案固定,不能改变情况下,做个曲线救国,加另一索引以固定次序,虽然看来费些点工夫,但能解决问题,不失为好的可用方法。
参考文档:
List(T).Sort Method (Comparison(T)) (System.Collections.Generic)
Why does List<T>.Sort method reorder equal IComparable<T> elements?