my first post here (it’s a real pity I hadn’t discovered this great community earlier).
Anyway, I’ve coded a C function that removes from string s any character contained in string del. I was wondering if there’s room for improvement, speed-wise, especially for the part that looks for chars contained in del, inside the for-loop (I was using strpbrk(), but pmg wisely suggested strchr() ).
Bug-hunters are most welcome too! I think it’s robust, but you never know.
Here’s the code (thanks in advance for any answers)…
Current version
// remove from string s any char contained in string del (return modified s)
// alg:
// parse s via cp1, keep desired *cp1's by copying them via cp2 to the start of s
// null terminate & return the trimmed s
char *s_strip(char *s, const char *del)
{
char *cp1; // for parsing the whole s
char *cp2; // for keeping desired *cp1's
for (cp1=s, cp2=s; *cp1; cp1++ )
if ( !strchr(del, *cp1) ) // *cp1 is NOT contained in del (thanks pmg!)
*cp2++ = *cp1; // copy it via cp2
*cp2 = 0; // null terminate the trimmed s
return s;
}
Original version
char *s_strip(char *s, const char *del)
{
char *cp1; // for parsing the whole s
char *cp2; // for keeping desired *cp1's
for (cp1=s, cp2=s; *cp1; cp1++ )
if ( cp1 != strpbrk(cp1, del) ) { // *cp1 is NOT contained in del
*cp2 = *cp1; // copy it via cp2
cp2++;
}
*cp2 = 0; // null terminate the trimmed s
return s;
}
How long are the input strings going to be, and how many characters are you going to be deleting, on average. If you work with 8-bit code sets and longer source strings, think about:
This replaces the function call (
strpbrk()orstrchr()) with a simple array lookup at the cost of initializing the array on entry to the function. If the processed strings are fairly long, this could easily pay itself back in the vastly reduced search time.This version of the code returns a pointer to the null at the end of the string – which allows you to compute the length of the compressed string on return without calling
strlen(). I find this a more useful design than returning the pointer to the start of the string – I already knew where the string started, but I no longer know where it ends, yet the functions_strip()does know that. (If we were able to start over, I’d lobby for the same design forstrcpy()andstrcat()too.)The coercions to ‘unsigned char’ ensure that it works properly on machines with signed and unsigned characters. I’m not really happy with the slathering of casts, but it is hard to avoid them (well, the cast assigning
stoup3could be avoided by copying `up2 instead).You probably could come up with a design that handles signed characters too, along the lines of:
You can then rely on the signed characters being in a range such that
map[-128]..map[+127]still lands within therealmaparray.The function above has been corrected a couple of times since it was first posted – it now seems to work consistently with the two solutions suggested in the question. I put the three solutions into a test harness to time them, mainly because I was curious about how the map code would perform, having lost out to assembler code inline functions in the past. The mapping solution very quickly shows itself to be fastest on my test system (Mac Mini 2 GHZ Intel Core 2 Duo, MacOS X 10.6.7, GCC 4.1.2).
The times are average times in seconds. Each algorithm was given 10 runs.
The size is the size of the source string; the delete string was 22 characters (6 each of lower-case letters, upper-case letters and digits, plus 4 punctuation). The data was a fixed string built from the letters, digits and punctuation in ASCII, repeated as often as necessary to reach the stated size, which excludes the terminating null. Note that the timing includes copying the source string each time – the ‘null’ column indicates the time spent on the copying.
Even with very short source strings, the map algorithm is much faster than either the
strchr()orstrpbrk()algorithms (5-10 times as fast as thestrchr()algorithm, and 5-50 times as fast as thestrpbrk()algorithm), with the disparity growing with search string size. (I did not expect this result – because there is setup overhead in the map code.)The ‘micro1’ and ‘micro2’ algorithms correspond to the modifications suggested by AShelly. When the strings are long enough (somewhere between 128 and 512 bytes is the switch-over), then the micro-optimized versions are quicker than the simple map.
Test Code
Contact me (see my profile) if you want the source for
timer.c,timer.h. I’d normally build them into a library and link with the library, but it was simpler to include the files in the program this time.Micro-optimizations