微信扫一扫
分享到朋友圈

世界上最好的编辑器Vim:1700多页数学笔记是如何实时完成的

作者:机器之心 来源:机器之心 公众号
分享到:

04-01

选自 castel.dev

作者:Gilles Castel

机器之心编译


一般你是用手写还是 MarkDown 做数学笔记?在这篇文章中,作者介绍了如何用 LaTex 和 Vim 实时做数学笔记,通过一系列炫酷的技巧,不论是表达式板书还是图像绘制,我们都能实时跟得上。


在的学习过程中,很多时候都需要手动推导或最优化过程。这些推导很可能是老师在黑板上一边写一边解释,有时候我们会仔细听解释,并顾不上记笔记与注解。因此课程音频与视频就很重要了。但如果我们能跟得上老师的手写速度,那么以后回忆这些笔记就方便很多。


例如在最近结课的 CS224n 2019 中,Christopher Manning 在第一节课就手动推 Word2Vec 的最优化与权重更新过程。由此可见,飞一般地记数学笔记还是很有必要的。


图片截图来自 CS224n 2019


在这篇文章中,作者在攻读数学专业学士学位的第二个学期开始用 LaTex 做课堂笔记,自此以后一直在使用,总共记下了 1700 多页的笔记。以下是一些例子,你可以看看用 LaTex 做出笔记是什么样子的。




这些包括图表在内的笔记,都是在上课期间完成的,之后没有修订过。为了使 LaTex 做笔记可行,作者在头脑中设下以下四个目标:


  • 利用 LaTex 记文本和数学公式应与讲师在黑板上写字的速度保持一致:不允许延误。

  • 画图的速度应尽量与讲师保持一致。

  • 做笔记,如添加注解、编辑所有注解、整合最后两堂课的内容、搜索注解等,应方便快捷。

  • 当我们想在 pdf 文件旁边添加注释时,利用 LaTex 应能够实现这一目的。


以下从 Vim+LaTex 到 Snip­pets,作者介绍了如何科学地记数学笔记。


Vim 和 LaTex


我使用 Vim 在 LaTex 中记文本和数学公式。Vim 是一个功能强大的通用文本编辑器,可扩展性很强。我使用 Vim 写代码、LaTex、mark­down 等一切基于文本的东西。Vim 具有一个非常陡峭的学习曲线,一旦你弄清楚了基本原理,则很难再使用那些缺少 Vim 快捷键绑定的编辑器。以下是我编辑 LaTex 时屏幕的样子:



左边是 Vim,右边是我的 pdf 查看器 Zathura(也有类似于 Vim 的快捷键绑定)。我正使用带有 bspwm 的 Ubuntu 作为自己的窗口管理器。我在 Vim 使用的 LaTex 插件是 vimtex。该插件提供句法高亮显示、内容图表以及 synctex 等。使用 vim 插件的设置如下所示:


Plug 'lervag/vimtex'
let g:tex_flavor='latex'
let g:vimtex_view_method='zathura'
let g:vimtex_quickfix_mode=0
set conceallevel=1
let g:tex_conceal='abdmg'


最后两行的设置隐藏属性。这是一个特征,当你的光标不在那一行时,LaTex 代码会被替代或隐藏。通过隐藏 \[、\] 和$等标志符,你可以更舒服地浏览文件。这一特征也以∩替代\bigcap,以∈替代\in 等。以下动画应能够使这一过程更清楚。



这篇博客要解决的核心问题是:用 LaTex 做笔记如何与讲师在黑板上写字的速度保持一致。这都要归功于 snippet。


Snippets


一个 snippet 是一段可重复使用的短文本,可用来编辑其他文本。例如,当我键入 sign 并按下 Tab 时,单词 sign 将会补全为一个自定义的签名。



Snippet 也可以是动态的:当我键入 today 并按下 Tab 时,单词 today 将会被当前日期替代;键入 box Tab 变成一个可以自动增大的框。



你甚至可以在另一个 snippet 中使用 snippet。



利用 UltiSnip 创建 Snippet


我使用插件 UltiSnip 来管理我的 snippet,其设置如下:


lug 'sirver/ultisnips'
let g:UltiSnipsExpandTrigger = '<tab>'
let g:UltiSnipsJumpForwardTrigger = '<tab>'
let g:UltiSnipsJumpBackwardTrigger = '<s-tab>'


定义 sign 的 snippet 的代码如下所示:


snippet sign "Signature"
Yours sincerely,

Gilles Castel
endsnippet


对于动态 snippet,你可以在倒引号``之间输入代码,并在 snippet 扩展时运行。我在这里使用 bash 编译器格式化当前日期:date + %F。


snippet today "Date"
`date +%F`
endsnippet


你也可以在`!p ... `块内部使用 Python。你可以看一下定义 box 的 snippet 代码:


snippet box "Box"
`!p snip.rv = '┌' + '─' * (len(t[1]) + 2) + '┐'`
│ $1 │
`!p snip.rv = '└' + '─' * (len(t[1]) + 2) + '┘'`
$0
endsnippet


这些 Python 代码块将会被变量 snip.rv 的值替代。在这些代码块内部,你可以访问 snippet 的当前状态,如 t[1] 包含第一个制表位,fn 表示当前文档名称。


LaTeX snippet


借助于 snippet,利用 LaTeX 做笔记要比手写快得多。一些复杂的 snippet 可以为你节省大量时间并消除做笔记的挫败感。让我们开始了解一些简单的 snippet。


环境


为了嵌入环境,我必须在一行开端键入 beg。之后我键入环境名称,后者会直接在\end{} 指令中映出。按下 Tab 使光标位于新创建的环境中。



该 snippet 的代码如下所示:


snippet beg "begin{} / end{}" bA
\begin{$1}
    $0
\end{$1}
endsnippet


b 意味着该 snippet 将只能在一行开端扩展,并且 A 代表自动扩展,这意味着我不需要键入 Tab 就可以扩展 snippet。制表位--即可以通过按下 Tab 和 Shift+Tab 跳转到的地方--以$1、$2 等表示,同时最后一个为$0。


inline math 和 display math


我最常使用的两个 snippet 是 mk 和 dm。这些 snippet 负责数学代码的开始。第一个是 inline math snippet,第二个是 display math snippet。



in­line math snippet 是「智能的」:它知道何时在$符号后嵌入一个位置。当我在结尾$的正后方开始键入一个单词时,它添加一个位置。但是,当我键入一个非单词字符时,它不添加一个位置,例如下图的$p$-value。



该 snippet 的代码如下所示:


snippet mk "Math" wA
$${1}$`!pif t[2] and t[2][0] not in [',', '.', '?', '-', ' ']:
    snip.rv = ' 'else:
    snip.rv = ''
`$2endsnippet


第一行结尾处的 w 意味着该 snippet 将在词边界扩展,所以举例而言,hellomk 不会扩展,而 hello mk 会扩展。


diaplay math snippet 更简单,但同时也相当方便;该 snippet 使我在一段时间内不会忘记结束方程式。



snippet dm "Math" wA
\[
$1
.\] $0
endsnippet


上下标


另一个有用的 snippet 主要针对下标,该 snippet 自动将 a1 更改为 a_1 以及 a_12 更改为 a_{12}。



该 snippet 代码使用正则表达式作为触发器。当你在 [A-Za-z]\d 编码的数字后面键入一个字符,或者在 _以及两个数字 [A-Za-z]_\d\d 后面键入一个字符时,触发器会扩展该 snippet。


snippet '([A-Za-z])(\d)' "auto subscript" wrA
`!p snip.rv = match.group(1)`_`!p snip.rv = match.group(2)`
endsnippet

snippet '([A-Za-z])_(\d\d)' "auto subscript2" wrA
`!p snip.rv = match.group(1)`_{`!p snip.rv = match.group(2)`}
endsnippet


当你在使用圆括弧包装部分正则表达式时,如 (\d\d),你可以通过 Python 中的 match.group(i) 在扩展 snippet 中使用它们。


对于上标而言,我使用 td 自动扩展为 ^{} 的。但是,对于平方、立方、以及一小部分其他常见上标,我使用 sr、cb、comp 等专门的 snippet。



snippet sr "^2" iA
^2
endsnippet

snippet cb "^3" iA
^3
endsnippet

snippet compl "complement" iA
^{c}
endsnippet

snippet td "superscript" iA
^{$1}$0
endsnippet


分数


用于分数的 snippet 是我使用最方便的自动扩展方法之一,它可以进行以下扩展:



第一个的代码非常简单:


snippet // "Fraction" iA
\\frac{$1}{$2}$0
endsnippet


第二和第三个例子使用正则表达式匹配 3/、4ac、 6\pi^2/、a_2/等表达式。


snippet '((\d+)|(\d*)(\\)?([A-Za-z]+)((\^|_)(\{\d+\}|\d))*)/' "Fraction" wrA
\\frac{`!p snip.rv = match.group(1)`}{$1}$0
endsnippet


如你所见,正则表达式可能非常复杂,这里有个图表可以解释:



在第四、第五个例子中,它试图找到匹配圆括弧。由于不能使用 Ul­tiSnips 的正则表达式引擎,我转向了 Python:


priority 1000
snippet '^.*\)/' "() Fraction" wrA
`!p
stripped = match.string[:-1]
depth = 0
i = len(stripped) - 1
while True:
    if stripped[i] == ')': depth += 1
    if stripped[i] == '(': depth -= 1
    if depth == 0break;
    i -= 1
snip.rv = stripped[0:i] + "\\frac{" + stripped[i+1:-1] + "}"
`{$1}$0
endsnippet


最后一个与分数有关的 snippet 即使用你的选择来制作分数。你可以先用它选择一些文本,然后按下 Tab,打出/,然后再按下 Tab。



这些代码用到了 ${VISUAL} 变量表示你的选择。


snippet / "Fraction" iA
\\frac{${VISUAL}}{$1}$0
endsnippet


sympy 和 Math­e­mat­i­ca


另一个很酷但不太常用的 snippet 是利用 sympy 评估数学表达式。例如,sympy Tab 键扩展为 sympy | sympy,sympy 1 + 1 sympy Tab 键扩展为 2。



snippet sympy "sympy block " w
sympy $1 sympy$0
endsnippet

priority 10000
snippet 'sympy(.*)sympy' "evaluate sympy" wr
`!p
from sympy import *
x, y, z, t = symbols('x y z t')
k, m, n = symbols('k m n', integer=True)
f, g, h = symbols('f g h', cls=Function)
init_printing()
snip.rv = eval('latex(' + match.group(1).replace('\\''') \
    .replace('^''**') \
    .replace('{''(') \
    .replace('}'')') + ')')

`
endsnippet


Math­e­mat­i­ca 用户也可以进行类似的操作:



priority 1000
snippet math "mathematica block" w
math $1 math$0
endsnippet

priority 10000
snippet 'math(.*)math' "evaluate mathematica" wr
`!p
import subprocess
code = 'ToString[' + match.group(1) + ', TeXForm]'
snip.rv = subprocess.check_output(['wolframscript''-code', code])
`
endsnippet


后缀 snippet


值得分享的还有后缀 snippet,如 phat → \hat{p},zbar → \overline{z} 等。一个类似的 snippet 是后缀向量,如 v → \vec{v}。这些 snippet 真的非常节省时间,因为你可以借此跟上老师板书的节奏。



注意,我还是可以用 bar 和 hat 前缀,我以较低的优先级将它们添了进去。那些 snippet 的代码如下:


priority 10
snippet "bar" "bar" riA
\overline{$1}$0
endsnippet

priority 100
snippet "([a-zA-Z])bar" "bar" riA
\overline{`!p snip.rv=match.group(1)`}
endsnippet


priority 10
snippet "hat" "hat" riA
\hat{$1}$0
endsnippet

priority 100
snippet "([a-zA-Z])hat" "hat" riA
\hat{`!p snip.rv=match.group(1)`}
endsnippet


snippet "(\\?\w+)(,\.|\.,)" "Vector postfix" riA
\vec{`!p snip.rv=match.group(1)`}
endsnippet 


其他 snippet


我还有 100 多个其他常用的 snippet,其中多数非常简单。例如,!>变成 \mapsto,->变成\to 等。


下载地址:https://castel.dev/tex-e0c2e8b64036f77db00411d562750c12.snippets



上下文管理


写这些 snip­pet 的时候需要考虑:这些 snippet 和普通文本有冲突吗?例如,我的词典里有大约 72 个英文单词、2000 个包含 sr 的荷兰语单词,也就是说,如果我打出 disregard,sr 就会变成^2,我会得到 di^2egard。


解决方案是在 snippet 中加入一个上下文管理文(Con­text)管理方法。使用 Vim 的句法高亮,就可以根据你是在写数学还是文本来决定 Ul­tiSnips 是否应该扩展 snippet。我的想法如下:


global !p
texMathZones = ['texMathZone'+x for x in ['A''AS''B''BS''C',
'CS''D''DS''E''ES''F''FS''G''GS''H''HS''I''IS',
'J''JS''K''KS''L''LS''DS''V''W''X''Y''Z']]

texIgnoreMathZones = ['texMathText']

texMathZoneIds = vim.eval('map('+str(texMathZones)+", 'hlID(v:val)')")
texIgnoreMathZoneIds = vim.eval('map('+str(texIgnoreMathZones)+", 'hlID(v:val)')")

ignore = texIgnoreMathZoneIds[0]

def math():
    synstackids = vim.eval("synstack(line('.'), col('.') - (col('.')>=2 ? 1 : 0))")
    try:
        first = next(
            i for i in reversed(synstackids)
            if i in texIgnoreMathZoneIds or i in texMathZoneIds
        )
        return first != ignore
    except StopIteration:
        return False
endglobal


现在你可以将 context "math()"添加到你只想在数学上下文中扩展的 snippet 了。


注意,「数学上下文」这个说法也很微妙。有时候你通过使用\text{...} 将一些文本添加到数学环境中。在那种情况下,你不想让 snip­pet 扩展。然而,在\[ \text{$...$} \] 中,你又需要扩展。所以「数学上下文」这个说法有点不好界定,如下图所示:



实时纠正拼写错误


尽管学习数学是我做笔记的一个重要部分,但大部分时间我都在打英语单词。我的打字技术还不错,每分钟 80 词左右,但我还是会时不时地出错。所以我在 Vim 上添加了快捷键绑定,纠正拼写错误,以免打断我的工作流程。我按下 Ctrl+L 键就可以纠正之前的拼写错误,就像这样:



我的拼写检查设置如下:


setlocal spell
set spelllang=nl,en_gb
inoremap <C-l> <c-g>u<Esc>[s1z=`]a<c-g>u


它跳转到之前的拼写错误 [s,然后选取第一个建议 1z=,接下来跳回 `]a。中间的<c-g>u 使得快速纠正拼写错误成为可能。


结论


使用 Vim 中的 snip­pet 使得书写 LaTeX 不再那么头疼,反而成为一种享受。与实时拼写检查结合之后,记数学笔记变得非常舒服。后续博文将讨论数字绘图及将图嵌入 LaTex 文本等内容。虽然前期学习成本会有一些,但熟悉后板书推导就能飞一般地记载。


原文链接:https://castel.dev/post/lecture-notes-1/



本文为机器之心编译,转载请联系本公众号获得授权

✄------------------------------------------------

加入机器之心(全职记者 / 实习生):hr@jiqizhixin.com

投稿或寻求报道:content@jiqizhixin.com

广告 & 商务合作:bd@jiqizhixin.com

阅读38187
最好 数学 如何 
举报0
关注机器之心微信号:almosthuman2014

用微信扫描二维码即可关注
声明

1、头条易读遵循行业规范,任何转载的稿件都会明确标注作者和来源;
2、本文内容来自“机器之心”微信公众号,文章版权归机器之心公众号所有。

评论
更多

文章来自于公众号:

机器之心

微信号:almosthuman2014

邮箱qunxueyuan#163.com(将#换成@)
微信编辑器
免责声明
www.weixinyidu.com   免责声明
版权声明:本站收录微信公众号和微信文章内容全部来自于网络,仅供个人学习、研究或者欣赏使用。版权归原作者所有。禁止一切商业用途。其中内容并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。如果您发现头条易读网站上有侵犯您的知识产权的内容,请与我们联系,我们会及时修改或删除。
本站声明:本站与腾讯微信、微信公众平台无任何关联,非腾讯微信官方网站。