* 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:
Axel Dörfler
2010-08-11 10:14:20 +00:00
parent 15575718e8
commit 6a960972b5
+109 -107
View File
@@ -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;