1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Replaced the old, badly-named and badly-implemented String::compareLexicographically() method with String::compareNatural(), which uses a smarter algorithm. Also added a method StringArray::sortNatural() which uses this.

This commit is contained in:
jules 2014-06-15 10:37:31 +01:00
parent 9c8afb5b48
commit 3b88555140
9 changed files with 131 additions and 43 deletions

View file

@ -924,11 +924,11 @@ private:
int compareElements (XmlElement* first, XmlElement* second) const
{
int result = first->getStringAttribute (attributeToSort)
.compareLexicographically (second->getStringAttribute (attributeToSort));
.compareNatural (second->getStringAttribute (attributeToSort));
if (result == 0)
result = first->getStringAttribute ("ID")
.compareLexicographically (second->getStringAttribute ("ID"));
.compareNatural (second->getStringAttribute ("ID"));
return direction * result;
}

View file

@ -481,7 +481,7 @@ struct FileSorter
{
static int compareElements (const File& f1, const File& f2)
{
return f1.getFileName().compareIgnoreCase (f2.getFileName());
return f1.getFileName().compareNatural (f2.getFileName());
}
};

View file

@ -743,7 +743,7 @@ struct ItemSorter
{
static int compareElements (const ValueTree& first, const ValueTree& second)
{
return first [Ids::name].toString().compareIgnoreCase (second [Ids::name].toString());
return first [Ids::name].toString().compareNatural (second [Ids::name].toString());
}
};
@ -755,7 +755,7 @@ struct ItemSorterWithGroupsAtStart
const bool secondIsGroup = second.hasType (Ids::GROUP);
if (firstIsGroup == secondIsGroup)
return first [Ids::name].toString().compareIgnoreCase (second [Ids::name].toString());
return first [Ids::name].toString().compareNatural (second [Ids::name].toString());
return firstIsGroup ? -1 : 1;
}

View file

@ -259,15 +259,15 @@ struct PluginSorter
switch (method)
{
case KnownPluginList::sortByCategory: diff = first->category.compareLexicographically (second->category); break;
case KnownPluginList::sortByManufacturer: diff = first->manufacturerName.compareLexicographically (second->manufacturerName); break;
case KnownPluginList::sortByCategory: diff = first->category.compareNatural (second->category); break;
case KnownPluginList::sortByManufacturer: diff = first->manufacturerName.compareNatural (second->manufacturerName); break;
case KnownPluginList::sortByFormat: diff = first->pluginFormatName.compare (second->pluginFormatName); break;
case KnownPluginList::sortByFileSystemLocation: diff = lastPathPart (first->fileOrIdentifier).compare (lastPathPart (second->fileOrIdentifier)); break;
default: break;
}
if (diff == 0)
diff = first->name.compareLexicographically (second->name);
diff = first->name.compareNatural (second->name);
return diff * direction;
}

View file

@ -221,8 +221,8 @@ private:
static inline StringHolder* bufferFromText (const CharPointerType text) noexcept
{
// (Can't use offsetof() here because of warnings about this not being a POD)
return reinterpret_cast <StringHolder*> (reinterpret_cast <char*> (text.getAddress())
- (reinterpret_cast <size_t> (reinterpret_cast <StringHolder*> (1)->text) - 1));
return reinterpret_cast<StringHolder*> (reinterpret_cast<char*> (text.getAddress())
- (reinterpret_cast<size_t> (reinterpret_cast<StringHolder*> (1)->text) - 1));
}
void compileTimeChecks()
@ -618,19 +618,98 @@ int String::compare (const char* const other) const noexcept { return text
int String::compare (const wchar_t* const other) const noexcept { return text.compare (castToCharPointer_wchar_t (other)); }
int String::compareIgnoreCase (const String& other) const noexcept { return (text == other.text) ? 0 : text.compareIgnoreCase (other.text); }
int String::compareLexicographically (const String& other) const noexcept
static int stringCompareRight (String::CharPointerType s1, String::CharPointerType s2) noexcept
{
CharPointerType s1 (text);
for (int bias = 0;;)
{
const juce_wchar c1 = s1.getAndAdvance();
const bool isDigit1 = CharacterFunctions::isDigit (c1);
while (! (s1.isEmpty() || s1.isLetterOrDigit()))
++s1;
const juce_wchar c2 = s2.getAndAdvance();
const bool isDigit2 = CharacterFunctions::isDigit (c2);
CharPointerType s2 (other.text);
if (! (isDigit1 || isDigit2)) return bias;
if (! isDigit1) return -1;
if (! isDigit2) return 1;
while (! (s2.isEmpty() || s2.isLetterOrDigit()))
++s2;
if (c1 != c2 && bias == 0)
bias = c1 < c2 ? -1 : 1;
return s1.compareIgnoreCase (s2);
jassert (c1 != 0 && c2 != 0);
}
}
static int stringCompareLeft (String::CharPointerType s1, String::CharPointerType s2) noexcept
{
for (;;)
{
const juce_wchar c1 = s1.getAndAdvance();
const bool isDigit1 = CharacterFunctions::isDigit (c1);
const juce_wchar c2 = s2.getAndAdvance();
const bool isDigit2 = CharacterFunctions::isDigit (c2);
if (! (isDigit1 || isDigit2)) return 0;
if (! isDigit1) return -1;
if (! isDigit2) return 1;
if (c1 < c2) return -1;
if (c1 > c2) return 1;
}
}
static int naturalStringCompare (String::CharPointerType s1, String::CharPointerType s2) noexcept
{
bool firstLoop = true;
for (;;)
{
const bool hasSpace1 = s1.isWhitespace();
const bool hasSpace2 = s2.isWhitespace();
if ((! firstLoop) && (hasSpace1 ^ hasSpace2))
return hasSpace2 ? 1 : -1;
firstLoop = false;
if (hasSpace1) s1 = s1.findEndOfWhitespace();
if (hasSpace2) s2 = s2.findEndOfWhitespace();
if (s1.isDigit() && s2.isDigit())
{
const int result = (*s1 == '0' || *s2 == '0') ? stringCompareLeft (s1, s2)
: stringCompareRight (s1, s2);
if (result != 0)
return result;
}
const juce_wchar c1 = s1.getAndAdvance();
const juce_wchar c2 = s2.getAndAdvance();
if (c1 == c2 || CharacterFunctions::toUpperCase (c1)
== CharacterFunctions::toUpperCase (c2))
{
if (c1 == 0)
return 0;
}
else
{
const bool isAlphaNum1 = CharacterFunctions::isLetterOrDigit (c1);
const bool isAlphaNum2 = CharacterFunctions::isLetterOrDigit (c2);
if (isAlphaNum2 && ! isAlphaNum1) return -1;
if (isAlphaNum1 && ! isAlphaNum2) return 1;
return c1 < c2 ? -1 : 1;
}
jassert (c1 != 0 && c2 != 0);
}
}
int String::compareNatural (StringRef other) const noexcept
{
return naturalStringCompare (getCharPointer(), other.text);
}
//==============================================================================
@ -1760,13 +1839,13 @@ String String::formatted (const String pf, ... )
va_start (args, pf);
#if JUCE_WINDOWS
HeapBlock <wchar_t> temp (bufferSize);
HeapBlock<wchar_t> temp (bufferSize);
const int num = (int) _vsnwprintf (temp.getData(), bufferSize - 1, pf.toWideCharPointer(), args);
#elif JUCE_ANDROID
HeapBlock <char> temp (bufferSize);
HeapBlock<char> temp (bufferSize);
const int num = (int) vsnprintf (temp.getData(), bufferSize - 1, pf.toUTF8(), args);
#else
HeapBlock <wchar_t> temp (bufferSize);
HeapBlock<wchar_t> temp (bufferSize);
const int num = (int) vswprintf (temp.getData(), bufferSize - 1, pf.toWideCharPointer(), args);
#endif
@ -1921,12 +2000,12 @@ struct StringEncodingConverter
{
static CharPointerType_Dest convert (const String& s)
{
String& source = const_cast <String&> (s);
String& source = const_cast<String&> (s);
typedef typename CharPointerType_Dest::CharType DestChar;
if (source.isEmpty())
return CharPointerType_Dest (reinterpret_cast <const DestChar*> (&emptyChar));
return CharPointerType_Dest (reinterpret_cast<const DestChar*> (&emptyChar));
CharPointerType_Src text (source.getCharPointer());
const size_t extraBytesNeeded = CharPointerType_Dest::getBytesRequiredFor (text) + sizeof (typename CharPointerType_Dest::CharType);
@ -1936,7 +2015,7 @@ struct StringEncodingConverter
text = source.getCharPointer();
void* const newSpace = addBytesToPointer (text.getAddress(), (int) endOffset);
const CharPointerType_Dest extraSpace (static_cast <DestChar*> (newSpace));
const CharPointerType_Dest extraSpace (static_cast<DestChar*> (newSpace));
#if JUCE_DEBUG // (This just avoids spurious warnings from valgrind about the uninitialised bytes at the end of the buffer..)
const size_t bytesToClear = (size_t) jmin ((int) extraBytesNeeded, 4);

View file

@ -346,15 +346,15 @@ public:
*/
int compareIgnoreCase (const String& other) const noexcept;
/** Lexicographic comparison with another string.
/** Compares two strings, taking into account textual characteristics like numbers and spaces.
The comparison used here is case-insensitive and ignores leading non-alphanumeric
characters, making it good for sorting human-readable strings.
This comparison is case-insensitive and can detect words and embedded numbers in the
strings, making it good for sorting human-readable lists of things like filenames.
@returns 0 if the two strings are identical; negative if this string comes before
the other one alphabetically, or positive if it comes after it.
*/
int compareLexicographically (const String& other) const noexcept;
int compareNatural (StringRef other) const noexcept;
/** Tests whether the string begins with another string.
If the parameter is an empty string, this will always return true.

View file

@ -199,6 +199,11 @@ int StringArray::indexOf (StringRef stringToLookFor, const bool ignoreCase, int
return -1;
}
void StringArray::move (const int currentIndex, const int newIndex) noexcept
{
strings.move (currentIndex, newIndex);
}
//==============================================================================
void StringArray::remove (const int index)
{
@ -255,12 +260,17 @@ void StringArray::trim()
//==============================================================================
struct InternalStringArrayComparator_CaseSensitive
{
static int compareElements (String& first, String& second) { return first.compare (second); }
static int compareElements (String& s1, String& s2) noexcept { return s1.compare (s2); }
};
struct InternalStringArrayComparator_CaseInsensitive
{
static int compareElements (String& first, String& second) { return first.compareIgnoreCase (second); }
static int compareElements (String& s1, String& s2) noexcept { return s1.compareIgnoreCase (s2); }
};
struct InternalStringArrayComparator_Natural
{
static int compareElements (String& s1, String& s2) noexcept { return s1.compareNatural (s2); }
};
void StringArray::sort (const bool ignoreCase)
@ -277,12 +287,12 @@ void StringArray::sort (const bool ignoreCase)
}
}
void StringArray::move (const int currentIndex, int newIndex) noexcept
void StringArray::sortNatural()
{
strings.move (currentIndex, newIndex);
InternalStringArrayComparator_Natural comp;
strings.sort (comp);
}
//==============================================================================
String StringArray::joinIntoString (StringRef separator, int start, int numberToJoin) const
{

View file

@ -134,18 +134,12 @@ public:
/** Returns a pointer to the first String in the array.
This method is provided for compatibility with standard C++ iteration mechanisms.
*/
inline String* begin() const noexcept
{
return strings.begin();
}
inline String* begin() const noexcept { return strings.begin(); }
/** Returns a pointer to the String which follows the last element in the array.
This method is provided for compatibility with standard C++ iteration mechanisms.
*/
inline String* end() const noexcept
{
return strings.end();
}
inline String* end() const noexcept { return strings.end(); }
/** Searches for a string in the array.
@ -387,11 +381,16 @@ public:
//==============================================================================
/** Sorts the array into alphabetical order.
@param ignoreCase if true, the comparisons used will be case-sensitive.
*/
void sort (bool ignoreCase);
/** Sorts the array using extra language-aware rules to do a better job of comparing
words containing spaces and numbers.
@see String::compareNatural()
*/
void sortNatural();
//==============================================================================
/** Increases the array's internal storage to hold a minimum number of elements.

View file

@ -222,7 +222,7 @@ struct FileInfoComparator
return first->isDirectory ? -1 : 1;
#endif
return first->filename.compareIgnoreCase (second->filename);
return first->filename.compareNatural (second->filename);
}
};