要添加到@WilliamPursell 的答案,这里有一种使用位掩码而不是字符数组查找表的方法:
#include <stdio.h>
// Check if this is a ISBN and its 13 digits uniqueness.
// Parameters:
// ISBN, ASCIIZ
// Returns: [int]
// -1 : Not a ISBN
// 0 : ISBN with only unique digits
// 1 : ISBN with duplicated digits
int isISBNDigitsUnique(char *pStr) {
if (pStr == NULL) return -1;
unsigned long long mask = 0, refmask;
unsigned char bit;
int expected_length = 0, ret = 0;
while((bit = *pStr++) && expected_length++ < 14) {
if (bit < '0' || bit > '9' && bit < 'A') return -1;
if ((bit &= 0xDFu) > 'Z') return -1;
if (ret==0) {
refmask = mask;
if ((bit -= 0x10u) > 0x30u) bit -= 0x20u;
if ((mask |= (0x01u << bit)) == refmask) ret = 1;
}
}
if (expected_length != 13) return -1;
return ret;
}
int main(int nbargs, char *args[]) {
if (nbargs != 2) {
printf("Usage : %s ISBN\n", args[0]);
return 1;
}
switch (isISBNDigitsUnique(args[1])) {
case 1:
printf("duplicate digit!\n");
break;
case 0:
printf("all digits are unique.\n");
break;
default:
printf("%s is not an ISBN.\n", args[1]);
}
}
应用于每个数字的布尔逻辑:
我们认为 0-9、a-z 和 A-Z 是 ISBN 的唯一有效数字。
因为我们有一个 ISBN 的 ASCIIZ 缓冲区,所以我们可以期望每个数字
以下为数字 ASCII 码的二进制值。
0011 0000 到 0011 1001 => 数字 0-9
0100 0001 到 0101 1010 => 数字 A-Z
0110 0001 到 0111 1010 => 数字 a-z
我们首先检查数字是否满足以下要求:
- 如果数字代码低于 48,则它不是有效数字。
- 如果数字代码大于 57 且小于 65 则无效
数字。
我们通过将第 7 位归零来应用大小写缩减,因此与掩码为 0xDF。
然后我们有以下数字的值范围:
0001 0000 到 0001 1001 => 数字 0-9
0100 0001 到 0101 1010 => 数字 A-Z
0100 0001 到 0101 1010 => 数字 a-z
如果数字代码大于 90,则它不是有效数字。
我们将 0x10 减去数字代码,目标是为每个值拟合一个 64 位掩码。
然后我们有以下数字的值范围:
0000 0000 到 0000 1001 => 数字 0-9,十进制值 0-9
0011 0001 到 0100 1010 => 数字 A-Z,十进制值 49-74
0011 0001 到 0100 1010 => 数字 a-z,十进制值 49-74
我们可以将第 6 位归零,因为第 5 位足以区分 0-9 和 A-Z,但由于我们的结果范围将超过 64,因此如果数字代码大于 48,则减去 0x20 会更有效。
然后我们有以下数字的值范围:
0000 0000 到 0000 1001 0-9 => 数字 0-9,十进制值 0-9
0001 0001 到 0010 1010 A-Z => 数字 A-Z,十进制值 17-42
0001 0001 到 0010 1010 a-z => 数字 a-z,十进制值 17-42
我们的数字代码值范围现在是 0-9 和 17-42,它们很容易适合 64 位掩码。所以我们只需要标记每个数字的每个位,如果数字代码没有改变掩码,那么我们就有一个重复的数字。
P.S.:@interjay 对于数学方法,我希望检查数字总和 和 数字乘积,但这需要数学演示......