编程之美2016复赛题目翻译

问题描述

微软学术图谱 (MAG) 是一个大型的异构图,其中包含很多实体,例如作者,论文,期刊,会议和实体间的关系。微软为这次比赛提供学术知识API (Academic Knowledge API) ,实体属性的定义在这里 (here)。

参赛者需要提供一个REST服务结点,给定MAG中一对实体的标识符,能够找到所有连接它们的1跳,2跳,3跳路径。给定的实体的标识符可以是[Id, Id], [Id, AA.AuId], [AA.AuId, Id], [AA.AuId, AA.AuId]。路径中的每一个结点必须是这些标识符:Id, F.Fid, J.JId, C.CId, AA.AuId, AA.AfId。路径中的边可以是:

Id1→Id2 当且仅当 Id1对应的论文中有一个RId等于Id2(即Id1引用了Id2)
Id1→F.FId2 当且仅当 Id1对应的论文中有一个F.FId等于F.FId2(即Id1的领域中包含F.FId2)
F.FId2→Id2 当且仅当 Id2对应的论文中有一个F.FId等于F.FId1(即Id2的领域中包含F.FId1)
Id1→C.CId2 当且仅当 Id1对应的论文中的C.CId等于C.CId2(即Id1在C.CId2会议上发表)
C.CId1→Id2 当且仅当 Id2对应的论文中的C.CId等于C.CId1(即Id2在C.CId1会议上发表)
Id1→J.JId2 当且仅当 Id1对应的论文中的J.JId等于J.JId2(即Id1在J.JId2期刊上发表)
J.JId1→Id2 当且仅当 Id2对应的论文中的J.JId等于J.JId1(即Id2在J.JId1期刊上发表)
AA.AuId1→AA.AfId2 当且仅当 AA.AuId1对应的AA.AfId等于AA.AfId2(即作者与单位有一条边)
AA.AfId1→AA.AuId2 当且仅当 AA.AuId2对应的AA.AfId等于AA.AfId1(即单位与作者有一条边)
AA.AuId1→Id2 当且仅当 AA.AuId1对应的一个Id等于Id2(即作者与他参与编写的某篇文章有一条边)
Id1→AA.AuId2 当且仅当 Id1对应的论文中的一个AA.AuId等于Id1(即论文与其作者之间有一条边)

对于每个测试样例,REST服务结点会通过HTTP接收一个JSON数组,包含一对实体的标识符,标识符是64位的整型,例如[2147483647,2147483648]。服务结点需要在300秒内做出回应。回应的JSON数组包含一个路径列表[path1, path2, …, pathn],每一个路径对应一个实体的标识符的数组。举个例子,如果你的程序找到了一个1跳路径,两个2跳路径,一个3跳路径,结果就看起来像这样:[[123,456], [123,2,456], [123,3,456], [123,4,5,456]]。对于路径[123,4,5,456],这些整数代表了路径上实体的标识符。当收到你的回应之后,评测机将会等待一个随机长度的时间,然后再发送下一个请求。

评测方法

在最终的评测中,REST服务必须部署到一个Standard_A3虚拟机。不限定编程的语言。
在最终的评测之前,你无法得到测试用例。当评测开始时,评测系统会给每个队的REST终端结点单独发送测试用例。每个队伍会接收到10个测试用例(Q1到Q10)。设回应测试用例Qi的时间为Ti(1≤i≤10),那么最终得分的计算公式:

20160426175207674

其中Ni 是Qi 的解的规模 (正确路径的总数量) , Ki 是你的REST服务返回的路径总数,Mi 是你的REST服务返回的正确的路径数(重复的路径不重复计算)

记微软实习生面试

上周周三上课的时候,就收到59开头的北京座机号打来的电话。感觉这个电话有些奇怪,于是就小声的接了。果然是MSRA打来的。由于我不方便接,于是约好两个小时之后再打电话。

两个小时之后就吃完饭,回寝室了。那个号码又打过来了。赶紧接了。对面那头用着粤式普通话,问了问我的个人信息,以及可以工作的时间。我说放假前一周只能够保证三天,放假后可以每天都去。他说他们想招一个full-time intern(听了两遍都没听清),于是就没有下文了。

周二下午上课的时候,又一个59开头的北京座机号打来个电话。这次索性溜出教室接了电话。对面的大哥说他是MSRA系统与计算组的,想面试我,问我什么时候有时间。我说那就周五吧,正好一天没课。

然后一周都挺忙的,也没时间准备,以及不知道要准备啥,周四晚上计算机网络实验也做到快11点,洗了个澡就早早睡觉了。早上一来一脸懵逼,怕面试问关于硬件的问题(以为他们是操作系统部OSG),于是翻了翻组成原理的课件,但是没有心情看。罢了,玩两局炉石,就去微软大厦了。到了发现去的有些早,于是绕着微软大厦走了一圈。微软大厦分为两个楼,我突然发现面试官忘了告诉我去哪个楼了。于是提前10分钟跟他打了个电话。

和面试官碰面,登记了访客名单后,就带我上去了。在一个小的会议室里,就开始了面试。

在开始之前,我特意问了一下他们是属于MSRA还是OSG,结果发现他们是MSRA的一个组。感觉有些尴尬……

他跟我寒暄两句后,就要我做个自我介绍。我在简历上写的算法能力还不错,于是我就随便扯了扯关于算法的。结果他问我有什么研究的方向,于是随口说了个数据挖掘,然后他要我谈谈具体的方向,我回答不上来,只好说现在仍处于学习阶段(装逼失败……)。问了我一道算法题,如何判断一个链表是否有环?虽然有点懵逼,想了两秒钟后,我就说:如果链表头为空,则为空链表;从链表头开始向后遍历,如果发现其中一项与前面的某一项相同,则链表有环。显然他不会对这个答案满意(我也是),要我想想复杂度更低的算法。我想的是在遍历的时候给链表项做标记,如果遍历到某一链表项有标记,则链表存在环。于是问他能不能给链表项内容加入一个项目,他说不能修改链表。然后我就懵逼了……

然后面试官让我谈了谈简历上参与的项目。讲了讲具体做了哪些工作。先扯了扯同袍的UWP应用,以及两个用WPF开发的小程序,但是感觉他不怎么感兴趣的样子。于是他问我简历上的项目:一个是python的爬虫项目,另一个是安卓的项目。说安卓项目的时候,我有意表现出了一点对某老师的不满,正好他问我这么一个问题:如果你是管理者,你会怎么管理这个团队?那……软件工程的内容就可以随便扯扯了。既然说到了安卓,他问了我这么一个问题:安卓开发里最重要的四个部分是什么?以前好像见过这个问题,但是不记得答案了……先回答了个Activity,Intent,再乱说了个Fragment,最后他提醒我了Service。他说答案应该是Activity,Intent,Service和Broadcast Receiver(晕根本没听说过……)。再问了问Activity和Service的区别。我对这种题最无感了,只能告诉他Activity在前台,Service在后台。应该还有很多别的区别。

这期间他还问了问我的熟悉的语言,简历上写的熟悉C/C++/Java,用过C#和Swift。于是我就谦虚的说C++的高级功能不会,C还可以,Java写的也比较多。他本以为我用Swift写过iPhone的应用,于是我自嘲说没有iPhone,所以没写过;只写过Mac OS X上的应用。

在他问我了一堆关于开发的问题之后,我反问了一句,MSRA也有开发安卓的项目吗?他说,在MSRA,什么人都有。(这个逼装的好)

嗯,大概就问了这么些问题吧。之后他问我还有没有什么问题。处于懵逼状态的我就先问问他们组的信息吧。于是他介绍了他们组,系统与计算组。一半的人做云计算,一半的人做移动计算,他是做移动计算的。怪不得他懂安卓开发。他说准备要我们做定位的相关应用,于是我表现出了极大兴趣:)。顺便问了他要做什么端的开发,他说优先安卓,然后iOS,我反问WP呢→_→他说WP用的人少就不考虑了(哦……)

然后……他就要我回去等通知了……整个面试过程估计没到一个小时。

然而最后还是没过……

利用百度地图JS API显示运动轨迹

之前用过百度地图Android SDK,觉得功能很强大,而且工具封装的很好,语句写出来也很好懂,照着官方的例子就能很快学会。这会儿需要在WPF程序中实现,只能用百度地图JavaScript API了。要实现的功能是:程序不断获得到GPS坐标信息,并将轨迹显示在地图上,标注出最后一个位置。这其中还涉及到了百度坐标转换API。

首先当然要在xaml中加入一个控件WebBrowser用来显示HTML页面。

<WebBrowser x:Name="map" />
在MainWindow的构造函数中,加入以下内容,使WebBrowser加载我们需要加载的HTML文件。这里文件名为"map.html"。
Uri mapUri = new Uri(@"map.html");
map.Navigate(mapUri);
程序获取到一个新的GPS坐标点,利用WebBrowser控件调用JavaScript里的函数move(lat, lng)。
double lat = 39.918;
double lng = 116.409;
map.InvokeScript("move", new object[] {lat, lng });
map.html里的内容如下(因为懒所以直接把Javascript代码写到HTML里了)
<!DOCTYPE html>  
<html>  
<!-- saved from url=(0014)about:internet -->
<head>  
<meta http-equiv="X-UA-Compatible" content="IE=9" >
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />  
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
<title>Hello, World</title>   <!--标题-->
<style type="text/css">  
html{height:100%}  
body{height:100%;margin:0px;padding:0px}  
#container{height:100%}  
</style>  
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=8pEG3G7pYGIya6gaHGcU8pbE">
//v2.0版本的引用方式:src="http://api.map.baidu.com/api?v=2.0&ak=您的密钥"
</script>
</head>
<body>  
<div id="container"></div> 
<script type="text/javascript"> 
var map = new BMap.Map("container");   // 创建地图实例  
var point = new BMap.Point(116.404, 39.915);   // 创建点坐标  
map.centerAndZoom(point, 15);   // 初始化地图,设置中心点坐标和地图级别       
var opts = {type: BMAP_NAVIGATION_CONTROL_SMALL}    // 设置地图类型控件样式为小型
map.addControl(new BMap.NavigationControl(opts));   // 添加地图类型控件
var lastpoint = null;   // 最后一次到达的点(百度坐标)
var marker;   // 当前位置的标注
translateCallback = function (data){   // 百度坐标转换回调函数
    if(data.status === 0) {   // 转换成功
        var newpoint = data.points[0];   // 新到达的点(百度坐标)
        if (lastpoint != null) {
            var polyline = new BMap.Polyline([    
                lastpoint, newpoint   
            ], {strokeColor:"red", strokeWeight: 6, strokeOpacity: 0.5});
            map.addOverlay(polyline);   // 如果最后一次到达的点不为空,则在地图上添加一条线
        }
        map.removeOverlay(marker);   // 删除之前的标注
        marker = new BMap.Marker(newpoint);   // 根据新到达的点创建标注    
        map.addOverlay(marker);   // 将标注添加到地图中
        map.panTo(newpoint);   // 移动地图中心到新到达的点
        lastpoint = newpoint;   // 修改最后一次到达的点
    }
}
function move(lat, lng) {
    var convertor = new BMap.Convertor();   // 新建一个坐标转换器
    var pointArr = [];
    var ggPoint = new BMap.Point(lng, lat);   // 将要转换的坐标(GPS坐标)
    pointArr.push(ggPoint);   // 把坐标添加到转换数组中
    convertor.translate(pointArr, 1, 5, translateCallback);   // 调用百度坐标转换,设置回调函数为translateCallback
}
</script>  
<style type="text/css"> 
.anchorBL{ 
display:none; 
} /*去除百度地图版权信息,不建议这么做*/
</style> 
</body>  
</html>

最终效果如图。

baidumap_js

玩玩基于照片的三维重建

最近有个项目需要的通过照片得到三维的模型。记得安卓上有个叫Autodesk 123D的应用,通过手机拍摄照片,再发送到云上处理,一段时间之后就能看到三维模型了。在网上搜了一下,发现可以用VisualSFM+Meshlab来实现。

VisualSFM : A Visual Structure from Motion System

VisualSFM是一个SFM(Structure From Motion)三维重构的程序,其带有图形界面,非常方便用新手的使用。这个程序集成了许多优秀的工具,比如在密集重建部分,使用了Yasutaka Furukawa的PMVS/CMVS工具。

在Windows下配置方法非常的简单:

  1. 尝试打开VisualSFM,如果不显示窗口,则需要先安装Visual C++ 2010 运行库
  2. 下载CMVS/PMVS的二进制文件。可以从源地址下载
  3. 把下载好的cmvs.exe/pmvs.exe/genOption.exe/pthreadVc2.dll放在VisualSFM.exe所在的目录下即可
    在Windows下的使用方法:

使用方法(图片来自http://ccwu.me/)

  1. 添加需要进行三维建模的图片,为了保证效果,建议至少10张图片。
  2. 点击匹配图像按钮
  3. 点击稀疏重建按钮
  4. 点击密集重建按钮,并选择结果保存位置。
    或者用CMD执行以下命令:

VisualSFM.exe sfm+pvms ./images ./results/result.nvm
其中./images为输入的图片文件夹,./results/result.nvm为输出的nvm文件的路径。其实输出为一个nvm文件,一个ply文件,和一个文件夹,所以建议输出位置指定到一个文件夹内。

可以使用MeshLab导入ply文件查看结果。下图为采用该方法建立的样例模型

%e5%b1%8f%e5%b9%95%e6%88%aa%e5%9b%be1-1024x576

后续的三维图形处理可以参考这篇文章,玩玩三维重建

修复分区表损坏的记录

原来在装黑苹果的时候,为了能直接进行系统升级,于是整了个Legacy BIOS+GPT磁盘+Clover引导的结构。去年暑假的时候,我就领略到了这种的结构不稳定性。一旦EFI分区遭到破坏,Clover就不能用,那么就无法进入任何一个系统了,解决的方法就是用做个带有Clover的启动U盘。而这次,遇到了一个比较大问题——分区表损坏。

美赛期间,由于需要用到MATLAB,所以下载安装占用了不少硬盘空间,导致我的系统盘接近爆满。遂决定将Mac的Time Machine备份盘格式化成FAT32,用作数据盘。于是进Mac系统,完成格式化;但是进Windows 10一看,依然是HFS+。随手找到了一个旧版的DiskGenius,看到分区的id为0x06而不是正确的0x07。于是毫无考虑的修改了分区id,结果Windows瞬间蓝屏。

虽然感觉哪里不对,但还是等着室友打完那局LOL,借同学电脑做了个Clover启动盘。然后发现还是进不去。为了确认磁盘现在的状态,又做了个DOS版的DiskGenius进去看看。结果发现除了格式化为空的备份盘,其它的盘都不见了。心中一万头草泥马呼啸而过……

还记得之前的磁盘分区大概是这样的:

EFI分区 Mac系统盘 备份盘 Windows系统盘 文件盘
FAT32 HFS+ HFS+ NTFS NTFS
200MB 180GB 70GB 90GB 110G

现在却变成了这样:

未知 备份盘 未知
180GB 70GB 200GB

心想这可怎么办……文件盘里的东西虽然不是什么重要的商业资料,但是也是大学以来所有资料的汇总。冷静下来,觉得这个时候应该只是分区表损坏了,文件应该都在盘里没有丢失!然后我试着用DiskGenius搜索已丢失的分区,无果,好像只能找到FAT32格式的分区。然而第二天还要继续数学建模,系统也是不能没有的。没有办法,只好在原来Windows系统盘的区域重建一个新的分区,装个新的系统救急。

美赛完后,睡了一早上,吃罢午饭,开始修电脑。在维基百科查了查GPT磁盘的结构。于是下载了一个最新版的DiskGenius(咦为什么又是这玩意),对照着查看扇区内容。

维基上说,LBA1,即第二个扇区,内容应该是GPT磁盘的分区表头,但是我发现我的第二个扇区为全0。而且我看到,这个扇区的内容有CRC校验,这说明我没法手动修改扇区内容来回复分区表头。还有我发现,DiskGenius已经将磁盘识别为MBR,我感觉将磁盘重新弄回GPT的可能性不大了。

接着往下看,下一个扇区开始应该记录的是分区的项目。万幸的是,磁盘的这部分内容还是完好的。

105947e0tctrtrdzgcflg4

根据维基上的信息,我们可以知道,128个字节用来说明一个分区项目。前16个字节说明分区的类型,接下来的16字节是分区的UUID,接下来的两个8字节是分区起始扇区和结束扇区,采用小端序,这正是我所需要的。再接下来的8字节是属性标志。接下来的72字节是UTF-16LE编码的分区名。

知道了分区的位置,那么我们的工作就完成大半了。接下来,MBR磁盘分区的记录方式和GPT磁盘分区的相同分区格式的文件记录方式是否是一样的呢?我觉得是一样的。因为分区格式本来都是独立于磁盘分区表的。分区表也没有根据分区的格式而做出区别对待。最后事实证明,我的猜想是对的。

接下来,就只需要在MBR分区表中加入分区项目就可以了。为了偷懒,直接在DiskGenius里填写相应信息就可以添加分区项目了。保存更改的时候就不能用格式化。

114030bms6j1sjkdjdj6js

然后分区就回来了:)但是由于采取了MBR,OS X不能直接升级了:(

附:遇到分区表丢失问题的时候,一定要冷静,不要对分区进行多余的操作,减少对原分区的影响,这样才能提高恢复数据的成功率。

美赛小试MATLAB解偏微分方程

2016年1月29日到2月2日两个计算机系的和一个数学系的大神开始了他们的愉快的数学建模美赛之旅。今年的MCM有三道题,A题(连续型)是A Hot Bath,B题(离散型)是Space Junk,C题(大数据型)是The Goodgrant Challenge。经过一天的思考和讨论,发现A题比较贴近实际,需要用到热力学和动力学知识;B题需要查找大量的数据,翻阅大量的文献,同时需要用到航天物理知识;C题数据可以直接导入数据库,但是项目类别较多,而且数据挖掘算法需要自己思考,虽然我是计算机专业的,我也不想选这题……最后就敲定做A题了。

第二天看了两眼热力学和流体力学的书,整个人都是不好的……面对满书的公式,感觉自己并不能帮上什么忙。好在数学系的同学很快给我一个公式,说可以用MATLAB进行计算。于是兴冲冲的下了个MATLAB R2014b。

先说说这个三维热传导公式:

snip20170212_1

于是我在网上搜MATLAB的偏微分方程的相关内容。方法有很多:

  1. 使用图形化界面的PDE Tool,但是只能接二维特殊形式的偏微分方程;
  2. 使用pdepe()函数,可以求解一微一般形式的偏微分方程;
  3. 使用elliptic(),parabolic(),hyperbolic()函数求解特殊形式的偏微分方程。
    经过确认,要求解的偏微分方程为抛物线型。又在网上找了半天,都说第三种方法只能解二维的偏微分方程。就在准备要放弃的时候,我想到了可以查官方文档。官方是这么写的:

snip20170212_3

那就说明可以解三维的!
先试试官方的例子,但是第一句话都没法正常运行。

model = createpde();

不应该啊,这可是官方文档。排查了半天,可能是MATLAB版本的问题。官方文档应该是提供给最新的R2015b的,这也许是R2015b新加入的函数呢?于是抓紧时间下载并安装了最新的R2015b(这其中还有一段小插曲),然后运行官方的例子,就没有问题了。下面附上代码及简单说明。

%新建模型(一个立方体中间刨去一个圆柱)
[xg, yg] = meshgrid(-3:0.25:3,-1.5:0.25:1.5);
xg = xg(:);
yg = yg(:);
t = (pi/24:pi/24:2*pi)';
x = cos(t);
y = sin(t);
circShp = alphaShape(x,y,2);
in = inShape(circShp,xg,yg);
xg = [xg(~in); cos(t)];
yg = [yg(~in); sin(t)];
zg = ones(numel(xg),1);
xg = repmat(xg,5,1);
yg = repmat(yg,5,1);
zg = zg*(0:.25:1);
zg = zg(:);
shp = alphaShape(xg,yg,zg);
[elements,nodes] = boundaryFacets(shp);
nodes = nodes';
elements = elements';
model = createpde();
geometryFromMesh(model,nodes,elements);
generateMesh(model);
%显示所建模型的图形
handl = pdegplot(model,'FaceLabels','on');
view(-42,24)
handl(1).FaceAlpha = 0.5;
%添加边界条件,设置1号面恒为37度(狄利克雷条件)
applyBoundaryCondition(model,'Face',1,'u',37);
generateMesh(model);
p = model.Mesh.Nodes;
x = p(1,:);
y = p(2,:);
z = p(3,:);
%设置初始温度条件
u0 = 42-0.5*(x-3).*(x-3)-0.5*(y-1.5).*(y-1.5)-0.5*(z-1).*(z-1);
%设置参数
d = 4.2*10^6;
c = 5000;
a = 1000;
f = 1000*25;
[Kc,Fc,B,ud] = assempde(model,c,a,f);
[~,M,~] = assema(model,0,d,f);
%设置计算区间,t从0到1800秒,每60秒算一次结果
tlist = linspace(0,1800,31);
u = parabolic(u0,tlist,Kc,Fc,B,ud,M);
%计算最小温度和最大温度
umin = min(min(u));
umax = max(max(u));
%显示第一个子图,显示第1个结果
subplot(2,2,1)
pdeplot3D(model,'colormapdata',u(:,1))
colorbar
view(125,22)
title 't = 0'
caxis([umin umax]);
%显示第二个子图,显示第11个结果
subplot(2,2,2)
pdeplot3D(model,'colormapdata',u(:,11))
colorbar
view(125,22)
title 't = 10'
caxis([umin umax]);
%显示第三个子图,显示第21个结果
subplot(2,2,3)
pdeplot3D(model,'colormapdata',u(:,21))
colorbar
view(125,22)
title 't = 20'
caxis([umin umax]);
%显示第四个子图,显示第31个结果
subplot(2,2,4)
pdeplot3D(model,'colormapdata',u(:,31))
colorbar
view(125,22)
title 't = 30'
caxis([umin umax]);

运行的结果如图所示:

heat

附:比赛最后一天真的是越写越high……然后五点交完论文滚去睡觉了。睡到十二点起来聚餐。这次美赛真的是很有意思(还是队友比较有意思→_→)。

申请免费的学生版Microsoft Azure

今天申请到了免费的学生Azure,说说过程:

  1. 进入Dreamspark官网的Microsoft Azure for DreamSpark页面
  2. 登陆你的学生账户
  3. 点击“立即注册”,会跳转到Microsoft Azure注册页面。
  4. 此时会提示不可用,修改地区为“香港”,稍等片刻,即可看到注册页面。
  5. 填写你的基本信息,并验证手机号(现在可以验证中国的手机号了)
  6. 点击“注册”,完成!
    不过毕竟是免费的东西,容量都不是很大,例如数据库就最多只能用32MB。不过可以在上面练练手,搭个简单的东西是可以的。

建站随笔

一、起因

一年之前,申请到了Github Education Pack,里面送一年的.me域名,域名服务商是namecheap。其实还送了一年的SSL,当时懒得用了。1月底域名就要到期了。编译11号就测完了,12号码了码数据库的文档,14、15号去展示设计的数据库,然后就放寒假了。于是就打决定理一下自己年久未用的网站。

二、购买域名

这次就不在namecheap买域名了,支付不方便。于是GoDaddy上买了jogle.xyz域名,之所以买这个域名,一来是因为jogle.com域名早就被占用了,cn域名只能在国内买,而且肯定需要备案,二来是因为xyz域名比较便宜。据说xyz域名开始火起来是源于Alphabet公司买下了abc.xyz。
刚开始在DNSPod注册了,然后把域名的Name Server改成DNSPod,后来发现DNSPod也是种种限制,还是作罢。Name Server又改成了GoDaddy默认。
毕竟我搭网站没什么经验,之前弄过一次,现在好多东西都忘了,什么A,CNAME,NS分不清楚。每次修改完DNS记录都不知道对不对……而每次改完都要一段时间来生效,所以只好在这段时间玩玩游戏,刷刷论坛。

三、WordPress

其实之前早就听说过这个东西,而且也经常看到很多做的好的博客都是基于WordPress搭建的。我的上一个站点使用Github Pages生成的模板,然后我在上面手动改的静态页。之所以不用WordPress,也许是因为我没学过php吧……后来才知道用WordPress并不需要php的基础,内置的控制面板其实可以很傻瓜的写博客,于是这次决定试试。

四、主机

主机不准备花钱买,想用免费的。网上说免费的空间一般都不稳定,而且服务器距离中国很远,往往连接速度很慢。其实之前用的hostpark的香港虚拟主机,感觉还不错,基本上很稳定,就是空间太小了,只有50M,试着装了一个WordPress,就占了20多M。而且月流量只有100M,要是访问量稍微一大,估计就不够了。于是准备换一家虚拟主机商。
后来搜到了一个叫Hostinger的虚拟主机商,2GB的空间,100GB流量,看起来应该是够了。于是注册了个号,搭了一个WordPress,第二天发现主机被暂停了,说主机CPU有45次超过70%,然而我啥也没干,就是在看WordPress的主题商店和下载插件。于是果断换一家。看到2GB的空间,100GB流量的都直接忽略了。
接着又找到了红帽的OpenShift,它是一个云计算服务平台,搭建一个WordPress站点是肯定没有问题的。里面也有一键安装WordPress的功能。现在这个站点就是在OpenShift上搭建的。用了两天,连接基本稳定,由于用的是美国东部机房,速度一般。

五、绑定域名

由于你懂的原因,OpenShift处于半屏蔽状态,国内只能通过https连接。OpenShift主机可以通过CNAME的方式绑定域名,这样的话必须强制以https的方式才能访问。而且由于SSL证书不一样,Chrome等浏览器会显示警告信息,令人十分不悦。然而OpenShift要更换SSL证书,需要绑定信用卡,升级为Bronze账户,于是作罢。
后来想想,尝试了一下用301转发来实现。于是设置了Forward with masking,发现转发成功,没有SSL证书不一致的提示,显示的是http连接。但是网址栏会一直显示www.jogle.xyz,同时,WordPress后台也不能直接进入,手机端访问也会显示电脑端的页面。
改成了Forward only之后,网址栏会直接变成OpenShift原始地址,所有功能正常,但是这样域名的意义就不大了。权衡了一下,还是选择了Forward only。

六、主题

其实WordPress里有很多绚丽的主题,但是由于本来网站打开速度就不快,不适合用那些非常好看的主题。无意间看到了这个主题,部分控件采用了Android的Material Design,自己也很喜欢,遂决定采用。在首页,每篇文章都会显示张图,要不然就是灰的,不好看……算了,每篇文章都随便配个图吧……

七、后记

其实搭这个网站,花了快一个星期的时间,才从搜索尝试到布置妥当,希望自己在得闲之时能够记录下自己的生活,而不要浪费了这些资源。

浅谈Swift新特性

这篇文章写于2015年6月9日,学习Swift时所写。从原来的站点移动而来。

期末考试全部结束了,终于有时间写写自己的心得和总结了。在考期间隙的两个星期,简单看了一下Swift,在此写写Swift相较于Java的一些新的特性。

运行环境:OS X 10.10.3 | Xcode 6.3.2 | Swift 1.2

一、表达方式

Swift中的一些表达方式,与C风格的表达方式有些不同。

常量赋值

const int x = 1; // Java
let x: Int = 1 // Swift

变量赋值

int y = 2; // Java
var y: Int = 2 // Swift

函数表示

int func(char arg0, String arg1){} //Java
func(arg0: Character, arg1: String) -> Int {} // Swift

switch不需要写break

switch (x) {
    case 1: x++
    case 2: x--
    default: x = 0
}

每一行不需要分号结尾,除非需要一行表达多个语句。

二、强类型检查

Swift是强类型语言,在语言中没有指针的概念,取而代之的是引用,这点跟Java类似。

值的缺失

var s1: String = "123" //此处的s1必须有值
var s2: String? = nil //此处的s2可以有值,也可以为nil。Swift中用nil代表值缺失。

缺失类型与非缺失类型的转换

var s2: String? = "abc" //s2允许值缺失
var s1: String = s2 as! String //用as!声明s2强制变成非缺失类型。如果此时s2为nil,程序会崩溃。

还有其它的一些编程上的限制,例如子类方法覆盖父类方法必须加@override。

三、计算型属性

计算型属性可以定义在类、结构体和枚举中。这种属性不直接储存值,而是提供一个 getter 来获取值,一个可选的 setter 来间接设置其他属性或变量的值。

struct square {
    var a = 0
    var s: Int { // s为计算属性
        get {
            return a * a
        }
    }
}

四、扩展类

扩展可以向已有类型添加计算型属性。通过这种方法可以给String类型添加length属性(为什么Swift语言不自带length属性。。。)

extension Double {
    var km: Double { return self * 1_000.0 }
    var m : Double { return self }
    var cm: Double { return self / 100.0 }
    var mm: Double { return self / 1_000.0 }
    var ft: Double { return self / 3.28084 }
}

五、元组

元组是多个值组合而成的复合值。元组中的值可以是任意类型,而且每一个元素的类型可以是不同的。不用在函数返回值需要多个的情况纠结了!

let http404Error = (404, "Not Found") // 元组的赋值
let (statusCode, statusMessage) = http404Error // 元组的拆解
let (statusCode, _) = http404Error // 只拆解出部分
func(arg0: Int, arg1: String) -> (Int, String){} // 函数可以返回元组

六、闭包

其实这玩意很像匿名函数。

var reversed = sort(names, { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

然而在这种情况下,可以根据上下文推断类型,所以可以这么写:

var reversed = sort(names, { s1, s2 in return s1 > s2 } )

有些神奇的是,对于这种单表达式的闭包,甚至可以偷懒连return都不用敲。。。

reversed = sort(names, { s1, s2 in s1 > s2 } )

但还没有结束,Swift提供了参数名称缩写功能,所以。。。

reversed = sort(names, { $0 > $1 } )

Swift的String类型定义了关于大于号 (>) 的字符串实现,而这正好与sort函数的第二个参数需要的函数类型相符合。

reversed = sort(names, >)

真是神奇的偷懒技巧。。。

七、一点感想

虽然Swift看起来很实用的样子,但是由于语言还不够成熟,有时候用起来也是很令人心烦的。

就拿Cocoa框架中的NSString来说,通过()转成String的时候,字符会被Optional()包围。每次转换都要截取一下。

有时候想通过下标获取String中的单个字符。[]中竟然要填一个String.Index类型的东西。而这个东西却要用advance函数来得到。advance函数的时间复杂度可是O(n)的。估计问题的根源在于Swift的String支持Unicode。

还有,Java里的s.split()在Swift中是s.componentsSeparatedByString(),Java里的s.replace()在Swift中是s.stringByReplacingOccurrencesOfString()。虽然Xcode有自动补全但是我觉得还是不能忍。。。

面对对象,不再迷茫

这篇文章写于2015年4月6日,学习面对对象编程(OO)课程时所写。从原来的站点移动而来。

一、面对对象二三事

所谓面对对象,就是Object-Oriented。我觉得,OO本不该是很难的一门课。因为面对对象无外乎就是抽象,封装,继承,多态。然而最近的几次作业,我们发现,事情并不是想象中的那么简单。一个慢慢变得智能的电梯,想把它做好确并不是那么容易。殊不知,做好那八字口诀,也需要自己的一番苦行修炼。

 

  • 抽象指的是把事物抽象成高级程序语言中类的过程。

    • 那么类是什么呢?以C++为例,类实际上是特殊的结构体(struct)。对于结构体而言,里面存放的多为变量(其实也可以存放函数),而且具有外部可访问性。而类是结构体的一种拓展,既可以有属性变量,也可以有方法,而且可以单独控制每个属性和方法的对外访问性。
    • 而抽象的过程是一个模块化的过程。把要实现的功能根据属性和功能划分为一个个类,从而凸显出软件的结构与层次,提高软件的可读性和可维护性。一个好的抽象方式,会让程序更加易于维护,减少自己犯错的可能。
    • 我的抽象思考方法:

      1.  数据应该由谁储存?输入输出的数据可能是字符串的形式,那么,考虑到要实现的功能,字符串是否能够方便进行数据处理呢?如果答案是否定的,就可以用一个数据类来储存这个字符串中所包含的信息。注意,此时的我们类如同一个C语言里的结构体。在构造方法中我们可以进行对类的实例进行初始化。如果有多个这样的实例,我们要进行储存,你可以新建一个列表类来将它们存放于其中。如果用Java库中的ArrayLIst能直接满足你的需要,也可以直接使用。
      
      1. 数据需要进行什么操作?比如比较,获取值,改变值,重新变成字符串等等。那么这些方法应当就在数据类中实现。通过这种方式,数据类中就储存了数据,也具有处理单个数据和处理多个数据的方法。同理,列表类如果需要实现特定的排序,也应当写在列表类中。这么做,让类具有了完备性,涉及到该类的操作可以直接在类中完成,这样的设计才算得上合理。
      2. 对整个程序的功能进行划分。划分可以有多种方式,可以是按照指令执行的先后顺序进行分阶段,比如流水线工作的每一级可以划分为一个类;也可以是按照行为划分,完成某种行为的都归为一类。这么做之后,几个大类就分出来了。
      3. 对每个类的功能进行评估,对功能复杂的类进行分拆,那么就会得到许多功能更明确的类。这样每个类的功能并不复杂,而且更加专一,从而避免了出现庞大的类的情况。
      4. 分析类与类之间的依赖关系,避免出现一个类需要知道很多类,避免类与类之间形成强耦合关系,这样才更利于维护与调试。
         
  • 封装指的是将属性私有化,提供公有的方法访问私有属性。

    • 显然当我们不想让别的类修改当前类中的属性时,我们可以把属性声明为private,通过get()方法来获取值。但是如果我们想让别的类修改当前类中的属性时,为什么也需要声明为private呢?直接用public直接访问就好了嘛?其实不然。用统一的接口来访问属性是为了防止属性被莫名修改而不知道原因。通过统一的接口,我们可以知道,是哪个类的哪个方法调用了get()或者set(),从而便于管理与调试。
    • 所以,在一般情况下,类的属性应当声明为private或者protected,并根据需要设置相应的get()和set()方法。
       
  • 继承是从原有的类中派生出新的类,在已有的行为和属性上扩展出新的行为。

    • 继承的一个显而易见的功能就是,两个类的属性和方法基本重合,为了防止复制粘贴大段的代码,可以直接继承,减少了代码量,使代码更加简洁。
    • 然而这并不意味着一旦有相同的方法,你就需要用到继承。继承的重点有两个,一是重用,二是扩展。如果子类不能是对父类进行扩展,甚至是减少了父类的功能,这样的继承就不是很有必要,而且这是一种不好的设计风格。
    • 所以,使用继承关系时,两个类应当一个为另一个的扩展。如果两个类相似而又非常不同,可以让它们继承同一个抽象类。例如圆形和三角形都继承于形状类。
       
  • 多态是父类型的引用可以指向子类型的对象。

    • 我们知道继承扩展了类,即子类比父类更丰富。那么对父类型的引用可以指向子类也应当是合理的,因为子类中有父类的所有属性和方法(可能被重写但依然存在)。
    • 多态增加了程序的可扩展性。比如用篮子放水果,对于不同的水果,又不需要用不同的篮子。就让各种水果继承水果抽象类,篮子类中放置水果类的对象就可以了。这样当水果变化的时候,就不需要修改代码。

二、 弄崩这个程序!

在OO课中,另一个收获就是安全的程序设计。我觉得对于OIer而言,正缺少这种训练。信息学竞赛题目的输入往往是符合题目中所给的格式的。而现实中用户的输入却往往是不可测的,那么对于程序员来说,保持程序的正常工作是非常重要的。因为用户不希望看到的是程序的崩溃,我们也必须尽可能地让程序能显示错误信息或者提示信息。

在这么几次的训练当中,我收获了一些经验:

  1. 每当使用条件语句的时候,尽量考虑处理每一个分支的情况。特别是看似无法达到的分支,一定要加入出错提示。
  2. 在使用数组索引时,都要检查索引是否越界。
  3. 如果一个对象可能为null,则必须在调用该对象方法前检查其是否为null,以免出错。
  4. 必要时候给可能出问题的语句加入try{}catch(Exception e){}