Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

Sorting Rectangular Arrays By Any Column in C# and VB.NET

5.00/5 (1 vote)
12 Apr 2016CPOL4 min read 13.9K   289  
The following article was born from a wish to share a deep re-elaboration of a nice tip about sorting rectangular arrays

Introduction

The .NET framework provides us with a very easy and convenient way to sort an array- the static Sort method of the Array class. The only limit of this method is that it works only with mono dimensional arrays. Of course, this includes arrays of complex objects or classes, but they still need to be mono dimensional arrays. Therefore, unless we are willing to write a custom procedure to sort a rectangular array, we need to relay on the Array.Sort method.

Sorting Mono Dimensional Arrays

When we need to sort mono dimensional arrays of complex type, one very good approach is to write a class that implements the IComparer interface and pass an instance of this class to the Array.Sort method. Let's see a quick example of this. Let's suppose we have a simple class with the name of a country and its population:

C#
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);
   }
}

We want to create an array of instances of this class and then sort it by country name. To accomplish this, we can write a simple class that implements the IComparer interface and inside the Compare function, we compare the Name property of two CCountry objects:

C#
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);

   }
}

To be sure, the two lines of codes:

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

are not strictly necessary, but the casting of the two arguments to a specific type will make sure that this specific implementation of the IComparer interface is used only with the right type of list.

Array.Sort uses our implementation of IComparer.Compare to decide which object between any two is the 'biggest' and to perform the appropriate sorting. Also note that this comparison is done directly on the two objects.

Image 1

At this point, we can load our array, create an instance of the CCountryComparer class and use it in the Array.Sort method as shown in the code below (Disclaimer: The countries listed and the population data are randomly taken from a list published on Wikipedia).

C#
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());
	}
}

Sorting Rectangular Arrays

We will examine a solution to sort rectangular arrays that will follow the same path but with a twist to it.

  • The mono dimensional array will be a simple array of integers representing the indexes of the main array.
  • The class that will implement the IComparer interface will compare the elements of our rectangular array corresponding to our indexes array.

Image 2

We will call our implementation of the IComparer interface ArrayComparer. The ArrayComparer class will need a reference to the main array in order to perform the comparison. Therefore, we will add a constructor that will take in the main array and the column number we wish to sort it by. In the Compare function, we will compare the values of the main array elements. Note that in order to make the class more flexible, the parameter ArrayTosort has been declared as a object[,].

C#
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]);
   }
}

We then feed the array of indexes and the instance of the ArrayComparer class to the Array.Sort method. What we get in return is an Indexes array whose values follow the sorting order of the main array by the chosen column.

C#
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]));
		
	}
}

Enhancements

As you can see, the main array is not really sorted. The sorting order is kept in the Indexes() array and the main array needs to be accessed through the Indexes() array, as shown in this loop:

C#
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]));
	
}

To fix this limitation, we just need to introduce a slightly more sophisticated version of the ArrayComparer class with the addition of a new function named GetSortedArray that will return the main array sorted. The function, shown in the code below should be pretty self-explanatory.

Finally, as a bonus, we are adding a parameter to the constructor to select the type of ordering, ascending or descending. Therefore, we will also add two private functions, one to be used with ascending sort and the other with descending sort, and we will use a delegate function to select which one to use (see code below).

One little note about these two private functions (CompareAsc and CompareDesc): because the array is declared object, we cannot use the CompareTo() method to compare two elements of the array unless we cast them into a IComparable object. This line of code...

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

...does exactly that. Here is the code:

C#
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;
   }
}

Packing It All In A Class

All the above code can be packed in a class and used without worrying about the Indexes() array and other nuances. You will find the class and an example on how to use it in the downloadable code, both for VB.NET and C#.

Happy coding!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)