удалить узел структуры вызвать другую проблему - PullRequest
1 голос
/ 26 апреля 2020

Я пытаюсь добавить слова из двух файлов (их может быть больше) в структуру. Это работает. Однако у меня есть возможность удалить некоторые слова (которые находятся в stop.txt ) из структуры. Это приводит к неправильному выводу, когда он активирован и два файла находятся в нем.

Например, в test.txt У меня есть несколько случайных строк:

kiio
luio
kiio
ohaio
lol

В test1.txt :

vola
kiio
kiio
haio
lol

и stop.txt :

luio
kiio

Вывод при активации remove_word:

lol     test.txt        [1] {5}                                                                                                
lol     %~      [1] {5}                                                                                                        
lol     %~      [1] {5}                                                                                                        
luio    test.txt        [1] {2}                                                                                                
ohaio   test.txt        [1] {4}                                                                                                
vola    test1.txt       [1] {1}  

Когда это не так:

kiio    test.txt        [2] {1,3}     I need to have two nodes with the same word but different `fileno`                                                                                         
kiio    test1.txt       [2] {2,3}                                                                                              
lol     test.txt        [1] {5}                                                                                                
lol     test1.txt       [1] {5}                                                                                                
luio    test.txt        [1] {2}                                                                                                
ohaio   test.txt        [1] {4}                                                                                                
vola    test1.txt       [1] {1}                                                                                                
haio    test1.txt       [1] {4}    

Я думаю, что проблема в функции remove_word, но я не уверен, потому что она работает (удаляет слова) только для одного файла.

Вот определения структуры:

typedef struct _word {
    char *s;                /* the word */
    int count;              /* number of times word occurs */
    int *line_numbers;      // Array of line numbers
    int num_line_numbers;   // Size of the array of line numbers
    char *fileno;
} word;
// Creating a struct to hold the data. I find it's easier
typedef struct {
    word *words;      // The array of word structs
    int num_words;    // The size of the array
} word_list;

remove_word Функция:

void remove_word(word_list *words, const char *word_to_delete) {
    for (int i = 0; i < words->num_words; i++) {
        if (0 == strcmp(words->words[i].s, word_to_delete)) {
            // TODO: handle special case where there is only 1 word in list

            // Calc number of words after found word
            int number_of_words_to_right = words->num_words - i - 1;
            // Free mem
            free(words->words[i].s);
            free(words->words[i].line_numbers);
            free(words->words[i].fileno);

            // Copy remaining words
            memcpy(&words->words[i], &words->words[i + 1], sizeof(word) * number_of_words_to_right);
            // Resize the array (technically not required)
            word *tmp = realloc(words->words, sizeof(word) * --words->num_words);
            if (NULL == tmp) exit(0);
            words->words = tmp;
        }
    }
    return;
}

Main:

int main() {
    int i, n, m;
    int option = 0;
    n = 0;

    FILE *file = fopen("test.txt", "r"); 

    word_list *words = malloc(sizeof(word_list));
    if (NULL == words)
        exit(0);
    memset(words, 0, sizeof(word_list));

    char s[1000];
    int line_number = 1;
    while (fgets(s, sizeof(s), file)) {
        char *word = strtok(s, " ");
        while (word != NULL) {
            size_t len = strlen(word);
            if (len > 0 && word[len - 1] == '\n')
                word[--len] = 0;
            insert_word(words, word, line_number, "test.txt");
            word = strtok(NULL, " ");
        }
        line_number += 1;
    }
    fclose(file);

    FILE *file1 = fopen("test1.txt", "r"); 

    line_number = 1;
    while (fgets(s, sizeof(s), file)) {
        char *word = strtok(s, " ");
        while (word != NULL) {
            size_t len = strlen(word);
            if (len > 0 && word[len - 1] == '\n')
                word[--len] = 0;
            insert_word(words, word, line_number, "test1.txt");
            word = strtok(NULL, " ");
        }
        line_number += 1;
    }
    fclose(file1);

     if (option == 0) {
         FILE *stopfile = fopen("stop.txt", "r"); /* should check the result */
         char fline[256];

         while (fgets(fline, sizeof(fline), stopfile)) {
             remove_word(words, fline);
         }
         fclose(stopfile);
    }

    printlist(words);

    for (int i = 0; i < words->num_words; i++) {
        free(words->words[i].s);
        free(words->words[i].line_numbers);
        free(words->words[i].fileno);
    }
    free(words->words);
    free(words);
}

Ответы [ 2 ]

2 голосов
/ 26 апреля 2020

Мы не можем протестировать вашу программу, поскольку вы не предоставили исходный код для insert_word и printlist.

. Однако в опубликованном коде есть несколько проблем:

  • в функции remove_word, когда слово найдено и удалено, вы должны уменьшить значение i, чтобы l oop проверял один и тот же индекс на следующей итерации, если одно и то же слово присутствует в обоих файлах.

  • во втором чтении l oop, вы читаете из file, но этот FILE* был закрыт, и вы использовали другой указатель FILE file1, чтобы открыть второе файл test1.txt . Это имеет неопределенное поведение. Возможно, вам повезет, и file1 может указать на то же место в памяти, что и file, случайно. Просто используйте одну и ту же переменную file для всех файлов, или лучше: используйте отдельную функцию для чтения слова из файла, заданного в качестве аргумента.

  • вы не удаляете завершающий символ новой строки из слова, которые вы передаете remove_word, поэтому из словаря ничего не будет удалено.

Вот модифицированная версия вашей программы:

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

typedef struct _word {
    char *s;                /* the word */
    int count;              /* number of times word occurs */
    int *line_numbers;      // Array of line numbers
    int num_line_numbers;   // Size of the array of line numbers
    char *fileno;
} word;

// Creating a struct to hold the data. I find it's easier
typedef struct {
    word *words;      // The array of word structs
    int num_words;    // The size of the array
} word_list;

char *strlower(char *s) {
    for (size_t i = 0; s[i]; i++) {
        s[i] = (char)tolower((unsigned char)s[i]);
    }
    return s;
}

void printlist(const word_list *words) {
    for (int i = 0, j; i < words->num_words;) {
        const word *wp = &words->words[i];
        /* check for identical words from different files */
        for (j = i + 1; j < words->num_words; j++) {
            if (strcmp(wp->s, words->words[j].s) != 0)
                break;
        }
        printf("%s\t[%d]", wp->s, j - i);
        const char *prefix = "";
        for (; i < j; i++, wp++) {
            printf("%s\t%s\t[%d]\t{%d", prefix, wp->fileno, wp->count, wp->line_numbers[0]);
            prefix = "\t";
            for (int k = 1; k < wp->num_line_numbers; k++) {
                printf(",%d", wp->line_numbers[k]);
            }
            printf("}\n");
        }
    }
}

/* insert the word in the dictionary.
 * words are inserted in lexicographical order,
 * identical words are inserted in order of calls to insert_word
 */
int insert_word(word_list *words, const char *s, int line_number, const char *filename) {
    int i, j;
    word *wp;
    /* locate the word in the dictionary */
    for (i = 0, j = words->num_words; i < j;) {
        int m = i + (j - i) / 2;
        if (strcmp(words->words[m].s, s) < 0)
            i = m + 1;
        else
            j = m;
    }
    wp = &words->words[i];
    /* check identical words already in the dictionary */
    for (; i < words->num_words && !strcmp(wp->s, s); i++, wp++) {
        if (!strcmp(wp->fileno, filename)) {
            /* found word from the same file */
            wp->count++;
            /* check if word appears for a new line number */
            for (j = 0; j < wp->num_line_numbers; j++) {
                if (wp->line_numbers[j] == line_number)
                    break;
            }
            if (j == wp->num_line_numbers) {
                /* add a new line */
                int *lp = realloc(wp->line_numbers, (j + 1) * sizeof(*wp->line_numbers));
                if (lp == NULL)
                    return 1;
                wp->line_numbers = lp;
                wp->line_numbers[wp->num_line_numbers++] = line_number;
            }
            return 0;
        }
    }
    /* insert new word into the dictionary at offset i */
    /* allocate all elements for easier memory management */
    char *new_s = strdup(s);
    char *new_filename = strdup(filename);
    int *new_line_numbers = malloc(1 * sizeof(*wp->line_numbers));
    if (!new_s || !new_filename || !new_line_numbers) {
        free(new_s);
        free(new_filename);
        free(new_line_numbers);
        return 1;
    }
    word *new_words = realloc(words->words, (words->num_words + 1) * sizeof(*words->words));
    if (new_words == NULL) {
        free(new_s);
        free(new_filename);
        free(new_line_numbers);
        return 1;
    }
    words->words = new_words;
    /* shift the rest of the dictionary to the right */
    wp = &words->words[i];
    memmove(wp + 1, wp, (words->num_words - i) * sizeof(*wp));
    wp->s = new_s;
    wp->count = 1;
    wp->line_numbers = new_line_numbers;
    wp->line_numbers[0] = line_number;
    wp->num_line_numbers = 1;
    wp->fileno = new_filename;
    words->num_words++;
    return 0;
}

int remove_word(word_list *words, const char *word_to_delete) {
    int found = 0;
    for (int i = 0; i < words->num_words; i++) {
        if (!strcmp(words->words[i].s, word_to_delete)) {
            // Calc number of words after found word
            int number_of_words_to_right = words->num_words - i - 1;
            // Free mem
            free(words->words[i].s);
            free(words->words[i].line_numbers);
            free(words->words[i].fileno);

            if (--words->num_words == 0) {
                free(words->words);
                words->words = NULL;
            } else {
                // Copy remaining words if any
                memcpy(&words->words[i], &words->words[i + 1],
                       sizeof(word) * number_of_words_to_right);
                // Resize the array (technically not required)
                word *tmp = realloc(words->words, sizeof(word) * words->num_words);
                if (tmp != NULL)
                    words->words = tmp;
            }
            found++;
            i--; // restart from the same index in the loop
        }
    }
    return found;
}

/* read all words from filename into word_list
 * return 0 if no error.
 */
int read_file(word_list *words, const char *filename) {
    char s[1000];
    int line_number = 1;
    FILE *file = fopen(filename, "r");
    if (file == NULL) {
        fprintf(stderr, "cannot open %s\n", filename);
        return 1;
    }
    while (fgets(s, sizeof(s), file)) {
        char *word = strtok(s, " \n");
        while (word != NULL) {
            if (insert_word(words, strlower(word), line_number, filename)) {
                fprintf(stderr, "error inserting from %s at line %d\n", filename, line_number);
                fclose(file);
                return 1;
            }
            word = strtok(NULL, " \n");
        }
        line_number += 1;
    }
    fclose(file);
    return 0;
}

int main() {
    int option = 1;
    word_list *words = calloc(sizeof(word_list), 1);
    if (words == NULL) {
        fprintf(stderr, "cannot allocate memory\n");
        return 1;
    }
    read_file(words, "test.txt");
    read_file(words, "test1.txt");

    if (option != 0) {
        char s[1000];
        FILE *file = fopen("stop.txt", "r"); /* should check the result */
        if (file == NULL) {
            fprintf(stderr, "cannot open %s\n", "stop.txt");
        } else {
            while (fgets(s, sizeof(s), file)) {
                char *word = strtok(s, " \n");
                while (word != NULL) {
                    remove_word(words, strlower(word));
                    word = strtok(NULL, " \n");
                }
            }
            fclose(file);
        }
    }
    printlist(words);

    for (int i = 0; i < words->num_words; i++) {
        free(words->words[i].s);
        free(words->words[i].line_numbers);
        free(words->words[i].fileno);
    }
    free(words->words);
    free(words);
    return 0;
}

Вывод:

haio    [1]     test1.txt       [1]     {4}
lol     [2]     test.txt        [1]     {5}
                test1.txt       [1]     {5}
ohaio   [1]     test.txt        [1]     {4}
vola    [1]     test1.txt       [1]     {1}
0 голосов
/ 26 апреля 2020

Я что-то нашел, не уверен, что этого достаточно для исправления кода: вам нужно уменьшить num_words при удалении мира из массива, попробуйте добавить:

words->num_words--;

Ниже words->words = tmp; в конце if в функции remove_word

...