* 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 *);
|
||||
|
||||
|
||||
// #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
|
||||
IsDigit(const char c)
|
||||
IsNaturalDigit(const char c)
|
||||
{
|
||||
if ((c >= 48 && c <= 57) || c == 32)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
return (c >= '0' && c <= '9') || c == ' ';
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
inline int
|
||||
NaturalCompare(const char *s1, const char *s2)
|
||||
NaturalCompare(const char* stringA, const char* stringB)
|
||||
{
|
||||
struct Chunk {
|
||||
int32 type;
|
||||
union {
|
||||
char* ascii;
|
||||
// Type = 0
|
||||
int32 num;
|
||||
// Type = 1
|
||||
};
|
||||
};
|
||||
natural_chunk a;
|
||||
natural_chunk b;
|
||||
|
||||
Chunk a;
|
||||
Chunk b;
|
||||
|
||||
size_t len1 = strlen(s1);
|
||||
size_t len2 = strlen(s2);
|
||||
|
||||
char bufferA[len1 + 1];
|
||||
char bufferB[len2 + 1];
|
||||
|
||||
uint32 i = 0;
|
||||
uint32 j = 0;
|
||||
uint32 indexA = 0;
|
||||
uint32 indexB = 0;
|
||||
|
||||
while (true) {
|
||||
// determine type of next chunks in each string based on first char
|
||||
if (i == len1)
|
||||
a.type = -1;
|
||||
else if (IsDigit(s1[i]))
|
||||
a.type = 1;
|
||||
// Determine type of next chunks in each string based on first char
|
||||
if (stringA[indexA] == '\0')
|
||||
a.type = natural_chunk::END;
|
||||
else if (IsNaturalDigit(stringA[indexA]))
|
||||
a.type = natural_chunk::NUMBER;
|
||||
else
|
||||
a.type = 0;
|
||||
a.type = natural_chunk::ASCII;
|
||||
|
||||
if (j == len2)
|
||||
b.type = -1;
|
||||
else if (IsDigit(s2[j]))
|
||||
b.type = 1;
|
||||
if (stringB[indexB] == '\0')
|
||||
b.type = natural_chunk::END;
|
||||
else if (IsNaturalDigit(stringB[indexB]))
|
||||
b.type = natural_chunk::NUMBER;
|
||||
else
|
||||
b.type = 0;
|
||||
b.type = natural_chunk::ASCII;
|
||||
|
||||
// check if we reached the end of either string
|
||||
if (a.type == b.type && a.type == -1)
|
||||
return 0;
|
||||
if (a.type == -1)
|
||||
return -1;
|
||||
if (b.type == -1)
|
||||
// Check if we reached the end of either string
|
||||
if (a.type == natural_chunk::END)
|
||||
return b.type == natural_chunk::END ? 0 : -1;
|
||||
if (b.type == natural_chunk::END)
|
||||
return 1;
|
||||
|
||||
if (a.type != b.type) {
|
||||
// different chunk types, just compare the remaining strings
|
||||
return strcasecmp(&s1[i], &s2[j]);
|
||||
// Different chunk types, just compare the remaining strings
|
||||
return strcasecmp(&stringA[indexA], &stringB[indexB]);
|
||||
}
|
||||
|
||||
// fetch the next chunk for a
|
||||
if (a.type == 0) {
|
||||
// string chunk
|
||||
int32 k = i;
|
||||
while (!IsDigit(s1[k]) && s1[k] != 0) {
|
||||
bufferA[k - i] = s1[k];
|
||||
k++;
|
||||
}
|
||||
bufferA[k - i] = 0;
|
||||
a.ascii = bufferA;
|
||||
i += k - i;
|
||||
// Fetch the next chunks
|
||||
FetchNaturalChunk(a, &stringA[indexA]);
|
||||
FetchNaturalChunk(b, &stringB[indexB]);
|
||||
|
||||
indexA += a.length;
|
||||
indexB += b.length;
|
||||
|
||||
// Compare the two chunks based on their type
|
||||
if (a.type == natural_chunk::ASCII) {
|
||||
// String chunks
|
||||
int result = strcasecmp(a.buffer, b.buffer);
|
||||
if (result != 0)
|
||||
return result;
|
||||
} else {
|
||||
// number chunk
|
||||
int32 k = i;
|
||||
while (IsDigit(s1[k]) && s1[k] != 0) {
|
||||
bufferA[k - i] = s1[k];
|
||||
k++;
|
||||
}
|
||||
bufferA[k - i] = 0;
|
||||
a.ascii = bufferA;
|
||||
a.num = atoi(bufferA);
|
||||
i += k - i;
|
||||
// Number chunks - they are compared as strings to allow an
|
||||
// arbitrary number of digits.
|
||||
NormalizeNumberChunks(a, b);
|
||||
|
||||
int result = strcmp(a.buffer - 1 + sizeof(a.buffer) - a.length,
|
||||
b.buffer - 1 + sizeof(b.buffer) - b.length);
|
||||
if (result != 0)
|
||||
return result;
|
||||
}
|
||||
|
||||
// fetch the next chunk for b
|
||||
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;
|
||||
}
|
||||
}
|
||||
// The chunks were equal, proceed with the next chunk
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user