Click here to Skip to main content
Click here to Skip to main content

Numeric String Sort in C#

By , 8 Aug 2005
 

Introduction

In this short article, I will explain how to sort strings in the natural numeric order. The .NET System.IO.Directory class returns file and directory names ordered in alphabetic order (Win32 API functions for dealing with files order the strings alphabetically), whereas Windows Explorer shows them in natural numeric order. The table below shows the difference between these two orderings:

Alphabetic sort Natural numeric sort
DOS (CMD prompt) style Windows Explorer Style
1.txt
10.txt
3.txt
a10b1.txt
a1b1.txt
a2b1.txt
a2b11.txt
a2b2.txt
b1.txt
b10.txt
b2.txt
1.txt
3.txt
10.txt
a1b1.txt
a2b1.txt
a2b2.txt
a2b11.txt
a10b1.txt
b1.txt
b2.txt
b10.txt

In the alphabetic order '3.txt' comes before '10.txt' whereas in the natural numeric order '10.txt' comes after '3.txt', which is what we would expect. Windows Explorer uses the natural numeric order for files.

Thanks to Richard Deeming (see comments) I now know that there is a similar function in shlwapi.dll, called StrCmpLogicalW used by Windows Explorer that works only in XP. My current .NET implementation emulates StrCmpLogicalW in pure C# code so it can be used with the .NET version in any version of Windows where .NET runs. My implementation is, however, not fully compatible with the one in Windows Explorer. There are some very slight differences which I will explain after the examples.

The class StringLogicalComparer in my C# code emulates StrCmpLogicalW, and NumericComparer is a class implementing the System.Collections.IComparer interface to be used to sort collections.

Using the code

The natural numeric order comparer for strings is defined in a class named NumericComparer : IComparer and can be found in the source code of this article. I will give several examples here of how the NumericComparer class can be used in code.

Example 1 - Ordering an Array of Strings

This example shows how to use the NumericComparator class to order strings. I will suppose, the strings are in a string[] array as shown next:

string[] files = System.IO.Directory.GetFiles();

NumericComparer ns = new NumericComparer();
Array.Sort(files, ns);

// files will now sorted in the numeric order
// we can do the same for directories

string[] dirs = System.IO.Directory.GetDirectories();
ns = new NumericComparer(); // new object
Array.Sort(dirs, ns);

While we can reuse the same NumericComparer object instance more than once, it is more efficient to create a new object when the set of strings to order changes (I will explain the implementation of NumericComparer later, after the examples).

Example 2 - Ordering Items in a ListView Control

There are several ways to order elements in a ListView control. I will show only how to order the elements responding to a column head click. To define a generic custom way to order rows of a ListView control, when we click the ListView headers, we need to respond to the ColumnClick event and set the ListViewItemSorter property of the ListView control to a class that implements IComparer. The custom ListComparer comparer will usually take other arguments in the constructor, e.g. the column header being clicked that will serve as the index for sorting the ListView elements, as demonstrated by the code snippet below:

private void lstFiles_ColumnClick(object sender,
              System.Windows.Forms.ColumnClickEventArgs e)
{
    ...
    ListComparer lc = new ListComparer(e.Column, ...);
    lstFiles.ListViewItemSorter = lc;
    ...
}

Inside the custom ListComparer, we will have code to order the ListView elements depending on the clicked column header. If we suppose the first column (index 0) contains the strings that need to be ordered in the natural numeric order, then we can use the StringLogicalComparer class that comes with NumericComparer directly inside ListComparer as follows (the code is simplified and has no error checking):

internal class ListComparer : IComparer
{
    ...

    public int Compare(object x, object y)
    {
        ListViewItem lx = (ListViewItem)x;
        ListViewItem ly = (ListViewItem)y;

        switch(column) // set in the constructor
        {
            case 0: // first column
            int c = StringLogicalComparer.Compare(lx.SubItems[0].Text, 
                                                 ly.SubItems[0].Text);
            // take care of the other columns if needed
            // modify c to respond to ascending
            // or descending order, as needed
            ...
            return c;
            ...
        }
    }
}

The strings (file names) in the first column of the ListView control will now be in the natural numeric order.

Example 3 - Ordering Dictionary Entries

Sometimes we need to associate a data object with a key string and we may need to order the string keys and then access the data objects ordered by the keys.

If the keys are unique, we can use a Hashtable to keep them and the associated data objects. .NET offers the possibility to order the keys of a Hashtable and then retrieve the data objects with it:

Hashtable hash = new Hashtable();
// fill with data string key, object data
...
hash.Add(key, data); // key must be unique
...

// numeric sort
NumericComparer nc = new NumericComparer();
SortedList list = new SortedList(hash, nc);
// now use the sorted list
foreach(DictionaryEntry de in list)
{
    // use de.Key and de.Value
    ...
}

A more interesting situation arises when the keys are not unique, that is when we can have different data objects that map to the same key. In this case, we have two choices.

  1. We can keep the data objects as ArrayLists associated with the string keys in a Hashtable. The order of data objects inside the ArrayLists does not matter because they have the same key.
  2. We can build a simple data structure to keep the key and the value data objects or we can use a System.Collections.DictionaryEntry structure. We can now store our data as DictionaryEntry elements of an ArrayList:
    ArrayList list = new ArrayList();
    
    // populate the list, possibly from some other structure as part of a loop
    ...
    list.Add(new DictionaryEntry(fileName, data));

    To order the elements in this case, we need also to create a custom (generic) comparer:

    public class DictionaryEntryComparer : IComparer
    {
        private IComparer nc = null;
    
        public DictionaryEntryComparer(IComparer nc)
        {
            if(nc == null) throw new Exception("null IComparer");
            this.nc = nc;
        }
    
        public int Compare(object x, object y)
        {
            if((x is DictionaryEntry) && (y is DictionaryEntry))
            {
                return nc.Compare(((DictionaryEntry)x).Key,
                                  ((DictionaryEntry)y).Key);
            }
            return -1;
        }
    }

    We can now order the items of list according to the keys, in the natural numeric order, using:

    list.Sort(new DictionaryEntryComparer(new
                  NumericComparer()));

    Of course, we can use any other IComparer with the DictionaryEntryComparer class we created.

Points of Interest

The complete code can be found in files StringLogicalComparer.cs and NumericComparer.cs in the source code files of this article.

A small difference with Windows Explorer

There is currently a small difference with Windows Explorer. My code will order files that start with special characters based on the code table order. Windows Explorer uses another order. For example:

Windows Explorer: (1.txt, [1.txt, _1.txt, =1.txt
My code: (1.txt, =1.txt, [1.txt, _1.txt

I cannot think of any reason why one order can be better than the other. So I see no reason why my code should emulate this specific order. My code uses the current profile to find the order of chars in the code page. Note that, the difference exists only in the first character. If such a special character is inside the file name, Windows Explorer gives the same order as my code.

A practical implication of this special behavior for the first character is that both StrCmpLogicalW and my code work better with file names not with full paths. Use code similar to Example 3 to order file names and keep the directory information.

Implementation

If you are a developer and want just to use the code, then there is no need to read beyond this point. If you are a student in an introductory computer science course, then the following could be interesting.

There are several ways to order strings in the numeric natural order. The problem is when a list of N items is sorted using quick sort then the Compare function will be called more than N times which means that it would be nice to optimize the implementation if any.

The first version of my code used another implementation (see below). I thought the code was nicely optimized and the results of computation were cached. However, the comments of Richard Deeming below, made me wonder if I had it right. In the beginning I thought that the problem was with RegEx, and the Hashtable may be because of deadline :), and even Richard did not do better :) to guess it right (see comments). To understand the problem with my first solution, I will list several possible implementations very shortly:

  1. A simple technique is to use padding with a special character ‘/’. This character has several nice properties. It is not used in file paths in Windows and its ASCII code is smaller than the one for digits. It can be used to pad the numeric parts of two strings so that they have the same length. Example: a10.txt and a1.txt will become a10.txt and a/1.txt. Then the alphabetical order can be used and a/1.txt will be smaller than a10.txt. Finally the '/' padding needs to be removed. This method works, but has some serious limitations. The '/' can be used only for file paths in Windows. If the strings contain ‘/’, this method will not work. This method requires also too many passes over the string and cannot be implemented with fixed char arrays. The method, however, treats numbers somehow uniformly and is different from the rest so it is interesting per se.
  2. The two strings to be compared are split into lists with alphabetical and numeric parts, the parts are then compared one by one. One optimization of this technique would be to remember the split in parts (cache it in a Hashtable). Numeric parts can be converted to numbers and compared. The number conversions can also be cached. My first implementation used this technique.

    The implementation is, however, slower than StrCmpLogicalW despite the caching (it would be even slower without dynamic programming). As I read the Richard Deeming comments about the speed of code compared to StrCmpLogicalW, I did not understand the problem at first. The technique is, however, naïve for two reasons. First, it does eager evaluation. The split of the strings is complete and so is the numeric conversion. When two strings are compared the comparison will be often interrupted before all parts are needed. So the eager evaluation consumes a lot of time. The second problem is that numeric parts are explicitly converted to numbers (long). This not only consumes time, it also is an error-prone method because numeric parts that are longer than a long number will throw an exception. (So if you used the previous code it is time to replace it with the new implementation.)

  3. One solution to the problems above is that numeric parts should be compared as special strings not as numbers. Second, using lazy evaluation would also remove the cost of over splitting. The lazy evaluation code for splitting can, however, be complicated.
  4. The full splitting is, however, rarely needed so we can be optimistic and avoid caching. This is similar to using StrCmpLogicalW. The current implementation of StringLogicalComparer only parses the two strings at the same time and stops parsing at the moment the result of the comparison in known. The technique is also very fast (I hope Richard will test the code again) because it works using fixed-size char arrays. The only look-ahead is to find the end index of the current numerical parts in both strings.

History

  • 03 August 2005
    1. New version. Several bugs corrected.
  • 02 August 2005
    1. New version. Replaced the old one.
  • 15 July 2005
    1. Modified NumericComparer to allow cache size initialization.
    2. Modified the article to show a Hashlist example and use DictionaryEntry.
    3. Several minor article corrections.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

vcepa
Germany Germany
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionSmall bugsmemberJohn Lau1 Mar '13 - 10:02 
The following test scenario results in wrong results:
s1: "test1"
s2: "test"
 
Comparing these results in -1 instead of 1 (test1 follows test).
 
if((i1 >= s1.Length) && (i2 >= s2.Length))
{
    return 0;
}
else if(i1 >= s1.Length)
{
    return -1;
}
else if(i2 >= s2.Length)
{
    return 1; // Should be 1 instead of -1
}
 
Also I wrote the following test which are my expected results. The StringLogicalComparer didn't pass the test so I modified the Compare method
[TestMethod]
public void Should_sort_array_logically()
{
    var files = new List<string>
    {
        "1",
        "_1",
        "[1",
        "=1",
        "10",
        "3",
        "a10b1",
        "a1b1",
        "a2b1",
        "a2b11",
        "a2b2",
        "b1",
        "b10",
        "b2",
        "b[1",
        "b01",
        "c30",
        "c25",
        "c35",
        "c40",
        "big",
        "a1b1[",
        "a.b1"
    };
 
    var logicalComparer = new StringLogicalComparer();
    files.Sort(logicalComparer.Compare);
 
    var expected = new List<string>
    {
        "[1",
        "_1",
        "=1",
        "1",
        "3",
        "10",
        "a.b1",
        "a1b1",
        "a1b1[",
        "a2b1",
        "a2b2",
        "a2b11",
        "a10b1",
        "b[1",
        "b01",
        "b1",
        "b2",
        "b10",
        "big",
        "c25",
        "c30",
        "c35",
        "c40",
    };
    CollectionAssert.AreEqual(expected, files);
}
public class StringLogicalComparer : IComparer<string>
    {
        public int Compare(string s1, string s2)
        {
            //get rid of special cases
            if (string.IsNullOrEmpty(s1) && string.IsNullOrEmpty(s2))
            {
                return 0;
            }
            if (string.IsNullOrEmpty(s1))
            {
                return -1;
            }
            if (string.IsNullOrEmpty(s2))
            {
                return 1;
            }
 
            //WE style, special case
            bool sp1 = Char.IsLetterOrDigit(s1, 0);
            bool sp2 = Char.IsLetterOrDigit(s2, 0);
            if (sp1 && !sp2) return 1;
            if (!sp1 && sp2) return -1;
 
            int i1 = 0, i2 = 0; //current index
            int r; // temp result
            while (true)
            {
                bool c1 = Char.IsDigit(s1, i1);
                bool c2 = Char.IsDigit(s2, i2);
                bool letter1 = Char.IsLetter(s1, i1);
                bool letter2 = Char.IsLetter(s2, i2);
                if (!c1 && !c2)
                {
                    if ((letter1 && letter2) || (!letter1 && !letter2))
                    {
                        r = s1.Substring(i1, 1).ToLower().CompareTo(s2.Substring(i2, 1).ToLower());
                        if (r != 0) return r;
                    }
                }
                else if (c1 && c2)
                {
                    r = CompareNum(s1, ref i1, s2, ref i2);
                    if (r != 0) return r;
                }
                else if (c1)
                {
                    return letter2 ? -1 : 1;
                }
                else if (c2)
                {
                    return letter1 ? 1 : -1;
                }
                i1++;
                i2++;
                if ((i1 >= s1.Length) && (i2 >= s2.Length))
                {
                    return 0;
                }
                else if (i1 >= s1.Length)
                {
                    return -1;
                }
                else if (i2 >= s2.Length)
                {
                    return 1;
                }
            }
        }
 
// The remaining of class
}

AnswerRe: Small bugsmembervcepa1 Mar '13 - 20:08 
It seems you are using an old version of the code. As I already wrote in another comment below, the latest version of this code is at: http://madebits.com/articles/numsort/index.php[^]. I am not sure if the code in this article has been updated. I had a look at the code in the mentioned link and it seems it is already ok there:
 
....
else if(i2 >= s2.Length)
{
 return 1;
}

QuestionThank you!!!memberNicolas Mathieu28 Jun '12 - 11:51 
Thank you very much!
 
This really should be in the .Net framework!!
GeneralMy vote of 5memberMember 643021 Mar '12 - 12:38 
solves my urgent problem by explaining well the solution using a comparer in c#.
SuggestionSmall problem/typomemberKent K8 Nov '11 - 15:30 
In your 2nd paragraph, a small problem: "In the alphabetic order '3.txt' comes before '10.txt' whereas in the natural numeric order '10.txt' comes after '3.txt'"
GeneralMy vote of 5memberAhoshi29 Jul '11 - 1:14 
easy to use and works perfectly for me!
GeneralMy vote of 5membertehtb2 Mar '11 - 14:52 
Thank you for this
GeneralThanks!memberyahp23 Jan '10 - 7:14 
Though others did before, I'd like to thank you for the work presented here. Worked for my task within seconds!
GeneralThanks Vasianmemberstever(398)7 Jul '09 - 21:48 
This saved me a bunch of time. I was able to integrate it with a multi-column sort and it works great.
GeneralMore compact method...memberTurion6 Jul '09 - 21:53 
I stumbled upon this article, and decided to provide my own version of the code which I wrote a while ago. Its adantage is that uses no look-ahead whatsoever, and it's a single method (with convenient overloads). I'm not great at testing, but it gave correct results for my tests.
Here is the code:
 
public static int CompareLogical(string strA, string strB) { return CompareLogical(strA, strB, null, CompareOptions.None); }
 
public static int CompareLogical(string strA, string strB, CultureInfo culture, CompareOptions compareOptions) { return CompareLogical(strA, 0, -1, strB, 0, -1, culture, compareOptions); }
 
public static int CompareLogical(string strA, int indexA, int lengthA, string strB, int indexB, int lengthB, CultureInfo culture, CompareOptions compareOptions)
{
	if (strA == null) { throw new ArgumentNullException("strA"); }
	if (strB == null) { throw new ArgumentNullException("strB"); }
	
	if (culture == null) { culture = CultureInfo.CurrentCulture; }
 
	if (lengthA < -1) { throw new ArgumentOutOfRangeException("lengthA", lengthA, "Number must be either non-negative and less than or equal to Int32.MaxValue or -1."); }
	if (lengthA == -1) { lengthA = strA.Length - indexA; }
	if (lengthB < -1) { throw new ArgumentOutOfRangeException("lengthB", lengthB, "Number must be either non-negative and less than or equal to Int32.MaxValue or -1."); }
	if (lengthB == -1) { lengthB = strB.Length - indexB; }
 
	if (indexA < 0 || indexA + lengthA > strA.Length)
	{ throw new ArgumentOutOfRangeException("indexA", "Index and length must refer to a location within the string."); }
	if (indexB < 0 || indexB + lengthB > strB.Length)
	{ throw new ArgumentOutOfRangeException("indexB", "Index and length must refer to a location within the string."); }
 
	int startIndexPlusLengthA = indexA + lengthA, startIndexPlusLengthB = indexB + lengthB;
 
	int iStartA = indexA, iStartB = indexB;
	int iA = iStartA, iB = iStartB;
 
	if (char.IsDigit(strA, iA) ^ char.IsDigit(strB, iB)) { return string.Compare(strA, strB, culture, compareOptions); }
	else
	{
		for (; ; )
		{
			int cmp;
			while ((iA < startIndexPlusLengthA && !char.IsDigit(strA, iA)) & (iB < startIndexPlusLengthB && !char.IsDigit(strB, iB)))
			{
				cmp = string.Compare(strA, iA, strB, iB, 1, culture, compareOptions);
				if (cmp != 0) { return cmp; }
				iA++;
				iB++;
			}
			if (iA < startIndexPlusLengthA && !char.IsDigit(strA, iA)) { return 1; }
			else if (iB < startIndexPlusLengthB && !char.IsDigit(strB, iB)) { return -1; }
 
			//Now we're either at the end of the string or at numbers

			if (iA == startIndexPlusLengthA) { return iB < startIndexPlusLengthB ? /*b is longer*/ -1 : 0; }
			else if (iB == startIndexPlusLengthB) { return iA < startIndexPlusLengthA ? /*a is longer*/ 1 : 0; }
 

			//Let's compare the numbers, ignoring leading zeros

			while (iA < startIndexPlusLengthA &&
				string.Compare(strA, iA, ZERO_STRING, 0, ZERO_STRING.Length, culture, compareOptions) == 0)
				{ iA++; }
			while (iB < startIndexPlusLengthB &&
				string.Compare(strB, iB, ZERO_STRING, 0, ZERO_STRING.Length, culture, compareOptions) == 0)
			{ iB++; }
 
			//Now there are no leading zeros

			iStartA = iA;
			iStartB = iB;
 
			cmp = 0;
			while ((iA < startIndexPlusLengthA && char.IsDigit(strA, iA))
				 & (iB < startIndexPlusLengthB && char.IsDigit(strB, iB)))
			{
				if (cmp == 0) { cmp = string.Compare(strA, iA, strB, iB, 1, culture, compareOptions); }
				iA++;
				iB++;
			}
 
			if (iA < startIndexPlusLengthA && char.IsDigit(strA, iA)) { return 1; }
			else if (iB < startIndexPlusLengthB && char.IsDigit(strB, iB)) { return -1; }
			else { if (cmp != 0) { return cmp; } }
		}
	}
}

QuestionLicensing Inquirymember2twotango17 Jun '09 - 7:25 
Hello Vasian Cepa,
 
Nice - exactly what I was looking for.
What's the story with its reuse in commercial products, is it allowed?
Are there any restrictions, copyright statements to be inserted, credit given etc?
Thank you.
 
Best regards,
RBitango
AnswerRe: Licensing InquirymemberVasian Cepa17 Jun '09 - 8:12 
Hello RBitango,
 
see comment by mark_ledwich below:
 
Numeric String Sort in C#[^]
 
and my answer there
 
Numeric String Sort in C#[^]
GeneralThanks!!!memberfarizvi8 Mar '09 - 3:04 
Thanks mate!!! your article saved my precious time. Big Grin | :-D
GeneralPerfectmemberSysproKevin18 Aug '08 - 1:01 
Thank, I was really struggling with this, u saved me alot of hassel
GeneralYou just saved me hours of work. Thank you!!!membermcpbooks7 Aug '08 - 12:59 
You just saved me hours of work. Thank you!!!
GeneralThanksmemberjfost17 Jul '08 - 5:22 
Your numeric sort worked great!! and it is very fast and easy Big Grin | :-D .
 
Thanks again.
 
John Foster
GeneralWhy NOT Use APImemberSameers (theAngrycodeR )8 Sep '07 - 18:12 
Thank you for sharing this code, first of ALL. IT was really the pain in the .... for me to do it and honestly, I never succeeded. But Now, I am.
 
I was just wondering that WHY did you code that a LOT if you know the API? Why NOT just use the API then?
 
VB Declaration for the API:
<DllImport("shlwapi.dll", CharSet:=CharSet.Unicode, ExactSpelling:=True)> _
Private Shared Function StrCmpLogicalW(ByVal x As String, ByVal y As String) As Integer
End Function
 
C# Declaration for the API:
[DllImport("shlwapi.dll", CharSet=CharSet.Unicode, ExactSpelling=true)]
static extern int StrCmpLogicalW(String x, String y);
 

And you finish job in just one line:
 
Return StrCmpLogicalW(x, y) (add terminator for C# :->)
 
For sure, it will be VERY Fast as compared to manual code and lot of checks etc. And secondly, there will be no difference in Explorer sort and our code. No?
 

Thanks again for sharing the damn hidden thing.
Sameers

 

 
FREE MSN Auto Responder[^]
 
History Remember Vendors, NOT Developers

GeneralRe: Why NOT Use APImemberVasian Cepa10 Sep '07 - 1:37 
If you read the article carefully, the reasons not to use the shell function are:
 
a) it is supported in XP and up, not in older systems
b) it is slower than the .net code given here
c) it makes your app depend on the shell
 
If all of these are ok for you, you can use the shell version, there is nothing wrong with it.
 

GeneralRe: Why NOT Use APImemberbob1697222 Sep '08 - 8:19 
Vasian Cepa wrote:
a) it is supported in XP and up, not in older systems

 
Unless you have IE 5.5 installed Big Grin | :-D
GeneralA quick questionmemberjubilanttiger25 Apr '07 - 7:21 
Hi,
 
This code is awesome and I am glad I found it. It makes my work for the sort implementation simple. But, I have a quick question. When I sort a set of string numbers, the order which I get back is:
15235.1
15235.02
 
I was expecting that the order would be other way around:
15235.02
15235.1
 
I am hoping that you would be able to point to the changes in the code to accomplish this.
GeneralRe: A quick question [modified] - donememberVasian Cepa25 Apr '07 - 21:19 
Most people need it like below (and this also how Windows Explorer and my code order them):
 
001
01
1
002
02
2
 
and you need it like this:
 
001
002
01
02
1
2
 
This is of course possible, but no matter what order I choose some people will not like it. So my code does it by default the same as Windows Explorer does.
 
I added now ns.StringLogicalComparer.DefaultZeroesFirst that does what you expect. The latest version can be found at http://madebits.com/articles/numsort/index.php
QuestionCompare an ArrayList of Objects [modified]memberrue2k322 Mar '07 - 6:14 
I recently used your StringLogicalComparer class to sort an ArrayList which contained Custom Objects for example:-
 
public class Person: IComparable
{
public string Name;
public string Email;
public string Reference;
 
public int CompareTo(object obj)
{
if( !(obj is Person) )
throw new InvalidCastException("Not a valid Person object.");
Person person = (Person)obj;
return StringLogicalComparer.Compare(this.Reference,person.Reference);
}
}
 
Then of course when you wanted to sort this ArrayList all I had to do was:-
people.Sort();
 
Now to the question. Is there a way of specifying what we sort by? Name, Email or Reference?
 
Cheers
 
Andy M
 

-- modified at 12:22 Thursday 22nd March, 2007
 
Sorry I was being a bit dumb when I asked this question. What I would need would be an IComparer for each of the three values.
 
EXAMPLE:-
 
public class PersonNameComparer: IComparer
{
public int Compare(object x, object y)
{
if((x is Person) && (y is Person))
{
return StringLogicalComparer.Compare(((Person)x).Name,((Person)y).Name);
}
return -1;
}
}
 
people.Sort(new PersonNameComparer());
 

Thanks for the original Code anyway!!!

GeneralGood WorkmemberRamkumar Vasudevan3 Nov '06 - 8:57 
Hi,
 
Great job.
 
Was looking for this algorithm since two days. Finally your code saved me and it is working fine. Thank you.
 

 
Ramkumar

GeneralThanks for your work!memberLukas Fellechner25 Sep '06 - 12:28 
Hi, just wanted to say: Thank You!!
 
I've been searching for such a sorting class, google returned nothing but Code Project once again saved me from doing all the stuff myself.... Great one, works excellent, thanks!! Smile | :)
QuestionLicensingmembermark_ledwich11 Dec '05 - 19:09 
Am I able to use this in commercial code if I leave the copyright comment at the top?

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 9 Aug 2005
Article Copyright 2005 by vcepa
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid