Skip to content

Commit 7d5bfcd

Browse files
committed
Modified tNstrcmp so it only uses one while-loop and does not need the mode/state variable. Updated it to sort similarly to Windows natural sorting by treating hyphens similarly. Fixed a bug when one of the two characters was a digit -- it was getting the order reversed. Added some code to do a case-sensitive compare all things being equal (so that AAA sorts before aaa). Fixed places in Tacent that were using tString Left and Right now that the behaviour is consistent with ExtractLeft and ExtractRight.
1 parent 6853a1b commit 7d5bfcd

File tree

5 files changed

+102
-63
lines changed

5 files changed

+102
-63
lines changed

Modules/Foundation/Inc/Foundation/tFundamentals.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ template<typename T> inline bool tInInterval(const T val, const T min, const T m
9292
template<typename T> inline bool tInRange(const T val, const T min, const T max) /* Returns val E [min, max] */ { return tInInterval(val, min, max); }
9393
template<typename T> inline bool tInRange(const T val, const T min, const T max, tBias bias) { return tInInterval(val, min, max, bias); }
9494

95-
template<typename T> inline T tSign(T val) { return val < T(0) ? T(-1) : val > T(0) ? T(1) : T(0); }
96-
template<typename T> inline T tBinarySign(T val) { return val < T(0) ? T(-1) : T(1); } // Same as Sign but does not return 0 ever. Two return values only.
95+
template<typename T> inline T tSign(T val) /* +1,-1, or 0 for equal, */ { return val < T(0) ? T(-1) : val > T(0) ? T(1) : T(0); }
96+
template<typename T> inline T tBinarySign(T val) { return val < T(0) ? T(-1) : T(1); } // Same as Sign but does not return 0 ever. Two return values only.
9797
template<typename T> inline bool tIsZero(T a) { return a == T(0); }
9898
template<typename T> inline bool tApproxEqual(T a, T b, float e = Epsilon) { return (tAbs(a-b) < e); }
9999
template<typename T> inline bool tEquals(T a, T b) { return a == b; }

Modules/Foundation/Inc/Foundation/tStandard.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ int tPstrncmp(const char8_t* a, const char8_t* b, int n);
8383

8484
// These do a 'natural' string compare by treating groups of base 10 digits as separate objects to be compared by
8585
// numeric value rather than alpha-numerically based on the encoding. This results in strings like "page10" coming
86-
// after "page2" because 10 > 2.
86+
// after "page2" because 10 > 2. This function can become arbitrarily complex. Some discussion on the topic is here:
87+
// https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
88+
// See the unit tests for examples that match windows explorer sorting. In particular the hyphen character may be
89+
// skipped when sorting and we fall back to case-sensitive compare all things being equal.
90+
// The implementation of tNstrcmp is a modified version of the one written by GitHub user ClangPan.
8791
int tNstrcmp(const char* a, const char* b);
8892
inline int tNstrcmp(const char8_t* a, const char8_t* b) { return tNstrcmp((const char*)a, (const char*)b); }
8993

Modules/Foundation/Src/tStandard.cpp

+51-49
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#endif
2020
#include "Foundation/tStandard.h"
2121
#include "Foundation/tString.h"
22+
#include "Foundation/tFundamentals.h"
2223
#pragma warning (disable: 4146)
2324
#pragma warning (disable: 4018)
2425

@@ -64,68 +65,69 @@ void* tStd::tMemsrch(void* haystack, int haystackNumBytes, void* needle, int nee
6465

6566
int tStd::tNstrcmp(const char* a, const char* b)
6667
{
67-
// This implementation of tNstrcmp was written by GitHub user ClangPan.
68-
enum class Mode
69-
{
70-
String,
71-
Number
72-
};
73-
Mode mode = Mode::String;
68+
const char* origa = a;
69+
const char* origb = b;
7470

71+
// This implementation of tNstrcmp is a modified version of the one written by GitHub user ClangPan.
7572
while (*a && *b)
7673
{
77-
if (mode == Mode::String)
74+
bool aDigit = tIsdigit(*a);
75+
bool bDigit = tIsdigit(*b);
76+
77+
if (!aDigit && (*a == '-'))
7878
{
79-
char aChar, bChar;
80-
while ((aChar = tolower(*a)) && (bChar = tolower(*b))) // We lowercase the chars for proper comparison
81-
{
82-
// Check if the chars are digits
83-
const bool aDigit = isdigit(aChar), bDigit = isdigit(bChar);
84-
85-
// If both chars are digits, we continue in NUMBER mode
86-
if (aDigit && bDigit)
87-
{
88-
mode = Mode::Number;
89-
break;
90-
}
91-
92-
// If only the left char is a digit, we have a result
93-
if (aDigit) return -1;
94-
95-
// If only the right char is a digit, we have a result
96-
if (bDigit) return +1;
97-
98-
// compute the difference of both characters
99-
const int diff = aChar - bChar;
100-
101-
// If they differ we have a result
102-
if (diff != 0) return diff;
103-
104-
// Otherwise process the next characters
105-
++a; ++b;
106-
}
79+
++a;
80+
continue;
10781
}
108-
else
82+
83+
if (!bDigit && (*b == '-'))
84+
{
85+
++b;
86+
continue;
87+
}
88+
89+
// We're comparing (possibly multidigit) numbers.
90+
if (aDigit && bDigit)
10991
{
110-
char *end; // Represents the end of the number string
92+
char* enda;
93+
char* endb;
11194

112-
// Get the left number
113-
unsigned long aInt = strtoul((char*) a, &end, 10);
114-
a = end;
95+
// Get the left number.
96+
int aInt = strtoul((char*)a, &enda, 10);
11597

116-
// Get the right number
117-
unsigned long bInt = strtoul((char*) b, &end, 10);
118-
b = end;
98+
// Get the right number.
99+
int bInt = strtoul((char*)b, &endb, 10);
119100

120101
// if the difference is not equal to zero, we have a comparison result
121-
const long diff = aInt - bInt;
122-
if (diff != 0) return diff;
102+
int sign = tMath::tSign(aInt - bInt);
103+
if (sign) return sign;
123104

124-
// otherwise we process the next substring in STRING mode
125-
mode = Mode::String;
126-
}
105+
a = enda;
106+
b = endb;
107+
continue;
108+
}
109+
110+
// If only the left char is a digit, we have a result.
111+
if (aDigit) return +1;
112+
113+
// If only the right char is a digit, we have a result.
114+
if (bDigit) return -1;
115+
116+
// compute the difference of both characters
117+
int sign = tMath::tSign(tToLower(*a) - tToLower(*b));
118+
119+
// If they differ we have a result.
120+
if (sign) return sign;
121+
122+
// Otherwise process the next characters.
123+
++a; ++b;
127124
}
128125

126+
// If both a and b are at end, we consider letter-case and compare as if we had never done the tToLowers.
127+
if (!(*a) && !(*b))
128+
return tStrcmp(origa, origb);
129+
130+
// Now only one of *a or *b are non-zero.
129131
if (*b) return -1;
130132
if (*a) return +1;
131133

Modules/System/Src/tFile.cpp

+7-7
Original file line numberDiff line numberDiff line change
@@ -196,14 +196,18 @@ tString tSystem::tGetFileName(const tString& file)
196196
{
197197
tString retStr(file);
198198
tPathStd(retStr);
199-
return retStr.Right('/');
199+
if (retStr.FindChar('/') != -1)
200+
return retStr.Right('/');
201+
return retStr;
200202
}
201203

202204

203205
tString tSystem::tGetFileBaseName(const tString& file)
204206
{
205207
tString r = tGetFileName(file);
206-
return r.Left('.');
208+
if (r.FindChar('.') != -1)
209+
return r.Left('.');
210+
return r;
207211
}
208212

209213

@@ -1900,11 +1904,7 @@ tStaticAssert(tNumElements(tSystem::FileTypeExtTable) == int(tSystem::tFileType:
19001904

19011905
tString tSystem::tGetFileExtension(const tString& file)
19021906
{
1903-
tString ext = file.Right('.');
1904-
if(ext == file)
1905-
ext.Clear();
1906-
1907-
return ext;
1907+
return file.Right('.');
19081908
}
19091909

19101910

UnitTests/Src/TestFoundation.cpp

+37-4
Original file line numberDiff line numberDiff line change
@@ -475,9 +475,40 @@ void PrintMultiObjList(const tList<MultiObj>& multiObjList)
475475
tTestUnit(ListSort)
476476
{
477477
tList<MultiObj> multiObjList;
478+
479+
// Add items with an extension.
480+
multiObjList.Append(new MultiObj("page100.txt"));
481+
multiObjList.Append(new MultiObj("Page20.txt"));
482+
multiObjList.Append(new MultiObj("Page4.txt"));
483+
multiObjList.Append(new MultiObj("Page.txt"));
484+
multiObjList.Append(new MultiObj("PagE.txt"));
485+
multiObjList.Append(new MultiObj("page5.txt"));
486+
multiObjList.Append(new MultiObj("Page5.txt"));
487+
multiObjList.Append(new MultiObj("aaa.txt"));
488+
multiObjList.Append(new MultiObj("AAA.txt"));
489+
multiObjList.Append(new MultiObj("zzz.txt"));
490+
multiObjList.Append(new MultiObj("ZZZ.txt"));
491+
multiObjList.Append(new MultiObj("Page-90.txt"));
492+
multiObjList.Append(new MultiObj("page -90.txt"));
493+
multiObjList.Append(new MultiObj("page-8.txt"));
494+
multiObjList.Append(new MultiObj("page -8.txt"));
495+
496+
// Add the same items without an extension.
497+
multiObjList.Append(new MultiObj("page100"));
478498
multiObjList.Append(new MultiObj("Page20"));
479499
multiObjList.Append(new MultiObj("Page4"));
480500
multiObjList.Append(new MultiObj("Page"));
501+
multiObjList.Append(new MultiObj("PagE"));
502+
multiObjList.Append(new MultiObj("page5"));
503+
multiObjList.Append(new MultiObj("Page5"));
504+
multiObjList.Append(new MultiObj("aaa"));
505+
multiObjList.Append(new MultiObj("AAA"));
506+
multiObjList.Append(new MultiObj("zzz"));
507+
multiObjList.Append(new MultiObj("ZZZ"));
508+
multiObjList.Append(new MultiObj("Page-90"));
509+
multiObjList.Append(new MultiObj("page -90"));
510+
multiObjList.Append(new MultiObj("page-8"));
511+
multiObjList.Append(new MultiObj("page -8"));
481512

482513
bool ascending = true;
483514
MultiCompFunObj compFunObj(MultiCompFunObj::SortKey::NameAlphaNumeric, ascending);
@@ -486,26 +517,28 @@ tTestUnit(ListSort)
486517

487518
compFunObj.Key = MultiCompFunObj::SortKey::NameAlphaNumeric;
488519
compFunObj.Ascending = true;
489-
multiObjList.Sort(compFunObj);
490520
tPrintf("\nSorted Alpha Numeric Ascending\n");
521+
multiObjList.Sort(compFunObj);
491522
PrintMultiObjList(multiObjList);
492523

493524
compFunObj.Key = MultiCompFunObj::SortKey::NameAlphaNumeric;
494525
compFunObj.Ascending = false;
495-
multiObjList.Sort(compFunObj);
496526
tPrintf("\nSorted Alpha Numeric Descending\n");
527+
multiObjList.Sort(compFunObj);
497528
PrintMultiObjList(multiObjList);
498529

530+
// Except for the fact that there are extra items that are not supported by NTFS since you can't have two files
531+
// that only differ by case, when this list gets sorted naturally it results in the same order as Windows explorer.
499532
compFunObj.Key = MultiCompFunObj::SortKey::NameNatural;
500533
compFunObj.Ascending = true;
501-
multiObjList.Sort(compFunObj);
502534
tPrintf("\nSorted Natural Ascending\n");
535+
multiObjList.Sort(compFunObj);
503536
PrintMultiObjList(multiObjList);
504537

505538
compFunObj.Key = MultiCompFunObj::SortKey::NameNatural;
506539
compFunObj.Ascending = false;
507-
multiObjList.Sort(compFunObj);
508540
tPrintf("\nSorted Natural Descending\n");
541+
multiObjList.Sort(compFunObj);
509542
PrintMultiObjList(multiObjList);
510543
}
511544

0 commit comments

Comments
 (0)