I want a function like strncmp()
except that it only compares characters in a given character set. Given:
#define IDENT_CHARS "ABCDEFGHIJKLMNOPQRSTUVWXYZ_" \
"abcdefghijklmnopqrstuvwxyz" \
"0123456789"
int strncmp_in_set( char const *s1, char const *s2, size_t n, char const *charset );
Then the following would all be true:
strncmp_in_set( "", "A", 0, IDENT_CHARS ) == 0
strncmp_in_set( "A", "", 0, IDENT_CHARS ) == 0
strncmp_in_set( "", "A", 1, IDENT_CHARS ) < 0
strncmp_in_set( "A", "", 1, IDENT_CHARS ) > 0
strncmp_in_set( "A", "A", 1, IDENT_CHARS ) == 0
strncmp_in_set( "A", "B", 1, IDENT_CHARS ) < 0
strncmp_in_set( "B", "A", 1, IDENT_CHARS ) > 0
strncmp_in_set( "A", "A", 9, IDENT_CHARS ) == 0
strncmp_in_set( "A", "B", 9, IDENT_CHARS ) < 0
strncmp_in_set( "B", "A", 9, IDENT_CHARS ) > 0
strncmp_in_set( "A", "AB", 2, IDENT_CHARS ) < 0
strncmp_in_set( "AB", "A", 2, IDENT_CHARS ) > 0
strncmp_in_set( "AB", "AX", 1, IDENT_CHARS ) == 0
strncmp_in_set( "-A", "A", 2, IDENT_CHARS ) == 0
strncmp_in_set( "A-", "A", 2, IDENT_CHARS ) == 0
strncmp_in_set( "-A-", "A", 3, IDENT_CHARS ) == 0
strncmp_in_set( "A", "-A", 2, IDENT_CHARS ) == 0
strncmp_in_set( "A", "A-", 1, IDENT_CHARS ) == 0
strncmp_in_set( "A", "-A-", 2, IDENT_CHARS ) == 0
strncmp_in_set( "A-", "A-B", 2, IDENT_CHARS ) == 0
strncmp_in_set( "AB", "AA", 3, IDENT_CHARS ) > 0
strncmp_in_set( "AB", "A-A", 3, IDENT_CHARS ) > 0
strncmp_in_set( "AB", "A~A", 3, IDENT_CHARS ) < 0
strncmp_in_set( "A-B", "A-BC", 3, IDENT_CHARS ) == 0
strncmp_in_set( "-e-a--st-", "east", 9, IDENT_CHARS ) == 0
strncmp_in_set( "-e-a--st-", "east-const", 9, IDENT_CHARS ) == 0
strncmp_in_set( "east-const", "-e-a--st-", 9, IDENT_CHARS ) == 0
strncmp_in_set( "non-e", "non-empty", 5, IDENT_CHARS ) == 0
strncmp_in_set( "non-empty", "non-e", 5, IDENT_CHARS ) == 0
strncmp_in_set( "no-foo", "nofoo", 6, IDENT_CHARS ) == 0
In particular, for cases that should return:
- zero (strings compare equal), the order of the arguments
s1
ands2
must not matter, i.e., you must get0
in either case. - non-zero (strings don't compare equal), the sign of the value returned must match whatever
strncmp()
would return.
The second bullet is important to preserve ordering. For example, an implementation that first stripped characters not in the set from both strings then called strncmp()
on those strings wouldn't work. For example, given "AB"
and "A~A"
, stripping would yield "AB"
and "AA"
. strncmp()
on those strings would return +1
, but it should return -1
.
I came up with this:
int strncmp_in_set( char const *s1, char const *s2, size_t n,
char const *charset ) {
assert( s1 != NULL );
assert( s2 != NULL );
assert( charset != NULL );
// First, determine shorter string and its length.
// Note: doing it this way is faster than calling strlen() on both strings.
char const *t1, *t2;
for ( t1 = s1, t2 = s2; *t1 != '\0' && *t2 != '\0'; ++t1, ++t2 )
;
char const *shorter, *longer;
size_t shorter_len;
if ( *t1 == '\0' ) {
shorter = s1;
longer = s2;
shorter_len = (size_t)(t1 - s1);
} else {
shorter = s2;
longer = s1;
shorter_len = (size_t)(t2 - s2);
}
// Second, see if shorter string contains any chars in set.
// If not, just return whatever strncmp() returns.
for ( t1 = shorter; *t1 != '\0'; ++t1 ) {
if ( strchr( charset, *t1 ) == NULL )
--shorter_len;
}
if ( shorter_len == 0 )
return strncmp( s1, s2, n );
// Finally, compare strings.
size_t const orig_n = n;
while ( n > 0 && *shorter != '\0' ) {
if ( strchr( charset, *shorter ) == NULL ) {
++shorter;
continue;
}
if ( strchr( charset, *longer ) != NULL && *shorter++ != *longer )
return strncmp( s1, s2, orig_n );
++longer;
--n;
}
return 0;
}
AFAICT, it works, but it seems verbose. Is there any way this can be simplified?
size_t n
more. Does it limit the number ofchars
to compare or limit the number of in set chars to compare or ...? \$\endgroup\$n
has exactly the same role as it does forstrncmp()
.man strncmp
\$\endgroup\$