Удалить несколько строк на основе заданной подстроки - PullRequest
0 голосов
/ 05 марта 2020

Я пытаюсь создать код, в котором есть имена (например, Энди, Барри, Матильда ), и имена будут удалены при вводе определенной подстроки (например, подстрока y , поэтому Энди и Барри будут удалены. Осталось только Матильда ). Кто-нибудь может предложить мне помощь?

Мой код:

void del(char key[]) 
{
    if(head == NULL)
    {
        printf("There's no data\n");
    }
    else{
        curr = head;
        while(curr != NULL && strcmp(curr->name, key) != 0)
        {
            curr = curr->next;
        }
        if(curr == NULL)
        {
            printf("Node is not in the list\n");
        }
        if(curr == head & curr == tail)
        {
            free(curr);
            head = tail = NULL;
        }
        else if(curr == head)
        {
            head = head->next;
            free(curr);
            head->prev = NULL;
        }
        else if(curr == tail)
        {
            tail = tail->prev;
            free(curr);
            tail->next = NULL;
        }
        else
        {
            curr->prev->next = curr->next;
            curr->next->prev = curr->prev;
            free(curr);
        }
    }
}

Ответы [ 2 ]

0 голосов
/ 06 марта 2020

Вы не предложили много информации для go, но я полагаю, что это новость для сообщества. Из того, что вы предоставляете, ясно, что, как минимум, у вас есть Doubly-Linked-List , где у вас есть строка в качестве члена данных. Также очевидно, что вы объявили оба указателя head и tail как глобальные переменные (не очень хорошая практика, поскольку вы должны передавать любую информацию, требуемую вашей функцией, в качестве параметра, но для этого учебное упражнение обеспечивает минимальное упрощение)

Из того, что вы описываете, вы хотите, чтобы ваша функция del выполняла, вы хотите выполнить итерации по тестированию связанного списка, содержит ли член name подстроку key и если это так, вы хотите удалить этот узел из списка.

У вас есть две основные проблемы:

  1. вы используете неправильную строковую функцию для проверки подстроки в вашем name член. strcmp() найдет только те узлы, где полный name соответствует вашему key. Вместо этого вы хотите strstr(), где вы можете сопоставить key в любом месте в пределах name;
  2. вы чрезмерно усложняете удаление узла, пытаясь использовать указатель только для текущего узла, чтобы итерируйте по списку вместо использования указателя и адреса для текущего узла. См. Линус о понимании указателей

Чтобы исправить проблему с кулаком, вам просто нужно изменить strcmp() на strstr() для соответствия key в любом месте name, например

        ...
        if (strstr(curr->name, key) != NULL) {  /* strstr, not strcmp, to find key */
            ...

Чтобы решить вторую проблему, вам нужно переработать функцию del. Для этого вы должны понять, чем итерация при удалении нескольких узлов отличается от итерации, чтобы найти один узел для удаления. При циклическом поиске отдельного узла для удаления вы просто переходите к следующему узлу в списке на каждой итерации до тех пор, пока узел не будет найден, а затем удаляете этот узел.

Вы не можете сделать это при потенциальном удалении нескольких узлы из вашего списка. Почему? Когда вы удаляете узел с соответствующей подстрокой, следующий узел в списке занимает его место. Вы не можете просто перейти к следующему узлу после этого удаления, потому что узел, который является текущим узлом после удаления первого, также может содержать подстроку, которую вы хотите найти. Если вы просто слепо переходите к следующему узлу, а текущий после удаления также содержит подстроку, вы пропустите удаление этого узла.

Что это означает с точки зрения кода, вам нужно добавить предложение else ниже вашего if, например

        if (strstr(curr->name, key) != NULL) {  /* strstr, not strcmp, to find key */
            ...
        else
            ...

Согласно вашему предложению if, следующий узел заменяет текущий после удаления, поэтому вы НЕ продвигаетесь снова к следующему узлу в вашем списке. Таким образом, новый текущий узел будет проверен на соответствие подстроки на следующей итерации. Вы переходите к следующему узлу в соответствии с предложением else, если key не совпадает.

При выполнении итерации с указателем на текущий узел вместе с адресом текущего узла, вам не нужно обрабатывать особые случаи. Вы всегда будете устанавливать контент по текущему адресу на следующий узел в вашем списке. (head не изменяется, потому что вы установили структуру по этому адресу на следующую при удалении) Единственная проверка, которая вам нужна, - это проверка, удаляете ли вы узел tail. В этом случае вам нужно обновить узел tail, чтобы он указывал на предыдущий узел, так как вы будете удалять то, на что в данный момент указывает tail. В противном случае все, что вам нужно сделать, это обновить указатель ->prev узла, который вы переместили в текущий адрес, чтобы он указывал на предыдущий узел в списке.

Учитывая это, ваша функция del уменьшает to:

void del (char key[])
{
    if (head == NULL) {                         /* check empty */
        puts ("list-empty");
        return;
    }
    node_t  **ppnode = &head,                   /* address of current node */
            *curr = head;                       /* pointer to current node */

    while (curr) {
        if (strstr(curr->name, key) != NULL) {  /* strstr, not strcmp, to find key */
            *ppnode = curr->next;               /* fill address w/next node */
            if (curr != tail)                   /* if not tail */
                (*ppnode)->prev = curr->prev;   /* set ->prev to prev node */
            else    /* otherwise */
                tail = curr->prev;              /* update tail pointer */
            free (curr);                        /* free node */
            curr = *ppnode;                     /* set new current node */
        }
        else {  /* node to keep */
            ppnode = &curr->next;               /* set address to addres of next */
            curr = curr->next;                  /* advance to next node */
        }
    }
}

Не зная больше о вашем коде, все, что мы можем сделать, это написать короткий пример, который добавляет ваши строки как узлы в список (используя фиксированный name для упрощения). Короткий пример как:

int main (void) {

    add ("Andy");           /* add nodes to list */
    add ("Barry");
    add ("Matilda");

    prnfwd();               /* print forward and reverse */
    prnrev();
    putchar ('\n');

    del ("y");              /* delete nodes containing substring "y" */

    prnfwd();               /* print forward and reverse */
    prnrev();

    del_list();             /* free allocated memory */
}

Это добавляет узлы, перебирает список в обоих направлениях, вызывает del ("y");, чтобы удалить все узлы, где строка содержит подстроку "y" (обратите внимание, что она отличается от символа 'y'), а затем перебирает снова список в обоих направлениях, выводящий то, что осталось до освобождения всей памяти в списке.

Пример использования / Вывод

Результат, полученный в примере строки, будет:

$ ./bin/lldglobaldelkey
 Andy Barry Matilda
 Matilda Barry Andy

 Matilda
 Matilda

Полная реализация примера:

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

#define MAXNM 64

typedef struct node_t {
    char name[MAXNM];
    struct node_t *prev, *next;
} node_t;

node_t *head, *tail;

/** add node at end of list, update tail to end */
node_t *add (const char *s)
{
    node_t *node = malloc (sizeof *node);   /* allocate node */
    if (!node)                              /* validate allocation */
        return NULL;

    strcpy (node->name, s);                 /* initialize new node */
    node->prev = node->next = NULL;

    if (!head)                              /* if 1st node, node is head/tail */
        head = tail = node;
    else {                                  /* otherwise */
        node->prev = tail;                  /* set prev to tail */
        tail->next = node;                  /* add at end, update tail pointer */
        tail = node;
    }

    return node;    /* return new node */
}

/* print list forward */
void prnfwd (void)
{
    if (!head) {                            /* check empty */
        puts ("list-empty");
        return;
    }

    for (node_t *n = head; n; n = n->next)  /* iterate over nodes - forward */
        printf (" %s", n->name);
    putchar ('\n');
}

/* print list reverse */
void prnrev (void)
{
    if (!head) {                            /* check empty */
        puts ("list-empty");
        return;
    }

    for (node_t *n = tail; n; n = n->prev)  /* iterate over nodes - reverse */
        printf (" %s", n->name);
    putchar ('\n');
}

/** delete all nodes in list */
void del_list (void)
{
    node_t *n = head;

    if (!head) {                            /* check empty */
        puts ("list-empty");
        return;
    }

    while (n) {                             /* iterate over nodes - forward */
        node_t *victim = n;                 /* save ptr to node to delete */
        n = n->next;                        /* advance to next */
        free (victim);                      /* delete node */
    }

    head = tail = NULL;                     /* set pointers NULL */
}

void del (char key[])
{
    if (head == NULL) {                         /* check empty */
        puts ("list-empty");
        return;
    }
    node_t  **ppnode = &head,                   /* address of current node */
            *curr = head;                       /* pointer to current node */

    while (curr) {
        if (strstr(curr->name, key) != NULL) {  /* strstr, not strcmp, to find key */
            *ppnode = curr->next;               /* fill address w/next node */
            if (curr != tail)                   /* if not tail */
                (*ppnode)->prev = curr->prev;   /* set ->prev to prev node */
            else    /* otherwise */
                tail = curr->prev;              /* update tail pointer */
            free (curr);                        /* free node */
            curr = *ppnode;                     /* set new current node */
        }
        else {  /* node to keep */
            ppnode = &curr->next;               /* set address to addres of next */
            curr = curr->next;                  /* advance to next node */
        }
    }
}

int main (void) {

    add ("Andy");           /* add nodes to list */
    add ("Barry");
    add ("Matilda");

    prnfwd();               /* print forward and reverse */
    prnrev();
    putchar ('\n');

    del ("y");              /* delete nodes containing substring "y" */

    prnfwd();               /* print forward and reverse */
    prnrev();

    del_list();             /* free allocated memory */
}

Использование памяти / проверка ошибок

В любом написанном вами коде, который динамически выделяет память , у вас есть 2 обязанностей относительно любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блока памяти, поэтому (2) это может быть освобожден , когда он больше не нужен.

Обязательно используйте программу проверки ошибок памяти, чтобы гарантировать, что вы не пытаетесь получить доступ к памяти или писать за пределами или за пределами вашего выделенного блока, попытайтесь прочитать или основать условный переход на неинициализированном значении и, наконец, подтвердить, что вы освобождаете всю выделенную память.

Для Linux valgrind это нормальный выбор. Есть похожие проверки памяти для каждой платформы. Все они просты в использовании, просто запустите вашу программу через нее.

$ valgrind ./bin/lldglobaldelkey
==10704== Memcheck, a memory error detector
==10704== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==10704== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==10704== Command: ./bin/lldglobaldelkey
==10704==
 Andy Barry Matilda
 Matilda Barry Andy

 Matilda
 Matilda
==10704==
==10704== HEAP SUMMARY:
==10704==     in use at exit: 0 bytes in 0 blocks
==10704==   total heap usage: 4 allocs, 4 frees, 1,264 bytes allocated
==10704==
==10704== All heap blocks were freed -- no leaks are possible
==10704==
==10704== For counts of detected and suppressed errors, rerun with: -v
==10704== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Всегда подтверждайте, что вы освободили всю выделенную память и что ошибок памяти нет.

Надеюсь это близко к тому, что ваша реализация. Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

0 голосов
/ 05 марта 2020

Ваш код корректно удаляет один узел списка, кроме случаев, когда key не найден - в этом случае return после printf("Node is not in the list\n"); отсутствует.

Чтобы удалить несколько узлов, нам необходимо l oop над списком, продолжая также, если совпадение найдено и удалено. Чтобы напечатать сообщение Node is not in the list, только если ничего не найдено, мы можем использовать флаг, который проверяется после l oop. Таким образом, вы можете заменить содержимое вашего внешнего else блока, например,

      void *next;
      int deleted = 0;
      for (curr = head; curr; curr = next)
      { next = curr->next;
       if (strstr(curr->name, key))
       { deleted = 1;
        // the rest of this block is your code unchanged
        if(curr == head & curr == tail)
        {
            free(curr);
            head = tail = NULL;
        }
        else if(curr == head)
        {
            head = head->next;
            free(curr);
            head->prev = NULL;
        }
        else if(curr == tail)
        {
            tail = tail->prev;
            free(curr);
            tail->next = NULL;
        }
        else
        {
            curr->prev->next = curr->next;
            curr->next->prev = curr->prev;
            free(curr);
        }
       }
      }
      if (!deleted) printf("Node is not in the list\n");
...