【问题标题】:C - Read non-alphabetic chars as word boundaryC - 将非字母字符读取为单词边界
【发布时间】:2014-02-24 10:07:52
【问题描述】:

我正在尝试解析一个文本文件,并将每个不同的单词添加到一个哈希表中,将单词作为键,将它们的频率作为值。问题被证明是阅读部分:该文件是一个非常大的“普通”文本文件,因为它有标点符号和特殊字符。我想将所有读入的非字母字符视为单词边界。我有一些基本的东西:

char buffer[128];
while(fscanf(fp, "%127[A-Za-z]%*c", buffer) == 1) {
    printf("%s\n", buffer); 
    memset(buffer, 0, 128); 
}

但是,当它实际碰到一个前面有空格的非字母字符时,就会窒息(例如,“the,cat was (brown)”将被读作“the cat was”)。我知道该代码有什么问题,但我不确定如何解决它。我是否会更好地阅读整行并手动进行解析?我正在尝试scanf,因为我觉得这是您可以使用格式字符串执行的迷你正则表达式的一个很好的候选者。

【问题讨论】:

  • "%127[A-Za-z]%*[^A-Za-z]" 作为一个简单的修复。
  • 谢谢!这就是我需要的。
  • 使用while (fscanf(fp, "%*[^A-Za-z]"), fscanf(fp, "%127[A-Za-z]", buffer) == 1 { 很好地处理可能以非字母开头的第一个字母组。 "%127[A-Za-z]%*[^A-Za-z]" 没有。这两种方法都可以很好地处理最后一个字母组,可以选择跟随非字母。
  • 很好地使用逗号运算符:)

标签: c regex string


【解决方案1】:

建议使用isalpha()fgetc() 和一个简单的状态机。

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

int AdamRead(FILE *inf, char *dest, size_t n) {
  int ch;
  do {
    ch = fgetc(inf);
    if (ch == EOF) return EOF;
  } while (!isalpha(ch));

  assert(n > 1);
  n--;  // save room for \0
  while (n-- > 0) {
    *dest++ = ch;
    ch = fgetc(inf);
    if (!isalpha(ch)) break;
  }

  ungetc(ch, inf);  // Add this is something else may need to parse `inf`.
  *dest = '\0';
  return 1;
}

char buffer[128];
while(AdamRead(fp, buffer, sizeof buffer) == 1) {
  printf("%s\n", buffer); 
}

注意:如果你想走"%127[A-Za-z]%*[^A-Za-z]" 路线,代码可能需要以一次性fscanf(fp, "*[^A-Za-z]"); 开头来处理前导非字母。

【讨论】:

  • 为什么我们需要ungetc(ch, inf);ch 是一个非字母字符,无论如何都会在下次调用 AdamRead 时被丢弃。
  • @ajay ungetc(ch, inf) 在这里很有用,如果在 AdamRead() 之后调用 不同的 函数,该函数期望开始扫描非字母。
  • @ajay 还有ch 可能是一封信。由于n 变为 0,while 循环可能已经停止。OP 的函数定义是开放的,涉及缓冲区填满时的操作。通过放回这个潜在的字母,它就有机会在下一个函数调用时被读取。
  • 必须先调用扫描非字母的函数,然后再调用AdamRead,否则将从流中删除前导的非字母。此外,在ch 是一个字母的情况下,如果令牌的长度等于或大于n,则令牌将被分解为2 或更多可能不需要的部分.我认为n 应该比最大令牌长度大一。
【解决方案2】:

除了评论中提到的方法之外,还有另一种方法。我不知道它是否更好。您可以使用fgets 从文件中读取行,然后使用strtok_r POSIX 函数标记该行。在这里,r 表示该函数是可重入的,这使其成为线程安全的。但是,您必须知道文件中一行的最大长度。

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

#define MAX_LEN 100

// in main

char line[MAX_LEN];
char *token;
const char *delim = "!@#$%^&*";  // all special characters
char *saveptr;       // for strtok_r 
FILE *fp = fopen("myfile.txt", "r");

while(fgets(line, MAX_LEN, fp) != NULL) {
    for(; ; line = NULL) {
        token = strtok_r(line, delim, &saveptr);
        if(token == NULL)
            break;
        else {
            // token is a string.
            // process it
        }   
    }    
}

fclose(fp);

strtok_r 修改了它的第一个参数line,因此如果需要,您应该保留一份副本以用于其他目的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-18
    • 2011-05-12
    • 1970-01-01
    • 2012-05-22
    相关资源
    最近更新 更多