(點選上方快速關註並設定為星標,一起學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 == 0: break;
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/
朋友會在“發現-看一看”看到你“在看”的內容