65.9K
CodeProject 正在变化。 阅读更多。
Home

在 C# 和 VB.NET 中按任意列对矩形数组进行排序

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2016年4月12日

CPOL

4分钟阅读

viewsIcon

14179

downloadIcon

339

以下文章源于分享对一个关于排序矩形数组的漂亮技巧的深度重新阐述的愿望。

引言

.NET 框架为我们提供了一种非常简单方便的方式来排序数组 - Array 类的 static Sort 方法。此方法的唯一限制是它仅适用于一维数组。当然,这包括复杂对象或类的数组,但它们仍然需要是一维数组。因此,除非我们愿意编写一个自定义过程来排序矩形数组,否则我们需要依赖 Array.Sort 方法。

排序一维数组

当我们需要对复杂类型的一维数组进行排序时,一个很好的方法是编写一个实现 IComparer 接口的类,并将该类的实例传递给 Array.Sort 方法。让我们看一个快速的例子。假设我们有一个简单的类,其中包含一个国家名称及其人口

public class CCountry {

   public CCountry(string Name, long Pop)   {
      this.Name = Name;
      this.Population = Pop;
   }
   
   public string Name { get; set; }
   public long Population { get; set; }

   public string ToString() {
      return string.Format("Country: {0} - Population {1}", this.Name, this.Population);
   }
}

我们希望创建一个该类实例的数组,然后按国家/地区名称对其进行排序。为了实现这一点,我们可以编写一个实现 IComparer 接口的简单类,并在 Compare 函数内部比较两个 CCountry 对象的 Name 属性

public class CCountryComparer : IComparer{

   public int Compare(object x, object y)   {
      CCountry o1 = (CCountry)x;
      CCountry o2 = (CCountry)y;

      // compare the items CountryName property
      return string.Compare(o1.Name, o2.Name);

   }
}

可以确定的是,这两行代码

CCountry o1 = (CCountry)x;
CCountry o2 = (CCountry)y;

并不是绝对必要的,但是将这两个参数强制转换为特定类型将确保此特定实现 IComparer 接口仅与正确类型的列表一起使用。

Array.Sort 使用我们对 IComparer.Compare 的实现来决定任意两个对象中哪个“最大”,并执行适当的排序。另请注意,此比较直接在两个对象上进行。

此时,我们可以加载我们的数组,创建 CCountryComparer 类的实例,并在 Array.Sort 方法中使用它,如下面的代码所示(免责声明:所列国家/地区和人口数据是从维基百科上发布的列表中随机抽取的)。

private void Form1_Load(object sender, EventArgs e) {
	CCountry obj = default(CCountry);
	CCountry[] oAr = new CCountry[11];
	int k = 0;

	obj = new CCountry("Indonesia", 258705000);
	oAr[k] = obj; k += 1;
	obj = new CCountry("Brazil", 205823000);
	oAr[k] = obj; k += 1;
	obj = new CCountry("Japan", 126920000);
	oAr[k] = obj; k += 1;
	obj = new CCountry("Mexico", 122273500);
	oAr[k] = obj; k += 1;
	obj = new CCountry("Philippines", 103003900);
	oAr[k] = obj; k += 1;
	obj = new CCountry("Haiti", 11078033);
	oAr[k] = obj; k += 1;
	obj = new CCountry("Bolivia", 10985059);
	oAr[k] = obj; k += 1;
	obj = new CCountry("Sweden", 9858794);
	oAr[k] = obj; k += 1;
	obj = new CCountry("Austria", 8699730);
	oAr[k] = obj; k += 1;
	obj = new CCountry("Yemen", 25956000);
	oAr[k] = obj; k += 1;
	obj = new CCountry("Kazakhstan", 17670900);
	oAr[k] = obj;

	CCountryComparer Comp = new CCountryComparer();

	Array.Sort(oAr, Comp);

	for (k = 0; k <= oAr.Length - 1; k++) {
		Console.WriteLine(oAr[k].ToString());
	}
}

排序矩形数组

我们将检查一个对矩形数组进行排序的解决方案,该方案将遵循相同的路径,但略有不同。

  • 一维数组将是一个简单的整数数组,表示主数组的索引。
  • 将实现 IComparer 接口的类将比较与我们的索引数组对应的矩形数组的元素。

我们将调用我们对 IComparer 接口的实现 ArrayComparerArrayComparer 类将需要对主数组的引用才能执行比较。因此,我们将添加一个构造函数,该构造函数将接受主数组和我们希望排序的列号。在 Compare 函数中,我们将比较主数组元素的值。请注意,为了使类更灵活,参数 ArrayTosort 已声明为 object[,].

public class ArrayComparer : IComparer {
   
   // maintain a reference to the 2-dimensional array being sorted
   private object[,] sortArray;
   private int mSortByColumn;


   public ArrayComparer(object[,] ArrayToSort, int SortByColumn) {
      if (SortByColumn <= ArrayToSort.GetUpperBound(1)) {
         mSortByColumn = SortByColumn;
      }
      else  {
         throw new Exception(
		"Argument of SortByColumn exceeds the number of columns of the array.");
      }
      sortArray = ArrayToSort;
   }

   public int Compare(object x, object y) {
      // x and y are row number indexes
      int i1 = (int)x;
      int i2 = (int)y;

      // compare the items in the sortArray
      return (sortArray[i1, mSortByColumn] as IComparable).CompareTo(sortArray[i2, mSortByColumn]);
   }
}

然后,我们将索引数组和 ArrayComparer 类的实例提供给 Array.Sort 方法。我们返回的结果是一个 Indexes 数组,其值遵循主数组按所选列的排序顺序。

private void Form1_Load(object sender, EventArgs e)
{
	int Rows = 20;
	int Cols = 2;
	object[,] oAr = new object[Rows, Cols];
	int COL_NAME = 0;
	int COL_POP = 1;

	oAr[0, COL_NAME] = "Indonesia"; oAr[0, COL_POP] = 258705000;
	oAr[1, COL_NAME] = "Brazil"; oAr[1, COL_POP] = 205823000;
	oAr[2, COL_NAME] = "Japan"; oAr[2, COL_POP] = 126920000;
	oAr[3, COL_NAME] = "Mexico"; oAr[3, COL_POP] = 122273500;
	oAr[4, COL_NAME] = "Philippines"; oAr[4, COL_POP] = 103003900;
	oAr[5, COL_NAME] = "Colombia"; oAr[5, COL_POP] = 48584700;
	oAr[6, COL_NAME] = "Kenya"; oAr[6, COL_POP] = 47251000;
	oAr[7, COL_NAME] = "Spain"; oAr[7, COL_POP] = 46423064;
	oAr[8, COL_NAME] = "Nepal"; oAr[8, COL_POP] = 28431500;
	oAr[9, COL_NAME] = "Yemen"; oAr[9, COL_POP] = 25956000;
	oAr[10, COL_NAME] = "North Korea"; oAr[10, COL_POP] = 25281000;
	oAr[11, COL_NAME] = "Burkina Faso"; oAr[11, COL_POP] = 19034397;
	oAr[12, COL_NAME] = "Kazakhstan"; oAr[12, COL_POP] = 17670900;
	oAr[13, COL_NAME] = "Netherlands"; oAr[13, COL_POP] = 17000720;
	oAr[14, COL_NAME] = "South Sudan"; oAr[14, COL_POP] = 12131000;
	oAr[15, COL_NAME] = "Somalia"; oAr[15, COL_POP] = 11079000;
	oAr[16, COL_NAME] = "Haiti"; oAr[16, COL_POP] = 11078033;
	oAr[17, COL_NAME] = "Bolivia"; oAr[17, COL_POP] = 10985059;
	oAr[18, COL_NAME] = "Sweden"; oAr[18, COL_POP] = 9858794;
	oAr[19, COL_NAME] = "Austria"; oAr[19, COL_POP] = 8699730;

	ArrayComparer ArComparer = new ArrayComparer(oAr, COL_NAME);

	int[] Indexes = new int[oAr.GetUpperBound(0) + 1];
	int Index = 0;
	int i;
	for (i = 0; i <= Indexes.Length - 1; i++) {
		Indexes[i] = i;
	}

	Array.Sort(Indexes, ArComparer);

	for (i = 0; i <= Indexes.Length - 1; i++) {
		Index = Indexes[i];
		Console.WriteLine(string.Format("Country: {0} - Population {1}", 
				oAr[Index, 0], oAr[Index, 1]));
		
	}
}

增强功能

正如您所看到的,主数组实际上并没有排序。排序顺序保留在 Indexes() 数组中,并且需要通过 Indexes() 数组访问主数组,如该循环所示

for (i = 0; i <= Indexes.Length - 1; i++) {
	Index = Indexes[i];
	Console.WriteLine(string.Format("Country: {0} - Population {1}", oAr[Index, 0], oAr[Index, 1]));
	
}

要解决此限制,我们只需要引入一个稍微复杂版本的 ArrayComparer 类,并添加一个名为 GetSortedArray 的新函数,该函数将返回排序后的主数组。下面代码所示的函数应该是不言自明的。

最后,作为奖励,我们向构造函数添加一个参数以选择排序类型,升序或降序。因此,我们还将添加两个 private 函数,一个用于升序排序,另一个用于降序排序,并且我们将使用委托函数来选择使用哪个(参见下面的代码)。

关于这两个 private 函数 (CompareAscCompareDesc) 的一个小说明:由于数组被声明为 object,除非将其转换为 IComparable 对象,否则我们无法使用 CompareTo() 方法比较数组的两个元素。这行代码...

return (sortArray[i1, mSortByColumn] as IComparable).CompareTo(sortArray[i2, mSortByColumn])

...正是这样做的。这是代码

public class ArrayComparer : IComparer
{
   // maintain a reference to the 2-dimensional array being sorted

   private object[,] sortArray;

   private int mSortByColumn;
   private delegate int ComparerType(object x, object y);

   ComparerType UseFunction;

   //constructor initializes the sortArray reference

   public ArrayComparer(object[,] ArrayToSort, int SortByColumn, SortOrder SortOrder){
      if (SortOrder == SortOrder.Ascending) {
         UseFunction = new ComparerType(CompareAsc);
      }
      else if (SortOrder == SortOrder.Descending) {
         UseFunction = new ComparerType(CompareDesc);
      }

      sortArray = ArrayToSort;
      if (SortByColumn <= ArrayToSort.GetUpperBound(1)) {
         mSortByColumn = SortByColumn;
      }
      else {
         throw new Exception("Argument of SortByColumn exceeds the number of columns of the array.");
      }
   }

   public int Compare(object x, object y) {
      return UseFunction(x, y);
   }

   private int CompareAsc(object x, object y) {
      // x and y are integer row numbers into the sortArray
      int i1 = (int)x;
      int i2 = (int)y;
      if (sortArray[i1, mSortByColumn] == null) return -1;
      if (sortArray[i2, mSortByColumn] == null) return 1;

      // compare the items in the sortArray
      return (sortArray[i1, mSortByColumn] as IComparable).CompareTo(sortArray[i2, mSortByColumn]);
   }

   private int CompareDesc(object x, object y) {
      // x and y are integer row numbers into the sortArray
      int i1 = (int)x;
      int i2 = (int)y;
      if (sortArray[i1, mSortByColumn] == null) return 1;
      if (sortArray[i2, mSortByColumn] == null) return -1;
      // compare the items in the sortArray
      return (sortArray[i2, mSortByColumn] as IComparable).CompareTo(sortArray[i1, mSortByColumn]);
   }

   public object[,] GetSortedArray(int[] Indexes) {
      int MaxColNum = sortArray.GetUpperBound(1);
      int MaxRowNum = sortArray.GetUpperBound(0);
      int Index = 0;

      object[,] SortedArray = new object[MaxRowNum + 1, MaxColNum + 1];
      int k = 0;
      int j = 0;

      for (k = 0; k < MaxRowNum; k++) {
         Index = Indexes[k];
         for (j = 0; j < MaxColNum; j++) {
            SortedArray[k, j] = sortArray[Index, j];
         }
      }

      return SortedArray;
   }
}

将所有内容打包在一个类中

以上所有代码都可以打包在一个类中,无需担心 Indexes() 数组和其他细微差别即可使用。您可以在可下载的代码中找到该类以及如何使用它的示例,该代码同时适用于 VB.NET 和 C#。

祝您编码愉快!

© . All rights reserved.