Test for contiguous alphabet in character set

S

Stefan Krah

Hello,

I am currently writing code where it is convenient to convert
char [a-zA-Z] to int [0-25]. The conversion function relies on
a character set with contiguous alphabets.

int set_mesg(Key *key, char *s)
{
char *x;

if (strlen(s) != 3)
return 0;

x = s;
while (*x != '\0') {
if (!isalpha(*x))
return 0;
*x = tolower(*x);
x++;
}

x = s;
/* l_mesg, m_mesg, r_mesg are int */
key->l_mesg = *x++ - 'a';
key->m_mesg = *x++ - 'a';
key->r_mesg = *x - 'a';

return 1;
}


According to K&R2 (p43) contiguous alphabets cannot be safely assumed.
This function would test the lowercase alphabet:

int cont_lower_alpha(void)
{
char a[26] = "abcdefghijklmnopqrstuvwxyz";
int i;

for (i = 0; i < 26; i++)
if (a - 'a' != i)
return 0;

return 1;
}

Is there an easier way of doing this?


Stefan Krah
 
E

Eric Sosman

Stefan said:
Hello,

I am currently writing code where it is convenient to convert
char [a-zA-Z] to int [0-25]. The conversion function relies on
a character set with contiguous alphabets.

int set_mesg(Key *key, char *s)
{
char *x;

if (strlen(s) != 3)
return 0;

x = s;
while (*x != '\0') {
if (!isalpha(*x))

FYI: Use `isalpha((unsigned char)*x)', and similarly
for the other said:
return 0;
*x = tolower(*x);
x++;
}

x = s;
/* l_mesg, m_mesg, r_mesg are int */
key->l_mesg = *x++ - 'a';
key->m_mesg = *x++ - 'a';
key->r_mesg = *x - 'a';

return 1;
}


According to K&R2 (p43) contiguous alphabets cannot be safely assumed.
This function would test the lowercase alphabet:

int cont_lower_alpha(void)
{
char a[26] = "abcdefghijklmnopqrstuvwxyz";
int i;

for (i = 0; i < 26; i++)
if (a - 'a' != i)
return 0;

return 1;
}

Is there an easier way of doing this?


This tests contiguity of the lower-case alphabet, and
upper-case could be tested in the same way. But if the
test says "discontiguous," then what? Your real problem,
I think, is not to determine whether the alphabets are
contiguous, but to find some code that will work correctly
even if they are not contiguous.

One way would be to use the position of the character
in a reference string instead of the character's code. For
example, you could write

const char alphabet[] = "abcdefghijklmnopqrstuvwxyz";
...
key->l_mesg = strchr(alphabet, *x++) - alphabet;

As written, this is unsafe if there's any chance that
the target character might not be found in alphabet[], because
strchr() would then return NULL and `NULL - alphabet' is
nonsense. (You may think this cannot happen since the code
has eliminated all non-alphabetics and converted everything
to lower-case, but keep in mind that isalpha() and tolower()
are locale-dependent. Letters like å, ç, ñ, and þ are found
in many character sets, and may be considered lower-case
alphabetics in some locales -- so they would pass through the
earlier portions of your code only to be found missing from
the alphabet[] array.) You could call strchr() and check the
result for NULL before trying to subtract, or you could make
sure that strchr() always finds the target character:

char alphabetplus[] = "abcdefghijklmnopqrstuvwxyz?";
int pos;
...
alphabetplus[26] = *x;
pos = strchr(alphabetplus, *x++) - alphabetplus;
if (pos < 26)
key->l_mesg = pos;
else
return 0; /* unknown lower-case alphabetic */

If you intend to compute a large number of these message
codes, though, it is probably better to use a table:

static char code[1+UCHAR_MAX];
if (code['a'] == 0) {
/* initialize table on the first call */
const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
int i;
for (i = 0; alpha != '\0'; ++i) {
code[alpha] = i + 1;
code[toupper(alpha)] = i + 1;
}
}
...
if (code[(unsigned char)*x] == 0)
return 0;
key->l_mesg = code[(unsigned char)*x] - 1;

(Note 1: Yes, I warned you to cast the argument of <ctype.h>
functions, yet I did not do so in the toupper() call. This
happens to be safe because I know that all the characters in
a..z are in the "basic execution" character set, and all these
are guaranteed to have non-negative code values. When you don't
have such knowledge of the input string, though, you must cast --
as in the references to the code[] array, although only the first
of those two is strictly necessary.)

(Note 2: Observe that the table-based method eliminates
the need to weed out non-alphabetics and convert case. All
letters outside a..z and A..Z will be detected by virtue of
their zero code[] values, and for all the rest you will have
code['a'] == code['A'], code['b'] == code['B'], and so on.)
 
S

Stefan Krah

* Eric Sosman said:
Stefan said:
Hello,

I am currently writing code where it is convenient to convert
char [a-zA-Z] to int [0-25]. The conversion function relies on
a character set with contiguous alphabets.

int set_mesg(Key *key, char *s)
{
char *x;

if (strlen(s) != 3)
return 0;

x = s;
while (*x != '\0') {
if (!isalpha(*x))

FYI: Use `isalpha((unsigned char)*x)', and similarly
for the other said:
return 0;
*x = tolower(*x);
x++;
}

x = s;
/* l_mesg, m_mesg, r_mesg are int */
key->l_mesg = *x++ - 'a';
key->m_mesg = *x++ - 'a';
key->r_mesg = *x - 'a';

return 1;
}


According to K&R2 (p43) contiguous alphabets cannot be safely assumed.
This function would test the lowercase alphabet:

int cont_lower_alpha(void)
{
char a[26] = "abcdefghijklmnopqrstuvwxyz";
int i;

for (i = 0; i < 26; i++)
if (a - 'a' != i)
return 0;

return 1;
}

Is there an easier way of doing this?


This tests contiguity of the lower-case alphabet, and
upper-case could be tested in the same way. But if the
test says "discontiguous," then what? Your real problem,
I think, is not to determine whether the alphabets are
contiguous, but to find some code that will work correctly
even if they are not contiguous.


Your observations are absolutely correct. My vague idea was to use
the test as a safety net for the (potentially) rare cases in which
a discontiguous alphabet is encountered.

Obviously it is silly not to write portable code in the first place.

One way would be to use the position of the character
in a reference string instead of the character's code. For
example, you could write

const char alphabet[] = "abcdefghijklmnopqrstuvwxyz";
...
key->l_mesg = strchr(alphabet, *x++) - alphabet;

As written, this is unsafe if there's any chance that
the target character might not be found in alphabet[], because
strchr() would then return NULL and `NULL - alphabet' is
nonsense. (You may think this cannot happen since the code
has eliminated all non-alphabetics and converted everything
to lower-case, but keep in mind that isalpha() and tolower()
are locale-dependent. Letters like å, ç, ñ, and þ are found
in many character sets, and may be considered lower-case
alphabetics in some locales -- so they would pass through the
earlier portions of your code only to be found missing from
the alphabet[] array.) You could call strchr() and check the

I was aware of the locale issue, but I thought the default locale
is "C" unless explicitly set:

SETLOCALE(3) (Linux Programmer's Manual):
| On startup of the main program, the portable "C" locale
| is selected as default.

C Standard Draft -- August 3, 1998:
| At program startup, the equivalent of
| setlocale(LC_ALL, "C");
| is executed.

isalpha() is equivalent to (isupper(c) || islower(c)) and those
two (in the C locale) only return true for [a-zA-Z].

So isalpha() shouldn't return false positives or am I missing something?

If you intend to compute a large number of these message
codes, though, it is probably better to use a table:

static char code[1+UCHAR_MAX];
if (code['a'] == 0) {
/* initialize table on the first call */
const char alpha[] = "abcdefghijklmnopqrstuvwxyz";
int i;
for (i = 0; alpha != '\0'; ++i) {
code[alpha] = i + 1;
code[toupper(alpha)] = i + 1;
}
}
...
if (code[(unsigned char)*x] == 0)
return 0;
key->l_mesg = code[(unsigned char)*x] - 1;


I like this one even though conversion doesn't have to be fast in my case.

(Note 1: Yes, I warned you to cast the argument of <ctype.h>
functions, yet I did not do so in the toupper() call. This
happens to be safe because I know that all the characters in
a..z are in the "basic execution" character set, and all these
are guaranteed to have non-negative code values. When you don't
have such knowledge of the input string, though, you must cast --
as in the references to the code[] array, although only the first
of those two is strictly necessary.)

(Note 2: Observe that the table-based method eliminates
the need to weed out non-alphabetics and convert case. All
letters outside a..z and A..Z will be detected by virtue of
their zero code[] values, and for all the rest you will have
code['a'] == code['A'], code['b'] == code['B'], and so on.)


Thanks a lot for your detailed comments,

Stefan Krah
 
M

Malcolm

Stefan Krah said:
int cont_lower_alpha(void)
{
char a[26] = "abcdefghijklmnopqrstuvwxyz";
int i;

for (i = 0; i < 26; i++)
if (a - 'a' != i)
return 0;

return 1;
}

Is there an easier way of doing this?

You could do conditional compilation with
#if 'z' == 'a' + 25

strictly it isn't foolproof, because a perverse implementer could make the
intervening letters non-contiguous. But there are only a few character sets
out there, and it is probably much more likely that you program will break
in some other way than that someone will bring out a character set that
breaks your program.
 
J

Jens.Toerring

Stefan Krah said:
I was aware of the locale issue, but I thought the default locale
is "C" unless explicitly set:

Well, until you use some library that changes it behind your back.
And, of course, that never happens on your own machine, but always
on a machine some 1000 km away. This resulted in a _lot_ of head
scratching until I figured out what was going on.... It's astoni-
shing in which places a wrongly set locale can mess things up;-)

Regards, Jens
 
S

SM Ryan

# Hello,
#
# I am currently writing code where it is convenient to convert
# char [a-zA-Z] to int [0-25]. The conversion function relies on
# a character set with contiguous alphabets.

#include <limits.h>

char map0[CHAR_MAX-CHAR_MIN+1]; memset(map0,26,sizeof map0);
char map = map0-CHAR_MIN;

int i; for (i=0; i<=25; i++) map[i["abcdefghijklmnopqrstuvwxyz"]] = i;


Anywhere you want to map letters to the integers, use map[lettercharacter].
Whether the letters are contiguous or positive or negative can be ignored.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top