【问题标题】:Most efficient way to find if a string is mixedCase查找字符串是否为混合大小写的最有效方法
【发布时间】:2020-01-01 02:25:45
【问题描述】:

假设我有很长的字符串,我想查看一列是 allLower、allUpper 还是 mixCase。例如以下列

text
"hello"
"New"
"items"
"iTem12"
"-3nXy"

文本将是mixedCase。确定这一点的简单算法可能是:

int is_mixed_case, is_all_lower, is_all_upper;
int has_lower = 0;
int has_upper = 0;
// for each row...for each column...
for (int i = 0; (c=s[i]) != '\0'; i++) {
    if (c >='a' && c <= 'z') {
        has_lower = 1;
        if (has_upper) break;
    }
    else if (c >='A' && c <= 'Z') {
        has_upper = 1;
        if (has_lower) break;
    }
}

is_all_lower = has_lower && !has_upper;
is_all_upper = has_upper && !has_lower;
is_mixed_case = has_lower && has_upper;

不过,我确信会有一种更高效的方法来做到这一点。执行此算法/计算的最有效方法可能是什么?

【问题讨论】:

  • 只要你在每种情况下找到一个字符,它就会混合大小写,总共可能只有两个字符。所以如果你遍历整个字符串,你可能会浪费很多时间。
  • @jonrsharpe -- 谢谢你的建议,我已经更新了它。
  • 获取第一个字符的大小写。然后使用strspn() 搜索相反情况下的字符。
  • (c &gt;='a' &amp;&amp; c &lt;= 'z') 不可移植。使用isupper()islower()
  • 另外...我们在说什么character set?使用 Unicode 时,事情变得非常棘手...... ;)

标签: c string algorithm optimization


【解决方案1】:

这应该是相当有效的 - 它检查所需的最少字符数。这假设偏向小写字符,因此首先检查小写应该稍微更有效:

#include <ctype.h>

int ismixed( const unsigned char *str )
{
    int hasUpper = 0;
    int hasLower = 0;

    while ( *str )
    {
        // can't be both upper and lower case
        // but it can be neither
        if ( islower( *str ) )
        {
            hasLower = 1;
        }
        else if ( isupper( *str ) )
        {
            hasUpper = 1;
        }

        // return true as soon as we hit
        // both upper and lower case
        if ( hasLower && hasUpper )
        {
            return( 1 );
        }

        str++;
    }

    return( 0 );
}

根据您的输入是偏小写还是大写,首先检查isupper() 可能会更好。

【讨论】:

  • 很遗憾没有charclassify() 就像有fpclassify(),因为那样你就可以做到cc = charclassfy(*str); hasX |= (ISX &amp; cc); hasY |= (ISY &amp; cc);
  • @Andrew - 感谢您的回答。根据上面的评论 (If you know you're dealing only with ASCII letters, the fastest way to check case is just &amp; 0x20),islower / isupper 是否使用按位运算?我想我的问题是islower 比我在上面问题中使用的幼稚实现更好吗?
  • @Shared C 标准没有指定 ASCII 编码。 ctypes.h 定义的函数可以正确地移植到字符编码中。
  • @Shared - 一般来说,您会发现任何给定函数的大多数编译器实现都将比您即时提出的优化得多,而且更不容易出错。 (特别是对于长字符串,其中许多库函数将取消引用的字符串转换为无符号,以便能够检查每次迭代的 4 个字节而不是一个)。一个好的测试是转储到汇编,然后从指令的角度进行比较。
  • 您可以通过在分配给hasLower 之前测试if (islower((unsigned char)*str) 代码块中的if (hasUpper) return 1; 来提高混合alpha 和非alpha 字符串的代码的微观效率.当字符不是字母时,这可以节省不必要的测试。此外,该问题要求“全部上”与“全部下”与“混合”(并隐含“无阿尔法”)。
【解决方案2】:

是否保证所述字符串只包含字母?如果是这样,可以检查任何两个连续字符是否是不同的情况。

#include <ctype.h>
#include <errno.h>
int mixed_case(const char *str) {
   if(!str){
      // sanity check
      errno = EINVAL;
      return -1;
   }

   // can't be mixed-case without more than one letter
   if(str[0] == '\0' || str[1] == '\0'){
      return 0;
   }

   for(int i = 1; str[i] != '\0' ; ++i) {
      if (!islower(str[i]) ^ !islower(str[i-1])) {
         // if two letter next to each other are not the same case, it's mixed case
         return 1;
      }
   }
   // didn't find any mismatches, so not mixed case
   return 0;
}

采用类似的方法,但不是检查连续字符,而是查找第一个字母字符并与找到的任何其他字母字符进行检查。这应该能够处理非字母字符的字符串。

int mixed_case(const char *str) {
   if(!str){
      // sanity check
      errno = EINVAL;
      return -1;
   }

   // can't be mixed-case without more than one letter
   if(str[0] == '\0' || str[1] == '\0'){
      return 0;
   }

   // find the first alphabetical character and store its index at 'i'
   int i = 0;
   for(;!isalpha(str[i]) || str[i] == '\0'; ++i);

   if(str[i] == '\0') {
      // no alphabetical characters means you can't have mixed cases
      return 0;
   }

   // See if any of the other alphabetical characters differ from the case of the first one
   for(int j = i+1; str[j] != '\0' ; ++j) {
      if(isalpha(str[j]) && (!islower(str[i]) ^ !islower(str[j]))) {
         return 1;
      }
   }
   // didn't find any mismatches, so not mixed case
   return 0;
}

【讨论】:

  • 这是一种有趣的方法,但是是的,我想文件可以命名为 FileVersion-1.txt 之类的名称,因此它可以包含非字母(在问题中添加了一个示例)。
  • @Shared 用另一个应该处理非字母字符串的版本进行了更新。不确定与更传统的方法相比,它会如何提高效率。
  • islower(str[i]) ^ islower(str[j]) 不一定正确。 islower() 产生 int,小写时为非零值。然而islower(str[i]) 返回的值可能与islower(str[j]) 不同,即使两者都是小写。 ^ 在这里进行按位异或,需要的是逻辑异或。建议!islower(str[i]) ^ !islower(str[j]) 实现逻辑异或。
  • @chux 很好。已更新。
【解决方案3】:

如果我们假设 ASCII

如果我们假设所有 alpha,

那么代码只需要计算“case”位。和是0,和字符串长度一样还是其他?

void test_case(const char *s) {
  const char *start = s;
  size_t sum = 0;
  size_t mask = 'A' ^ 'a';
  while (*s) {
    sum += *s++ & mask;
  }
  ptrdiff_t len = s - start;
  sum /= mask;
  if (len == 0) puts("Empty string");
  else if (sum == 0) puts("All UC");   
  else if (sum == len) puts("All LC");
  else puts("Mixed");
}

注意:稍加修改后,也适用于 EBCIDIC。

【讨论】:

  • 为什么不使用按位运算而不是计数?
  • @m69 如果 all/near all uppercase 和 all/near all lowercase 支配用例,那么简单地添加是最有效的。在审查中,我认为this 最普遍有效。
【解决方案4】:

另一种不假定 ASCII 也不全是 alpha 的方法。

评估第一个 char,然后执行 2 个优化循环之一。

这会在第一次不匹配时退出循环。由于while() 循环只进行一次测试,因此可以实现最佳性能。

#include <ctype.h>

void case_test(const char *s) {
  if (*s == '\0') {
    puts("Empty string");
    return;
  }

  unsigned char *us = (unsigned char *)s; // use unsigned char with is***() functions.
  if (islower(*us)) {
    while (islower(*us)) {
      us++;
    }
    if (*us) {
      puts("Mixed or not alpha");
    } else {
      puts("All lower");
    }
  } else if (isupper(*us)) {
    while (isupper(*us)) {
      us++;
    }
    if (*us) {
      puts("Mixed case or not alpha");
    } else {
      puts("All upper");
    }
  } else {
    puts("Not alpha");
  }
}

OP 添加了包括非 alpha 在内的案例。下面会及时处理。

void case_test_with_non_letters(const char *s) {
  unsigned char *us = (unsigned char *)s; // use unsigned char with is***() functions.

  //  Find first alpha or null character
  while (!isalpha(*us) && *us) {
    us++;
  }

  if (*us == '\0') {
    puts("Empty string");
    return;
  }

  if (islower(*us)) {
    while (!isupper(*us) && *us) {
      us++;
    }
    if (isupper(*us)) {
      puts("Mixed");
    } else {
      puts("All letters lower");
    }
  } else if (isupper(*us)) {
    while (!islower(*us) && *us) {
      us++;
    }
    if (*us) {
      puts("Mixed case");
    } else {
      puts("All letters upper");
    }
  } else {
    puts("Not alpha");
  }
}

【讨论】:

  • 问题中的示例以非字母开头并包含非字母字符,但可以混合大小写,大写或小写。
  • @JonathanLeffler 好点 - 但这要归功于 OP 编辑​​。将审查。
【解决方案5】:

如果您知道要使用的字符编码(我在代码示例中使用了ISO/IEC 8859-15),查找表可能是最快的解决方案。这还允许您决定扩展字符集中的哪些字符,例如 µ 或 ß,您将被视为大写、小写或非字母。

char test_case(const char *s) {
    static const char alphabet[] = {
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  //  ABCDEFGHIJKLMNO
        1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,  // PQRSTUVWXYZ
        0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  //  abcdefghijklmno
        2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,  // pqrstuvwxyz
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,1,0,2,0,2,0,0,0,0,  //        Š š ª
        0,0,0,0,0,1,2,0,0,2,0,2,0,1,2,1,  //      ޵  ž º ŒœŸ
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  // ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ
        1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,  // ÐÑÒÓÔÕÖ ØÙÚÛÜÝÞß
        2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  // àáâãäåæçèéêëìíîï
        2,2,2,2,2,2,2,0,2,2,2,2,2,2,2,2}; // ðñòóôõö øùúûüýþÿ
    char cases = 0;
    while (*s && cases != 3) {
        cases |= alphabet[(unsigned char) *s++];
    }
    return cases; // 0 = none, 1 = upper, 2 = lower, 3 = mixed
}

正如chux的评论中所建议的,您可以将alphabet[0]的值设置为4,然后在while循环中只需要一个条件cases &lt; 3

【讨论】:

  • 你能解释一下上面代码中发生了什么吗?我不太明白它是如何工作的,等等。
  • @Shared 对于每个字符(我假设这里是 8 位扩展 ASCII),如果它是大写的,则存储一个值 1,如果它是小写,则存储一个值,或者如果它是一个非字母字符。然后,要检查字符串,查找每个字符的值,然后将其与变量cases 进行或运算,变量cases 在开始时设置为0。当您遇到第一个字母时,cases 将根据大小写设置为 1 或 2。如果稍后遇到其他大小写的字母,cases 将被设置为 3,并且您知道该字符串是混合大小写的。
  • @mr69 -- 哇,这是一个非常酷的方法。我很想看看这是否会在大量文本输入字符串上使用标准的 lower/upper 表现优于。
  • @Shared 查找表的性能通常在很大程度上取决于它们的大小。如果您添加或删除少量元素,您可能会发现时间上有奇怪的跳跃。如果您不关心扩展字符并将表格减少到 128 个字符,或者跳过前 32 个控制字符并使用 *s++ -32 可能会提高速度。这都是关于编译器可以做的低级优化。
  • 这是个好主意,但可以改进。将 alphabet[0] = 4; 然后将 (*s &amp;&amp; cases != 3) 更改为 (cases &lt; 3)。这消除了重复的*s 测试。然后return cases &amp; 3;.
【解决方案6】:

97 = a = 1100001 65 = A = 0100001

您只需测试第 6 位即可。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-03
    • 1970-01-01
    • 1970-01-01
    • 2017-03-29
    • 1970-01-01
    • 2015-02-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多