1. 程式人生 > >字典樹應用——詞頻統計 (C++實現)

字典樹應用——詞頻統計 (C++實現)

來學校交流學習的第一個正式的小專案作業就是軟體工程老師所提出的詞頻統計了,具體要求如下。

要求:

       寫一個程式,分析一個文字檔案中各個詞出現的頻率,並且把頻率最高的10個詞打印出來。文字檔案大約是30KB~300KB大小。

解決思路:

 剛看到這個問題,我腦海浮現的問題就是如何儲存如此大量的資料呢,然後如何進行有效的統計。

      我在想解決方案時,也有參考以前學姐學長們的例子,發現大多數好像都是用陣列或者是連結串列來實現。

      雖然老師所要求的文章大小並不算大,但是,推而廣之考慮到檔案大小更大的文章呢。所以單詞量不是像原來所讀取的那樣少,若簡單用陣列連結串列實現的話,每匹配一個詞就要把所有的單詞遍歷一遍顯然是效率不高的,且讀取以後要遍歷多次進行單詞的匹配,以便統計相同單詞的個數。所以就考慮到一個效率的問題,恰好最近看到有關海量資料處理的相關文章,所以這裡我首先就想到用

字典樹來儲存資料。

       字典樹我就不詳細介紹了,前一篇博文有詳細介紹。Trie的核心思想是空間換時間,利用字串的公共字首來降低查詢時間的開銷以達到提高效率的目的。Trie的典型應用是統計和排序大量的字串,所以經常被搜尋引擎系統用於文字詞頻統計。

       (以上僅是個人見解,有任何異議錯漏歡迎提出,謝謝。)

       整個開發時間歷時兩天左右,一天時間來學習字典樹相關知識,另一天來實現該程式程式碼。

實現程式碼:

1.字典樹的相關定義:

(dic.h)

using namespace std ;

class DicNode
{
public:
	DicNode() ;
	virtual ~DicNode() ;
public:
	int			count	;
	bool		word	;
	DicNode*	next[26] ;
} ;

class Dic : public AddWord
{
public:
	Dic() ;
	virtual ~Dic() ;
    
private:
	DicNode*	head ;
    
public:
	bool	addFilter(Filter* filter) ;
	bool	addWord	(string word) ;
	bool	travel(WordSord* pool) ;
    
private:
	void	travel_inside(DicNode* p, string data, WordSord* pool) ;
	vector<Filter*> filter_array ;
    
} ;

(dic.cpp)
#include "dic.h"

DicNode::DicNode()
{
	this->count = 0 ;
	this->word = false ;
	for (int i = 0 ; i < 26 ; ++i)
	{
		this->next[i] = NULL ;
	}
}

DicNode::~DicNode()
{
	for (int i = 0 ; i < 26 ; ++i)
	{
		if (this->next[i])
		{
			delete next[i] ;
			this->next[i] = NULL ;
		}
	}
}


Dic::Dic()
{
	this->head = new DicNode() ;
}

Dic::~Dic()
{
	if (this->head)
	{
		delete this->head ;
		this->head = NULL ;
	}
}

bool Dic::addFilter(Filter* filter)
{
	if (NULL == filter)
	{
		return false ;
	}
	this->filter_array.push_back(filter) ;
	return true ;
}

bool Dic::addWord(std::string word)
{
	DicNode* p = this->head ;
	for (int i = 0 ; i < (int)word.length() ; ++i)
	{
		// ≈–∂œ «≤ª «“™±ª»•≥˝µÙ
		for (int j = 0 ; j <this->filter_array.size(); ++j)
		{
			if (this->filter_array[j]->existWord(word))
			{
				return true ;
			}
		}
        
		// ◊™≥…–°–¥
		if (word[i] >= 'A' && word[i]<= 'Z')
		{
			word[i] = word[i] - 'A' + 'a' ;
		}
        
		int index = word[i] - 'a' ;
		
		if (NULL == p->next[index])
		{
			p->next[index] = new DicNode() ;
		}
		p = p->next[index] ;
        
		if (i == word.length()-1)
		{
			p->word = true ;
			p->count++ ;
		}
	}
	return true ;
}

bool Dic::travel(WordSord* pool)
{
	if (NULL == pool)
	{
		return false ;
	}
    
	string data ;
	this->travel_inside(this->head, data, pool) ;
	return true ;
}

void Dic::travel_inside(DicNode* p, string data, WordSord* pool)
{
	if (NULL == p)
	{
		return ;
	}
    
	if (p->word)
	{
		//cout <<data <<" " << p->count<< endl ;
		pool->addString(data, p->count) ;
	}
    
	for (int i = 0 ; i < 26 ; ++i)
	{
		if (p->next[i])
		{
			string temp = data + char('a' + i) ;
			travel_inside(p->next[i], temp, pool) ;
		}
	}
}
2.讀取檔案內容

關鍵程式碼:

#include "file.h"
#include <fstream>
using namespace std ;

bool File::readFromFile(string file, AddWord* object)
{
	if (NULL == object)
	{
		return false ;
	}
	string lines ;
	ifstream in(file.c_str()) ;
	if (in)
	{
		string word = "" ;
		while (getline (in, lines))
		{
			
			for (int i = 0 ; i < (int)lines.length() ; ++i)
			{
				if ((lines[i] >= 'a' && lines[i] <= 'z')
					|| (lines[i] >= 'A' && lines[i] <= 'Z'))
				{
					word += lines[i] ;
				}
				else
				{
					if (word != "")
					{
						object->addWord(word) ;
					}
					word = "" ;
				}
			}
            
			if (word != "")
			{
				object->addWord(word) ;
				word = "" ;
			}
		}
        
		if (word != "")
		{
			object->addWord(word) ;
			word = "" ;
		}
	}
	else
	{
		cout<< "open file" << file << " failed..." << endl ;
	}
	return true ;
}

3.字典樹排序及輸出

關鍵程式碼:

#include "word_sord.h"
using namespace std;

bool operator < (const word_node& l, const word_node& r)
{
	if (l.count <= r.count)
	{
		return false ;
	}
	return true ;
}

WordSord::WordSord(int _size)
{
	this->_size = _size ;
}

bool WordSord::addString(string word, int num)
{
	if (this->_size <= 0)
	{
		return true ;
	}
    
	word_node _node ;
	_node.count = num ;
	_node.word = word ;
    
	if ((int)this->array.size() < _size)
	{
		this->array.push_back(_node) ;
	}
	else
	{
		int min_index = 0 ;
		int min_data = array[0].count ;
		for (int i = 0 ; i < (int)array.size() ; ++i)
		{
			if (min_data > array[i].count)
			{
				min_data = array[i].count ;
				min_index = i ;
			}
		}
        
		if (num > min_data)
		{
			array[min_index] = _node ;
		}
	}
	return true ;
}

bool WordSord::print_r()
{
	sort(array.begin(), array.end()) ;
	for (int i = 0 ; i < (int)this->array.size() ; ++i)
	{
		cout<<array[i].word.c_str()<<" "<<array[i].count<<endl ;
	}
	return true ;
}

4.主函式
//
//  main.cpp
//  dictree
//
//  Created by Emily on 14-10-6.
//  Copyright (c) 2014年 Emily. All rights reserved.
//

#include <iostream>
#include "time.h"
#include "file.h"
#include "dic.h"
#include "word_sord.h"
#include <string>
using namespace std ;

int main()
{

    
	// ¥¥Ω®◊÷µ‰ ˜
	Dic dic ;
    clock_t start_time = clock();

	// ∂¡»°Œƒº˛
	File file ;
	file.readFromFile("/Users/emily/Documents/dictree/dictree/1.txt" , &dic) ;
	
	// ¥¥Ω®≈≈–Ú
	WordSord sord(10) ;
    
	// ±È¿˙◊÷µ‰ ˜
	dic.travel(&sord) ;
    
	// ¥Ú”°≥ˆ¿¥ ˝æ›
	sord.print_r() ;
    
	system("pause") ;
    
    clock_t end_time = clock();
    
    cout<<"Running Time is:"<<static_cast<double>(end_time-start_time)/CLOCKS_PER_SEC*1000<<"ms"<<endl;
    cin.get();
	return 0 ;
}

專案檔案圖:

效能分析1(780KB左右):

測試文件大小:


測試結果:


相關效能:




效能分析2(50MB左右):

測試文件大小


測試結果:


相關效能:




專案總結:  整個程式寫下來,收穫頗豐,一方面熟悉瞭解了新的資料結構——字典樹,另一方面也認識到了使用合適的資料結構的重要性。因為這樣會節省大量的程式執行時間和記憶體,使我們的程式更加高效。     所以,我的程式在統計檔案大小偏小的時候,優勢也許不是特別明顯。但是,檔案大小一旦大了,改程式的優勢有明顯體現。 專案改進:     美中不足的是,我未能完成對於結果的篩選,就是過濾掉高頻詞。不過,我有一定的想法,只是實現上還是有點問題,所以沒有把程式碼貼上來。我的想法是,把一系列高頻詞再建一個樹,當我們列印結果前,把統計好的單詞依次再這個高頻詞樹中查詢,查到了就pass掉,沒查到就列印,以此類推列印十個詞出來。