【问题标题】:Calculate the number of times each letter appears in a string计算每个字母在字符串中出现的次数
【发布时间】:2020-07-23 16:40:21
【问题描述】:

我一直在使用一些旧代码,并且遇到了我不久前创建的一个函数,它计算每个字母在给定字符串中出现的次数。在我的初始函数中,我将遍历字符串 26 次,计算每个字母在循环中出现的次数。但是,我知道这确实效率低下,所以我尝试这样做:

int *frequency_table(char *string) { 
    int i;
    char c;
    int *freqCount = NULL;
    freqCount = mallocPtr(freqCount, 26, sizeof(int), "freqCount"); /* mallocs and checks for out of memory */

    for (i = 0; string[i] != '\0'; i++) {
        c = string[i];
        if (isalpha(c)) {
            isupper(c) ? freqCount[c - 65]++ : freqCount[c - 97]++;
        }
    }

    return (freqCount);
}

上面的代码循环遍历一个字符串并检查每个字符。如果字符是字母(az 或 AZ),那么我会在 freqCount 数组中的特定索引处增加频率计数(其中索引 0 = a\A, 1 = b\B, ... , 25 = Z Z)。

代码似乎计算得很好,但是当我打印数组时,我得到以下输出:

字符串:“abcdefghijklmnopqrstuvwxyziii”

a/A     -1276558703
b/B     32754
c/C     -1276558703
d/D     32754
e/E     862570673
f/F     21987
g/G     862570673
h/H     21987
i/I     4
j/J     1
k/K     1
l/L     1
m/M     1
n/N     1
o/O     1
p/P     1
q/Q     1
r/R     1
s/S     1
t/T     1
u/U     1
v/V     1
w/W     1
x/X     1
y/Y     1
z/Z     1

作为参考,我按以下方式打印数组:

for (i = 0; i < 26; i++) {
     printf("%c/%c     %d\n", i + 97, i + 65, freqCount[i]);
}

我检查以确保指针分配正确,我确定我没有覆盖这个内存位置。也许我遗漏了一些东西,但我真的不明白为什么它会从 a\A-h\H 打印垃圾内存值。

另外,如果有更有效的方法来做我想做的事,我很想听听。

谢谢

【问题讨论】:

  • 您从未将数组的值设置为 0,因此您++处理了一些留在内存中的垃圾。
  • malloc 不会初始化数组,因此代码需要使用memset 或循环来初始化它,或者使用calloc 而不是malloc
  • 顺便说一句,65 和 97 应该分别写为 'A''a'。这使代码更易于阅读,您不必记住这些数字是什么。
  • @Timothy Catana 顺便说一句,并非所有字母都没有间隙地相互跟随。例如,对于 EBCDIC 编码,该程序将无法运行。
  • memset(freqCount, 0, 26*sizeof(int))... 或者只是 calloc() 而不是 malloc()... 或者根本不使用动态分配,因为它只是一个 26 int 数组:int freqCount[26] = {0}; .

标签: c histogram


【解决方案1】:
  • 正如许多人提到的,您必须将 value 初始化为 0
  • 您还可以使用以下技巧来加快字母计数:如果是字母,则清除第 32 位,这是大写和小写之间的位差,这将为您提供正确的索引。
  • 最后,您可以使用短数组,除非您期望有很多字母。
#include <stdio.h>
#include <stdlib.h>

short *frequency_table(char *string){ 
    char c;
    short *freqCount;

    if (!(freqCount = (short*)calloc(26, sizeof(short))))
        return NULL;

    for(int i = 0; (c = string[i]) != '\0'; i++) {
        if(isalpha(c))
            freqCount[(c & ~32) - 'A']++;
    }

    return(freqCount);
}

主要测试:

int main() {
    short *n = frequency_table("helloiHEllo6456gdrgd#%#^#$^#_thirde");

    for (char c = 'a'; c <= 'z'; c++)
         printf("%c: %d\n", c, n[c - 'a']);
    return 0;
}

【讨论】:

  • 关于:printf("%d\n", n[4]); 你希望这个声明做什么?它只会打印频率数组中第 5 个元素中的计数。
  • 它打印 3,因为第 5 个字母是 e,如果你检查字符串的结尾我写了“第三个 e”,小写和大写来自 t“hello”
  • OPs 发布的代码打印所有 26 个计数,这是问题的一部分,因此仅打印一个计数是不够的
  • freqCount[(c &amp; ~32) - 65]++; 等丑陋的技巧会导致代码不可读和不可维护。至少写'A' 而不是65
  • 对于任何了解 ASCII 表的人来说,我认为它并不丑陋,而是高效和优雅。
【解决方案2】:

你的代码有两个问题:

  • 数组freqCount 未初始化。
  • 您应该避免将char 值传递给isalpha,因为如果string 在默认签名char 的系统上包含负的char 值,则会导致未定义的行为。

代替三元运算符或if 语句,您可以使用toupper() 将小写字符转换为大写,并且使用'A''a' 而不是硬编码的ASCII 值@ 更具可读性987654331@和97

这是一个更正的版本:

int *frequency_table(const char *string) { 
    size_t i;

    /* allocate the array with malloc and check for out of memory */
    int *freqCount = mallocPtr(freqCount, 26, sizeof(int), "freqCount");

    for (i = 0; i < 26; i++) {
        freqCount[i] = 0;
    }
    for (i = 0; string[i] != '\0'; i++) {
        unsigned char c = string[i];
        if (isalpha(c)) {
            /* this code assumes ASCII, so 'Z'-'A' == 25 */
            freqCount[toupper(c) - 'A']++;
        }
    }
    return freqCount;
}

【讨论】:

    【解决方案3】:

    以下建议的代码:

    1. 避免malloc()calloc()
    2. main()函数中保留数据等的定义
    3. 执行所需的功能
    4. 干净编译
    5. 使用简单的字符而不是“魔术”数字
    6. 需要 ASCII 字符集

    现在,建议的代码:

    #include <stdio.h>
    #include <ctype.h>
    #include <stdlib.h>
    
    #define MAX_ALPHA  26
    
    void charCounter( char *,  int * );
    
    int main( void )
    {
        char string[] = "abcdefghijklmnopqrstuvwxyziii";    
        int  freqCount[ MAX_ALPHA ] = {0};
    
        charCounter(  string, freqCount );
    
    
        for( size_t i = 0; i < 26; i++)
        {
            printf("%c/%c     %d\n", (char)(i + 'A'), (char)(i + 'a'), freqCount[i]);
        }
    }
    
    
    void charCounter( char *string, int freqCount[] )
    {
        for( size_t i=0; string[i]; i++ )
        {
            if( isalpha( string[i] ) )
            {
                freqCount[ toupper(string[i]) - 'A' ]++;
            }
        }
    }
    

    代码运行结果:

    A/a     1
    B/b     1
    C/c     1
    D/d     1
    E/e     1
    F/f     1
    G/g     1
    H/h     1
    I/i     4
    J/j     1
    K/k     1
    L/l     1
    M/m     1
    N/n     1
    O/o     1
    P/p     1
    Q/q     1
    R/r     1
    S/s     1
    T/t     1
    U/u     1
    V/v     1
    W/w     1
    X/x     1
    Y/y     1
    Z/z     1
    

    【讨论】:

    • 在带有签名char 的系统上,您必须将char 参数转换为isalpha()isalpha( (unsigned char)string[i] ),因为isalpha() 对于除EOF 之外的负值具有未定义的行为。
    • @chqrlieforyellowblockquotes,alpha 序列在前 127 个 ASCII 字符内是 ALL,因此 char 的符号无效
    • 你将字符串中的所有字符传递给isalpha(),你怎么知道它们是否都是正数?例如,如果字符串包含 utf-8 编码的非 ASCII 代码点,则这些字节将被视为负数。从技术上讲,toupper() 调用不需要强制转换,因为所有字母字符确实都是正数,但isalpha() 调用需要强制转换,因为您无法对字符串内容做出任何假设。
    • C17 7.4 字符处理&lt;ctype.h&gt; 标题&lt;ctype.h&gt; 声明了几个对字符分类和映射有用的函数。在所有情况下,参数都是int,其值应表示为unsigned char 或应等于宏EOF 的值。如果参数有任何其他值,则行为未定义。 isalpha 函数测试isupperislower 为真的任何字符,或任何iscntrlisdigit、@987654346 都没有的特定于区域设置的字母字符集之一的字符@ 或 isspace 为真。
    • 问题是在默认签名char 的平台上:如果字符串参数包含0x00-0x7F 之外的字节,则读取为char 值的这些字节具有负值,为此isalpha() 的行为未定义。为了避免这种潜在的未定义行为,必须将未知为正的char 值转换为(unsigned char)。因此你应该写isalpha((unsigned char)string[i])
    猜你喜欢
    • 2016-01-23
    • 1970-01-01
    • 1970-01-01
    • 2022-10-15
    • 1970-01-01
    • 2022-07-27
    • 2019-04-05
    • 2013-11-04
    • 1970-01-01
    相关资源
    最近更新 更多