Sorting arrays in C#
C# can sort most data with one call. Array.Sort rearranges an array in place, in ascending order by default:
int[] numbers = {11, 3, 7, 5, 2};
Array.Sort(numbers); // numbers is now {2, 3, 5, 7, 11}
A List<T> has its own method with the same behaviour:
List<int> list = new List<int> {11, 3, 7, 5, 2};
list.Sort(); // list is now 2, 3, 5, 7, 11
Both methods sort in place and return nothing. If you need the original order afterwards, copy the data first.
Custom orders with a comparison
Default ascending order is not always what you want. Both Array.Sort and List<T>.Sort accept a comparison function that decides the order of any two elements. It takes two values, a and b, and returns an int:
- negative:
agoes beforeb; - positive:
bgoes beforea; - zero: the two are considered equal for this sort.
The comparison is usually written as a lambda, a nameless function. The full form has a block body, and an expression body is the short form when the function is a single return:
(a, b) => { return a - b; }
(a, b) => a - b
To sort integers in descending order, flip the subtraction:
int[] numbers = {3, 23, 12};
Array.Sort(numbers, (a, b) => b - a); // 23, 12, 3
Unlike in some other languages, C# needs no wrapper type: a plain int[] can be sorted with a comparison directly.
Sorting by a property
The comparison can look at anything about the elements. To order words from shortest to longest:
string[] words = {"banana", "pie", "Washington", "book"};
Array.Sort(words, (a, b) => a.Length - b.Length);
// pie, book, banana, Washington
Sorting arrays of pairs
For an array of arrays, such as int[][], each element is an int[] and the comparison picks which values to compare. To order pairs by their first value, then by their second value when the first values tie:
int[][] pairs = new int[][] {
new int[] {1, 5},
new int[] {12, 5},
new int[] {10, 1},
new int[] {1, 3}
};
Array.Sort(pairs, (a, b) => {
if (a[0] == b[0]) {
return a[1] - b[1];
}
return a[0] - b[0];
});
// {1, 3}, {1, 5}, {10, 1}, {12, 5}
Sorting strings
For text, compare with CompareTo, which returns a negative number, zero or a positive number like the comparison above. Lowercasing both sides makes the order ignore case:
string[] names = {"Mircea", "john", "Andy"};
Array.Sort(names, (a, b) => a.ToLower().CompareTo(b.ToLower()));
// Andy, john, Mircea
A built-in alternative does the same job: Array.Sort(names, StringComparer.OrdinalIgnoreCase). Without any case handling, an ordinal comparison puts every uppercase letter before every lowercase one, so "john" would come last.
Gotchas
- Subtraction can overflow.
a - bworks for small numbers, butint.MinValue - 1wraps around silently to a large positive number and the order comes out wrong.a.CompareTo(b)is always safe, andb.CompareTo(a)gives descending order. Array.Sortis not stable. Elements that compare as equal may change their relative order. If that matters, LINQ'sOrderByis stable, though it returns a new sequence instead of sorting in place.- The sort changes the array. Code that still needs the old order must keep a copy.
- The comparison must be consistent. If it says
acomes beforeb, it should saybcomes aftera. Inconsistent rules give unpredictable results. - Method names are capitalised. It's
Array.Sortandlist.Sort, andLengthfor arrays, notlength.
Complexity
Sorting n elements takes O(n log n * C) time, where C is the cost of one comparison. Comparing two numbers is constant time, so sorting numbers is O(n log n). Comparing two strings takes time proportional to their length, so sorting n strings of average length L costs O(n log n * L). The sort happens in the array itself and doesn't allocate a second array.
The exercise, step by step
- Declare
numbers. Anint[]holding{2, 1, 3, 10}. - Sort it descending. Call
Array.Sortwith the lambda(a, b) => b - a. A positive result putsbfirst, so larger numbers come first. Without the lambda, the sort would be ascending. - Declare
coordinates. A jaggedint[][]created withnew int[][], holding the pairs{3, 2},{2, 3}and{3, 5}, one per line. - Sort the pairs. The first value ascending; when the first values are equal, the second value descending. In the block-bodied lambda, return
b[1] - a[1]whena[0] == b[0](reversed for descending), anda[0] - b[0]otherwise. - Declare
names. Astring[]with"Mircea","john"and"Andy". - Sort the names ignoring case. A lambda that compares
a.ToLower()withb.ToLower()usingCompareTo.
int[] numbers = {2, 1, 3, 10};
Array.Sort(numbers, (a, b) => b - a);
int[][] coordinates = new int[][] {
new int[] {3, 2},
new int[] {2, 3},
new int[] {3, 5}
};
Array.Sort(coordinates, (a, b) => {
if (a[0] == b[0]) {
return b[1] - a[1];
}
return a[0] - b[0];
});
string[] names = {"Mircea", "john", "Andy"};
Array.Sort(names, (a, b) => a.ToLower().CompareTo(b.ToLower()));
Afterwards, numbers is {10, 3, 2, 1}, coordinates is {2, 3}, {3, 5}, {3, 2}, and names is {"Andy", "john", "Mircea"}.
Key takeaways
Array.Sort(array)andlist.Sort()sort ascending, in place.- A comparison lambda
(a, b) => ...returns negative, zero or positive to define any order. - Flip the operands for descending order; compare a second field to break ties.
- Prefer
CompareToto subtraction when values may be extreme. - Sorting numbers is
O(n log n), andArray.Sortis not stable.