Improves string trimming and parsing

Refactors string trimming functions for improved performance by using a lookup table for whitespace characters.

Also adds a function to split a string into two tokens at the first whitespace.
This commit is contained in:
Magne Sjaastad
2025-07-17 13:52:35 +02:00
parent efc8735a76
commit 2305a73908
3 changed files with 126 additions and 9 deletions
@@ -23,19 +23,55 @@
#include <QString>
#include <array>
#include <charconv>
#include <regex>
#include <sstream>
const std::string WHITESPACE = " \n\r\t\f\v";
// Lookup table for fast whitespace checking
// Using array of 256 elements (all possible char values)
// where true means the character is whitespace
namespace
{
// Create a compile-time whitespace lookup table
constexpr std::array<bool, 256> createWhitespaceTable()
{
std::array<bool, 256> table = {}; // Initialize all to false
// Mark standard whitespace characters as true
table[' '] = true; // space
table['\n'] = true; // newline
table['\r'] = true; // carriage return
table['\t'] = true; // tab
table['\f'] = true; // form feed
table['\v'] = true; // vertical tab
return table;
}
// Create the lookup table at compile time
constexpr auto WHITESPACE_TABLE = createWhitespaceTable();
// Helper function to check if a character is whitespace
constexpr bool isWhitespace( unsigned char c )
{
return WHITESPACE_TABLE[c];
}
} // namespace
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::string_view RiaStdStringTools::leftTrimString( std::string_view s )
{
size_t start = s.find_first_not_of( WHITESPACE );
return ( start == std::string::npos ) ? "" : s.substr( start );
const char* data = s.data();
const char* end = data + s.size();
// Find first non-whitespace character
while ( data < end && isWhitespace( static_cast<unsigned char>( *data ) ) )
++data;
return std::string_view( data, end - data );
}
//--------------------------------------------------------------------------------------------------
@@ -43,8 +79,14 @@ std::string_view RiaStdStringTools::leftTrimString( std::string_view s )
//--------------------------------------------------------------------------------------------------
std::string_view RiaStdStringTools::rightTrimString( std::string_view s )
{
size_t end = s.find_last_not_of( WHITESPACE );
return ( end == std::string::npos ) ? "" : s.substr( 0, end + 1 );
const char* data = s.data();
const char* end = data + s.size();
// Find last non-whitespace character
while ( end > data && isWhitespace( static_cast<unsigned char>( *( end - 1 ) ) ) )
--end;
return std::string_view( data, end - data );
}
//--------------------------------------------------------------------------------------------------
@@ -68,7 +110,17 @@ std::string RiaStdStringTools::removeWhitespace( const std::string& line )
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiaStdStringTools::isNumber( const std::string& s, char decimalPoint )
char RiaStdStringTools::decimalPoint()
{
std::locale loc;
char decimalPoint = std::use_facet<std::numpunct<char>>( loc ).decimal_point();
return decimalPoint;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RiaStdStringTools::isNumber( std::string_view s, char decimalPoint )
{
if ( s.empty() ) return false;
if ( findCharMatchCount( s, decimalPoint ) > 1 ) return false;
@@ -222,7 +274,42 @@ std::string RiaStdStringTools::joinStrings( const std::vector<std::string>& s, c
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RiaStdStringTools::findCharMatchCount( const std::string& s, char c )
std::pair<std::string_view, std::string_view> RiaStdStringTools::splitAtWhitespace( std::string_view str )
{
const char* data = str.data();
const char* end = data + str.size();
const char* pos = data;
// Skip leading whitespace
while ( pos < end && isWhitespace( static_cast<unsigned char>( *pos ) ) )
++pos;
// Beginning of first token
const char* firstStart = pos;
// Find end of first token (first whitespace character)
while ( pos < end && !isWhitespace( static_cast<unsigned char>( *pos ) ) )
++pos;
if ( pos >= end ) return {};
std::string_view first( firstStart, pos - firstStart );
// Skip all whitespace characters
while ( pos < end && isWhitespace( static_cast<unsigned char>( *pos ) ) )
++pos;
if ( pos >= end ) return { first, {} };
std::string_view second( pos, end - pos );
return { first, rightTrimString( second ) };
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RiaStdStringTools::findCharMatchCount( std::string_view s, char c )
{
size_t count = 0;
size_t pos = 0;
@@ -36,7 +36,8 @@ public:
static std::string_view leftTrimString( std::string_view s );
static std::string removeWhitespace( const std::string& line );
static bool isNumber( const std::string& s, char decimalPoint );
static char decimalPoint();
static bool isNumber( std::string_view s, char decimalPoint );
static int16_t toInt16( std::string_view s );
static int toInt( std::string_view s );
@@ -56,6 +57,8 @@ public:
static std::vector<std::string> splitString( const std::string& s, char delimiter );
static std::string joinStrings( const std::vector<std::string>& s, char delimiter );
static std::pair<std::string_view, std::string_view> splitAtWhitespace( std::string_view str );
static int computeEditDistance( const std::string& x, const std::string& y );
static std::string removeHtmlTags( const std::string& s );
@@ -75,7 +78,7 @@ public:
private:
template <class Container>
static void splitByDelimiter( const std::string& str, Container& cont, char delimiter = ' ' );
static size_t findCharMatchCount( const std::string& s, char c );
static size_t findCharMatchCount( std::string_view s, char c );
};
//==================================================================================================
@@ -95,6 +95,7 @@ TEST( RiaStdStringToolsTest, LeftTrimString )
std::make_pair( "\tbla\v", "bla\v" ),
std::make_pair( "bla", "bla" ),
std::make_pair( "", "" ),
std::make_pair( "\t\t\t\t\t\t\t\t", "" ),
};
for ( auto [input, expectedText] : testData )
@@ -117,6 +118,7 @@ TEST( RiaStdStringToolsTest, RightTrimString )
std::make_pair( "\tbla\v", "\tbla" ),
std::make_pair( "bla", "bla" ),
std::make_pair( "", "" ),
std::make_pair( "\t\t\t\t\t\t\t\t", "" ),
};
for ( auto [input, expectedText] : testData )
@@ -139,6 +141,8 @@ TEST( RiaStdStringToolsTest, RemoveWhitespace )
std::make_pair( "\tbla\v", "bla" ),
std::make_pair( "bla", "bla" ),
std::make_pair( "", "" ),
std::make_pair( "\t\t\t\t\t\t\t\t", "" ),
};
for ( auto [input, expectedText] : testData )
@@ -147,6 +151,29 @@ TEST( RiaStdStringToolsTest, RemoveWhitespace )
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST( RiaStdStringToolsTest, StringInTwoTokens )
{
std::vector<std::pair<std::string, std::pair<std::string, std::string>>> testData = {
std::make_pair( "bla bla", std::make_pair( "bla", "bla" ) ),
std::make_pair( "bla bla", std::make_pair( "bla", "bla" ) ),
std::make_pair( " bla bla ", std::make_pair( "bla", "bla" ) ),
std::make_pair( "\tbla\tbla\t", std::make_pair( "bla", "bla" ) ),
std::make_pair( "\tbla\v bla\v", std::make_pair( "bla", "bla" ) ),
std::make_pair( "", std::make_pair( "", "" ) ),
std::make_pair( "\t\t\t\t\t\t\t\t", std::make_pair( "", "" ) ),
};
for ( auto [input, expectedText] : testData )
{
const auto& [first, second] = RiaStdStringTools::splitAtWhitespace( input );
EXPECT_EQ( first, expectedText.first );
EXPECT_EQ( second, expectedText.second );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------