精华在于发现不匹配时向后移动的位数。这里用到“部分匹配值”Partial Match Table的概念,PMT[n]表示字符串前n个字符的最场公共前/后缀的长度。AB的前后缀分别是A和B,前缀不包括最后一个字符,后缀不包括第一个字符。
当第n+1个字符不匹配时,按照PMT[n]向后移动相应位数即可。
例如:ABABC,PMT[2]=0,PMT[3]=1,PMT[4]=2,PMT[5]=0。
如果匹配到:
EEEABABEEEEE
SSSABABC //S表示Space
发现C和E不相同,则向后移动两位,因为C前面有4个字符,4-PMT[4] = 2,即移动两位。
所以算法难点就变成了如何计算PMT表,不过这比KMP算法简单多了~
在写grep命令时简单实现了一下:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<stdarg.h>
/**
* 验证长度为len的字符串pattern的部分匹配值是不是n
*/
int is_partial_match_equal(int n, const char *pattern, size_t len){
for (size_t i = 0, j = len - n; i < len && j < len; ++i, ++j){
if (pattern[i] != pattern[j]){
return 0;
}
}
return 1;
}
/**
* 获取长度是len的字符串pattern的部分匹配值
* notice:穷举法实现
*/
int get_partial_match_cnt(const char *pattern, size_t len){
if (len < 2) return 0;
for (size_t match = len - 1; match >= 1; --match){
if (is_partial_match_equal(match, pattern, len)){
return match;
}
}
return 0;
}
/**
* next数组的长度至少是pattern的长度。
* next[i]表示pattern[i]没有匹配上时应该往后移动的数量。
*/
int get_next(int next[], const char *pattern, size_t len){
if (len < 2){
return 0;
}
for (size_t match_len = 1; match_len <= len; ++match_len){
next[match_len - 1] = match_len - get_partial_match_cnt(pattern, match_len);
}
return 0;
}
#ifdef DEBUG
void debug_print_next_array(int next[], size_t len){
printf("Next array:\n");
for (size_t i = 0; i < len; ++i){
printf("next[%d]=%d\n", (int)i, next[i]);
}
printf("End\n");
}
void debug_log(const char *fmt, ...){
va_list mark;
va_start(mark, fmt);
vprintf(fmt, mark);
va_end(mark);
}
#else
void debug_print_next_array(int next[], size_t len){}
void debug_log(const char *fmt, ...){}
#endif
/**
* KMP算法
* 返回正数表示从str[i]开始,str和pattern匹配上了。
* 返回-1表示无法匹配。
*/
int KMP(const char *str, const char *pattern){
debug_log("KMP(%s, %s)\n", str, pattern);
size_t len = strlen(pattern);
int *next = (int *)malloc(sizeof(int) * len);
get_next(next, pattern, len);
debug_print_next_array(next, len);
unsigned int idx_str = 0, idx_pat = 0;
while (str[idx_str] && pattern[idx_pat]){
debug_log("Compare(%c, %c)\n", str[idx_str], pattern[idx_pat]);
if (str[idx_str] == pattern[idx_pat]){
++idx_str;
++idx_pat;
}
else
{
idx_str += next[idx_pat];
idx_pat = 0;
}
}
free(next);
if (!pattern[idx_pat]){
int pos = (int)(idx_str - idx_pat);
debug_log("Position = %d\n", pos);
return pos;
}else{ // (!str[idx_str]){
debug_log("Pattern not found\n");
return -1;
}
}
/**
* 从文件中读取每一行,进行grep
*/
int grep_file(FILE *file, char *buf, int buf_len, const char *pattern){
while (fgets(buf, buf_len, file) != NULL){
if (KMP(buf, pattern) != -1){
printf("%s", buf);
}
}
return 0;
}
/**
* grep的实现
* 返回1表示出错,0表示正确
*/
#define BUF_LEN 1024 // 假设一行不超过1024个ascii字符
static char buf[BUF_LEN];
int grep(int argc, char *argv[]){
// parse [OPTIONS]
// parse PATTERN
// parse [FILES]
// notice:规定实现的grep不包含options,pattern在argv[1]中。如果没有[FILES]则从stdin中读取数据。
int ret = 0;
if (argc < 2){
return 1;
}
const char *pattern = argv[1];
if (argc == 2){
grep_file(stdin, buf, BUF_LEN, pattern);
}
else
{
// 从文件读取数据
for (int idx_file = 2; idx_file < argc; ++idx_file){
const char *file_name = argv[idx_file];
FILE * file = fopen(file_name, "r");
if (file == NULL){
ret = 1;
continue;
}
grep_file(file, buf, BUF_LEN, pattern);
fclose(file);
}
}
return ret;
}
int main(int argc, char *argv[]){
debug_log("Warning: In debug mode\n");
return grep(argc, argv);
}
/* vim: set expandtab ts=4 sw=4 sts=4 tw=100: */