Resive world

Come world to Record life


  • Home

  • Tags

  • Categories

  • Archives

  • Sitemap

  • Search

C++11学习笔记2

Posted on 2017-04-30 | In C/C++

std::function std::bind

我们知道在C里面有函数指针这么回事,我们用函数指针的目的就是将仿函数作为参数,传递给另外一个函数,并供他调用。但是显然,函数指针那种写法还是相当恶心的,比如:

1
2
3
4
5
6
7
8
9
10
#include<iostream>
int callback(int x){
std::cout<<x<<std::endl;
}
int func(int(*f)(int)){
f(3);
}
int main(){
func(callback);
}

而C++11里提供了std::function这个工具来封装函数指针,上面的写法就可以变成下面的:

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
#include<functional>
int callback(int x){
std::cout<<x<<std::endl;
}
int func(std::function<int(int)>f){
f(3);
}
int main(){
func(callback);
}

用函数模板来体现作为参数的函数类型,看起来更优雅方便。

但是有时候,我们可能希望这个function能够绑定一些参数,那么就需要用到std::bind这个工具来封装旧的function,并返回新的function:

1
2
3
4
5
6
7
8
9
#include<iostream>
#include<functional>
int callback(int x1,int x2){
...
}
int main(){
std::function<int(int)> binded=std::bind(callback,10,std::placeholders::_1);
binded(3);
}

上面的意思是将int(int,int)这个函数封装成了int(10,int)这个函数并起名为binded,std::placeholders::_1表示的就是在调用时传入的第一个参数(这里是3)。非常好理解。

使用std::function和std::bind可以非常装逼的组合多个函数,非常具有模块化的思想,比如下面的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
#include<vector>
#include<functional>
#include<algorithm>
int main(){
int low=1;
int high=10;
std::function<bool(int)> judge=std::bind(
std::logical_and<bool>(),
std::bind(std::greater_equal<int>(),std::placeholders::_1,low),
std::bind(std::greater_equal<int>(),high,std::placeholders::_1)
);
std::vector<int>v={1,2,9,10,11};
int count = std::count_if(v.begin(),v.end(),judge);
std::cout<<count<<std::endl;
}

其实就是实现了统计[low,high]之间的数字个数的功能。。。

lambda表达式

lambda表达式我认为说白了就是一种匿名函数的简写形式,是一种仿函数的语法糖,可以看成是一个std::function对象,没什么稀奇的。

基本写法
基本写法如下:

1
[capture] (params) opt -> ret {body;};

比如下面的函数fun1、fun2就是等价的:

1
2
3
4
5
6
7
int callback(int x,int y){
return x+y;
}
int main(){
std::function<int(int,int)> fun1=callback;
std::function<int(int,int)> fun2=[](int x,int y)->int{return x+y;};
}

捕获外部变量
为了保证良好的封装性,lambda并不能随意访问非参数外的其他变量,这些变量的访问权限交由[]来控制,具体用法如下:

  • []不捕获任何变量
  • [&]按引用捕获所有变量
  • [=]按值捕获所有变量
  • [=,&foo]按值捕获所有变量,并按引用捕获foo变量
  • [foo]按值捕获foo变量,不捕获其他变量
  • [this]捕获当前类中的this指针

比如下面的例子就是按值捕获了外部变量x,如果使用[],那么编译会报错。

1
2
3
4
5
6
#include<iostream>
int main(){
int x=10;
auto f=[=](){std::cout<<x;};
f();
}

需要注意的是,捕获的过程是在函数定义的时候就发生的,在捕获时就会复制一个新的变量,因此我们注意到下面的例子:

1
2
3
4
5
6
7
#include<iostream>
int main(){
int x=10;
auto f=[x](){std::cout<<x;};
x=9;
f(); //the output is 10
}

如果我们需要即时的捕获外部变量,我们就需要按引用捕获。

mutable选项
由于C++的规定,lambda表达式的operator()是const的,也就是说我们按值捕获的值默认是const的,因此我们对按值捕获的值是无法修改的,这就很蛋疼了?这时候就用到了mutable选项来取消const的限制了:

1
2
3
4
5
6
#include<iostream>
int main(){
int x=10;
auto f1=[x]()mutable{x++;};//编译正确
auto f2=[x](){x++;};//编译错误
}

简单应用
有了lambda函数,很多事情就变的简单了,比如之前的统计一定范围内数字个数的程序就可以修改成下面这样:

1
2
3
4
5
6
7
8
9
10
#include<iostream>
#include<vector>
#include<algorithm>
int main(){
int low=1;
int high=10;
std::vector<int>v={1,2,9,10,11};
int count = std::count_if(v.begin(),v.end(),[low,high](int x){return x>=low&&x<=high;});
std::cout<<count<<std::endl;
}

tuple元组

没想到C++里面竟然也有元组,元组这个东西其实就是一个简化的结构体,方便我们将不同的数据进行打包,跟python中的用法类似:
创建元组

1
2
3
4
5
6
7
#include<iostream>
#include<tuple>
#include<string>
int main(){
std::tuple<std::string,int> t1("df",324);
auto t2=std::make_tuple(3432,"dsf");
}

两种方法,一种是构造函数,一种是make_tuple函数。

提取元素

1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>
#include<tuple>
#include<string>
int main(){
std::tuple<std::string,int> t1("df",324);

std::cout<<std::get<1>(t1)<<std::endl;

int d;
std::tie(std::ignore,d)=t1;
std::cout<<d<<std::endl;
}

两种方法,一种是get函数,可以指定提取的元素位置,另一种是std::tie方法,可以一次提取所有的元素也可以提取某一个元素,其他位置用std::ignore占位。
如果忘记了tuple的大小,我们可以使用tuple_size来获得tuple的大小:

1
2
3
4
5
6
7
#include<iostream>
#include<tuple>
int main(){
std::tuple<int,int> t(23,43);

std::cout<<std::tuple_size<decltype(t)>::value;
}

元组连接

1
2
3
4
5
6
7
8
9
#include<iostream>
#include<tuple>
#include<string>
int main(){
std::tuple<std::string,int> t1("df",324);
std::tuple<int,int> t2(23,43);

auto t3=std::tuple_cat(t1,t2);
}

通过tuple_cat来连接两个tuple。

参考资料

深入应用c++11
C++FAQ
cppreference–tuple

C++11学习笔记1

Posted on 2017-04-29 | In C/C++

前言

很久以前就知道C++11对我们课上讲的C++有很多改进的地方,当时也没有细学,最近一个偶然的机会陡然发现原来身边的同学好多都对C++11都颇有心得,推崇备至,回头想想在C++14,甚至C++17都不新鲜的现在,连C++11都不会的话显然有点说不过去了。。。于是呢我就打算利用最近闲着的时间,在补《人民的名义》的间隙顺便学学C++11应该也是极好的。

类型推导

auto关键字

目的

auto关键字不是C++11里诞生的关键字,在这之前,auto代表的意思是“具有全局存储期的局部变量”,限定的是变量的作用域。显然,这玩意并没什么用,于是在C++11里,他就变成了可以自动推导的变量类型。所谓自动推导,并不意味着他就跟javascript里的var一样,颠覆了C++强类型语言的性质,他其实只是在编译的过程中由电脑来推导变量类型,在运行时是不存在auto这种变量的。

用法

我们可以用auto声明其他的变量,但是需要注意下面几点:

  1. auto 声明的变量必须在编译阶段就能识别类型。
  2. auto 不能声明非静态成员变量。
  3. auto 不能用来声明函数参数。
  4. auto 的自动推导于模板的自动推导(Template argument deduction)本质相同。
  5. 当auto不被声明为指针或引用时,auto的推导结果将和初始化表达式的抛弃ref(引用)和cv(const volatile)限定符的类型一致。
  6. 当auto被声明为引用或指针时,auto的推导结果将继承初始化表达式的cv限定符。

不过细想,有时候还是有点绕人的,比如:

1
2
3
4
int x=0;

auto * x1 = &x; //x1 -> int *, auto -> int
auto x2 = &x; //x2 -> int *, auto -> int *

明明写法不一样,但是推导出来的x1和x2的类型竟然是一样的。这看上去有点不可思议,不过换一个看法估计就更清楚一点:

1
2
3
4
5
6
int x=0;
template<typename T>func(T *x1);
func(&x);

template<typename T>func(T x2);
func(&x);

对于x1,T被推导成了int,因此x1被推导成了int ;
对于x2,T被推导成了int
,因此x2也被推导成了int *;

这很好理解,那么auto也就很好理解了。(虽然还是有点怪怪的感觉。。。)

用途

  1. 简化代码。
  2. 判断泛型中的变量类型。

他出现的目的,主要是由于我们懒得写那些巨长无比的类型名,最典型的用处就是在声明迭代器的时候:

1
2
3
4
std::map<double,double> mp;
for(std::map<double,double>::iterator it=mp.begin(),it!=mp.end();it++){
.....
}

这迭代器的声明太傻了,我们不喜欢,我们喜欢下面的样子:

1
2
3
4
std::map<double,double> mp;
for(auto it=mp.begin(),it!=mp.end();it++){
.....
}

当然,有时候我们用这个也的确是因为我们无法判断真正的类型是什么,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A{
static int func(){}
};
class B{
static int* func(){}
};
template<class T>
void work(){
auto val=T::func():
...
}
int main(){
work<A>();
work<B>();
}

我们希望work函数能统一处理所有叫func的静态函数,但是由于返回值不同,所以我们不能直接指定val的类型,但是用auto就能暂时确定val的类型,用于后续处理。

decltype关键字

目的

decltype关键字使用来在编译时推导出一个表达式的类型,通常用这个结果来声明另一个变量。

用法

参照下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct A{
int x;
};
decltype(A.x) t1=2; //t1 -> int

int a=0,b=0;
decltype(a+b) t2=2; //t2 -> int
decltype(a+=b) t3=2; //t3 ->int &

const int &c=a;
decltype(c) t4=3; // t4 -> const int &;

int func(){}
decltype(func()) t5=2; //t5 -> int

需要注意的是当decltype的参数是函数的时候,他并不需要执行这个函数,毕竟这是在编译阶段完成的事情嘛。

用途

虽然decltype在泛型中有一些重要的用法,但是最常用的用一个类型来定义另一个类型:

1
2
typedef decltype(nullptr) nullptr_t;
typedef decltype(sizeof(0)) size_t;

这种重新定义类型的方法更加直观。

返回类型后置语法

目的

有时候我们在用模板函数的时候无法指定函数的返回值,需要通过一些参数的运算才能获得返回值类型,这时候就需要返回类型后置语法来处理了。

用法&用途

比如下面的场景:

1
2
3
4
5
6
7
int& foo(int &i);
float foo(float *i);

template<typename T>
??? func(T& val){
return foo(val);
}

显然,我们不能将???处简单的用T来替换,这看上去就很无解了,这时候就需要auto+decltype来共同处理了:

1
2
3
4
5
6
7
int& foo(int &i);
float foo(float *i);

template<typename T>
auto func(T& val)-> decltype(foo(val)){
return foo(val);
}

模板的优化

右尖号的细节处理

我们知道,在C++11之前,下面的声明是有问题的:

1
2
#include<vector>
vector<vector<int>>v;

编译器会报错,他把俩右尖号看成是位移符了,我们得改成这样:

1
2
#include<vector>
vector<vector<int> >v;

在C++11之后,设计者考虑了这个因素,第一种的写法也能够认了。不过带来一个小bug就是下面的语法行不通了:

1
vector< 16>>2 >v;

虽然没人这样写,但是他编译的时候的确会报错。实际写的时候还是加括号比较稳妥一点。

模板别名

c++11引了using的别名语法,事实上就是typedef的加强版。基本操作如下:

1
2
using uint=unsigned int ;
using func_t=void (*)(int,int);

这不新鲜,新鲜的是using语法甚至定义泛型,这是typedef所做不到的:

1
2
3
4
5
6
7
8
#include<map>
template<typename T>
using mp_str=std::map<string,T>;

void work(){
mp_str<int>mp1;
mp_str<string>mp2;
}

这个用法就像生成一个新模板一样将map的key的类型固定,只露出了值的类型。

基于范围的for循环(Range-for statement)

我们知道在python之类的语言里都支持类似for i in arr这样的for循环语法,这种语法相比显式用下标和迭代器来循环更加简洁。虽然在algorithm头文件里有一个for_each函数做到了类似的效果,但是他仍然需要显式地指定begin()和end(),并不是真正意义上的基于范围。
在C++11里引入了基于范围的for循环,它可以支持迭代任何容器、数组、初始化列表等类型。
具体方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
int a[]={2,3,4};
for(auto i :a){
...
}

std::vector<int>v={1,2,3};
for(auto i : v){
...
}

for(auto i : {1,2,3,4}){
...
}

除了能循环数组,vector这类容器之外,还能迭代map等特殊容器,不过需要注意的是,对于map来说,我们获得的迭代变量是pair类型的,因此我们需要用ite.first、ite.second来获得键和值:

1
2
3
4
5
std::map<int,int>m={{1,2},{3,4}};
for(auto ite : m){
std::cout<< ite.first ;
std::cout<< ite.second ;
}

同时,这个迭代变量还可以声明成引用类型,也就是说我们可以通过他对原数组进行修改:

1
2
3
4
5
6
7
8
9
10
#include<iostream>
int main(){
int a[]={1,2,3};
for(auto &i : a){
i*=2;
}
for(auto i : a){
std::cout<<i<<std::endl;
}
}

输出结果是:

1
2
3
2
4
6

参考资料

auto
基于模板的参数推断
decltype
类型别名
深入应用c++11
C++FAQ

Web 性能优化(3)——探讨 data URI 的性能

Posted on 2017-04-28 | In 实验室

业内常会有 Data URI 的利与弊、用与不用的讨论,即使在最有经验的前端开发者眼中,也会形成对 data URI 截然不同的看法。

Read more »

重新认识伪类和伪元素

Posted on 2017-04-28 | In CSS3

熟悉前端的人都会听过css的伪类与伪元素,然而大多数的人都会将这两者混淆(包括我)。那今天就让我们来看看伪类和伪元素的区别吧!

Read more »

Angular2学习笔记

Posted on 2017-04-26 | In Web

前言

阴差阳错,当初在选择写网站的时候选择了使用Angular2+RESTful,现在想起来,这个选择可能有点轻率了。虽然这套框架我个人觉得可能的确比较适合做移动端Web的开发,但是由于网站开发的经验明显不足,加上这套技术还不是相当的成熟,在学习的过程中走了很多的弯路。而且,功利一点的讲,对于找工作的帮助可能并不是很大,很多Web相关的职务招的都是Java方向的,而我直接跳过这种传统框架直接接触新知识难免会发现基础不牢的情况。不过还好,经过这一段时间的倒腾,好歹把Angular2的东西稍微消化了一点,相比啥都不会,也算是有点收获吧。

基础配置

刚学习Angular2的时候,是照着他的中文文档上来的。虽然这上面对框架解释的非常细致,讲的也很清晰,例子也很典型,但是这个文档还是有点偏旧了。文档中介绍的example中用的结构还是用的基于github源码的较早前的版本,现在用起来十分的不方便。现在基本上都是用angular-cli来组织文件,这个项目对Angular2提供了强大的支持,我们用起来也比以前方便了很多。
具体的过程就不细说了,可以参考angular-cli的wiki,主要是以下步骤:

  1. 安装新版本的node。(注意node的版本一定要是6以上的,否则会报奇奇怪怪的错);
  2. 安装angular-cli (npm install -g @angular/cli);
  3. 新建项目 (ng new PROJECTNAME);
  4. 选择一个合适的IDE,我选择的是WebStorm;

这样基本上就算是搭好了Angular2简单的开发环境。

开发细节

Angular2这类MVVM架构的框架跟传统的MVC框架有很大的不同,不过主要需要考虑的就是下面这几个部分:

  1. 模板。主要是模板语言部分以及在模板中使用组件的变量等等。
  2. 数据绑定。包括属性绑定、事件绑定、插值绑定以及双向绑定,主要用于组件内的变量在页面中的显示以及页面等。
  3. 服务。这包括两方面,一个是访问RESTFUL的服务,另一个是用来保存本地变量的。访问RESTFUL的服务通常是使用Promise来进行异步回调使用的,访问本地变量的服务则要注意不要写成全局的变量,否则就会出现类似所有同时访问网站的用户都共享同一个变量的尴尬场面。。。
  4. 依赖注入。依赖注入做的就是控制变量的传递关系,防止数据混乱的调用关系等等。

具体的使用方法等到需要的时候查看文档即可。

项目发布

如果是测试环境,直接ng serve就可以用node服务器在本地的默认4200端口显示页面了。
但是,用测试环境你会发现项目非常的巨大,一个啥依赖都没有的’Hello world’就足足有3MB的大小,这显然是用户无法接受的。

那么为什么他会有这么大呢?这是由于Angular2默认使用的是JIT(Just-in-Time - JIT)编译。这个JIT编译有他的好处,他意味这我们的代码是在客户端解释的,那么他编译的效率会比较高,编译的结果会更好。但是他也有很多的缺点,Angular2文档中列举了下面几点:

  1. 渲染得更快;
  2. 需要的异步请求更少;
  3. 需要下载的Angular框架体积更小;
  4. 提早检测模板错误;
  5. 更安全;

于是,Angular2又提出了一个新的编译方法叫AOT(Ahead of Time - AOT)。这个编译方法就是相当于静态编译,这样就可以提前筛掉没有使用过的组建,并且减轻了客户端的压力。显然这很棒棒喽,但是文档中介绍的转换方法实在是麻烦,弄了半天报了一堆错也没弄成,最后还是用了angular-cli才算搞定。

对于开发环境,可以使用ng serve --prod --aot来进行简单的优化。不过对于真正的生产环境我们显然不能用node服务器,我这里用的是nginx来部署,具体步骤如下:

  1. 使用命令ng build --prod --aot来生成dist/文件夹。
  2. 将上面的文件夹配置成nginx站点的根目录
  3. 配置gzip压缩,进一步减少文件传输量
  4. 使用try_files选项配置跳转的启动路径,否则直接输入二级路由是会报错的。

具体的配置应当是下面这样:

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    root /var/www/html/dist;
    index index.html index.htm;

    # Make site accessible from http://localhost/
    server_name localhost;

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri /index.html;
        # Uncomment to enable naxsi on this location
        # include /etc/nginx/naxsi.rules
    }
    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 8k;
    gzip_http_version 1.1;
    gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml;
}

这基本就是Angular2项目开发的基本过程了。

参考文章

angular-cli
angular中文文档
nginx发布Angular2

「Java教程」异常处理机制

Posted on 2017-04-25 | In Java教程

Java语言提供了完善的异常处理机制。正确运用这套机制,有助于提高程序的健壮性。

Read more »

云计算比赛总结

Posted on 2017-04-24 | In Others

2017年4月23日,今天为期近半年的云计算比赛终于落下了帷幕。尘埃落定,分完奖金,分完奖品,好像一切没有发生过一样,生活也慢慢步入了正轨。但是我总觉得,一切事情总得留下写什么值得记忆和回味的东西。在一切渐渐过去之后,比赛本身变得其实不那么重要,反而是那些看起来与比赛无关的细节更值得铭记。从前我一直觉得,有些事情体验了就好,但是我渐渐发现,体验过的东西几乎都特别的容易忘记,没有思考与总结的体验只能算的上是走马观花,我特别羡慕像杨振宁那样的学者,虽然身体渐渐衰老,但是他总能非常清楚的记得在某年某月某日,与某人发生过哪些对话。虽然我的记性不太好,但是我也希望我的经历能够不仅仅成为过去,而是成为自己一直受用的财富。

起因

其实参加这个比赛的原因非常搞笑,在上学期的一次大创项目的例会上,指导老师跟我们聊了聊项目的问题,发现没啥好聊的,然后话锋一转说,“哎?我听说最近有个云计算比赛,你们要不要参加看看,前几年某某老师带的队伍好像都拿了不错的名次,blabla。。。”,我们当然不敢表达什么反对意见了,然后就在我们的项目没啥进展的情况下,撂下手头的事开始搞这个云计算方面的事情。。。
然而尴尬的是,我们并没有人会scala,hadoop,spark这类搞分布式云计算常用的疙瘩,一切都得从头学起,但是我们其实也并没有太多的时间。因此拖着拖着,就过了春节。春节过来后,发现不能再拖了,就迎着头皮加班加点的搞了一个多礼拜,才稍微理清楚眉目。虽说是四人的小team,但是实际做起来就我跟某大佬两个人在做。比赛总共两道题,那就一人一道题,我负责基于kmeans聚类的大数据分类算法,大佬负责通用后缀树的构建算法。

研究

关于这个kmeans算法,其实spark的mllib里有一个kmeans的库。不过显然,这个在我们的数据集上跑的效率并不好,于是我们就参照大赛提供的一个名叫cluto的单机kmeans工具作为参照。里面有一些相关文档,因此我主要是参照这个进行处理。

问题描述

问题其实很简单,就是给定两个数据集,你给我用spark跑出这两个数据集的聚类结果。这两个数据集,一个是数目较多特征较少的稠密矩阵,另一个是数目较少特征较多的文本矩阵。初赛跑第一类数据,决赛跑第二类数据。然后评测的时候总和比较准确率(NMI)跟时间。

我的思路

照着cluto的意思,我就简单的利用spark mllib中的相关函数进行拼接处理。事实上mllib提供的kmeans库本身没有太大的毛病(感觉毛病其实有两点,后面会说),之所以在大多数情况下效果不是太好,我看主要是因为,人家在设计这个玩意的时候,压根就没打算让你直接套。你得根据你实际的数据集,进行相应的预处理,而这些预处理的手段他其实也在其他的模块里有提供,而且大部分都实现的没啥问题。问题的重点就在于我们如何去根据数据集去选择正确的预处理方式。
通常情况下,预处理的方式无非下面这几步:

  1. 特征提取(降维)
  2. 行列加权
  3. 归一化

所谓特征提取,通常也就是用PCA,SVD这类算法,从高维数据中提取更有代表性的维度来讨论,从而加快运算速度。
所谓行列加权,通常是采用一些经验算法,比如IDF加权,将数据进“强化”,使其能更加突出主要维度的作用。
所谓归一化,很简单,就是消除各条数据尺度不同带来的影响。虽然很简单,但是也很重要,对结果的影响也不小。

不过上面的这些步骤也不能一概而论,比如有些数据集上,降维有可能导致数据丢失,行列加权会导致数据异常,归一化会导致主要维度丢失等等。因此mllib在设计的时候就没有将这些方法融入kmeans里面,而是分开来供大家选择,有针对性的使用。

没错,虽然答辩的时候吹的天花乱坠(其实也没啥干货。。。),但是实际操作的时候用的就是上面的三个步骤,自己几乎没有实现任何功能模块,完全调用mllib中的库。。。

除此预处理之外,就是些细节参数的选择了,主要包括下面几点:

  1. 距离函数的选择
  2. 初始化中心点的选择
  3. 平均准确率与时间的抉择

mllib自带的kmeans模块有一个缺点就是他只支持了平方欧氏距离,虽然现在看起来,似乎所有的距离公式都可以某种程度上互相转换。但是简单的看,在很多情况下,不同的距离测度在不同数据集上的表现也不完全相同,因此更换一个合适的距离测度似乎是一个轻松讨巧的提高准确率的办法。我在实际操作中采用的是余弦距离,稍微修改下源码就可以办到。
初始化中心点的方法大多数情况采用其自带的kmeans++算法,准确率一般会提高一点。不过也不排除random更好的情况。
至于平均准确率与时间的抉择就十分有讲究了,我们比赛成绩说的过去的很大一部分原因就是这个抉择做的还算不错。我们知道在spark1.6以下的版本里,有一个参数叫runs,不过由于一些代码可维护性、执行效率等的原因,在高版本的spark里这个参数被取消了。这个参数曾经存在的意义在于,我们知道kmeans本身的初始点选择具有一定的随机性,那么每次聚类结果的准确率就不一定相同,有时好,有时不好。我们在比赛的时候显然希望尽可能的避免表现不好的情况,那么我们就可以通过运行多次数据,取代价函数最低的那组模型作为最终的模型,这个运行次数就是runs参数的意义。显然,这个参数并不能提高kmeans本身的准确率,而是通过时间,去换取准确率高的概率,减少准确率低的概率。这就是一个简单的“时间换准确率的想法”。与之对应的,也有用“准确率换时间的想法”,比如有许多队伍,在聚类时并不采用整个数据集,而是去进行采样,获取一些有代表性的数据,来做聚类,这样有的确会提高算法执行速度,但是也有降低准确率的可能。

一些问题

当然,我的算法是存在问题的。最突出的问题在于,在实际的运行中,我发现spark提供的kmeans算法并不能很好的并行计算,绝大多数的stage都集中在一个worker上。。,这是一个很尴尬的问题,他意味着这实际上就不是一个分布式程序。不过这个问题也不完全出于我,因为我采用的就是spark mllib中的kmeans实现。这个实现似乎在操作中有一些问题,导致几乎所有的数据都在一个worker上跑,我尝试采用了RangePartition来代替HashPartition,但是结果也并没有任何好转。情急之下我也就没有过多的考虑这个问题,就直接提交了代码。事实上如果这个问题解决了,那么运行时间应该能缩短成1/7左右,成绩排进前两名应该是没啥问题的。不过这或许就是差距所在吧。

结果

虽然总体上讲,在进入决赛的11支队伍里,我们在两道题上做的都不是很突出,但是由于我们两道题都比较稳定,没有出现短板,因此最后拿了第三名,低于南大PASA实验室的研究僧以及东北大学的队伍。相比之下,南师大的队伍虽然第二题全场最高,但是第一题却是倒数第二,混了个三等奖;东南大学的队伍,虽然第二题跟南大一样跑除了隐藏数据集,但是第一题同样扑街,也混了三等奖。。。
说起第一题的结果,其实也颇有争议,评委方认为这个聚类问题得综合考虑时间以及准确率来判分,但是实际操作中,为了防止高速低准确度混分情况的出现,在评价速度分的时候乘上了与准确率有关的系数,导致准确率的影响过大。我这种“时间换准确率”的做法就比较占便宜了,而那种“准确率换时间”的做法就吃了亏。。。

本博客的优化汇总

Posted on 2017-04-23 | In 博客栈

0000087.png

我一直在关注我的博客的浏览体验,其中,网站的加载速度对于浏览体验来说是非常重要的。

Read more »

Ubuntu 使用 Cron 实现计划任务

Posted on 2017-04-21 | In Cron

Windows 自带定时执行任务的工具叫做“计划任务”,Linux 下我们使用 Cron 实现这一功能。

Read more »

我的牛客网面试经历

Posted on 2017-04-19 | In Others

起因

其实本来我并不知道牛客网也在招人,不过一直觉得牛客网这个平台非常棒,之前也在这里刷过一些题目,平时没事的时候就会到讨论区默默窥屏,看看大牛们是怎么面试的,顺便也将自己的经历分享给其他人。一次偶然的经历,我得知了牛客网似乎也在招聘,抱着试试看的态度,就投了一份简历过去。可能由于是初创的小公司,办事效率很高,第一天晚上投简历,第二天就打电话约面试,第三天晚上就正式面试了。而且让我没想到的是,面试官就是牛客的CEO叶老大。。。

面试过程

和我想象的不一样,这次面试没谈项目,没谈人生,没谈理想,也没谈什么诗和远方。我想应该是由于面试官本身是一个资深程序员,相比hr出身的面试官,更有务实性,给人一种talk is cheap的感觉。不过我也没感受到来自他的压力,他本人应该是那种文静随和的类型,不会大段大段的罗嗦,而且,很显然他早就准备好了套路,估计也是阅人无数的那种类型。下面简要整理下这次的面试过程。
我们是在牛客网的面试平台上进行的,让我耳目一新的是,面试平台不仅仅是视频聊天,还可以直接编程运行。而且你所敲的每一个字母,面试官那里都能实时显示,所以可以非常方便的就代码进行沟通。当然,这样也基本杜绝的作弊的可能。
和我之前说的一样,主考官非常务实,上来就说我先搞几道题试试。。。
题目是他当场边打字边解释。

第一题

第一题大概是热身,让我实现一个字符串比较函数。
显然这个还是很简单的,不过发生了一个小插曲,就是他让我实现的函数名叫strcpy。。。我不知死活的向他提议何不改成strcmp。。。他想了想似乎很有道理,把函数名改成了strcompare。。。
题目很简单,一小会就写好了,他研究了一会,试了试边界条件,发现没什么问题,这道题就算是过了。

第二题

第二题稍微考察算法能力,让我实现一个正则匹配的判断函数,也就是给两个串s1,s2,其中s1是含有*和.的模式串,s2是普通字符串,让我写一个函数判断s1是否能匹配s2。
这就体现出我楞的一面了。
我上来想都没想,直接用递归搞,虽然三下五除二把一些简单的样例过了,但是仔细研究则发现很多边界条件都不对。面试官则跟我一起看,有种帮我一起找bug的感觉,不过我心里清楚,要是我自己的bug让别人找到了拿岂不是很没面子。还好我当时没有太抽,研究了一会终于找到了那个小bug。面试官看了看,试了试几个边界,分析了下我的代码,发现没啥问题,就说ok。但是,他接着问,你能分析你这代码的复杂度么。。。我有点小懵,其实递归算法的复杂度我还真没仔细研究过,于是瞎猜了猜,感觉不太对劲。他想估计有点觉得我开始不靠谱龙,问我你知道递归函数的复杂度怎么算么。。。我忽然想起了徐老师乐此不疲讲的归并排序,就说应该是要解一个。。递归方程?他表示大差不差吧,这才算放过我。
正当我以为万事大吉的时候,他又问我,你能不能把你的时间复杂度降低一点啊,我想了想没想出啥好办法。。。他问我会不会动态规划。。。我。。。好久没刷题,想了一会才想出正确的解法。。。回头一看。。。这不就是个简单的不能再简单的dp么。。。虽然绕了一些弯子,不过好歹搞了出来。。。

第三题

面试官问我会不会java,我当然会,不过考虑到好久没写,怕手生,顺便也给自己台阶下,我表示我们虽然上过这课,但是课比较水,没有项目练手,他笑了笑表示了解。于是就让我说一下java中抽象类跟接口的区别。我当时短路,只说我知道接口只定义函数的声明,而抽象类的函数可以有具体实现。然后他问我一个类能实现多少接口,一个类能继承多少个抽象类,我这才想起来原来这也是一个区别。。。

第四题

这是我整场面试最大的痛。。。
问,给你八颗药,AAAABBBB,他们从外观上一模一样,现在我要每天吃一个A一个B,问有没有好办法。
我想了半天,表示我办不到,如果有什么好方法,我也会觉得很神奇。。。他笑笑表示。。。你回去好好想想。
回去我问了舍友。。。被嘲讽。。。

第五题

第五题还是挺有意思的,有种诡辩的感觉。
问,我们照镜子的时候,大家都说左右是反的,但是为什么不说上下是反的呢?
显然,常识告诉我上下是不“反”的,但是他这么一说,倒是真给人一种上下也应该是反的的感觉。其实问题的关键应该在于参考系的选择,我们说左右反,是因为我们以上下为轴来看的,这显然不够“有说服力”,如果我们把脑袋横过来看,把以左右为轴来看,那么上下也是反的。我当时感觉答的意思应该差不多,面试官表示其实这其实是个开放性的题目,主要看你能不能把你的话圆过去,只要没有太大的逻辑问题都行。

其他

总体上,整个过程下来感觉还是挺轻松的,主要是现场编程可能会有点压力。有人说小公司更加注重让你直接做事,大公司才注重你的学习能力,其实我倒觉得这个表述还是不够客观的,至少这次面试,面试官没有出任何项目经验(板砖)的题目,反而全是算法题和思维题,而且虽然公司是用java做开发,但是他也能包容没有很多java开发经验的同学。不过这次体验最大的收获应该在于,小公司有时候也不比大公司容易进,从工作角度讲,小公司的工作激情可能比大公司更高,从待遇上讲,一个有潜力的小公司待遇甚至不输BAT。
Exp++。

1…293031…58

574 posts
69 categories
286 tags
© 2024 Companyd
Powered by Hexo
|
Theme — NexT.Muse v5.1.4