季承成 | CHENGCHENG JI

季承成 | CHENGCHENG JI

计算机科学与信息技术 博士 | Ph.D. in Computer Science and Information Technology

记一面试题: 数0

今天参与了一次某测试工程师技术面试,被问了一个很有意思的问题。
现在趁记忆比较新鲜,记录下来。
虽然这两天才开始学习python,不过由于坚信最好的学习语言的方法就是去使用,于是尝试使用python来解答。
不过似乎对于面试而言,思路会更重要一些,毕竟面试时还是有一定心理压力的说,尽管面试官还是很nice的
虽然并没有在现场直接给出完整的代码,不过从思考、解决问题的角度上来说,自觉勉强算及格吧。

问题描述

给定一个数组[n_0,n_1,...,n_k],其中n_i,k都为任意正整数且0<=i<=k,返回相乘每一位n_i所得结果的数字尾部连续0的个数。

例如:

[5,6,7,8]
=>1
# 即相乘结果为5*6*7*8=1680, 尾部共有1个连续的0

[138, 10, 6, 155, 158]
=>2
# 即相乘结果为138*10*6*155*158=202777200,尾部共有2个连续的0

思路

  1. 一个直白的思路是,把每一位相乘,接着去数尾巴的0

乘起来,存到product里:

nums=[...]
product=1
# calculate the product
for i in range(0,len(nums)):
    product*=nums[i]

这样问题就变成了对product数尾巴连续0的问题,解法的话直观上可以:

1-a. 不断地除以10,输出因数10的个数

# divide n by k, return the number of divisor k
# e.g., divide(100,10) => 2
def divide(n,k):
    counter=0
    while n % k == 0 and n!=0:
        n=n//k
        counter+=1
    return counter

print("direct calculation:",divide(product,10))

1-b. 转换成字符串,使用正则表达式截取尾巴所有的0:

_res=0
matchres=re.match(r"(\d*)[^0](0*)$",str(sum))
if (matchres):
    _res=len(matchres.group(2))

print("direct regex:",_res)

  1. 不过面试官的意思是可以不用全部乘出来,也可以解,于是我就开始思考:

如果数字尾部为0,那么意味着是10的倍数,同时也意味着有因数25
进一步的,如果数字尾部为00,那么意味着是100的倍数,同时也意味着有因数2,5,2,5

通过归纳,可以得知,尾部的0的个数与25的对数有关。

然而思考到这里我却不断地被我自己的直觉判断所影响,始终挥之不去一个不靠谱的怀疑:这样的方式会不会反而降低效率?

当时我单纯的想法是直白的版本的时间复杂度也就是一个O(n)+O(logn),而这个算法是O(n)其中对于每个n又有一个最坏O(logk)的处理。
于是我犹豫不前,不断地打消这个念头去尝试寻找新的可能性,不过花了很长时间都没有找到更好的。

可是我应该注意到的是,使用算法2的好处在于可以避免大数的计算,光这一个优势我就应该把它实现出来再说。

但是对于问题的执念,使得到了第二个问题我仍然在脑海中建立了与上一个问题的链接,以至于关于TCP至少三次握手都没能说明清楚。

惭愧惭愧。

不管如何,我还是实现了一下做了下对比,事实证明我当时那个直觉是不正确的,算法2即便在int没有上限的python里仍然有着无可比拟的优势。

five=0
two=0
for i in range(0,len(nums)):
    five+=divide(nums[i],5)
    two+=divide(nums[i],2)

print("tricky one:",min(five,two))

跑了一些随机数据,如下,算法2(tricky one)完胜。

----------------
tricky one: 2449 
in 0.165548589s

direct calculation: 2449 
in 0.398511995s

direct regex: 2449 
in 0.409039028s
----------------

----------------
tricky one: 2392 
in 0.167644693s

direct calculation: 2392 
in 0.383790995s

direct regex: 2392 
in 0.394147758s
----------------

----------------
tricky one: 24404 
in 0.563190995s

direct calculation: 24404 
in 21.995120594s

direct regex: 24404 
in 22.996077246s
----------------

感谢这次面试,让我又学到了很多。

正则表达式

前言

看了下人工智能关键词下的人气关键词,基本上提到机器学习、大数据的薪资相对比较高。
虽然我自己的专业就是人工智能的归纳定理自动证明,不过由于我的研究是前瞻的、理论的,未来50年也许能落地,到时候变成香饽饽倒是可以预见。
然而到那之前还是得生活啊。

顺应时代,准备对一些能落地的技术进行学习,关键词:
Hadoop, TensorFlow

不过今天想要先把正则表达式的部分复习、整理一下(想起了学自动机的日子),供以后查阅,捣鼓vim脚本

概要

深入浅出地介绍正则表达式
正则表达式速查表

关于正则表达式

正则表达式(regular expression,regex,正規表現)是指使用一个字符串来表达字符串集合的一种方式。

一个简单的例子:

^[0-9]+abc$
  • ^符号用来定义字符串开始位置。
  • [0-9]意思是匹配一位数字,而后面紧跟着的+则表示可匹配一位或多位。
  • abc匹配字母abc,而最后一个符号$则定义了字符串结束位置。

因此该正则表达式将会匹配字符串123abc或者43abc等等。

需要注意的是,如果对多行字符串如123abc\n43abc进行匹配,将返回空。

再来一个例子:

^[0-9a-z_-]{3,15}$

该正则表达式可以匹配任何长度在315之间仅仅包含字母、数字、下划线_和连字符号-的字符串。

有了以上两个例子,我们可以给出正则表达式的具体定义:

正则表达式是由普通字符(如a-z,A-Z)和特殊字符(元字符)组成的文字模式。

普通字符

普通字符是指没有显式指定为元字符的所有可打印字符不可打印字符

普通字符包括:所有大写和小写字母,数字,所有标点符号和一些其他符号。

不可打印字符

不可打印字符意味着它们将会被转义:

字符 描述
\cx 匹配一个由x指明的控制符。例如,\cm匹配为Ctrl+M(回车符)。x的值必须为a-z或A-Z,否则会直接被认为是原义字符’c’
\f 匹配一个换页符。等价于 \x0c 和 \cL
\n 匹配一个换行符。等价于 \x0a 和 \cJ
\r 匹配一个回车符。等价于 \x0d 和 \cM
\s 匹配任何种类的一个空白字符,包括空格,制表符,换页符等。等价于[ \f\n\r\t\v],注意 Unicode 正则表达式会匹配全角空格符。
\S \s的反义,匹配任何一个非空白字符。等价于 [^ \f\n\r\t\v]
\t 匹配一个制表符。等价于 \x09 和 \cI
\v 匹配一个垂直制表符。等价于 \x0b 和 \cK

特殊字符

特殊字符是指具有特殊意义的字符。如需匹配它们需要使用\进行转义。

字符 描述
$ 匹配字符串的结尾位置。如果开启Multiline属性,那也会匹配换行处\r\n,\n,\r
() 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用
* 匹配前面的子表达式0次或多次
+ 匹配前面的子表达式1次或多次
. 匹配除换行符之外的任何单字符
[ 标记一个中括号表达式的开始
? 匹配前面的子表达式0次或一次,或指明一个非贪婪限定符
\ 转义符,将下一个字符标记为一个不可打印字符、或一个原义字符、或一个向后引用、或一个八进制转义符。
^ 匹配字符串的开始位置;当在方括号[]中被使用时,则表示非这些字符的集合。例如,[abc]将匹配a或b或c,[^abc]将匹配任何非a、b、c的一位字符
{ 标记限定符表达式的开始

限定符

限定符是由特殊字符组成,用来指定正则表达式的一个组件必须要重复多少次才能满足匹配,分为如下6种:

字符 描述
* 匹配前面的子表达式0次或多次。例如:zo*能匹配"z","zo","zoo", 等价于zo{0,}
+ 匹配前面的子表达式1次或多次。例如:zo+能匹配"zo","zoo"但不匹配"z", 等价于zo{1,}
? 匹配前面的子表达式0次或1次。例如:do(es)?能匹配"do"或"does", 等价于do(es){0,1}
{n} 匹配前面的子表达式n次(n是一个非负整数)。例如:o{2}会匹配"cool"里的"oo",但不会匹配"fox"的"o"或"coool"里的"ooo"
{n,} 匹配前面的子表达式至少n次(n是一个非负整数)。例如:o{2,}会匹配"cool"、"coool"里的"oo"和"ooo",但不会匹配"fox"的"o"。'{0,}’等价于’*’, ‘{1,}’等价于’+’
{n,m} 匹配前面的子表达式k次(n和m都是非负整数, n<=m, n<=k<=m)。例如:o{2,3}会匹配"cooool"里的前3个"ooo"。'{0,1}’等价于’?’

例子:
可以使用如下正则表达式来匹配章节编号

Chapter [1-9][0-9]*

该表达式将会匹配Chapter n,其中n>=1(从第一章开始的所有章节)。

如果章节被限定在99以内,尝试使用如下表达式

Chapter [0-9]{1,2}

不过很显然的,这样做同时也会匹配Chapter 0;于是有了如下的版本:

Chapter [1-9][0-9]?
//等价
Chapter [1-9][0-9]{0,1}

不过要注意的是如果章节超过100,将会仅匹配前两位。

限定符的非贪婪匹配

限定符*,+,?默认是贪婪匹配的,也就是会尽量匹配更多的字符。
比如:

<h1>这是一个例子</h1>

如果使用<.*>进行匹配,会得到

<h1>这是一个例子</h1>

而如果在这之后加上?,变成<.*?>进行匹配的话,将会单独匹配到首标签<h1>
这是因为<h1>是第一个满足条件的字符串,但要注意这并不说明<.*?>无法匹配尾标签</h1>

如果仅仅想要匹配首标签<h1>,可以使用<\w+?><\w+>来进行匹配。

定位符

定位符可以将正则表达式定位到一些具体的位置,如字符串的开头或结尾,或者单词的边界。

字符 描述
^ 定位到字符串开始位置。如果multiline属性被启用,那么也会匹配换行符\n,\r之后的位置。
$ 定位到字符串结束位置。如果multiline属性被启用,那么也会匹配换行符\n,\r之前的位置。
\b 定位到单词的边界。即空格与字之间的位置
\B 定位到非单词的边界位置

注意,这些定位符都是无法和限定符同用的(比如:^*是错误的表达式),因为没有意义。

例子:
章节会在该行的行首,因此可以加入^进行定位:

^Chapter [1-9][0-9]{0,1}

同时由于章节出现的那一行不会再有其他字符,所以它也应该是该行最后的字符串,所以在后面可以加入$进行定位:

^Chapter [1-9][0-9]{0,1}$

关于边界定位符\b的使用,也很直观,比如:\bCha就能匹配Chapter里的Chater\b能匹配Chapter里的ter
边界定位符\B则会匹配所有非边界位置,比如:\Bapt\B会匹配Chapter里的apt,但并不会匹配aptitude里的apt

选择

使用()将所有选择项都包含起来,并且使用|隔开,注意默认情况下每一项都会被缓存。如果不希望被缓存,可以在该括号内的开头使用?:来取消该项的缓存。

而在?后面加上的?=,?!,?<=,?<!四种表达有更特殊的含义,用下面的例子来说明。
例子用字符串:

java6 java7

正向肯定预查?=,使用java(?=6)会匹配第一个java6 java7,意味着匹配后面跟着6java
正向否定预查?!,使用java(?!6)会匹配第二个java6 java7,意味着匹配后面不跟着6java;
逆向肯定预查?<=,使用(?<=j)a会匹配到两个a:java java,意味着匹配前面有ja
逆向否定预查?<!,使用(?<!j)a会匹配到两个a:java java,意味着匹配前面没有ja

反向引用缓存

使用圆括号()可以将其匹配到的子串进行缓存,按照括号从左到右的顺序,一共有从1号开始到99号缓存区。
每个缓存区可以使用\n来进行访问,其中1<=n<=99

利用这个功能,可以查找两个相同的、相邻的单词的匹配。
例子:
要查找的字符串:

Locate locate and filter filter the duplicated duplicated substrings.

使用如下正则表达式:

\b([a-z]+) \1\b

可以得到

filter filter

如果开启全局匹配模式,则可以得到

filter filter
duplicated duplicated

如果在开启全局匹配的同时再开启忽略大小写,则可以得到

Locate locate
filter filter
duplicated duplicated

利用缓存区,可以轻松地分割URL。比如:

https://capital-g.cc:443/#/blog

使用如下表达式进行分割匹配:

(^\w+)://(\w[^:/]+)(:\d+)?([^ ]+$)

匹配结果:

Found value: https://capital-g.cc:443/#/blog
Stored value: https
Stored value: capital-g.cc
Stored value: :443
Stored value: /#/blog

正则表达式速查

符号 意义
\ 转义符,将下一个字符标记为一个不可打印字符、或一个原义字符、或一个向后引用、或一个八进制转义符。
^ 匹配字符串的开始位置;当在方括号[]中被使用时,则表示非这些字符的集合。例如,[abc]将匹配a或b或c,[^abc]将匹配任何非a、b、c的一位字符
$ 匹配字符串的结尾位置。如果开启Multiline属性,那也会匹配换行处\r\n,\n,\r
* 匹配前面的子表达式0次或多次。例如:zo*能匹配"z","zo","zoo", 等价于zo{0,}
+ 匹配前面的子表达式1次或多次。例如:zo+能匹配"zo","zoo"但不匹配"z", 等价于zo{1,}
? 匹配前面的子表达式0次或一次,或指明一个非贪婪限定符
{n} 匹配前面的子表达式n次(n是一个非负整数)。例如:o{2}会匹配"cool"里的"oo",但不会匹配"fox"的"o"或"coool"里的"ooo"
{n,} 匹配前面的子表达式至少n次(n是一个非负整数)。例如:o{2,}会匹配"cool"、"coool"里的"oo"和"ooo",但不会匹配"fox"的"o"。'{0,}’等价于’*’, ‘{1,}’等价于’+’
{n,m} 匹配前面的子表达式k次(n和m都是非负整数, n<=m, n<=k<=m)。例如:o{2,3}会匹配"cooool"里的前3个"ooo"。'{0,1}’等价于’?’
. 匹配除换行符之外的任何单字符。如需要匹配换行符可以使用(.
(regex) 匹配regex并缓存该结果,在表达式内引用使用\num, 在结果引用使用$num, 99>=num>=1
(?:regex) 匹配regex但不缓存该结果,引用方式同上
str(?=regex) 正向预查。匹配具有regex后缀的str。例如:对于"java6 java7",使用java(?=6)来匹配前一个"java"
str(?!regex) 否定正向预查。匹配不具有regex后缀的str。例如:对于"java6 java7",使用java(?!6)来匹配后一个"java"
str(?<=regex) 逆向预查。匹配具有regex前缀的str。例如:对于"java",使用(?<=j)a来匹配前一个"a"
str(?<!regex) 否定逆向预查。匹配不具有regex前缀的str。例如:对于"java",使用(?<!j)a来匹配后一个"a"
a|b 匹配a或者b
[abc] 匹配[]中任意一个字符。例如:对于"carry",使用[abc]可以匹配到"a"
[^abc] 不匹配[]中任意一个字符。例如:对于"carry",使用[^abc]可以匹配到"r","r","y"
[a-z] 匹配小写字母a-z之间的字符。
[^a-z] 匹配非小写字母a-z之间的字符。
\b 定位到单词的边界。即空格与字之间的位置
\B 定位到非单词的边界位置
\d 匹配一个数字字符,等价于[0-9]
\D \d的反义,匹配一个非数字字符,等价于[^0-9]
\s 匹配任何种类的一个空白字符,包括空格,制表符,换页符等。等价于[ \f\n\r\t\v],注意 Unicode 正则表达式会匹配全角空格符。
\S \s的反义,匹配任何一个非空白字符。等价于 [^ \f\n\r\t\v]
\w 匹配数字、字母和下划线。等价于[a-zA-Z0-9_]
\W \w的反义,匹配任何一个非数字、字母或下划线。等价于[^a-zA-Z0-9_]
\cx 匹配一个由x指明的控制符。例如,\cm匹配为Ctrl+M(回车符)。x的值必须为a-z或A-Z,否则会直接被认为是原义字符’c’
\f 匹配一个换页符。等价于 \x0c 和 \cL
\n 匹配一个换行符。等价于 \x0a 和 \cJ
\r 匹配一个回车符。等价于 \x0d 和 \cM
\t 匹配一个制表符。等价于 \x09 和 \cI
\v 匹配一个垂直制表符。等价于 \x0b 和 \cK
\xNum 匹配十六进制数据。Num为十六进制数据,支持ASCII码
\Num 表达式内引用Num号缓存,1<=Num<=99,如果不存在该缓存,则当做八进制处理
\uNum 匹配一个Unicode码。

MacOS 10.9.5开启原生NTFS格式移动硬盘读写支持

前言

因为在整理书柜的时候发现很多U盘,寻思着如果能整合到一块移动硬盘里就不会担心搬家的时候少了一个掉了一个。
于是买了块移动硬盘,不过当有了移动硬盘才发现其实台式机里也有许多要备份的东西,万一带不回来呢
那很显然就是需要支持Windows和Mac的读写了,考虑到exFat对老版本Windows的支持,主要是想捣鼓,就用NTFS吧

概要

开启MacOS的内置硬盘读写功能
添加快捷方式

网上找了一圈攻略关键词看似很多,但内容都差不多且我这里也不能用,好消息是最后还是解决了,于是记录下来。

开启原生NTFS格式移动硬盘读写支持

据说MacOS在很久之前开启过对NTFS的原生读写支持,不过后来因为某些原因而取消了。
好在仍然可以手动开启。

  1. 确定硬盘的标签或UUID
//查看已挂载的卷
$ ls /Volumes/
HDPH-UT		Macintosh HD

//查看该移动硬盘信息,这里是HDPH-UT
$ diskutil info /Volumes/HDPH-UT/
...
Volume Name:              HDPH-UT
Escaped with Unicode:     HDPH-UT
//这个Escaped with Unicode就是等会脚本里要用到的
...
  1. 开启该硬盘的NTFS读写
$ sudo vifs
//注意这里要使用vifs工具来编辑fstab,直接用vi在/etc/生成fstab无效

进入该文件后添加一行:

LABEL=HDPH-UT none ntfs rw,auto,nobrowse

顺带man了一下手册,翻译记录一下参数意义:

字符串 意义
LABEL=HDPH-UT 挂载卷名。注意是Escape之后的名称,带空格等特殊名称需要转义特殊符号。
none 挂载点。可以自行指定挂载点,这里不使用挂载点的话就为none.
ntfs 挂载盘的文件系统,包括:hfs, nfs, ms-dos, cd9660, fdesc, union. 这里并不包含ntfs,你懂的。
rw 读写权限。rw为读写ro为只读;不常用的包括:作为交换分区sw和完全忽略该设备xx.
auto 自动挂载。noauto改为不自动挂载。
nobrowse 不在finder或桌面显示。注意如果没有这个参数将无法开启NTFS读写。
  1. 推出移动硬盘,重新连接
    这样这个NTFS系统的移动硬盘就被悄悄地可读写地挂载了。

添加快捷方式

虽然被挂载了,不过如果要使用GUI访问的话,可以创建一个快捷方式到桌面:

$ sudo ln -s /Volumes/HDPH-UT/ ~/Desktop/HDPH-UT

然后将其拖拽到finder的侧边栏就ok啦,撒花。

快速发布Vim写的Markdown到博客网站 (一键编译内容至剪贴板,无需插件)

概要

将编译出来HTML本体部分复制到剪贴板, 绑定到Vim快捷键
从而达成一键将Markdown文件编译并将代码本体部分拷贝到剪贴板,以便直接贴到博客里

自从有了Vim一键编译Markdown文件加预览,已经基本达成了日常使用需要。不过如果要发到自己的博客,则需要手动查看源代码并复制相关部分,然后拷贝到博客,不是很方便。如果可以直接一键获得编译好的内容直接贴到博客就好了,于是有了这篇文章。

思路为先编译出HTML代码然后打印到控制台然后使用命令行工具将内容拷贝到剪贴板,
由于笔者使用的是Mac所以使用pbcopy命令。

Markdown-it编译脚本

研究了下gulp-markdown-github-style模块发现内部其实调用的是Markdown-it的模块来渲染md文件然后通过拼接带有CSS的模版文件来实现github式的页面的,
于是去查阅了一下Markdown-it文档,写了一个可以支持内嵌HTML标签的md编译脚本,将其保存为markit.js并放在md源文件同一目录(也可以通过修改vim配置里路径来放至任意位置):

/**
 *	Author: Chengcheng Ji
 *	Date: 2019.3.11
 *	Website: capital-g.cc
 * */
var md = require('markdown-it')({
	html: 		true,	// 开启源代码HTML标签支持(即不进行转义)
	xhtmlOut: 	false,	// 使用'/'来结束单标签(例如<br />)
	breaks: 	false,	// 将'\n'转换为换行标签<br>
	langPrefix: 'language-',//代码块的语言名前缀,用于第三方代码高亮(如highlight.js)
	linkify:	false,	//自动将网址转换成HTML链接
	typographer:false,	//是否开启语言替换和引号美化
	quotes: '""``',	//引号美化参数
	highlight: function (/*str, lang*/) { return ''; }	//代码高亮设定," return '' "即为无特殊设定
});
var fs = require("fs");
//使用外部参数提示符"--file"
var file, i = process.argv.indexOf("--file");
if(i>-1) {
	file = process.argv[i+1];
}
//异步读取--file 标示的目标md文件
fs.readFile(file, function (err, data) {
	if (err) {
		return console.error(err);
	}
	//在编译后的HTML部分加上标签用于CSS
	var result = "<article class=\"markdown-body\">"+md.render(data.toString())+"</article>";
	//将完整结果输出到控制台,后续将其拷贝至剪贴板
	console.log(result);
	//将结果同时输出到./wordpress.txt中,可选
	fs.writeFile('wordpress.txt', result,  function(err){
		if (err){
			console.log(err);
		}
	});
});

绑定Vim快捷键

打开~/.vimrc, 添加如下代码,将以上功能绑定到F3:

" 按F3编译MD文件并拷贝HTML到剪贴板
map <F3> :call CompileMDtoClip()<CR>
imap <F3> <ESC>:call CompileMDtoClip()<CR>
func! CompileMDtoClip()
    exec "w"
    if &filetype == 'markdown'
        " 调用上面的markit.js并将其拷贝到剪贴板
        exec "!node markit.js --file % | pbcopy"
    endif
endfunc

这样就可以使用F3一键编译md文件并把文件内容拷贝至剪切板,接下来只要去博客网站按下Ctrl+v即可,撒花。

VIM的中文输入法问题


前言

自从多年前MacBook Pro重装更新以后就把它丢在一边投奔轻便的Air,已经有很久很久没有去弄Vim了。
寻思着将来的工作可能要写一些带中文的文档,觉得有必要把这个部分”捣鼓”一下。
这么一想又觉得早就该把各种自发的”捣鼓”都记录下来,虽然在正儿八经搞研究的时候都写成笔记本了,导致现在把这一箱箱的书本寄回国也成了个不大不小的麻烦。


言归正传,这篇文章的概要

能够使得Vim在输入模式下使用搜狗输入法输入中文,然后按Esc直接进入普通模式并自动切换成英文输入法进行命令操作。
而当再次进入输入模式时,会自动回到搜狗输入法,无需切换。

使用VimIM+SmartIM.

发现在使用Vim输入中文的时候有一个非常恼人的问题,那就是Vim在输入模式用中文输入法进行中文输入后,直接按Esc并不会切回普通模式,必须切回Eng然后再Esc才行。
经过一番捣鼓,发现有如下两个插件可以使用:

  • VimIM
    1. VimIM是一个Vim插件,实现了一个内嵌中文输入法框架,可以支持多种中文输入法并且默认支持云输入。
    2. 安装时只要下载vimim.vim到/usr/share/vim/vim73/plugin/中即可。
    3. 使用时用快捷键ctrl+-开启/关闭,ctrl+^切换输入法,ctrl+l切换全角和半角
    4. 然而,由于未知原因该插件开启时会自动进入vim兼容模式导致方向键失去作用,暂时并未启用
  • smartIM
    1. smartIM也是一个Vim插件,它的主要功能是能记录插入模式和普通模式下的输入法,使用Esc无缝切换
    2. 安装时下载插件本体smartIM.vim和与系统沟通程序im-select,拷贝到/usr/share/vim/vim73/plugin/
    3. 问题是在切换时会有1-2s的延迟会出现按了Esc也并没有切过去的情况

总结如果单独使用存在如下的问题:

  1. 单独使用VimIM并不能很好地兼容搜狗输入法
  2. 单独使用smartIM切换时会有1-2s延迟

而如果同时使用, 既可以很好地兼容搜狗输入法且也能很好地切换,实现一个近乎完美的效果。(不过仍然会有Vim进入兼容模式问题。)

都安装好之后,就可以进入输入模式,然后手动command+space切出搜狗输入法(或者其他任何输入法),输入中文。接下来在模式之间切换都不用去担心输入法的问题,插件会自动记忆并进行切换啦。

暂时就这样吧,好在平时写代码英文切英文不会有这个问题。

实际使用下来,发现其实已经习惯在中文输入后使用日文键盘的英文切换键,果然习惯才是最强大的。

使用Vim写Markdown
(编译,语法高亮和浏览器预览)


概要

使用Node.js的模块来编译md文件到html和pdf文件。
并使用vim脚本来绑定该编译功能到Vim编辑器。
添加vim对md文件的语法高亮支持。
支持一键编译,预览md文件。

编译Markdown文件

  1. 使用Node.js包管理工具npm安装编译md文件所需的模块gulp
//全局安装
$ npm -g i gulp
//依赖安装
$ npm i gulp --save-dev
  1. 安装两个模块,一个用来输出html (github格式),另一个用来输出pdf
//在项目文件夹下
$ npm i gulp-markdown-github-style
$ npm i gulp-markdown-pdf
  1. 写入gulp任务文件
    在项目文件夹下新建名为gulpfile.js的文件:
//载入模块
var gulp = require("gulp");
var markdown = require('gulp-markdown-github-style');
var mdpdf = require('gulp-markdown-pdf');

//接收外部参数,将--file后的参数写入变量file中,和vim宏绑定使用
var file, i = process.argv.indexOf("--file");
if(i>-1) {
    	file = process.argv[i+1];
}
//调用html模块输出, 使用file参数来定位文件源,输出到note_html文件夹中
gulp.task("compileMDToHtml", () =>
    gulp.src(file)
        .pipe(markdown())
        .pipe(gulp.dest('note_html'))
);
//调用pdf模块输出, 同上
gulp.task("compileMDToPdf", () =>
    gulp.src(file)
        .pipe(mdpdf())
        .pipe(gulp.dest('note_pdf'))
);
//安排任务同步进行,此处的回调函数done()为必须
gulp.task("default", gulp.parallel("compileMDToHtml", "compileMDToPdf", function(done) {
  done();
}));

Vim脚本绑定快捷键

.vimrc文件中加入编译部分,该配置文件可在~/.vimrc找到(或新建一个)。

"按F2编译运行,必须打开类型检测
map <F2> :call CompileRunGcc()<CR>
imap <F2> <ESC>:call CompileRunGcc()<CR>
func! CompileRunGcc()
    exec "w" 
    if &filetype == 'c' 
        exec "!g++ % -o %<"
        exec "! ./%<"
    elseif &filetype == 'cpp'
        exec "!g++ % -o %<"
        exec "! ./%<"
    elseif &filetype == 'java' 
        exec "!javac %" 
        exec "!java %<"
    elseif &filetype == 'sh'
        :!./%
    elseif &filetype == 'markdown'
        exec "!gulp --file %"
    endif
endfunc
" 开启文件类型检测
filetype plugin indent on  
" 添加markdown文件类型
autocmd BufNewFile,BufRead *.md set filetype=markdown

添加Markdown语法高亮

添加markdown文件语法高亮
觉得比较好的能在terminal使用的语法高亮文件https://github.com/plasticboy/vim-markdown
syntax/markdown.vim拷贝到 ~/.vim/下,并在.vimrc中加入

" 添加markdown文件语法文件
au! Syntax markdown source ~/.vim/markdown.vim

虽然在terminal下,强调和斜体存在一些问题,不过并不影响使用,毕竟已经可以用网页和pdf来看效果啦。

增加开启浏览器预览功能

既然已经把编译也加进去了,那么再加个自动打开编译后的html来预览的功能吧。
思路是获取生成的html的文件名然后调用默认浏览器打开。
查阅帮助手册:help expand,发现在vim脚本中对当前文件标示符%有如下的扩展:

Modifiers:
                        :p              expand to full path
                        :h              head (last path component removed)
                        :t              tail (last path component only)
                        :r              root (one extension removed)
                        :e              extension only

举一些用法上的例子:

" 假设完整文件路径为: /A/B/C/file.md
%					file.md
%:p					/A/B/C/file.md
%:p:h					/A/B/C/
%:p:t					file.md
%:p:r					/A/B/C/file
%:p:t:r					file
%:r					file
%:e					md

于是我们只要在配置文件.vimrc中,在编译之后加入自动打开该文件的部分即可:

elseif &filetype == 'markdown'
    " 这里改成仅编译html,会更快一些,当然如果喜欢预览pdf也可以自行修改
    exec "!gulp compileMDToHtml --file %"
    " 浏览器开启html预览
    exec "!open ./note_html/%:r.html"
endif

至此使用Vim写markdown,编辑,编译,预览一条龙,撒花。