SSH 用起来有一个不方便的地方,就是断线就要重连,不用个 screen / tmux 啥的浑身不舒服。这种现象在台式机上还好,因为网络稳定,但到了笔记本 / 手机之类的设备上,从休眠中恢复、网络断开啥的并不鲜见。mosh 的全称是 mobile shell,顾名思义,正是专为解决这些「移动」客户端的烦恼而设计的 (b ̄(I) ̄)b
安装非常简单,各大平台都有二进制包可以直接使用,比如:
pacman -S mosh # Arch Linux
brew install mosh # Mac OS X
官网上列有一大堆其它平台的安装方法和源码编译指南:https://mosh.mit.edu/#getting
安装好的 mosh 分三部分:
mosh
mosh-server
mosh-client
其中,我们直接使用的是 mosh
。用法和 ssh
一样,比如连接服务器:
mosh user@host
mosh 会先通过 SSH 连接到服务器,然后启动服务器上的 mosh-server
,启动成功后 SSH 被断开,一切就交给本地的 mosh-client
来和远程的 mosh-server
用 UDP 在 60000-61000 端口通信了。
如果要透过 mosh
给 ssh
传其它参数,那么就需要麻烦一些了,比如:
# 指定 SSH 端口
# mosh 自己的 -p 参数用来是指定 mosh-server 和 mosh-client 通信的 UDP 端口的
mosh --ssh="ssh -p 1024" user@host
使用过程中,如果网络延迟比较大,你会发现 mosh 不像 SSH 一样需要「盲打」,而是直接打出带下划线的字符。这就是 mosh 提供的「预测」模块的作用:在延迟比较大的时候,根据本地输入提前输出预期的结果,等收到远程服务器的实际结果后替换之。
网络意外断开时,窗口顶部会提示断开的时间和可用的转义序列,等网络恢复、顶部的提示消失,一切就会跟啥事都没发生过一样……
吃完饭在微博上看到 CoolShell 上发了一个解谜游戏,类似于很久以前很流行的那种黑客游戏。咱自然是要试试的啦~由于中午在父母家吃饭,电脑上没有任何 IDE 之类的,于是只能用在线 IDE 了。
严重剧透请注意!
第一关对于听说过 BrainFuck 的人都木有难度,正文里也有明显的提示。代码可以直接在 IDEOne 运行。
第二关是联想题:
- 2, 3, 6, 18, 108, ?
- What is the meaning of life, the universe and everything?
尽管后者我知道是《银河系漫游指南》的梗,但是前者我盯着看了半天没看出来,尝试了各种方法也没有线索……正在濒临崩溃之际,小 Q 童鞋跑过来扫了一眼屏幕,然后立马告诉我「2×3=6、3×6=18……」。我看了看……哦,好像确实哦……于是更加崩溃了 Orz...
第三关是关于 Dvorak 键盘布局的题目,可以直接通过在线工具转换。转出来的是一份 C 语言代码,其中涉及的原理在很久以前的这篇《String as Array Index》里有提到,当然最简单的解法还是直接通过 IDEOne 运行,嘿嘿。
第四关扫二维码后可以发现是一个字符映射表,用 Python 写个简单的脚本来转即可。解码后得到一句话,大意是让你用 rot13
去加密一个单词,同样找一个在线工具就可以轻松搞定了。
第五关的喵星人貌似难住了不少人。唯一的线索似乎只有明文给出的「回文」两个字,从网页源代码里找出所有回文词以后就没了头绪。正在一筹莫展之际,又是小 Q 同学叼着根棒冰过来,看了眼题目后很快就高屋建瓴地指出:前两个字符必须是一个大写字母、一个数字,中间的字符必须是小写。我抱着试一试的心态用了一疗程,结果居然真的拼出了一个单词 Orz...
第六关的提示是「勇往直前」,点击图片后可以得到一个莫名其妙的数字,数字所在页面的 URL 里也有个莫名其妙的数字。把 URL 里的数字换成 0、1 之类的没有反应,尝试直接把数字作为答案也不对,最后终于想起了「把网页返回的数字放到 URL 里」的方法。于是发现这个页面类似于随机数生成器:输入一个数字,返回下一个数字。结合「勇往直前」的提示,于是写了一个程序不断重复此过程,在若干次迭代后就得到了正确答案。
第七关是最蛋疼的二叉树题:根据中序、后序遍历二叉树的结果重建二叉树,然后找出最长路径,就可以得到密码。带入用题目中给出的命令对密文解密即可。今天是我第一次用 Python 写二叉树相关的东西,感觉好极了 :)
第八关是 N 皇后问题。题目中给出了一个八皇后的解,可以由此得到 code
与「解」的对应关系,剩下的就是找九皇后的解了。大学的课程设计让我对八皇后留下了非常不好的印象,感觉非常古板枯燥。所幸懒人有懒福,我很快发现维基百科英文版里有一个九皇后的解,而把这个解代入题中正好 OK,耶~ :P
第九关其实是一个 26 进制数的问题,没啥特别的。
第十关光看题目没啥思路,题目中说「如果你知道上面两张图是什么意思,那这道题很容易」,但我显然不清楚。所幸此处的图片命名都很科学,根据图片的命名,找到了编码方法(即便命名没有规律,用 Google 图片搜索也可以搜到),于是答案很容易就找到了。
做完后唯一的感觉是:对 Q 爷的敬佩真如滔滔江水……
以上。
前几天找资料,顺手看到了这篇博文,讲了博主如何优化自己以前的算法的过程。于是立马想起了 GoingNative 2013 上的演讲 C++ Seasoning 以及前不久看到的这篇 From goto to std::transform。于是不禁想,「寻找前 K 大数」这样的任务,能不能直接用 C++ 标准库来完成?如果能的话,性能又如何呢?于是我重新登录了那个以前做了两道题就尘封多年的 OJ 帐号……
凭着最直观最 naïve 想法,第一个版本我用了 std::sort
来代替手工排序。不出所料,这样的做法跑了 562 ms,要排到 700 名开外的样子。
#include <stdio.h>
#include <algorithm>
#include <functional>
#include <vector>
int main()
{
int numVillagers;
int topCount;
while (scanf("%d %d\n", &numVillagers, &topCount) == 2) {
if (numVillagers == 0 && topCount == 0) {
break;
}
std::vector<int> wealth(numVillagers);
for (int i = 0; i < numVillagers; i++) {
scanf("%d", &(wealth[i]));
}
std::sort(wealth.begin(), wealth.end(), std::greater<int>());
if (topCount > numVillagers) {
topCount = numVillagers;
}
for (int i = 0; i < topCount - 1; i++) {
printf("%d ", wealth[i]);
}
printf("%d\n", wealth[topCount - 1]);
}
}
于是优化:考虑到每次新建 std::vector
的开销,把 wealth
拎出来放到循环外,reserve()
题目中的最大值,每次在循环里重新 resize()
后,时间顿时缩短到了 296 ms,可以排到 180+ 的样子。
std::vector<int> wealth;
wealth.reserve(100000);
而在前面说过的演讲中,我还听到了一个之前从未留意的函数 std::nth_element
。其作用是确保调用后第 N 个元素是范围内第 N 大的元素。调用后,[begin, N) 内的任意元素都小于 (N, end) 内的任意元素。
std::nth_element(wealth.begin(), wealth.begin() + topCount, wealth.end(), std::greater<int>());
std::sort(wealth.begin(), wealth.begin() + topCount, std::greater<int>());
尝试修改成以上的略显罗嗦的代码后,程序运行时间缩短到了 171 ms,可以排到 70+ 的样子。时间上和博文中给出的最小堆实现相同,但是内存占用要比它大很多。
既然上面是先用 std::nth_element
大致排序了一下,而后再用 std::sort
排序前半部分,那么,STL 里是否存在一次性只排 [begin, N] 范围内的数,而无视 (N, end) 内的顺序的函数呢?答案是存在,可以直接使用 std::partial_sort
解决。
#include <stdio.h>
#include <algorithm>
#include <functional>
#include <vector>
int main()
{
int numVillagers;
int topCount;
std::vector<int> wealth;
wealth.reserve(100000);
while (scanf("%d %d\n", &numVillagers, &topCount) == 2) {
if (numVillagers == 0 && topCount == 0) {
break;
}
wealth.resize(numVillagers);
for (int i = 0; i < numVillagers; i++) {
scanf("%d", &(wealth[i]));
}
if (topCount > numVillagers) {
topCount = numVillagers;
}
std::partial_sort(wealth.begin(), wealth.begin() + topCount, wealth.end(), std::greater<int>());
for (int i = 0; i < topCount - 1; i++) {
printf("%d ", wealth[i]);
}
printf("%d\n", wealth[topCount - 1]);
}
}
此时的程序执行时间 156 ms,排名第 25 位。而截止到此时,我个人其实什么都没有干,实际任务都交给了 STL。
如同各种 MyString
、MyVector
一样,一般情况下自己去实现这些通用算法在 STL 面前几乎没有任何优势。尤其是 C++11 带来 lambda 表达式以来,大大方便了 <algorithm>
中各种算法的使用,我觉得不去用 STL 的理由越发稀少了。
以上。
经过 @Neuron Teckid 童鞋的提点,发现了 git bisect
这个非常有意思的工具。
话说,我第一眼把 bisect 看成了 biscuit,以为 Git 也开始学 Android 卖萌了呢……结果一查字典,这个词是「等分」的意思……
假设某天你发现你编译出的程序里有个 Bug,该如何找出它是从哪个版本开始引入的呢?在版本历史中找出有 Bug 和无 Bug 两个版本,用简单的二分查找法就可以定位啦。git bisect
正是用来帮助你完成这种二分查找法的自动化的。
基本用法
git bisect start # 初始化二分查找
git bisect bad # 标记当前版本存在问题
git bisect good 38a63d9 # 标记 38a63d9 版本没有问题
至少标记了一个没有问题的版本(Good)和一个有问题的版本(Bad)后,Git 就会开始二分查找的过程,不断检出 Good 和 Bad 中间的版本等待你检查后作出标记。每次检出后 Git 都会提示你还剩多少文件、大致还剩余多少次比较:
Bisecting: 441 revisions left to test after this (roughly 9 steps)
比如上面这三步后,Git 检出了中间版本 b17ff03
。你编译运行后发现这个版本没有问题,就可以用 git bisect good
将当前版本标记为没有问题。此时 Git 就会再把 b17ff03
和最初版本之间的区域二分,检出中间版本等待你的检查。
等一切完成以后,就可以用 git bisect reset
返回开始前的版本。
自动查找
手动标记 Good / Bad 可以帮助人类从挑选下一个合适的版本的工作中解放出来。(似乎可以理解为 C++ 从 for
到 std::for_each
的抽象过程。)但这还是远远不够的,因为测试某个版本是否正常的重任依旧需要人类的介入。
所幸你可以指定一个检测用的程序,让 git bisect
自动完成整个定位的过程。这个程序必须在当前版本没有问题时返回 0,而 1 到 127 之间的值则表示有问题(特殊值 125 表示没法确定)。
git bisect run <cmd>...
例如,让你从一个陌生的代码库里找到能够编译和不能编译的临界提交,我们可以通过传入 make
来实现自动化查找:
git bisect start HEAD 38a63d9 # 简单写法:初始化二分查找,HEAD 有问题,而 38a63d9 没有
git bisect run make # 利用 make 来检测某个版本是否能够通过编译
而后,Git 就会自己用二分查找法不断检出中间版本,每次检出后都会运行 make
,根据 make
的返回值来确定当前版本是否存在问题(是否能够通过编译)。
以上。
在介绍 Git 的时候,大多数文章都会提到它在 Working Copy 和 Repository 之间新增的 Staging Area,它使得你可以只提交 Working Copy 中的一小部分。作为一个半路出家的 Git 山寨用户,我之前知道的也就只是这三个地方了,不过这两天发现了第四个区域:Stashing Area。
假设你刚把代码改得乱七八糟,忽然想要从修改前的某个版本里做一个小改动,马上出一个紧急修正版。此时理论上,你只需先提交当前所有改动,然后就可以马上切换到以前的版本/分支开始工作了。但是作为「每次一提交都要能够通过编译」原则的忠实粉丝,这种思路所可能产生的垃圾提交是完全无法忍受的。
一种比较山寨的做法就是,手动把已修改的文件复制出去,然后 git checkout
回修改前的版本,最后切换版本/分支开始工作。做完以后再切换回来、把之前复制出去的文件复制回来。
Stashing Area 直接翻译过来是储藏间,是 Git 中用来暂存已作出的修改的地方,可以避免这种手动做法的繁琐。
git stash save
将当前 Working Copy 中的修改保存为 Stash 中的一条新的记录,Working Copy 则变成了修改前的样子。
- 切换到别的版本/分支干活。
- 切换回原先的版本/分支。
git stash pop
将 Stash 中最新的记录取出,并应用到 Working Copy 上。(从名字上可以看出,Stash 是一个栈式结构。)
Stash 的另一个方便之处是 git stash branch
,它可以直接从 Stash 上创建一个分支。比如在你刚把代码改得乱七八糟,忽然想起来自己忘了新建分支的时候很有用。
相关信息
- «
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- ...
- 15
- »