|
|
Comments and Discussions
|
|
 |

|
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; }
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)
{
if (string.IsNullOrEmpty(s1) && string.IsNullOrEmpty(s2))
{
return 0;
}
if (string.IsNullOrEmpty(s1))
{
return -1;
}
if (string.IsNullOrEmpty(s2))
{
return 1;
}
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; int r; 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;
}
}
}
}
|
|
|
|

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

|
Thank you very much!
This really should be in the .Net framework!!
|
|
|
|

|
solves my urgent problem by explaining well the solution using a comparer in c#.
|
|
|
|

|
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'"
|
|
|
|

|
easy to use and works perfectly for me!
|
|
|
|
|

|
Though others did before, I'd like to thank you for the work presented here. Worked for my task within seconds!
|
|
|
|

|
This saved me a bunch of time. I was able to integrate it with a multi-column sort and it works great.
|
|
|
|

|
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; }
if (iA == startIndexPlusLengthA) { return iB < startIndexPlusLengthB ? -1 : 0; }
else if (iB == startIndexPlusLengthB) { return iA < startIndexPlusLengthA ? 1 : 0; }
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++; }
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; } }
}
}
}
|
|
|
|

|
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
|
|
|
|
|

|
Thanks mate!!! your article saved my precious time.
|
|
|
|

|
Thank, I was really struggling with this, u saved me alot of hassel
|
|
|
|

|
You just saved me hours of work. Thank you!!!
|
|
|
|

|
Your numeric sort worked great!! and it is very fast and easy .
Thanks again.
John Foster
|
|
|
|

|
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
|
|
|
|

|
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.
|
|
|
|

|
Vasian Cepa wrote: a) it is supported in XP and up, not in older systems
Unless you have IE 5.5 installed
|
|
|
|

|
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.
|
|
|
|

|
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
|
|
|
|

|
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!!!
|
|
|
|

|
Hi,
Great job.
Was looking for this algorithm since two days. Finally your code saved me and it is working fine. Thank you.
Ramkumar
|
|
|
|

|
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!!
|
|
|
|

|
Am I able to use this in commercial code if I leave the copyright comment at the top?
|
|
|
|
 |
|
|
General News Suggestion Question Bug Answer Joke Rant Admin
|
An article on sorting strings in C# the way Windows Explorer does with file names.
| Type | Article |
| Licence | |
| First Posted | 17 Jul 2005 |
| Views | 202,182 |
| Bookmarked | 92 times |
|
|