【问题标题】:Unicity of the characters of a string in cc中字符串字符的唯一性
【发布时间】:2021-08-29 15:06:41
【问题描述】:

是否有任何有效的简单方法来检查 c 中字符串字符的唯一性? 就我而言,我必须检查用户输入的 ISBN 是否具有唯一字符且长度为 13 个字符。

【问题讨论】:

  • ISBN 由数字组成。有 10 位数字(0 到 9)。 13 位数字中的每个数字不可能在该数字中是唯一的,因为在使用十个不同的数字之后必须有重复。编辑问题以澄清您的意思。显示正确和错误输入的示例。
  • 在这种情况下,我使用 13 个字符的 ISBN(虚构的一个,因为它只是一个项目),例如:1234567890qwe 或 asdfghjklyxcv。输入错误:12345617890qw
  • “高效、简单、轻松”是什么意思。直接的方法似乎符合这些标准。 (扫描字符串,在数组中记录看到的条目,如果看到重复或计数超过 13 则无效)
  • 尽可能短,因为这是一个漫长而复杂的项目,我是初学者

标签: c string loops char substring


【解决方案1】:

要添加到@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 对于数学方法,我希望检查数字总和 数字乘积,但这需要数学演示......

【讨论】:

  • 不要将0x39 用于字符“9”。使用'9'。没有理由对字符使用数字代码,除非编写需要使用非本地字符代码的软件(例如在 EBCDIC 和 ASCII 之间转换的软件)。
  • @EricPostpischil 完成。当它的 ASCII 代码检查时,我不确定混合字符比较和掩码十六进制值是否可读。我会关注你的观点。
【解决方案2】:

鉴于字符串应该只有 13 个字节,蛮力方法似乎足够快,甚至比更复杂的方法更快,而且更容易编写和验证。

这是一个例子:

#include <ctype.h>
#include <stdio.h>
#include <string.h>

int check_ISBN(const char *p) {
    if (strlen(p) != 13)
        return -1;
    while (*p) {
        unsigned char c = *p++;
        // characters must be digits or lowercase letters
        if (!isdigit(c) && !islower(c))
            return 2;
        // characters must not be duplicated
        if (strchr(p, c))
            return 1;
    }
    return 0;
}

int main(int argc, char *argv[]) {
    int status = 0;
    if (argc < 2) {
        fprintf(stderr, "Usage: %s ISBN ...\n", argv[0]);
        return 2;
    }
    for (int i = 1; i < argc; i++) {
        switch (check_ISBN(argv[i])) {
          case 0:
            printf("%s: valid ISBN.\n", argv[i]);
            break;
          case 1:
            printf("%s: invalid ISBN: duplicate digit.\n", argv[i]);
            status = 1;
            break;
          case 2:
            printf("%s: invalid ISBN: invalid character.\n", argv[i]);
            status = 1;
            break;
          default:
            printf("%s: invalid ISBN: bad character count.\n", argv[i]);
            status = 1;
            break;
        }
    }
    return status;
}

【讨论】:

  • 不错的一个。只是一点,OP似乎没有明确排除大写。
  • @Zilog80:是的,OP 没有,但他的例子只显示小写字母。如果也允许大写,就会弹出另一个问题:unicity 测试是否区分大小写?我选择保持实现简单,只允许数字和小写字母,并在代码中明确说明。
【解决方案3】:

只需遍历字符串,在查找表中记录每个看到的字符。如果您看到一个 dup,请中止。如果长度不是 13 失败。例如:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>
#include <limits.h>

int
validate(const char *a, char **err)
{
        char lut[(1 << CHAR_BIT)] = {0};  /* 256 */
        int len = 0;
        *err = "too short";
        for(const char *s = a; *s; s += 1 ){
                if( ++len > 13 ){
                        *err = "too long";
                        return 0;
                }
                if( lut[(unsigned char)*s] ){
                        *err = "duplicate entry";
                        return 0;
                }
                lut[(unsigned char)*s] = 1;
        }
        return len == 13;
}


int
main(int argc, char **argv)
{
        int fail = 0;
        if( argc == 1 ){
                printf("usage: %s isbn [isbn...]\n", basename(argv[0]));
                return 0;
        }

        for( argv += 1; *argv; argv += 1 ){
                char *e;
                if( ! validate(*argv, &e) ){
                        fprintf(stderr, "%s is invalid: %s\n", *argv, e);
                        fail += 1;
                }
                else {
                        printf("%s is valid\n", *argv);
                }
        }
        return fail == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}

【讨论】:

  • lut 数组需要多出一个元素(或者在索引时需要减去一个元素),因为(1 &lt;&lt; CHAR_BIT) - 1 是一个有效的字符值,并且字符值需要是无符号的, char 不保证。投射到int 毫无意义。
  • C 因为它是意味着要写的 - 密集,简洁,对任何阅读它的人都有轻微的蔑视。我同意!
  • @EricPostpischil 确认。是的,当然 lut 需要 256 长。感谢您指出了这一点。因一个错误而自取灭亡。强制转换只是为了抑制编译器警告。
  • @JohnBode 为了可读性,我抑制了写if( lut[*s]++ ) 的愿望。 :)
  • 谢谢!我想通了
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-25
  • 1970-01-01
  • 2017-04-18
相关资源
最近更新 更多