Topk引发的一些简单的思考
软件工程课程的一个题目:写一个程序,分析一个文本文件中各个词出现的频率,并且把频率最高的10个词打印出来。文本文件大约是30KB~300KB大小。
首先说一下这边的具体的实现都是在linux上实现的。没有大型IDE的性能检测。其实30KB还不是瞬间的事情,基于语言和一些简单的策略。所以在后面可能会尝试考虑增加文件大小到G级,然后发生的东西。我只能是从简单的原理研究。至于调试我只能写个简单的shell来自己检测一下。嗯,就这样吧。能力还是有点小白,特别是看了v_JULY_v 的海量数据处理http://blog.csdn.net/v_july_v/article/details/6279498之后。
说回题目。首先这个题目的两个重点分别是分词和处理词语。第一个分词的话。高级一点的语言就可以用正则表达式来处理,像这样"\w+(?:[-']\w+)*|'|[-.(]+|\S\w*" 我也是从网上学习的。然后我比较侧重的就是处理词语。首先是分离词语后存储的数据结构选取方面。一个词语对应出现的次数,第一个想到的当然是数组。不过对于像java和c语言来说没有关联数组这种数据类型,所以只能利用hash table来做这个事情,毕竟查找确实是O(1)。后面也会说用像PHP写的关联数组版,因为PHP底层实现就是用的Hash table,还是很久之前大神告诉过我的一句话:语言只是工具,这些东西用PHP,Python或者Go实现起来更少代码,更快,不一定非得C/C++才能写出来。
先上c++版(因为我用STL方便点)
#include <string>
#include <fstream>
#include <iostream>
#include <map>
#include <ext/hash_map>
#include <algorithm>
#include <vector> using namespace std;
using namespace __gnu_cxx; struct str_hash
{
size_t operator()(const string &s)const
{
return __stl_hash_string(s.c_str());
}
}
struct str_compare
{
int operator()(const string &a,const string &b)const
{
return(a==b);
}
}
typedef hash_map<string,string,str_hash,str_compare>::value_type valType;
typedef pair<string,int> PAIR; int CmpByValue(PAIR const & a,PAIR const & b){
return a.second > b.second;
} int main(int argc,char **argv)
{
ifstream fin("file.txt");
string s;
int num;
map<string,int> Imap;
while(fin >> s)
{
map<string,int>::iterator it=Imap.find(s);
if(it == Imap.end()){
//cout << s << endl;
Imap.insert(valType(s,));
}else{
num = Imap[s];
Imap[s] = num+;
}
}
vector<PAIR> SortList(Imap.begin(),Imap.end());
sort(SortList.begin(),SortList.end(),CmpByValue);
//cout << SortList[0];
for(int i = ;i != SortList.size();i++){
cout << SortList[i].second << endl;
}
return ;
}
运行结果:
通过G++编译,编译HASH_MAP的时候需要using namespace __gnu_cxx哦。g++还得加上一个参数。忘了,不然会报错,不过可以运行。
其实上面的程序重点便是hash map的key value存储方式。当然以这种方式排序其实也是一种消耗,这里使用的将数据放到一个vector里面,利用vector容器的排序进行按value的排序。
然后我又敲了一下java版。首先先上代码:
package topk; import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern; public class topk {
public static void main(String[] args){
try{
BufferedReader reader = new BufferedReader(new FileReader("F:/java/topk/src/file.txt"));
StringBuffer buffer = new StringBuffer();
String line = null;
while((line = reader.readLine()) != null){
buffer.append(line);
}
reader.close();
Pattern expression = Pattern.compile("[a-zA-Z]+");
String string = buffer.toString();
Matcher matcher = expression.matcher(string);
Map<String,Integer> map = new HashMap<String,Integer>();
String word = "";
int times = 0;
while(matcher.find()){
word = matcher.group();
if(map.containsKey(word)){
times = map.get(word);
map.put(word, times+1);
}else{
map.put(word, 1);
}
}
List<Map.Entry<String,Integer>> infoIds = new ArrayList<Map.Entry<String,Integer>>(map.entrySet()); Collections.sort(infoIds,new Comparator<Map.Entry<String,Integer>>(){
public int compare(Map.Entry<String, Integer> o1,Map.Entry<String,Integer> o2){
return (o1.getValue().compareTo(o2.getValue()));
}
}); int last = infoIds.size()-1;
for(int i= last;i > last-10;i--){
String id = infoIds.get(i).toString();
System.out.println(id);
} }catch(FileNotFoundException e){
System.out.println("文件未找到");
}catch(IOException e){
System.out.println("du");
}
}
}
运行结果:
在写java中知道了BufferedReader 这个比较强大的文件输入流函数。其实就是把文件提取到缓冲区里面,然后依次从缓冲区里面读取。缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。这中间就有一个问题:当一个文件超出缓冲区大小的时候,是如何运作的。这里就是对缓冲区概念的熟悉。因为我可能还没学过c++中有输入缓冲区的库。所以得自己了解有机会去实现一个缓冲区去解决部分这样的问题。
缓冲区 分为三种类型:全缓冲、行缓冲和不带缓冲。
1、全缓冲
在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。
2、行缓冲
在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是键盘输入数据。
3、不带缓冲
也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。
缓冲区的刷新
下列情况会引发缓冲区的刷新:
1、缓冲区满时;
2、执行flush语句;
3、执行endl语句;
4、关闭文件。
可见,缓冲区满或关闭文件时都会刷新缓冲区,进行真正的I/O操作。另外,在C++中,我们可以使用flush函数来刷新缓冲区(执行I/O操作并清空缓冲区)。
当然使用当缓冲区满时再向下读文件刷新缓冲区的时候可能这个方法会是比较好的。这里我还得研究,后面应该会发文说说这方面的学习心得。
但是我发现当一个实在是太大的文件的时候,单纯使用这种做法的意义其实不大。因为大可以切割文件然后再进行hash,还是v_JULY_v 大神的方法。而且我有一个想法。就是分割文件之后可以多线程处理啊。。起线程来处理对应的一部分文章再合并起来。(YY有待实现,看过这方面的面试题)
然后,我们可以使用再高级一点的语言来实现这个题目:(PHP版)
$content = file_get_contents("file.txt");
$content = explode(" ",$content);
//preg_match_all($pattern,$content,$matches);
$list = array();
foreach($content as $row){
if(array_key_exists($row,$list)){
$list[$row]++;
}else{
$list[$row] = 1;
}
}
arsort($list);
运行结果:
看似很简单的代码,其实内里隐藏了很多有趣的东西。像PHP中的关联数组这种数据结构确实在开发的过程中会省很多事情。不过我们还是来研究一下他实现的一些原理性的东西。
HashTable是zend的核心数据结构,在PHP里面几乎并用来实现所有常见功能,我们知道的PHP数组即是其典型应用,此外,在zend内部,如函数符号表、全局变量等也都是基于hash table来实现。
PHP的hash table具有如下特点:
●支持典型的key->value查询
●可以当做数组使用
●添加、删除节点是O(1)复杂度
●key支持混合类型:同时存在关联数组合索引数组
●Value支持混合类型:array (“string”,2332)
●支持线性遍历:如foreach
从上面的描述中可以看到其实也是hash table这个强大的数据结构。不过Zend hash table实现了典型的hash表散列结构,同时通过附加一个双向链表,提供了正向、反向遍历数组的功能。这里也不展开讲,因为涉及到PHP存储变量的数据结构。从这里我才深深的感受到了其实语言的魅力真的很大。虽然说简单几行可以实现的功能变成c存储起来确实如此的麻烦。当然对于上层开发来说,这个的确是可以加快开发的速度。(扯远了)
其实我还从这个题目中想着去研究分析文本比较高效的shell啦。 awk '{split("'${b}'", array, " ");print array[1]}' 测试首选。
还有js版:
var file = require("fs");
file.readFile('file.txt','utf-8',function(err,data){
if(err){
return console.log(err);
}else{
var arr = data.split(" "||","||"?"||".");
var ArrLen=arr.length;
var object={}; for(var i=0;i<ArrLen;i++){
var val=arr[i];
if(val in object)
object[val]++;
else
object[val] = 1;
}
var Arrsort=[];
for(i in object){
Arrsort.push(object[i]);
}
Arrsort.sort(function(n1,n2){
return n2-n1;
}) }
})
运行结果:
对于性能调试,只是写个小shell测试了一下。没有直接在代码里面加时间。因为代码运行在用户态之外的时间呢。其实是我没有用IDE里比较方便的调试。
完。
Topk引发的一些简单的思考的更多相关文章
- 由异常:Repeated column in mapping for entity/should be mapped with insert="false" update="false 引发对jpa关联的思考
由异常:Repeated column in mapping for entity/should be mapped with insert="false" update=&quo ...
- try catch引发的性能优化深度思考
关键代码拆解成如下图所示(无关部分已省略): 起初我认为可能是这个 getRowDataItemNumberFormat 函数里面某些方法执行太慢,从 formatData.replace 到 une ...
- php各种设计模式简单实践思考
前言 我一直觉得什么框架,版本,甚至语言对于一个coder来说真的不算什么,掌握一个特别高大上的一个框架或者是一个新的,少众的语言真的不算什么,因为你可以,我要花时间也可以,大家都是这样的.所以基本的 ...
- 一个神秘现象引发对beego框架的思考
小强最近在项目中遇到了一个很奇怪的问题:在整改日志规范时,为了避免影响现有的代码结构以及改动尽可能小的前提下,在调用记日志的SDK处将某一个字段值首字母改为大写,代码示例如下: fmt.Println ...
- 由mv命令引发的对inode的思考
一场机器迁移引起的思考 最近团队一台机器老化了,准备做全量迁移,一不小心,就把100多个G的/data目录放到了新机器的/data/data目录下,上愁了,怎么削减一层data目录呢?难倒像Windo ...
- 由Menu小项目所引发的对软件工程的思考
学习了孟老师的这几节课程,我学习了如何搭建一个简单的命令行menu小程序,从最简单的程序开始,一步步的根据软件工程的一般规律,进行逐步开发.完善,最终实现了一个比较通用的menu程序,可以让别的开发者 ...
- 由”二进制里不能有3“引发的对parseInt的思考
看到一道面试题,["1", "2", "3"].map(parseInt) 答案是多少? 心生好奇,做做看,发现卡住,没什么头绪.首先对pa ...
- 特殊汉字“𣸭”引发的对于字符集的思考;mysql字符集;sqlalchemy字符集设置;客户端字符集设置;
字符集.字符序的概念与联系 在数据的存储上,MySQL提供了不同的字符集支持.而在数据的对比操作上,则提供了不同的字符序支持. MySQL提供了不同级别的设置,包括server级.database级. ...
- Java的字符串操作一些简单的思考
Java的字符串操作 1 .1不可变的String String对象事不可变的,String类中的每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符 ...
随机推荐
- 20个高级Java面试题
这是一个高级Java面试系列题中的第一部分.这一部分论述了可变参数,断言,垃圾回收,初始化器,令牌化,日期,日历等等Java核心问题. 程序员面试指南:https://www.youtube.com/ ...
- .NET 基础 一步步 一幕幕[面向对象之new、this关键字]
经常会有人问:小伙子,有没有对象啊,要不要大叔我帮你介绍一个啊,小伙子会说:大叔,不用我自己new一个就好了.(PS:活该你没有对象) 上边当然是一个段子咯,程序员那会没有对象,自己new一个就有了啊 ...
- Java学习的随笔(一)对象概念、this指针、权限修饰符
最近在看<Java编程思想>,下面按照最近看书的顺序梳理一下心得,由于是初次学习,大部分心得是摘抄自书中: 1. Java中,每个变量都是一个对象. 在创建时首先在内存的堆栈中创建一个该对 ...
- HDOJ 5073 Galaxy 数学 贪心
贪心: 保存连续的n-k个数,求最小的一段方差... .预处理O1算期望. .. Galaxy Time Limit: 2000/1000 MS (Java/Others) Memory Lim ...
- MatLab计算图像圆度
本文所述方法可以检测同一图像中的多个圆形(准确的说,应该是闭合图像). 在Matlab2010a中可以实现. 附录效果图: %颗粒圆度 clear;close all; %% %读取源图像 I = i ...
- Android之开发常用颜色
Android开发中常常要用一些个性化的颜色,然而茫茫的RBG颜色对照表,往往给人眼花缭乱的感觉,更别说从中轻易选出一两种比较满意的颜色,下面我就总结一下开发中常用到的比较绚丽的颜色,都是有名有姓的哦 ...
- cocos2dx c++ 在mac下写的中文凝视,在win32下编译时不通过
今天遇到个奇怪的问题,在mac下写的程序,加的中文凝视,编译没有问题,可是在win32下(使用的时vs2012, win7 64bit 系统)编译就总是报错 最后在中文凝视后 加一个空格,或者 换行, ...
- 获取context path或者basePath
转自:http://hexudonghot.blog.163.com/blog/static/532043422012112264411234/ 在jsp中获取context path或者basePa ...
- Java EE的十三种核心技术
1. JDBC: Java Database Connectivity 2. JNDI: Java Name and Directory Interface 3. EJB: Enterprise Ja ...
- 完美解决Android完全退出程序(转)
背景:假说有两个Activity, Activity1和Activity2, 1跳转到2,如果要在2退出程序,一般网上比较常见的说法是用 System.exit(0) 或是 android.os.Pr ...