BLOG

So, you want to implement an alphanumeric sort method that sorts like intuitively

17 Nov 2014, by Sonny Nguyen in Developer

A long time ago, I had to solve a scenario where we had to sort a list of names like so:

1
2
2A
2B
3C
3D
4
5
26
100
101
102A
1000

It took a few hours of digging to find the actual solution that I wanted, so if this is what you what you’re looking for, look no further.

The source: http://www.davekoelle.com/files/AlphanumComparator.cs
And if it no longer exists, here is the snippet:

    using System;
    using System.Collections.Generic;
    using System.Text;

    public class NumericStringSorter : IComparer<string> {
        private enum ChunkType {
            Alphanumeric,
            Numeric
        };

        private bool InChunk(char ch, char otherCh) {
            var type = ChunkType.Alphanumeric;

            if (char.IsDigit(otherCh)) {
                type = ChunkType.Numeric;
            }

            if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
                || (type == ChunkType.Numeric && !char.IsDigit(ch))) {
                return false;
            }

            return true;
        }

        public int Compare(string x, string y) {
            var s1 = x;
            var s2 = y;
            if (s1 == null || s2 == null) {
                return 0;
            }

            int thisMarker = 0, thisNumericChunk = 0;
            int thatMarker = 0, thatNumericChunk = 0;

            while ((thisMarker < s1.Length) || (thatMarker < s2.Length)) {
                if (thisMarker >= s1.Length) {
                    return -1;
                }
                if (thatMarker >= s2.Length) {
                    return 1;
                }
                char thisCh = s1[thisMarker];
                char thatCh = s2[thatMarker];

                var thisChunk = new StringBuilder();
                var thatChunk = new StringBuilder();

                while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || this.InChunk(thisCh, thisChunk[0]))) {
                    thisChunk.Append(thisCh);
                    thisMarker++;

                    if (thisMarker < s1.Length) {
                        thisCh = s1[thisMarker];
                    }
                }

                while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || this.InChunk(thatCh, thatChunk[0]))) {
                    thatChunk.Append(thatCh);
                    thatMarker++;

                    if (thatMarker < s2.Length) {
                        thatCh = s2[thatMarker];
                    }
                }

                int result = 0;
                // If both chunks contain numeric characters, sort them numerically
                if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0])) {
                    thisNumericChunk = Convert.ToInt32(thisChunk.ToString());
                    thatNumericChunk = Convert.ToInt32(thatChunk.ToString());

                    if (thisNumericChunk < thatNumericChunk) {
                        result = -1;
                    }

                    if (thisNumericChunk > thatNumericChunk) {
                        result = 1;
                    }
                } else {
                    result = thisChunk.ToString().CompareTo(thatChunk.ToString());
                }

                if (result != 0) {
                    return result;
                }
            }

            return 0;
        }
    }

Now that we have the sort method we want, we need to actually implement it.

We need a list that we want to sort. (LockerConfigurations)

We add to that list. (AddRange)

We sort that list. (OrderBy)

private readonly BindableCollection<EditLockerViewModel> _lockerConfigurations = new BindableCollection<EditLockerViewModel>();
public BindableCollection<EditLockerViewModel> LockerConfigurations {
            get { return _lockerConfigurations; }
        }
LockerConfigurations.AddRange(_fullLockerConfigurations.OrderBy(x => x.LockerNumber, new NumericStringSorter()));

BindableCollection is a collection we use from CaliburnMicro that is similar to a List (https://caliburnmicro.codeplex.com/ which is used for our implementation of MVVM)

We use .NET’s implementation of OrderBy and use our own comparer.

To order a sequence by the values of the elements themselves, specify the identity function (x => x in Visual C# or Function(x) x in Visual Basic) for keySelector.

Two methods are defined to extend the type IOrderedEnumerable<TElement>, which is the return type of this method. These two methods, namely ThenBy and ThenByDescending, enable you to specify additional sort criteria to sort a sequence. ThenBy and ThenByDescending also return an IOrderedEnumerable<TElement>, which means any number of consecutive calls to ThenBy or ThenByDescending can be made.

Note
Because IOrderedEnumerable<TElement> inherits from IEnumerable<T>, you can call OrderBy or OrderByDescending on the results of a call to OrderBy, OrderByDescending, ThenBy or ThenByDescending. Doing this introduces a new primary ordering that ignores the previously established ordering.
If comparer is null, the default comparer Default is used to compare keys.

This method performs a stable sort; that is, if the keys of two elements are equal, the order of the elements is preserved. In contrast, an unstable sort does not preserve the order of elements that have the same key.

So now you have successfully implemented your own version of an alphanumeric sorter.

 

About the Author

Sonny NguyenSonny Nguyen has three years experience in the technology industry.
He is a currently a .NET Developer and System Administrator at BDCSoft.
His specialty lies within Troubleshooting and Problem Solving.
With outside the box thinking, he can approach problems in new, innovative ways.
His broad range of technical skills enables him to tackle any issue that may arise.

You can reach the author of this post at sonny.nguyen@bdcsoft.com or on Twitter at @nguyenSONNY

LEAVE A COMENT