歡迎光臨
每天分享高質量文章

1700頁數學筆記火了!全程敲程式碼,速度飛快易搜尋,硬核小哥教你上手LaTeX+Vim

(點選上方快速關註並設定為星標,一起學Python)

曉查 乾明 發自 凹非寺 

轉自: 量子位 | 公眾號 QbitAI

 

又出現一位“神仙”本科生!

數學課上,全程鍵盤手打1700頁筆記。

速度緊追老師板書,公式、圖形一個不落。

效果?請看下圖:

不僅排版媲美教科書,而且還能夠批註,檢索關鍵詞……

筆記被他Po到網上之後,便引來大量圍觀。

不到一天,相關推文就已經有2000多贊,Hacker News論壇上蓋了200多樓。

甚至有網友評論稱:“你就是我們需要的英雄!”

他是怎麼做到的呢?秘密武器就是:LaTeX+Vim

這位來自歐洲的小哥非常強烈安利Vim文字編輯器,他說:

用LaTeX寫數學公式,我選Vim編輯器。它強大、通用、可擴充套件性很強。只要是基於文字的任務我都用它,寫程式碼、編輯LaTeX、寫markdown都是。

雖然入門階段的學習曲線超級陡峭,但只要掌握了基本的操作方式,就會欲罷不能。

下麵就讓我們看一下他完成這一壯舉的具體流程,文中提到的工具下載地址,我們都附在了最後。

快速上手教程

我們先看看小哥的工作環境配置。

他用Vim編輯LaTeX的場景,就像下麵這樣:

左邊是Vim,右邊是pdf閱讀器Zathura,它也有類似Vim的快捷鍵。

小哥用的作業系統是Ubuntu,使用bspwm作為視窗管理器。在Vim中,使用的LaTex外掛是vimtex,它有語法高亮顯示、目錄檢視、同步物件等功能。

然後,使用vim-plug做如下配置:

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程式碼就都會隱藏或者替換成其他符號。

比如說在下麵動圖裡,隱藏了[,],$之後,沒有了它們的幹擾,整個檔案就更易讀。這個功能還會用∩替代igcap,∈替代in等等。

設定完成,接下來就到了整個教程的精華所在:

用LaTeX記筆記,怎麼才能像老師寫板書一樣快?

這就是片段(snippets)發揮作用地方了。

片段

片段是什麼?

片段是一小段可復用的文字,由其他文字觸發。

例如,輸入sign,再按下Tab鍵,這個單詞就會自動擴充套件為一段簽名:

片段也可以是動態的:輸入today並按下Tab鍵,它就會變成當前的日期。

而輸入box按Tab,就會出現一個框,還會隨著輸入文字自動變大。

片段,甚至可以巢狀在另一個片段裡用:

怎麼建立片段?使用UltiSnips

管理片段的外掛UltiSnips,小哥是這樣配置的:

Plug 'sirver/ultisnips'
let g:UltiSnipsExpandTrigger = ''
let g:UltiSnipsJumpForwardTrigger = ''
let g:UltiSnipsJumpBackwardTrigger = ''

關於sign片段的程式碼如下:

snippet sign "Signature"
Yours sincerely,

Gilles Castel
endsnippet

對於動態的片段,你可以將程式碼放在“之間, 在片段擴充套件的時候,就會執行。下麵的例子,就是用 bash 格式化當前日期:date+%f。

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

你也可以在!p …程式碼塊裡使用Python,比如上面box片段的程式碼就是這樣的:

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

這些 Python 程式碼塊將被變數 snip.rv 的值替換。在這些程式碼塊中,你可以訪問程式碼段的當前狀態,例如t[1]包含第一個製表位,fn是當前檔案名等等。

LaTex片段

使用片段編寫LaTeX,要比純手工編寫快得多。特別有些非常複雜的片段能幫你大大節約時間,有效防止抓狂。

下麵是一些非常有用且容易上手的片段:

環境

想插入一個環境,只需要在一行的開頭輸入beg。然後鍵入環境的名稱,這個名稱在end{}命令中也是一樣。按下Tab鍵,就能夠將游標放置在新建立的環境中。

這個片段的程式碼如下:

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

其中,b表示這個片段只會在程式碼行的開頭展開,A代表自動展開,也就是說不用按Tab鍵了。製表位(Tab stop)——也就是你可以透過按Tab 和Shift+Tab跳轉到的位置——用$1、 $2、……來表示,最後一個用$0。

行內和數學顯示

在記數學筆記的過程中,最常用的兩個片段是mk和dm。

它們負責啟動數學樣式。第一個片段用於“行內數學”,第二個用於“顯示數學”。

程式碼行內的數學片段是“智慧的”:它知道什麼時候在$符號後面直接輸入一個單詞,它會自動加個空格。但如果輸入一個非單詞的字元,它就不會新增空格了,比如在““$p$-value”情況下,是這樣的:

這個片段的程式碼如下:

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

第一行末尾的w,意味著這個片段會在單詞邊界處擴充套件,例如,hellomk不會擴充套件,但是hello mk會。

用於顯示數學的片段更簡單,也更加方便;有了它,你可能再也不會忘記用句號結束方程了。

程式碼:

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

小寫和上標

另一個很有用的片段就是下標。能夠把a1改為a1,把a_12改為a{12}。

這個片段的觸發器是使用正則運算式。有兩種情況會擴充套件片段。一是你鍵入一個字元,後面跟著一個數字,比如[A-Za-z]d;另一種是,一個字元後面有並跟著兩個數字,比如[A-Za-z]dd。

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])_(dd)' "auto subscript2" wrA
`!p snip.rv = match.group(1)`_{`!p snip.rv = match.group(2)`}
endsnippet

當你使用括號將正則運算式的一部分裝在一個組中時,例如(dd),你可以在 Python中透過match.group (i)來使用它們擴充套件片段。

至於上標,可以使用td,它就會變成^{}。然而,對於平方、立方和其他一些常見的片段,可以使用專門的程式碼片段,如 sr、cb等等。

效果圖:

程式碼:

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

分數

分數是一個用起來最方便的一個片段,擴充套件的形式如下:

/ / → frac {}{}
3 / → frac {3}{}
4 pi ^ 2 / → frac {4 pi ^ 2}{}
(1 + 2 + 3) / → frac {1 + 2 + 3}{}
(1 + (2 + 3) /)→(1 + frac {2 + 3}{})
(1 + (2 + 3)) / → frac {1 + (2 + 3)}{

第一個片段的程式碼很簡單:

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

第二個和第三個示例,可以使用正則運算式來匹配3/、4ac/、6pi^2/、a2/等運算式。

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

看了上邊這些,你可能覺得正則運算式太難了。沒關係,下麵有一個解釋得非常直觀的圖表:

在第四和第五種示例下,要換一種方法。使用UltiSnips的正則運算式引擎解決不了的,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] + "rac{" + stripped[i+1:-1] + "}"
`{$1}$0
endsnippet

這裡最後要分享的關於分數的片段,能根據你的選擇,來生成一個分數。

你可以先選擇一些文字,然後按Tab鍵,繼續輸入、然後再按Tab鍵。

程式碼中,使用${VISUAL}變數來表示所選的內容。

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

Sympy和Mathematica

還有一個很酷但用得不多的片段,是使用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

用Mathematica,也可以做類似的事情:

片段程式碼:

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

字尾片段

除了上邊這些之外,字尾片段也很值得分享。例如phat→hat{p}和zbar→overline{z}。還有類似的字尾向量,例如v,.→vec{v}和v.,→vec{v}。.和,的順序沒關係,所以可以同時按下它們兩個。

這些片段真的可以節省時間,可以按照和老師寫板書一樣的順序來記。

註意,bar和hat字首也依然可以用,只要以較低的優先順序新增它們就行。

這些片段的程式碼是:

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 

其他片段

此外,小哥還有大約100個常用的片段(下載地址附於文末),大多數都很簡單。比如,輸入!>變成mapsto,輸入->變成o等等。

fun變成f: R o R :,!>變成mapsto,->變成o,cc變成subset。

lim變成lim{n o infty},sum變成sum{n = 1}^{infty},ooo變成infty。

特定課程的片段

除了一些常用的片段,也可以針對特定的課程設定片段。例如,在量子力學這門課中,可以設定一些關於bra/ket符號的片段。

→ket{a}
|ψ>→ket{psi}
→raket{a}{b}

程式碼:

snippet "bra" riA
ra{`!p snip.rv = match.group(1).replace('q', f'psi').replace('f', f'phi')`}
endsnippet

snippet "|(.*?)>" "ket" riA
ket{`!p snip.rv = match.group(1).replace('q', f'psi').replace('f', f'phi')`}
endsnippet

snippet "(.*)ra{(.*?)}([^|]*?)>" "braket" riA
`!p snip.rv = match.group(1)`raket{`!p snip.rv = match.group(2)`}{`!p snip.rv = match.group(3).replace('q', f'psi').replace('f', f'phi')`}
endsnippet

背景關係

在編寫這些片段時需要考慮的一件事是,“這些片段會與長與常用的文字衝突嗎?”

例如,在英語中大約有72個單詞包含sr,這意味著當輸入disregard這個詞時,sr會擴充套件到^2,出現一個di^2egard。

這個問題的解決方案是,為程式碼片段新增背景關係。

透過使用 Vim 的語法突出顯示,可以確定UltiSnips是否應該擴充套件片段,這取決於你使用的是數學還是文字。

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()”新增到只希望在數學背景關係中展開的片段中。

context "math()"
snippet sr "^2" iA
^2

endsnippet

請註意,“數學背景關係”是一個微妙的東西。 有時你可以使用ext{…}在數學環境中新增一些文字。在這種情況下,你不需要擴充套件片段。但是,在以下情況下: [ ext{$…$} ],它們可以擴充套件。 這就是為什麼math背景關係的程式碼有點複雜。下麵的動圖說明瞭這些微妙之處。

除了上述一些片段,你也可以根據自己的需要,來自己新增一些外掛或者片段,來提高自己的效率。

用筆還是用電腦?

純手打記下1700頁數學筆記,awesome都不夠形容了這位小哥了,堪稱理工科學生中的“英雄”。

並非所有人都贊同小哥的做法,強大的高科技工具在傳統面前常常會被質疑。

有部分網友認為手寫比電腦打字印象深刻,而且要達到這位小哥的熟練程度,恐怕LaTeX和Vim得練習好幾年。

既然用筆更方便,為什麼還要用電腦來記筆記呢?原因很簡單:字太醜!

如果記下來的內容連自己看的慾望都沒有,怎麼複習課堂筆記呢?至少用電腦記下來的排版工整,讓人賞心悅目。

雖然國外網友爭論不休,但在國內只要一個條件就可以徹底否決這個方法:不讓帶電腦進課堂。

對此,你怎麼看?

工具傳送門:

Linux和Mac系統自帶Vim。

Windows使用者安裝Vim:
https://ftp.nluug.nl/pub/vim/pc/gvim81.exe

Vim外掛管理:
https://github.com/junegunn/vim-plug

Vim上的LaTeX外掛:
https://github.com/lervag/vimtex

視窗平鋪管理器:
https://github.com/baskerville/bspwm

管理Vim片段工具:
https://github.com/SirVer/ultisnips

如果你用不慣Vim,還有Emacs、Atom、VS Code、Sublime,它們都有LaTeX外掛,總有一款文字編輯器適合你。

LaTeX常見數學符號輸入方法:
https://en.wikibooks.org/wiki/LaTeX/Mathematics

想要熟悉更多的LaTeX使用方法,就需要系統地學習,平時多加練習也必不可少。

博文連結:

https://castel.dev/post/lecture-notes-1/

    已同步到看一看
    贊(0)

    分享創造快樂