在 C 中使用函数的 Pangram

问题描述

当我输入 The quick brown fox jumps over the lazy dog 时,以下程序打印 not a pangram。然而,我希望 s 为 26 并且 printf("pangram") 将被执行。我做错了什么?

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

char findpan(char arr[]) {
    int i,j,count = 0;
    for (i = 0; i < strlen(arr); i++) {
        if (isalpha(arr[i]))
            count++;
    }
    for (i = 0; i < strlen(arr); i++) {
        for (j = i + 1; j < strlen(arr); j++) {
            if (arr[i] == arr[j])
                count--;
        }
    }
    return (count);
}

int main() {
    int s;
    char str[60];
    fgets(str,60,stdin);
    s = findpan(str);
    if (s == 26)
        printf("pangram");
    else
        printf("not a pangram");
    return 0;
}

解决方法

如果我已经理解你想要做什么,那么这些嵌套循环

for (i = 0; i < strlen(arr); i++) {
    for (j = i + 1; j < strlen(arr); j++) {
        if (arr[i] == arr[j])
            count--;
    }
}

不正确。假设您有字符串“AAA”。所以在前面的循环计数之后将等于 3。

现在,在这些嵌套循环之后,计数将等于 0 而不是 1。也就是说,当 i = 0 时,对于 j = 1 和 j = 2,arr[j] 等于 arr[i]。所以计数将减少两次。当 i = 1 时 j = 2 再次 arr[j] = arr[i] 并且计数将再减一。

此外,您似乎应该忽略字母大小写。

我可以建议以下函数实现,如下面的演示程序所示。

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

size_t findpan( const char *s )
{
    size_t count = 0;
    
    for ( const char *p = s; *p; ++p )
    {
        if ( isalpha( ( unsigned char ) *p ) )
        {
            char c = tolower( ( unsigned char )*p );
            
            const char *q = s;
            while ( q != p && c != tolower( ( unsigned char )*q ) ) ++q;
            
            if ( q == p ) ++ count;
        }
    }
    
    return count;
}

int main(void) 
{
    printf( "%zu\n",findpan( "The quick brown fox jumps over the lazy dog" ) );
    
    return 0;
}

程序输出为

26

不使用指针,函数看起来如下

size_t findpan( const char *s )
{
    size_t count = 0;
    
    for ( size_t i = 0; s[i] != '\0'; i++ )
    {
        if ( isalpha( ( unsigned char ) s[i] ) )
        {
            char c = tolower( ( unsigned char )s[i] );
            
            size_t j = 0;
            while ( j != i && c != tolower( ( unsigned char )s[j] ) ) ++j;
            
            if ( j == i ) ++count;
        }
    }
    
    return count;
}
,

简单的解决方案?

这里有一个简单的解决方案,我猜测您可能只想知道它是还是不是一个panagram所以我把你的函数改成了 boolean 函数:

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

bool findpan(char arr[]) {
    int i,j;
    for (i = 'a'; i < 'z'; ++i) { // goes through the alphabet
        for (j = strlen(arr); j > 0; j--) // goes through the arr[] 
            if (tolower(arr[j]) == i) // checks if the letter exists
                break; // breaks the inner for-loop if letter found
          
        if (j == 0) // if letter not found
            return false;  
    }
    return true;
}

int main() {
    bool isPanagram;
    char str[60];
    
    fgets(str,60,stdin);
    isPanagram = findpan(str);
    
    if (isPanagram)
        printf("panagram");
    else
        printf("not a panagram");
    return 0;
}

说明

'a''z' 表示 Decimal 数字中小写字母的范围,在 ASCII table :

for (i = 'a'; i < 'z'; ++i) 

tolowerarr[j] character 转换为小写,然后将其与 i 进行比较:

if (tolower(arr[j]) == i)

stdbool.h 是为使用 bool aka boolean:

而引入的
#include <stdbool.h>
,

将自己限制为纯 ASCII,您可以创建一个简单的数组,每个字母一个元素,每个元素初始化为零。然后循环遍历字符串,并为每个字母将其转换为数组的索引并增加相应的元素值。

处理完输入字符串后,您遍历数组,并为每个非零值增加一个计数器,然后返回。

也许是这样的:

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

int main(void)
{
    char input[512];

    if (!fgets(input,sizeof input,stdin))
        return 1;  // Failed to read input

    int letters[26] = { 0 };  // 26 letters in the English alphabet

    for (unsigned i = 0; input[i] != '\0'; ++i)
    {
        if (isalpha(input[i]))
        {
            // Limiting myself to e.g. plain ASCII here
            ++letters[tolower(input[i]) - 'a'];
        }
    }

    // Count the number of non-zero elements in the letters array
    unsigned counter = 0;
    for (unsigned i = 0; i < 26; ++i)
    {
        counter += letters[i] != 0;
    }

    // Print result
    printf("Counter = %d\n",counter);
}

使用您的示例输入 (The quick brown fox jumps over the lazy dog) 输出

Counter = 26

这仅对输入字符串执行一次传递,然后对 letters 数组执行一次传递。没有嵌套循环,也没有对输入字符串的多次传递。

,

如果我们假设 8 位字符并且可以在堆栈上临时分配 256 个字节,那么这既可读又紧凑且相当高效:

bool is_pangram (const char* str)
{
  char used [256]={0};
  for(; *str!='\0'; str++)
  {
    used[*str]=1;
  }
  return memchr(&used['a'],26)==NULL; // 26 letters in the alphabet
}

256 字节的零输出似乎效率低下,但主流 x86 编译器在 16 条指令中运行它。此函数也没有假设 'a''z' 的邻接。要添加对大写的支持,只需执行 used[tolower(*str)]=1;,尽管这可能会引入很多分支。

测试代码:

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

bool is_pangram (const char* str)
{
  char used [256]={0};
  for(; *str!='\0'; str++)
  {
    used[*str]=1;
  }
  return memchr(&used['a'],26)==NULL;
}

int main (void) 
{
  const char* test_cases[] = 
  {
    "","hello,world!","the quick brown fox jumps over the lazy dog","the quick brown cat jumps over the lazy dog","junk mtv quiz graced by fox whelps","public junk dwarves hug my quartz fox",};

  for(size_t i=0; i<sizeof test_cases/sizeof *test_cases; i++)
  {
    printf("\"%s\" is %sa pangram\n",test_cases[i],is_pangram(test_cases[i])?"":"not ");
  }

  return 0;
}