* We cannot use a natural sorting algorithm that only supports int32 - that
cannot be stable for entries with up to B_FILE_NAME_LENGTH (256) bytes. * Rewrote the natural sorting code to be simpler where possible, but also supports sorting numbers of arbitrary length. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@38012 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
+109
-107
@@ -180,136 +180,138 @@ void DisallowMetaKeys(BTextView *);
|
|||||||
void DisallowFilenameKeys(BTextView *);
|
void DisallowFilenameKeys(BTextView *);
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark - Natural sorting
|
||||||
|
|
||||||
|
|
||||||
|
struct natural_chunk {
|
||||||
|
enum chunk_type {
|
||||||
|
NUMBER,
|
||||||
|
ASCII,
|
||||||
|
END
|
||||||
|
};
|
||||||
|
chunk_type type;
|
||||||
|
char buffer[B_FILE_NAME_LENGTH];
|
||||||
|
int32 length;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
inline bool
|
inline bool
|
||||||
IsDigit(const char c)
|
IsNaturalDigit(const char c)
|
||||||
{
|
{
|
||||||
if ((c >= 48 && c <= 57) || c == 32)
|
return (c >= '0' && c <= '9') || c == ' ';
|
||||||
return true;
|
}
|
||||||
else
|
|
||||||
return false;
|
|
||||||
|
inline void
|
||||||
|
FetchNaturalChunk(natural_chunk& chunk, const char* source)
|
||||||
|
{
|
||||||
|
if (chunk.type == natural_chunk::ASCII) {
|
||||||
|
// string chunk
|
||||||
|
int32 pos = 0;
|
||||||
|
while (!IsNaturalDigit(source[pos]) && source[pos] != '\0') {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
strlcpy(chunk.buffer, source, pos + 1);
|
||||||
|
chunk.length = pos;
|
||||||
|
} else {
|
||||||
|
// number chunk
|
||||||
|
int32 pos = 0;
|
||||||
|
while (IsNaturalDigit(source[pos]) && source[pos] != '\0') {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
strlcpy(&chunk.buffer[sizeof(chunk.buffer) - 1 - pos], source, pos + 1);
|
||||||
|
chunk.length = pos;
|
||||||
|
|
||||||
|
// replace leading zeros with spaces
|
||||||
|
for (pos = sizeof(chunk.buffer) - 1 - pos;
|
||||||
|
pos < (int32)sizeof(chunk.buffer) - 1; pos++) {
|
||||||
|
if (chunk.buffer[pos] != ' ' && chunk.buffer[pos] != '0')
|
||||||
|
break;
|
||||||
|
|
||||||
|
chunk.buffer[pos] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//! Makes sure both number strings have the same size
|
||||||
|
inline void
|
||||||
|
NormalizeNumberChunks(natural_chunk& a, natural_chunk& b)
|
||||||
|
{
|
||||||
|
if (a.length > b.length) {
|
||||||
|
memset(&b.buffer[sizeof(b.buffer) - 1 - a.length], ' ',
|
||||||
|
a.length - b.length);
|
||||||
|
b.length = a.length;
|
||||||
|
} else if (b.length > a.length) {
|
||||||
|
memset(&a.buffer[sizeof(a.buffer) - 1 - b.length], ' ',
|
||||||
|
b.length - a.length);
|
||||||
|
a.length = b.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//! Compares two strings naturally, as opposed to lexicographically
|
//! Compares two strings naturally, as opposed to lexicographically
|
||||||
inline int
|
inline int
|
||||||
NaturalCompare(const char *s1, const char *s2)
|
NaturalCompare(const char* stringA, const char* stringB)
|
||||||
{
|
{
|
||||||
struct Chunk {
|
natural_chunk a;
|
||||||
int32 type;
|
natural_chunk b;
|
||||||
union {
|
|
||||||
char* ascii;
|
|
||||||
// Type = 0
|
|
||||||
int32 num;
|
|
||||||
// Type = 1
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
Chunk a;
|
uint32 indexA = 0;
|
||||||
Chunk b;
|
uint32 indexB = 0;
|
||||||
|
|
||||||
size_t len1 = strlen(s1);
|
|
||||||
size_t len2 = strlen(s2);
|
|
||||||
|
|
||||||
char bufferA[len1 + 1];
|
|
||||||
char bufferB[len2 + 1];
|
|
||||||
|
|
||||||
uint32 i = 0;
|
|
||||||
uint32 j = 0;
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
// determine type of next chunks in each string based on first char
|
// Determine type of next chunks in each string based on first char
|
||||||
if (i == len1)
|
if (stringA[indexA] == '\0')
|
||||||
a.type = -1;
|
a.type = natural_chunk::END;
|
||||||
else if (IsDigit(s1[i]))
|
else if (IsNaturalDigit(stringA[indexA]))
|
||||||
a.type = 1;
|
a.type = natural_chunk::NUMBER;
|
||||||
else
|
else
|
||||||
a.type = 0;
|
a.type = natural_chunk::ASCII;
|
||||||
|
|
||||||
if (j == len2)
|
if (stringB[indexB] == '\0')
|
||||||
b.type = -1;
|
b.type = natural_chunk::END;
|
||||||
else if (IsDigit(s2[j]))
|
else if (IsNaturalDigit(stringB[indexB]))
|
||||||
b.type = 1;
|
b.type = natural_chunk::NUMBER;
|
||||||
else
|
else
|
||||||
b.type = 0;
|
b.type = natural_chunk::ASCII;
|
||||||
|
|
||||||
// check if we reached the end of either string
|
// Check if we reached the end of either string
|
||||||
if (a.type == b.type && a.type == -1)
|
if (a.type == natural_chunk::END)
|
||||||
return 0;
|
return b.type == natural_chunk::END ? 0 : -1;
|
||||||
if (a.type == -1)
|
if (b.type == natural_chunk::END)
|
||||||
return -1;
|
|
||||||
if (b.type == -1)
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
if (a.type != b.type) {
|
if (a.type != b.type) {
|
||||||
// different chunk types, just compare the remaining strings
|
// Different chunk types, just compare the remaining strings
|
||||||
return strcasecmp(&s1[i], &s2[j]);
|
return strcasecmp(&stringA[indexA], &stringB[indexB]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch the next chunk for a
|
// Fetch the next chunks
|
||||||
if (a.type == 0) {
|
FetchNaturalChunk(a, &stringA[indexA]);
|
||||||
// string chunk
|
FetchNaturalChunk(b, &stringB[indexB]);
|
||||||
int32 k = i;
|
|
||||||
while (!IsDigit(s1[k]) && s1[k] != 0) {
|
indexA += a.length;
|
||||||
bufferA[k - i] = s1[k];
|
indexB += b.length;
|
||||||
k++;
|
|
||||||
}
|
// Compare the two chunks based on their type
|
||||||
bufferA[k - i] = 0;
|
if (a.type == natural_chunk::ASCII) {
|
||||||
a.ascii = bufferA;
|
// String chunks
|
||||||
i += k - i;
|
int result = strcasecmp(a.buffer, b.buffer);
|
||||||
|
if (result != 0)
|
||||||
|
return result;
|
||||||
} else {
|
} else {
|
||||||
// number chunk
|
// Number chunks - they are compared as strings to allow an
|
||||||
int32 k = i;
|
// arbitrary number of digits.
|
||||||
while (IsDigit(s1[k]) && s1[k] != 0) {
|
NormalizeNumberChunks(a, b);
|
||||||
bufferA[k - i] = s1[k];
|
|
||||||
k++;
|
int result = strcmp(a.buffer - 1 + sizeof(a.buffer) - a.length,
|
||||||
}
|
b.buffer - 1 + sizeof(b.buffer) - b.length);
|
||||||
bufferA[k - i] = 0;
|
if (result != 0)
|
||||||
a.ascii = bufferA;
|
return result;
|
||||||
a.num = atoi(bufferA);
|
|
||||||
i += k - i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch the next chunk for b
|
// The chunks were equal, proceed with the next chunk
|
||||||
if (b.type == 0) {
|
|
||||||
// string chunk
|
|
||||||
int32 k = j;
|
|
||||||
while (!IsDigit(s2[k]) && s2[k] != 0) {
|
|
||||||
bufferB[k - j] = s2[k];
|
|
||||||
k++;
|
|
||||||
}
|
|
||||||
bufferB[k - j] = 0;
|
|
||||||
b.ascii = bufferB;
|
|
||||||
j += k - j;
|
|
||||||
} else {
|
|
||||||
// number chunk
|
|
||||||
int32 k = j;
|
|
||||||
while (IsDigit(s2[k]) && s2[k] != 0) {
|
|
||||||
bufferB[k - j] = s2[k];
|
|
||||||
k++;
|
|
||||||
}
|
|
||||||
bufferB[k - j] = 0;
|
|
||||||
b.ascii = bufferB;
|
|
||||||
b.num = atoi(bufferB);
|
|
||||||
j += k - j;
|
|
||||||
}
|
|
||||||
|
|
||||||
// compare the two chunks based on their type
|
|
||||||
if (a.type == 0) {
|
|
||||||
// string chunks
|
|
||||||
int stringCompareResult = strcasecmp(a.ascii, b.ascii);
|
|
||||||
// if the chunk strings are the same, keep using natural
|
|
||||||
// sorting for the next chunks
|
|
||||||
if (stringCompareResult != 0)
|
|
||||||
return stringCompareResult;
|
|
||||||
} else {
|
|
||||||
// number chunks
|
|
||||||
if (a.num != b.num) {
|
|
||||||
if (a.num < b.num)
|
|
||||||
return -1;
|
|
||||||
if (a.num > b.num)
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user