欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C# 中的List.Sort()--集合排序方法全面解析

程序员文章站 2022-07-02 19:14:28
在c#中,list.sort() 不仅为我们提供了默认的排序方法,还为我们提供了4种自定义排序的方法,通过默认排序方法,我们无需重写任何sort()方法的实现代码,就能对单参数类型的list数据进行单...

在c#中,list.sort() 不仅为我们提供了默认的排序方法,还为我们提供了4种自定义排序的方法,通过默认排序方法,我们无需重写任何sort()方法的实现代码,就能对单参数类型的list数据进行单一规则的排序,如果通过对这些方法进行改进我们可以轻松做到对多参数、多规则的复杂排序。

下面是c#自定义排序的4种方法:

list<t>.sort();   
list<t>.sort(icomparer<t> comparer);
list<t>.sort(int index, int count, icomparer<t> comparer);
list<t>.sort(comparison<t> comparison);

实现目标

假设存在一个people类,包含name、age属性,在客户端中创建list保存多个实例,希望对list中的内容根据name和age参数进行排序,排序规则为,先按姓名升序排序,如果姓名相同再按年龄的升序排序:

class people
{
 public people(string name, int age) { name = name; age = age; }
 public string name { get; set; } //姓名
 public int age { get; set; } //年龄
}
 
// 客户端
class client
{
 static void main(string[] args)
 {
  list<people> peoplelist = new list<people>();
  peoplelist.add(new people("张三", 22));
  peoplelist.add(new people("张三", 24));
  peoplelist.add(new people("李四", 18));
  peoplelist.add(new people("王五", 16));
  peoplelist.add(new people("王五", 30));
 }
}

方法一、对people类继承icomparable接口,实现compareto()方法

该方法为系统默认的方法,单一参数时会默认进行升序排序。但遇到多参数(name、age)排序时,我们需要对该默认方法进行修改。

方法一:people类继承icomparable接口,实现compareto()方法

icomparable<t>:定义由值类型或类实现的通用比较方法,旨在创建特定于类型的比较方法以对实例进行排序。

原理:自行实现的compareto()方法会在list.sort()内部进行元素两两比较,最终实现排序

class people : icomparable<people>
{
 public people(string name, int age) { name = name;age = age; }
 public string name { get; set; }
 public int age { get; set; }
 
 // list.sort()时会根据该compareto()进行自定义比较
 public int compareto(people other)
 {
  if (this.name != other.name)
  {
   return this.name.compareto(other.name);
  }
  else if (this.age != other.age)
  {
   return this.age.compareto(other.age);
  }
  else return 0;
 }
}
 
// 客户端
peoplelist.sort();
 
// output:
//  李四 18
//  王五 16
//  王五 30
//  张三 22
//  张三 24

方法二:增加people类的外部比较类,继承icomparer接口、实现compare()方法

区别于上述继承icomparable的方法,该方法不可在people内继承实现icomparer接口,而是需要新建比较方法类进行接口实现

方法二:新建peoplecomparer类、继承icomparer接口、实现compare()方法

原理:list.sort()将peoplecomparer类的实例作为参数,在内部使用compare()方法进行两两比较,最终实现排序(注:上述方法为compareto(),此处为compare()方法)

// 自定义比较方法类
class peoplecomparer : icomparer<people>
{
 // 区别于compareto()单参数,此处为双参数
 public int compare(people x, people y)
 {
  if (x.name != y.name)
  {
   return x.name.compareto(y.name);
  }
  else if (x.age != y.age)
  {
   return x.age.compareto(y.age);
  }
  else return 0;
 }
}
 
// 客户端
// 传入参数为自定义比较类的实例   
peoplelist.sort(new peoplecomparer());
 
// output:
//  李四 18
//  王五 16
//  王五 30
//  张三 22
//  张三 24

同理,list<t>.sort(int index, int count, icomparer<t> comparer) 方法的参数:待排元素起始索引、待排元素个数、排序方法

方法三、采用泛型委托 comparison<t>,绑定自定义的比较方法

区别于上述继承接口的方法,此方法的参数为 泛型委托 comparison<t>

委托原型:public delegate int comparison<in t>(t x, t y);

方法三:依照委托的使用方法,首先创建委托实例mycomparison,并绑定到自定义的比较方法peoplecomparison()上,最终调用list.sort()时 将委托实例传入

原理:list.sort()根据传入的委托方法,进行两两元素比较最终实现排序

// 客户端
class client
{
 // 方法0 自定义比较方法
 public static int peoplecomparison(people p1, people p2)
 {
  if (p1.name != p2.name)
  {
   return p1.name.compareto(p2.name);
  }
  else if (p1.age != p2.age)
  {
   return p1.age.compareto(p2.age);
  }
  else return 0;
 }
 
 static void main(string[] args)
 {
  / 创建list ... /
  
  // 方法0 创建委托实例并绑定
  comparison<people> mycomparison = peoplecomparison;
 
  // 传入该实例实现比较方法
  peoplelist.sort(mycomparison);
 
  // output:
  //  李四 18
  //  王五 16
  //  王五 30
  //  张三 22
  //  张三 24
 }
}

此外,既然comparison<t>是泛型委托,则完全可以用 lambda表达式 进行描述:

// lambda表达式实现comparison委托
peoplelist.sort((p1, p2) =>
{
 if (p1.name != p2.name)
 {
  return p2.name.compareto(p1.name);
 }
 else if (p1.age != p2.age)
 {
  return p2.age.compareto(p1.age);
 }
 else return 0;
});
 
// output:
//  张三 24
//  张三 22
//  王五 30
//  王五 16
//  李四 18

总结

虽然本文仅使用了list<t>一种容器对sort()方法进行阐述,但是不同容器的使用sort()的方法大相径庭,因为核心的原理都是应用两种接口及泛型委托:

两种接口:icomparable<t> 、 icomparer<t>

泛型委托:comparison<t>

参考

icomparable接口 - microsoft

comparison委托 - microsoft

icomparer接口 - microsoft

附:一个完整的测试demo

using system;
using system.collections.generic;
using system.linq;
using system.text; 
namespace listsort
{
 class program
 {
  static void displayinfo<t>(list<t> list) {
   //输出list元素内容
   foreach(var item in list) {
    system.console.write("{0} ",item.tostring());
   }
   system.console.writeline("");
  }
 
  // 方法3 自定义委托泛型比较方法
  public static int peoplecomparison(people p1, people p2)
  {
   if (p1.name != p2.name)
   {
    return p1.name.compareto(p2.name);
   }
   else if (p1.age != p2.age)
   {
    return p1.age.compareto(p2.age);
   }
   else return 0;
  }
  static void main(string[] args)
  {
   list<people> peoplelist = new list<people>();
   peoplelist.add(new people("张三", 22));
   peoplelist.add(new people("张三", 24));
   peoplelist.add(new people("李四", 18));
   peoplelist.add(new people("王五", 16));
   peoplelist.add(new people("王五", 30));
 
   system.console.writeline("排序前原始数据:");
   displayinfo(peoplelist);
   system.console.writeline("------------------------------------");
 
   system.console.writeline("方法1排序后数据:");
   peoplelist.sort();
   displayinfo(peoplelist);
 
   system.console.writeline("方法2排序后数据:");
   displayinfo(peoplelist);
 
   // 方法1 使用icomparer<t>接口。
   peoplelist.sort(new peoplecomparer());
 
   // 方法2 除以上两种方法以外还可以使用另一种方法,在people类中实现icomparable<t>
   peoplelist.sort();
   system.console.writeline("方法3排序后数据:");
   displayinfo(peoplelist);
 
   // 方法3 创建泛型委托实例并绑定
   comparison<people> mycomparison = peoplecomparison;
 
   // 传入该实例实现比较方法
   peoplelist.sort(mycomparison);
 
   system.console.writeline("方法3排序后数据:");
   displayinfo(peoplelist);
 
   // 方法3 使用comparison<t>委托,lambda写法
   peoplelist.sort((left, right) =>
   {
    //先按姓名排序,如果姓名相同再按年龄排序
    int x = left.name.compareto(right.name);
    if(x==0) {
     if (left.age > right.age)
      x = 1;
     else if (left.age == right.age)
      x = 0;
     else
      x = -1;
    }
    return x;
   }); 
  }
 }
 
  //方法一
 public class people : icomparable<people>
 {
  public int age { get;set;}
  public string name { get;set;}
  public people(string name,int age) {
   this.name = name;
   this.age = age;
  }
 
  public override string tostring() {
   string result = "";
   result = "["+this.name+","+ this.age.tostring()+"]";
   return result; 
  }
 
  public int compareto(people other)
  {
   int x = this.name.compareto(other.name);
   if(x==0) {
    if (this.age > other.age)
     x = 1;
    else if (this.age == other.age)
     x = 0;
    else
     x = -1;
   }
   return x;
  }
 }
 
 //方法二
 public class peoplecomparer : icomparer<people>
 {
  public int compare(people left, people right)
  {
   int x = left.name.compareto(right.name);
   if(x==0) {
    if (left.age > right.age)
     x = 1;
    else if (left.age == right.age)
     x = 0;
    else
     x = -1;
   }
   return x;
  }
 } 
} 

补充:c# icomparable和icomparer接口和自定义比较器

前言

arraylist里面有一个方法:

public virtual void sort(icomparer comparer);

使用指定的比较器对整个 system.collections.arraylist 中的元素进行排序。

comparer:比较元素时要使用的 system.collections.icomparer 实现。

啥玩意啊?

正文

1.comparer类简单介绍

想弄清楚这个,我们先来看看这么一个类。

在system.collections名称空间中,有这么一个类:comparer。顾名思义,他可以实现对简单类型的比较,什么意思呢?来看如下代码:

int a=1,b=2;

正常情况下,我们要怎样比较他们的大小?if,运算符,……?这当然可以,不过comparer已经给我们提供了一个函数,可以直接使用:(需要using system.collections;)

console.writeline(comparer.default.compare(a,b));

因为a<b,所以控制台会输出-1。(这个函数总是返回-1,0,1三个值。)

这里通过comparer里的静态属性default获得comparer的实例调用了comparer里的非静态函数compare。

(还可以比较根据字母比较两个string类型,这里就省略介绍了)

2.自定义比较器,icomparable,icomparer接口

当然,这个类不仅仅只是用来比较两个数的大小的。有时候我们想直接比较两个对象,但是引用里面的属性或许比较麻烦。尤其是参考要素过多,不好直接比较的时候,怎样才能更高效地比较两个对象呢?这时候,我们就需要自定义比较器了。

首先来介绍icomparable接口。这个接口里只有一个方法compareto()。让你的类实现这个接口的compareto方法,就可以直接调用这个方法和另一个对象比较。下面是例子:

public class classtest : icomparable
{
 public int inttest;
 public int compareto(object obj)
 {
 return inttest-((classtest)obj).inttest;
 //这里的代码可以按需要自己编写,这里只是一个简单的示例
 }
}

然后就可以直接使用啦:

classtest a = new classtest(){inttest=1};
classtest b = new classtest(){inttest=2};
console.writeline(a.compareto(b));//输出-1
comparer类已经为我们提供了icomparer的默认实现,但我们仍然可以自定义它。新建一个类:(记得using system.collections;)
public class classtestcomparer : icomparer
{
 public static icomparer default = new classtestcomparer();
 //这里必须使用这样的定义,将对象转化为icomparer类型有很大用处,下面会介绍
 public int compare(object a,object b)
 {
 return ((classtest)a).inttest - ((classtest)b).inttest;
 //同样这里使用最简单的示例,但是你可以大放异彩
 }
}

注意,如果用于比较的类和设定的类不一样,就会出现错误。

使用示例:

classtest a = new classtest(){inttest=1};
classtest b = new classtest(){inttest=2};
console.writeline(classtestcomparer.default.compare(a,b));
//结果是-1

可以发现,这两个接口的不同之处在于:icomparable在要比较的对象的类中实现,可以比较该对象和另一个对象。icomparer在一个单独的类中实现,可以比较任意两个对象(关键是你的设置)。

3.对集合排序

当然,这两个接口还有更强大的用处。我们可以使用这两个接口对集合进行排序。还记得前言里的sort()方法吗?接下来就以arraylist为例,介绍如何使用。

arraylist classtests = new arraylist();
classtest a = new classtest(){inttest=1};
classtest b = new classtest(){inttest=2};
classtest c = new classtest(){inttest=3};
classtests.add(a);
classtests.add(b);
classtests.add(c);
classtests.sort();
//使用无参的sort,将调用类中的compareto()方法,因为classtest实现了这个方法,所以是可以调用的。如果没有实现,编译器会报错。
classtests.sort(classtestcomparer.default);
//这将使用compare()方法对集合中的元素排序。classtestcomparer类实现了这个方法,并且提供了一个icomparer类型的属性。

需要注意的是:

两个接口提供的方法返回值都是int类型的,负数代表小于,0代表等于,正数代表大于。所以对数字之外的自定义比较器,需要人工设定什么是“大”,什么是“小”。所以上文示例中两个数直接相减,就可以比较大小。

排序完之后,按照返回的int值,集合是由小到大排列的。

使用无参sort()时,集合中至少要有一个类实现了icomparable,否则会报错。

一般来说,都是对同一个类进行比较。不过,也可以实现对不同类比较的代码,这就看具体需要了。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。如有错误或未考虑完全的地方,望不吝赐教。