在说明该算法前,首先需要了解一下两个shell命令:sort与uniq
linux的sort 命令是一种对文件排序的工具,例 sort file 即可对file进行排序,但是sort命令默认是输出终端的,若想重定向至文件,用-o选项
如sort file -o newfile,即对 file进行排序,并将排序结果输出至newfile。
linux的uniq 命令用于去除文本文件的重复行,其实sort 的-u选项也可以实现去重操作,只不过二者还是有区别的。
例如某文本文件file内容如下
aaaaa
aaaaa
bbbbb
aaaaa
------------------------------------------------------------
则用uniq进行去重:uniq file
结果为 aaaaa
bbbbb
aaaaa
若用sort -u 进行去重:sort -u file
结果为 aaaaa
bbbbb
可见sort -u 进行去重是先排序再去重;而uniq 仅是去除相邻行的重复;
但是对于文本内容如下的文件
aaaaa
bbbbb
aaaaa
bbbbb
......
又该如何去重呢?很显然上述案例文本是存在重复序列的。举一反三,这种重复序列最少以两行为一个重复单元,甚至以若干行为一个重复单元,可能这样一说感觉很笼统,
但是上述情况也是我在工作中切实遇到的问题,项目要求:仅去除相邻文本序列的若干次重复,不去除非相邻文本序列的若干次重复,而且还应保持原有顺序,不能先排序再去重,这样破坏了文件文本原有的顺序,言外之意,不能应用sort -u,而单纯的uniq又不能解决问题,因此需要重够新的算法解决问题。
去除相邻文本序列的若干次重复
--------------------------------------------------------------------------------
aaaaa aaaaa
bbbbb bbbbb
cccccc cccccc
aaaaa 去重后结果 --->
bbbbb
cccccc
不去除非相邻文本序列的若干次重复
-----------------------------------------------------------------------------------
aaaaa aaaaa
bbbbb bbbbb
cccccc cccccc
ddddd 去重后结果 ---> ddddd
aaaaa aaaaa
bbbbb bbbbb
cccccc cccccc
----------------------------------------------------------------------------------------------------------------------
而且处理的文本文件大小不固定,少则几行,多则上千万行,所以要求算法的处理效率达到一定的高度,原则上处理文件的时间越短越好。
谈及时间问题不得不说的是计算机科学的两个概念:时间和空间,这两者只能兼顾其一,而不可兼得,在解决实际问题的时候依情况而定,有时需要“以时间换取空间”,有时需要“以空间换取时间”。上述问题的解决中,我们应用的思想是“以空间换取时间”,将文本文件映射用mmap到内存中,一般文件都很大,普遍几百万行,此时便体现出了“空间换取时间”的思想,然后逐行读取文件,但是我们在读取文件的时候,应该额外考虑文件中看不到的字符,打开文本文件直观展示的是一行行文本,但是每行文本是以'\n'结束的还是以'\0'结束的,我们都应该明确,在实际项目中我们遇到的是每行以换行符‘\n’结束,而字符串的是以'\0'作为字符串的结束标志,如果我们需要一行一行的进行处理,逐行读取文本之后还需要在每行最后一个字符后面添加上'\0'。
将文件映射到内存中后,我们便拿到了文件映射到内存中的首地址,逐行读取文件内容,并将每行文本的起始地址 line_start 和字符个数 count存入链表,
这样一来,链表的节点数与文件行数便相对应。上述思想仅仅是简单的处理了文本内容,并没有涉及到文本序列的去重,此时需要提到哈希算法的算法思想,哈希算法是根据散列函数将关键字映射到数组中某个单元,当然也可能一个单元对应于多个关键字,即多个关键字对应于同一个单元,这样说来,我们脑动大开,有没有一种算法可以将文本文件每行内容映射到一个位置?那么对于两行文本是否相同的判断即可转化为这两行文本映射的位置是否相同,这样便极大简化了文件的对比操作。于是我们想到了二叉树结构,二叉树的插入和查找对于即时插入即时平衡的的二叉树有很高的执行效率,我们在逐行读取文本之后,将每行文本的起始地址和字符个数记录在链表中,然后我们将每行文本添加'\0'(形成完整的字符串的格式)后插入二叉树,然后将每行文本在二叉树中的地址作为链表元素再记录在链表中,若两行文本相同,则在二叉树中的位置便相同,二者在二叉树中的地址相同。因为涉及到序列对比,为方便链表向前查找,我们应用双向链表。链表处理完后,接下来我们只对链表进行操作,因为链表包含了我们需要的所有内容,一个链表节点便代表了一行文本,为删除相邻行的若干次重复序列,我们需要从头开始逐行处理,即从链表第一个节点开始,一个节点一个节点的处理,因为涉及到序列对比,因此我们规定相邻序列靠前的为“上序列”,靠后的为“下序列”,每个序列中至少对应一行文本,当前行作为下序列的起始行,根据要对比的序列大小,向前查找上序列的起始行,然后逐行依次对比上序列与下序列文本(根据每行文本在二叉树中的地址),若对比后上下序列相同,便通过删除链表节点达到删除上序列若干行文本的目的,不相同则不删除。
此外,还应考虑到条件不满足时的解决方法,当根据当前行向前查找上序列的起始行时,发现不满足条件时,继续下一行。
若当前行为第2行时,向前3行查找上序列起始行,发现向前查找上2行便已经达到了链表头,因此应停止向前行数 >3 行的起始行查找,并将当前行变为第3行,继续以2行为一个序列、以3行为一个序列....的起始行查找;
若文本一共有1000行,此时当前行为第999行,第999行下面还有1行,而上面还有998行,此时若以2行为一个序列进行对比还满足条件,但是当以3行为一个序列进行对比时
上序列起始行查找成功,即上序列起始行为第996行,此时仍可进行对比,即996与998对比、997于1000对比,998再对比即与链表头对比,显然不应该再进行对比,因此上序列的起始行查找至关重要,对比前下序列对比行判断是否为链表头也至关重要,当对比前发现下序列对比行为head,应停止对比。
通过上述处理后,便删除了链表中相邻重复序列的若干节点,遍历链表重新输出即为去除相邻行若干次重复的文本内容。
代码如下:
----------------------------------------------------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define handle_error(msg) do{perror(msg);exit(EXIT_FAILURE);}while(0)
#define MAXSIZE 1024
#define MAX_COMPARE_LINE 100
struct list
{
char *addr_line; //记录每行文本起始行
int count_char; //记录每行文本字符个数
char *addr_tree; //记录每行文本在二叉树中的地址
struct list *prev;
struct list *next;
};
struct tree
{
char text[MAXSIZE];
int height;
struct tree *left;
struct tree *right;
};
/* 初始化有头双向链表 */
{
struct list *head;
head = (struct list *)malloc(sizeof(struct list));
if(head == NULL)
handle_error("init_list()->malloc");
head->addr_line = NULL;
head->count_char = 0;
head->addr_tree = NULL;
head->prev = head;
head->next = head;
return head;
}
int height(struct tree *root)
{
if(root == NULL)
return -1;
else
return root->height;
}
int max(int a, int b)
{
if(a >= b)
return a;
return b;
}
/* 左单旋转 */
struct tree *singlerotatewithleft(struct tree *p2)
{struct tree *p1 = p2->left;
p2->left = p1->right;
p1->right = p2;
p2->height = max(height(p2->left), height(p2->right)) + 1;
p1->height = max(height(p1->left), height(p1->right)) + 1;
return p1;
}
/* 右单旋转 */
{
struct tree *p1 = p2->right;
p2->right = p1->left;
p1->left = p2;
p2->height = max(height(p2->left), height(p2->right)) + 1;
p1->height = max(height(p1->left), height(p1->right)) + 1;
return p1;
}
/* 左双旋转 */
{
p->left = singlerotatewithright(p->left);
return singlerotatewithleft(p);
}
struct tree *doublerotatewithright(struct tree *p)
{
p->right = singlerotatewithleft(p->right);
return singlerotatewithright(p);
}
/* 二叉树插入函数 */
struct tree *insert_tree(struct tree *root, char *line_start, int count, struct list *last)
{int result;
char string[count+1];
strncpy(string, line_start, count);
string[count] = '\0';
//printf("string = %s\troot->text = %s\n", string, root->text);
if(root == NULL)
{
root = (struct tree *)malloc(sizeof(struct tree));
if(root == NULL)
handle_error("insert_tree()->malloc root");
strcpy(root->text, string);
//printf("root->text = %s\n", root->text);
root->height = 0;
root->left = NULL;
root->right = NULL;
last->addr_tree = root;
}
else if((result = strcmp(string, root->text)) == 0)
{
last->addr_tree = root;//node's address at structure of tree
}
else if(result < 0)
{
root->left = insert_tree(root->left, line_start, count, last);
if(height(root->left) - height(root->right) == 2)
{
if(strcmp(string, root->left->text) < 0)
root = singlerotatewithleft(root);
else
root = doublerotatewithleft(root);
}
}
else
{
root->right = insert_tree(root->right, line_start, count, last);
if(height(root->right) - height(root->left) == 2)
{
if(strcmp(string, root->right->text) > 0)
root = singlerotatewithright(root);
else
root = doublerotatewithright(root);
}
}
root->height = max(height(root->left) , height(root->right)) + 1;
return root;
}
void print_cur_line(char *addr, int count)
{
char p[count+1];
strncpy(p, addr, count);
p[count] = '\0';
//printf("------->> %s\n", p);;
}
/* 链表插入函数 */
{
struct list *new;
struct list *p = NULL;
new = (struct list *)malloc(sizeof(struct list));
if(new == NULL)
handle_error("insert_list()->malloc");
new->addr_line = line_start;
new->count_char = count;
new->next = head;
new->prev = head->prev;
new->prev->next = new;
new->next->prev = new;
}
int open_argv(char argv[])
{
int fd;
fd = open(argv, O_RDONLY);
if(fd == -1)
handle_error("open_argv()->open");
return fd;
}
void print_list(struct list *head)
{
if(!head) return;
struct list *p = head->next;
while(p != head)
{
printf("%p\n", p->addr_line);
// printf("%d\n", p->count_char);
// printf("%p\n", p->addr_tree);
p = p->next;
}
}
void draw_tree(struct tree *root, int level)
{
int i;
if(root == NULL)
return ;
draw_tree(root->right, level+1);
for(i = 0;i < level; i++)
printf(" ");
printf("%s\n", root->text);
draw_tree(root->left, level+1);
}
/* 查找上序列起始行 */
struct list *find_start_line(struct list *head, struct list *current_line, int number)
{
int var = 1;
struct list *prev_start = current_line;
for(var == 1; var <= number; var++)
{
prev_start = prev_start->prev;
if(prev_start == head)
{
//printf("prev_start == head\n");
break;
}
}
return prev_start;
}
/*
* prev_start = find_start_line();* next_start = current_line;
*
* prev_start == head;
* return 0;
* prev and next compare number counts
* if(same) return 1;
* else return 2;
*
* if return 0 or return 2,continue next line
*
* */
int compare_prev_next(struct list *head, struct list *current_line, int number)
{
int n = 1;
int same_count = 0;
struct list *prev_start = find_start_line(head, current_line, number);
//printf("------prev_start = %p\n", prev_start->addr_line);
struct list *next_start = current_line;
if(prev_start == head)
return 0;// prev_start line not found
for(n = 1; n <= number; n++)
{
if(prev_start->addr_tree == next_start->addr_tree)
{
same_count++;
prev_start = prev_start->next;
next_start = next_start->next;
if(next_start == head)
break; //对比前判断下序列当前对比节点是否为head
}
else
break;
}
if(same_count == number)
return 1;
else
return 2;//compare compeleted, but not same
}
/* 上下序列对比相同时删除上序列若干行 */
void delete_line_n(struct list *current_line, int number)
{
int var = 1;
struct list *p1 = current_line->prev;
struct list *p2 = NULL;
if(!current_line) return;
if(number < 1) return;
for(var = 1; var <= number; var++)
{
p2 = p1->prev;
p1->prev->next = p1->next;
p1->next->prev = p1->prev;
//fprintf(stderr, "----------->>> free %p\n", p1->addr_line);
free(p1);
p1 = p2;
}
}
/* 上序列、下序列对比函数 */
{
struct list *current_line = NULL;
int number;
int result = -1;
for(number = 1; number <= MAX_COMPARE_LINE; number++)
{
current_line = head;
while(current_line->next != head)
{
current_line = current_line->next;
//printf("current_line = %p\tnumber = %d\n", current_line->addr_line, number);
if((result = compare_prev_next(head, current_line, number)) == 1) // same
delete_line_n(current_line , number);
else // not same or prev start line not found
continue;
}
}
}
/* 链表操作完成后重新遍历链表 */
{
char new_file[512];
int fd;
struct list *p = head->next;
sprintf(new_file, "%s__new", file);
fd = open(new_file, O_WRONLY | O_TRUNC | O_CREAT, 0644);
if(fd == -1)
handle_error("travel_new_list()->open");
while(p != head)
{
write(fd, p->addr_line, p->count_char);
write(fd, "\n", 1);
p = p->next;
}
}
/* 销毁链表 */
void destroy_list(struct list *head)
{if(!head) return;
struct list *p1 = head->next;
struct list *p2 = NULL;
while(p1 != head)
{
p2 = p1->next;
free(p1);
p1 = p2;
}
free(head);
}
/* 销毁二叉树 */
{
if(root == NULL)
return;
destroy_tree(root->left);
destroy_tree(root->right);
free(root);
}
int main(int argc, char *argv[])
{
if(argc < 2)
{
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
exit(EXIT_FAILURE);
}
if(access(argv[1], F_OK) != 0)
{
fprintf(stderr, "%s not exist\n", argv[1]);
exit(EXIT_FAILURE);
}
int fd = 0;
struct stat statres;
char *addr;
fd = open_argv(argv[1]);
if(stat(argv[1], &statres) == -1)
handle_error("main()->stat");
addr = mmap(NULL, statres.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if(addr == MAP_FAILED)
handle_error("main()->mmap");
printf("=======-------------->>> addr = %p\n", addr);
close(fd);
//puts(addr);
int step = 0;
char *line_start = addr;
int count = 1;
struct list *head = init_list();
struct tree *root = NULL;
while(1)
{
addr++;
step++;
if(step == statres.st_size)
break;
if(*addr != '\n')
count++;
else
{
insert_list(head, line_start, count);
root = insert_tree(root, line_start, count, head->prev);
//print_cur_line(line_start, count);
if(*(addr+1))
{
line_start = addr+1;
count = 0;
}
}
}
// print_list(head);
// draw_tree(root, 0);
compare_line_n(head);
travel_new_list(head, argv[1]);
destroy_tree(root);
destroy_list(head);
munmap(addr, statres.st_size);
return 0;
}