【问题标题】:String Tokenization problem occurs when deleting duplicate words from a string从字符串中删除重复单词时出现字符串标记化问题
【发布时间】:2019-05-18 19:48:44
【问题描述】:

我在以下代码中尝试做的是对字符串进行标记并将每个标记存储在动态分配的结构中,但排除任何重复项。

这种代码可以正常工作,直到我输入一个包含两个相同单词的字符串。例如,字符串“this this”也会存储第二个单词,即使它是相同的。但是如果我输入“this this is”,它会删除第二个“this”,并完全忽略字符串的最后一个单词,这样如果字符串中有重复项,它就不会被删除。

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

#define dim 70


typedef struct string {
  char* token[25];
} string;

int main() {

  string* New = malloc(dim*sizeof(string));

  char* s;
  char* buffer = NULL;
  int i = 0, r = 0;

  s = malloc(dim * sizeof(char));

  fgets(s, dim, stdin);

  printf("The string is: %s\n", s); 

  New->token[i] = malloc(dim*sizeof(char));
  New->token[i] = strtok(s, " ");
  ++i;

  while((buffer = strtok(NULL, " ")) && buffer != NULL){

    printf("\nbuffer is: %s", buffer);

    for(r = 0; r < i; ++r) {

      if(strcmp(New->token[r], buffer) != 0 && r == i-1) {

        New->token[i] = malloc(strlen(buffer)*sizeof(char)+1);
        New->token[i] = buffer;
        ++i;

      }
      else if(New->token[r] == buffer) {
            break;
      }

    }



  }

printf("\n New string: ");
for(i = 0; New->token[i] != NULL; ++i) {
   printf(" %s", New->token[i]);
}


return 0;
}

在我看来,这应该可以正常工作,但我真的很难找到我在这里做错了什么。如果您需要更多信息,请直接问我,对于任何最终缺乏清晰度(以及我的英语),我深表歉意。

【问题讨论】:

  • 您是否尝试过在每一步打印New-&gt;token 数组,以查看其内容以及程序如何与之交互? strtok() 在处理过程中修改标记化字符串,这肯定会干扰New-&gt;token[0]for 循环中的输出。
  • 请对您显示的代码应用一些一致的缩进。
  • @xing 我在分隔符中添加了\n,但没有任何变化。另外,我应该使用strcpy() 函数更改New-&gt;token[i] = malloc(...New-&gt;token[i] = strtok(s, " "); 吗?
  • sizeof(char) == 1 - 总是。所以s = malloc(dim * sizeof(char));变成s = malloc(dim);
  • 我看到的步骤: 1)输入一个包含几个空格分隔的子字符串的字符串。 2) 将字符串解析为子字符串数组。 3) 不允许在子字符串的最终集合中出现重复。如果这是您需要做的,还有更简单的方法。您是否需要使用动态分配?是否需要使用结构体?

标签: c string duplicates tokenize strtok


【解决方案1】:

完全重写此答案,以解决我第一次没有看到的一些根本错误的事情。请参阅底部代码中的内联 cmets 以解释一些构造更改:

我完全按原样运行了您的代码,并看到了您所描述的内容,除了在另一个答案中关于使用 strcmp 的注释外,还发现了几行可以调整或删除的代码以使其执行您的操作描述它应该:

首先,结构定义创建一个指向 char 数组的指针。根据您稍后在代码中所做的事情,您需要的是一个简单的 char 数组

typedef struct string {
  //char* token[25]; //this create a pointer to array of 25 char
  char token[25]; //this is all you need
} string;

正如您稍后将看到的,这将大大简化内存分配。

一些基本问题:

在解析分隔符中包含 \n 换行符。当&lt;enter&gt; 作为输入字符串的结尾被击中时,将追加一个换行符,导致this 的第一个实例和this\n 的第二个实例不相等。

while((buffer = strtok(NULL, " \n")) && buffer != NULL){
                               ^^

这一行正在创建未初始化的内存。

string* New = malloc(dim*sizeof(string)); 

关于使用 malloc()calloc() 的注意事项:malloc() 保留它创建的内存未初始化,而 calloc() 创建一个全部初始化为0 的内存块。

使用malloc()创建的内存

使用calloc()创建的内存:

这在您的代码中的几个地方变得很重要,但特别是我在最后一节中看到了一个问题:

for(i = 0; New->token[i] != NULL; ++i) {
   printf(" %s", New->token[i]);
}

如果为New 创建的内存未初始化,则当索引i 增加超出您显式写入的内存区域时,您可能会收到运行时错误,并且循环尝试测试@987654342 @。如果New-&gt;token[i] 包含除0 之外的任何内容,它将尝试打印该内存区域。

您还应该通过对 free() 的相应调用来释放代码中创建的每个内存实例。

所有这些以及更多内容都将在以下代码重写中得到解决: (针对 this is a string a string 进行测试。)

typedef struct string {
  //char* token[25]; //this create a pointer to array of 25 char
  char token[25]; //this is all you need
} string;

int main() {
    char* s;
    char* buffer = NULL;
    int i = 0, r = 0;

    string* New = calloc(dim, sizeof(string));//Note: This creates an array of New.
                                              //Example: New[i]
                                              //Not: New->token[i]
    s = calloc(dim , sizeof(char));
    fgets(s, dim, stdin);
    printf("The string is: %s\n", s); 
    buffer = strtok(s, " \n");
    strcpy(New[i].token, buffer); //use strcpy instead of = for strings
    //restuctured the parsing loop to a more conventional construct
    // when using strtok:
    if(buffer)
    {
        ++i;
        while(buffer){
            printf("\nbuffer is: %s", buffer);
            for(r = 0; r < i; ++r) {
                if(strcmp(New[r].token, buffer) != 0 && r == i-1) {
                    strcpy(New[i].token, buffer);
                    ++i;
                }
                else if(strcmp(New[r].token, buffer)==0) {
                    break;
                }
            }
            buffer = strtok(NULL, " \n");
        }
    }
    printf("\n New string: ");
    for(i = 0; i<dim; i++) {
        if(New[i].token) printf(" %s", New[i].token);
    }
    free(New);
    free(s);
    return 0;
}

【讨论】:

  • 谢谢,我现在了解calloc() 的用法,它解决了我使用此代码时遇到的第一个问题。尽管如此,当我输入类似“this is a string a string”之类的内容时,代码并没有按预期运行,因为它会打印相同的给定字符串而不会删除任何内容。
  • @Waco - 我需要编辑我的答案。我发现了其他几个我想解决的问题。它将在几分钟后发布。
  • @Waco - 我进行了重大更改,其中一些您可能不理解。有关解释,请参见 in-line cmets。如果您有其他问题,请在 cmets 中提问。
【解决方案2】:

结构似乎没有必要。
这使用一个指针数组来存储令牌。
输入可以用strspnstrcspn解析。
唯一标记被添加到指针数组中。

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

#define DIM 70

int main() {
    char* token[DIM] = { NULL};

    char s[DIM];
    char* buffer = s;
    int unique = 0, check = 0;
    int match = 0;
    int loop = 0;
    size_t space = 0;
    size_t span = 0;

    fgets(s, DIM, stdin);

    printf("The string is: %s\n", s);

    while ( unique < DIM && *buffer){//*buffer not pointing to zero terminator
        space = strspn ( buffer, " \n\t");//leading whitespace
        buffer += space;//advance past whitespace
        span = strcspn ( buffer, " \n\t");//not whitespace
        if ( span) {
            printf("\ntoken is: %.*s", (int)span, buffer );//prints span number of characters
        }
        match = 0;
        for ( check = 0; check < unique; ++check) {
            if ( 0 == strncmp ( token[check], buffer, span)) {
                match = 1;//found match
                break;
            }
        }
        if ( ! match) {//no match
            token[unique] = malloc ( span + 1);//allocate for token
            strncpy ( token[unique], buffer, span);//copy span number of characters
            token[unique][span] = 0;//zero terminate
            ++unique;//add a unique token
        }
        buffer += span;//advance past non whitespace for next token
    }

    printf("\n New string: ");
    for( loop = 0; loop < unique; ++loop) {
        printf(" %s", token[loop]);//print the unique tokens
    }
    printf("\n");
    for( loop = 0; loop < unique; ++loop) {
        free ( token[loop]);//free memory
    }
    return 0;
}

【讨论】:

    【解决方案3】:

    您比较指针而不是比较字符串。替换

      }
      else if(New->token[r] == buffer) {
            break;
    

      }
      else if(strcmp(New->token[r], buffer) == 0) {
            break;
    

    你还需要复制缓冲区:

    memcpy(New->token[i],buffer,strlen(buffer)+1);
    

    而不是

    New->token[i] = buffer;
    

    或将两行(连同 malloc)替换为

    New->token[i] = strdup(buffer);
    

    并且最好将strtok替换为strtok_r(strtok不可重入)。

    【讨论】:

    • 谢谢,解决了最后一个字的问题。尽管如此,当输入仅由两个相同的单词组成时,它仍然会同时打印这两个单词。但我想这是因为我在New-&gt;token[i] = strtok(s, " "); 中做了同样的事情吗?
    猜你喜欢
    • 2013-05-26
    • 2016-02-02
    • 2012-03-14
    • 2013-08-11
    • 1970-01-01
    • 1970-01-01
    • 2014-11-21
    相关资源
    最近更新 更多