cs50 pset 5的Frowny face“正确处理大多数基本单词”和“拼写检查不区分大小写”

oxf4rvwz  于 2023-11-16  发布在  其他
关注(0)|答案(1)|浏览(81)

我运行了check50,得到了一张皱眉的脸,因为“拼写检查不区分大小写”和“正确处理大多数基本单词”。
我该怎么解决这个问题呢?这和我的哈希函数有关吗?

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

#include "dictionary.h"

// Represents a node in a hash table
typedef struct node
{
    char word[LENGTH + 1];
    struct node *next;
}
node;

// Number of words in dictionary
int word_count = 0;

// Number of buckets in hash table
const unsigned int N = 26;

// Hash table
node *table[N];

// Returns true if word is in dictionary, else false
bool check(const char *word)
{
    unsigned int n = hash(word);

    node *cursor = table[n];

    while (cursor != NULL)
    {
        if (strcasecmp(word, cursor->word) == 0)
        {
            return true;
        }

        cursor = cursor->next;
    }
    return false;
}

// Hashes word to a number
// Function credit to staff on CS50 reddit page
unsigned int hash(const char *word)
{
    unsigned int hash_value = 0;

    for (int i = 0, n = strlen(word); i < n; i++)
    {
         hash_value = (hash_value << 2) ^ word[i];
    }
    return hash_value % N; 
}

// Loads dictionary into memory, returning true if successful else false
bool load(const char *dictionary)
{
    // Open dictionary and check for memory issue
    //Function guide credit to CS50 Guide by Anvea on YouTube
    FILE *dict = fopen(dictionary, "r");
    char word[LENGTH + 1];

    // Check for memory issue with dict
    if (dict == NULL)
    {
        printf("Dictionary is null\n");
        unload();
        return false;
    }

    while (fscanf(dict, "%s", word) != EOF)
    {
        node *n = malloc(sizeof(node));
        if (n == NULL)
        {
            return false;
        }

        strcpy(n->word, word);
        word_count++;

        // Index word using hash function
        int dict_index = hash(word);

        // Insert into hash table if already empty
        if (table[dict_index] == NULL)
        {
            n->next = NULL;
        }
        // Insert work as new node if not empyty
        else
        {
            n->next = table[dict_index];
        }

        table[dict_index] = n;
    }

    // Close dictionary file
    fclose(dict);

    // Indicate success
    return true;
}

// Returns number of words in dictionary if loaded else 0 if not yet loaded
unsigned int size(void)
{
    return word_count;
    return 0;
}

// Unloads dictionary from memory, returning true if successful else false
bool unload(void)
{
    for (int i = 0; i < N; i++)
    {
        node *cursor = table[i];

        while (cursor)
        {
            node *tmp = cursor;
            cursor = cursor->next;
            free(tmp);
        }
    }
    return true;
}

字符串

rks48beu

rks48beu1#

一些问题...
1.所有的字典单词都是,每行一个。所以,哈希是在字符上完成的。
1.在check中,它在混合大小写文本上调用hash,因此它可以为某些单词(例如)Won't this work?No, it won't.生成不同的哈希值
1.在check中,我们需要在 * 调用hash之前 * 对字符串进行重命名,这样我们就可以得到 * 相同的 * 哈希值/索引。作为一个有益的副作用,我们可以用strcmp替换strcasecmp(这样更快)。
1.在load中,当table[n]NULL时,* 没有 * 需要有特殊情况,所以我们可以简化它。
1.虽然我没有检查这一点,但在hash中执行<<可能会产生一个不均匀分布的哈希值,从而减慢check中的搜索速度。
1.因为字典文件是[保证]所有的行和每行一个条目,在load中,我们可以用fgets替换fscanf [剥离换行符],因为fgets快得多。
1.在gcc 8.3.1中,编译器标记了node *table[N];,因为:const unsigned int N = 26;与C++不同,这在C中是不允许的--它需要一个文字常量,而不仅仅是const
1.* 风格说明:* 永远不要做(例如):x -> y而不是x->y
下面是更正后的代码。我已经检查了所有可能的输入文件和字典。它带有bug和修复的注解:

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#if 1
#include <ctype.h>
#endif

#include "dictionary.h"

// Represents a node in a hash table
typedef struct node {
    char word[LENGTH + 1];
    struct node *next;
} node;

// Number of words in dictionary
int word_count = 0;

// Number of buckets in hash table
// NOTE/BUG: C (vs C++) doesn't support global arrays from const
#if 0
const unsigned int N = 26;
#else
enum {
    N = 26
};
#endif

// Hash table
node *table[N];

// locase -- copy lower cased word
void
locase(char *dst,const char *src)
{

    for (;  *src != 0;  ++src, ++dst)
        *dst = tolower((unsigned char) *src);
    *dst = 0;
}

// Returns true if word is in dictionary, else false
bool
check(const char *word)
{
// NOTE/FIX: we _must_ take lowercase of word _before_ calculating the hash
// otherwise, the hash value will differ for uppercase words here and lowercase
// words in the dictionary (i.e. different hash buckets)
// NOTE/FIX: we should convert test word to lowercase _once_ (it allows us to
// use strcmp below)
#if 1
    char lobuf[LENGTH + 1];
    locase(lobuf,word);
    word = lobuf;
#endif
    unsigned int n = hash(word);

    node *cursor = table[n];

    while (cursor != NULL) {
// NOTE/BUG: strcasecmp is very slow compared to strcmp
#if 0
        if (strcasecmp(word, cursor->word) == 0)
            return true;
#else
        if (strcmp(word, cursor->word) == 0)
            return true;
#endif
        cursor = cursor->next;
    }

    return false;
}

// Hashes word to a number
// Function credit to staff on CS50 reddit page
unsigned int
hash(const char *word)
{
    unsigned int hash_value = 0;

#if 0
    for (int i = 0, n = strlen(word); i < n; i++)
        hash_value = (hash_value << 2) ^ word[i];
#endif
#if 0
    for (int i = 0; word[i] != 0; i++)
        hash_value = (hash_value << 2) ^ word[i];
#endif
#if 1
    for (int i = 0; word[i] != 0; i++)
        hash_value = (hash_value * 31) ^ word[i];
#endif

    return hash_value % N;
}

// Loads dictionary into memory, returning true if successful else false
bool
load(const char *dictionary)
{
    // Open dictionary and check for memory issue
    // Function guide credit to CS50 Guide by Anvea on YouTube
    FILE *dict = fopen(dictionary, "r");
#if 0
    char word[LENGTH + 1];
#else
    char word[LENGTH + 10];
#endif

    // Check for memory issue with dict
    if (dict == NULL) {
        printf("Dictionary is null\n");
        unload();
        return false;
    }

// NOTE/BUG: dictionary is guaranteed to be one word per line and fscanf is
// slow compared to fgets
#if 0
    while (fscanf(dict, "%s", word) != EOF) {
#else
    while (1) {
        if (fgets(word,sizeof(word),dict) == NULL)
            break;
        char *cp = strchr(word,'\n');
        if (cp != NULL)
            *cp = 0;
#endif

        node *n = malloc(sizeof(node));

        if (n == NULL) {
            return false;
        }

        strcpy(n->word, word);

        word_count++;

        // Index word using hash function
        int dict_index = hash(word);

// NOTE/BUG: no need to special case this
#if 0
        // Insert into hash table if already empty
        if (table[dict_index] == NULL) {
            n->next = NULL;
        }
        // Insert work as new node if not empyty
        else {
            n->next = table[dict_index];
        }
#else
        n->next = table[dict_index];
#endif

        table[dict_index] = n;
    }

    // Close dictionary file
    fclose(dict);

    // Indicate success
    return true;
}

// Returns number of words in dictionary if loaded else 0 if not yet loaded
unsigned int
size(void)
{
    return word_count;
    return 0;
}

// Unloads dictionary from memory, returning true if successful else false
bool
unload(void)
{
    for (int i = 0; i < N; i++) {

        node *cursor = table[i];

        while (cursor) {
            node *tmp = cursor;

            cursor = cursor->next;
            free(tmp);
        }
    }
    return true;
}

字符串
在上面的代码中,我使用了cpp条件来表示旧代码和新代码:

#if 0
// old code
#else
// new code
#endif

#if 1
// new code
#endif


注意:这可以通过unifdef -k运行文件来清理。
以下是清理后的版本(通过unifdef -k运行,并进行了一些注解清理):

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

#include "dictionary.h"

// Represents a node in a hash table
typedef struct node {
    char word[LENGTH + 1];
    struct node *next;
} node;

// Number of words in dictionary
int word_count = 0;

// Number of buckets in hash table
enum {
    N = 26
};

// Hash table
node *table[N];

// locase -- copy lower cased word
void
locase(char *dst,const char *src)
{

    for (;  *src != 0;  ++src, ++dst)
        *dst = tolower((unsigned char) *src);
    *dst = 0;
}

// Returns true if word is in dictionary, else false
bool
check(const char *word)
{
    char lobuf[LENGTH + 1];
    locase(lobuf,word);
    word = lobuf;
    unsigned int n = hash(word);

    node *cursor = table[n];

    while (cursor != NULL) {
        if (strcmp(word, cursor->word) == 0)
            return true;
        cursor = cursor->next;
    }

    return false;
}

// Hashes word to a number
// Function credit to staff on CS50 reddit page
unsigned int
hash(const char *word)
{
    unsigned int hash_value = 0;

    for (int i = 0; word[i] != 0; i++)
        hash_value = (hash_value * 31) ^ word[i];

    return hash_value % N;
}

// Loads dictionary into memory, returning true if successful else false
bool
load(const char *dictionary)
{
    // Open dictionary and check for memory issue
    // Function guide credit to CS50 Guide by Anvea on YouTube
    FILE *dict = fopen(dictionary, "r");
    char word[LENGTH + 10];

    // Check for memory issue with dict
    if (dict == NULL) {
        printf("Dictionary is null\n");
        unload();
        return false;
    }

    while (1) {
        if (fgets(word,sizeof(word),dict) == NULL)
            break;
        char *cp = strchr(word,'\n');
        if (cp != NULL)
            *cp = 0;

        node *n = malloc(sizeof(node));

        if (n == NULL) {
            return false;
        }

        strcpy(n->word, word);

        word_count++;

        // Index word using hash function
        int dict_index = hash(word);

        n->next = table[dict_index];

        table[dict_index] = n;
    }

    // Close dictionary file
    fclose(dict);

    // Indicate success
    return true;
}

// Returns number of words in dictionary if loaded else 0 if not yet loaded
unsigned int
size(void)
{
    return word_count;
    return 0;
}

// Unloads dictionary from memory, returning true if successful else false
bool
unload(void)
{
    for (int i = 0; i < N; i++) {

        node *cursor = table[i];

        while (cursor) {
            node *tmp = cursor;

            cursor = cursor->next;
            free(tmp);
        }
    }
    return true;
}


虽然上面的代码[现在]是正确的,但它可以进一步改进。
对于一些额外的修复和加速,请参阅我的cs50拼写答案:cs50 pset5 Speller optimisation
以下是您的[固定]版本与我的[优化]版本之间check函数的运行时间比较:

Yours       Mine     File
 27.900433   0.271936 aca.txt
  8.713822   0.093062 austen.txt
  1.553954   0.015378 birdman.txt
  3.996230   0.041159 burnett.txt
  1.927505   0.021139 carroll.txt
  0.001437   0.000007 cat.txt
  0.477209   0.005412 constitution.txt
 12.684350   0.141040 federalist.txt
  5.947873   0.060730 frankenstein.txt
  6.810451   0.081574 grimm.txt
  1.279325   0.013508 her.txt
 78.311622   0.861220 holmes.txt
 14.265868   0.145593 homer.txt
  1.297946   0.013600 lalaland.txt
  4.110416   0.042714 mansfield.txt
  0.002796   0.000017 pneumonoultramicroscopicsilicovolcanoconiosis.txt
  1.739467   0.017283 revenant.txt
  5.238748   0.054731 rinehart.txt
 68.048034   0.680697 shakespeare.txt
  6.071052   0.064508 stein.txt
 11.721317   0.121086 stoker.txt
 14.137166   0.146902 surgery.txt
 40.615153   0.433262 tolstoy.txt
  9.731160   0.099112 wells.txt
 39.266734   0.457958 whittier.txt
  0.014416   0.000132 wordsworth.txt
 13.774792   0.144299 xueqin1.txt
 18.768864   0.196494 xueqin2.txt

相关问题