置頂介紹文

這裡主要放一些關於數學和 LaTeX 的文章, 文章內的檔案一律用 xelatex 編譯而成。
如果你想下載我的 tex 檔回去修改編譯, 請將 preamble.7z 解壓縮後和 tex 檔放在同一個資料夾中即可編譯。
部落格內的文章也有部份是網路或書籍中的資料經過統整編輯而成, 如有侵權請告知。
有任何問題也歡迎留言或 E-mail 給我。

2017年1月18日 星期三

LaTeX 自訂巨集 - 題目與解答

看這篇之前, 可以參考一下這篇《用 LaTeX 做數學考卷 (六) : 答案卷與解答篇 -- 文字標籤與解答對應》, 不過這篇還是會把有用到的指令完整介紹。

這次我們的目標是要設計出下圖這樣的東西, 並且在文稿內要將題目和解答放在一起, 讓 LaTeX 自動幫我們分出題目和解答, 並且將解答自動編號排序下來。



因為這裡有大量的指令要介紹, 所以我們先列出一個大綱:
  1. 用 enumitem 套件來自訂條列式環境的樣式並設定隨文條列式 (inline) 的用法
  2. 用 counter 來做到計數自動化
  3. 用 label 和 ref 將輸入的文字排版到自訂的位置
  4. 用 loop 讓 latex 自動計算題數來排版解答
  5. 結語

這裡我們就先把上面那個文件的完整指令碼貼出來, 再慢慢來看

\documentclass[12pt]{article}
\usepackage[CJKnumber]{xeCJK}%中文
\setCJKmainfont[BoldFont={cwTeX Q Hei Bold}]{cwTeX Q Ming Medium} % 設預設中文字型及預設粗體
%\setCJKmainfont{思源黑體 TW}
%\setCJKmainfont{思源黑體}
\setCJKfamilyfont{kai}{cwTeX Q Kai Medium}% 楷書          
\setCJKfamilyfont{hei}{cwTeX Q Hei Bold}% 黑體
\setCJKfamilyfont{ming}{cwTeX Q Ming Medium}% 明體
\setCJKfamilyfont{yuan}{cwTeX Q Yuan Medium}% 圓體
\setCJKfamilyfont{fsong}{cwTeX Q Fangsong Medium}% 仿宋體

\newcommand{\kai}[1]{{\CJKfamily{kai}#1}}% 用 \kai{使用楷書}
\newcommand{\hei}[1]{{\CJKfamily{hei}#1}}% 用 \hei{使用黑體}
\newcommand{\ming}[1]{{\CJKfamily{ming}#1}}% 用 \ming{使用明體}
\newcommand{\yuan}[1]{{\CJKfamily{yuan}#1}}% 用 \yuan{使用圓體}
\newcommand{\fsong}[1]{{\CJKfamily{fsong}#1}}% 用 \fsong{使用仿宋體}

\newcommand{\Z}{\ming{。}}
\newcommand{\Q}{\ming{、}}
\newcommand{\ZB}{\hei{。}}
\newcommand{\QB}{\hei{、}}

\usepackage{amsmath, amsfonts, amssymb, bbding}%數學符號
\usepackage{graphicx}%圖形
\usepackage[colorlinks=true, linkcolor=black]{hyperref}%讓\label和\ref的連動在pdf 檔中產生超連結, 點擊即可跳頁
\usepackage{tcolorbox}

\everymath{\displaystyle}

\usepackage[inline]{enumitem}%與beamer相衝

\newlist{assessment}{enumerate}{1}%自我評量條列環境
\setlist[assessment]{labelindent=1\parindent, leftmargin=*,label*=\CrossOpenShadow\;\arabic*)}

\newlist{ill}{enumerate*}{1}
\setlist[ill]{label=(\arabic*)}

\newlist{ilc}{enumerate*}{1}
\setlist[ilc]{label=(\Alph*)}

%計數器(純數字) \no
\newcounter{nocounter}
\setcounter{nocounter}{1} 
\newcommand{\no}{\arabic{nocounter}\addtocounter{nocounter}{1}}

%自我評量解答與標籤(自動) \ansass{<answer>} (放在要填入的位置上) 要有hyperref套件
\newcounter{assessmentlabel}\setcounter{assessmentlabel}{0}
\makeatletter
\DeclareDocumentCommand{\ansass}{m g}{%
  \protected@write \@auxout {}{\string \newlabel {assessment\theassessmentlabel}{{\IfValueTF{#2}{#1, \par #2}{#1}}{\thepage}{\IfValueTF{#2}{#1, \par #2}{#1}}{assessment\theassessmentlabel}{}}}%
  \hypertarget{assessment\theassessmentlabel}{} \stepcounter{assessmentlabel}
}

\makeatother
%自我評量答案, 直接在格子中填入 \ansassis 即可
\newcounter{assessmentans}\setcounter{assessmentans}{0}
\newcommand{\ansassis}{\ref{assessment\theassessmentans}\stepcounter{assessmentans}}
%重複輸入, 一直到第一個{}內的數字才停止
\newcounter{assloop}
\setcounter{assloop}{1}
\newcommand\ansassloop[2]{
  \loop \ifnum\numexpr\value{assloop}-1 < #1
   #2%
    \stepcounter{assloop}%
  \repeat
}

\newcommand{\showtheanswer}{
\everymath{\textstyle}
\begin{footnotesize}
\begin{tcolorbox}[colback=white,title=\CrossOpenShadow 自我評量參考答案 \CrossOpenShadow]
\ansassloop{\theassessmentlabel}{(\no) \ansassis ; \;}
\end{tcolorbox}
\end{footnotesize}
\everymath{\displaystyle}
}

\begin{document}

\section{自我評量}
\begin{assessment}
\item \ansass{$\frac{28}{11}$} 求 $0.\overline{23}+2.3\overline{13}$ 的值\Z
\item \ansass{ACD} 下列哪些數值是有理數?
\begin{ilc}
\item $0.1\overline{3}$
\item $3+\sqrt{2}$
\item $0$
\item $\frac{\sqrt{75}}{\sqrt{12}}$
\item $\pi$
\end{ilc}
\item \ansass{$16$} $n\in\mathbb{N}$, $\frac{n}{5}<\sqrt{11}<\frac{n+1}{5}$, 則 $n$ 值為何?
\item \ansass{$6$} 已知 $\sqrt{16+\sqrt{252}}$ 的整數部份為 $a$, 小數部份為 $b$, 試求 $2a+b-\frac{3}{b}$ 的值\Z
\item \ansass{ACE} 若 $a$, $b$, $c$, $l$, $m$, $n$ 是實數, $a>b>c$, $l>m>n$, $a+b+c=0$, 則下列何者為真? \\
\begin{ilc}
\item $a>0$
\item $b>0$
\item $c<0$
\item $al>0$
\item $al+bm+cn>0$
\end{ilc}
\item \ansass{$6$, $(3,2)$} 設 $a$, $b$ 為正實數且 $2a+3b=12$, 則 $ab$ 的最大值為何? 此時 $(a,b)$ 分別為何?
\item \ansass{$0\leq\alpha\leq 17$, $-19\leq\beta\leq 9$, $1\leq\gamma\leq 109$, $\frac{1}{5}\leq\delta\leq\frac{3}{2}$} $x$, $y\in\mathbb{R}$, 若 $(x-2)^2\leq 1$ 且 $|y-\frac{7}{2}|\leq\frac{13}{2}$, 試求下列各式的範圍\Z \\
\begin{ill}
\item $\alpha=2x+y+1$, 
\item $\beta=x-2y$, 
\item $\gamma=x^2+y^2$, 
\item $\delta=\frac{3}{y+5}$\Z
\end{ill}
\item \ansass{$(-2,5)$} 若不等式 $|ax+1|\geq b$ 的解為 $x\geq 3$ 或 $x\leq -2$, 則數對 $(a,b)$ 為何?
\item \ansass{$-\frac{2}{3}<x<2$} 解不等式 $|x|+2|x-1|<4$\Z
\item \ansass{$20$, $7040$, $7039$} 令 $x=\sqrt{7}+\sqrt{3}$, $y=\sqrt{7}-\sqrt{3}$, 則 \\
\begin{ill}
\item $x^2+y^2$, 
\item $x^6+y^6$, 
\item $(\sqrt{7}+\sqrt{3})^6$ 的整數部份為何?
\end{ill}
\end{assessment}
\showtheanswer

\end{document}

接著就按著大綱來介紹完整的指令了。
  1. 用 enumitem 套件來自訂條列式環境的樣式並設定隨文條列式 (inline) 的用法
    我們藉由這個主題來介紹自訂 enumerate 的用法

    \usepackage[inline]{enumitem}%與beamer相衝

    \newlist{assessment}{enumerate}{1}%自我評量條列環境
    \setlist[assessment]{labelindent=1\parindent, leftmargin=*,label*=\CrossOpenShadow\;\arabic*)}

    \newlist{ill}{enumerate*}{1}
    \setlist[ill]{label=(\arabic*)}

    \newlist{ilc}{enumerate*}{1}
    \setlist[ilc]{label=(\Alph*)}

    \usepackage[inline]{enumitem} 使用套件 enumitem, 並設定 inline 以便設定隨文條列式。

    \newlist 定義新的條列式, 第一個 {} 是指對應的原條列式內容 (itemize; enumerate; description), enumerate* 則是隨文條列式 (inline); 第二個 {} 是指可用的(巢狀式)階層數。

    定義完之後"一定要"設定條列式的樣式 (\setlist), 至少要定義 \label, enumitem 可以讓我們非常自由的定義 \label 的樣式, 最簡單的樣式就是 \arabic (阿拉伯數字), 也可以用 \alph: 小寫英文字母; \Alph: 大寫英文字母; \Roman: 大寫羅馬數字; \roman: 小寫羅馬數字。

    leftmargin=* 用來設定左方留白, labelindent=1\parindent 則用來設定左方縮排, \parindent 是指段落開始時的縮排距離。

  2. 用 counter 來做到計數自動化
    在程式碼中我們用了大量的計數器 (counter), 因此我們一併介紹計數器的功能和用法。

    \newcounter{nocounter}\setcounter{nocounter}{1}
    \newcommand{\no}{\arabic{nocounter}\addtocounter{nocounter}{1}}

    用 \newcounter 設定一個計數器, 然後設定起始值 (\setcounter)。

    定義一個新的指令 (\newcommand{\no}) 將計數器以用阿拉伯數字顯示 (\arabic{}), 並在同時將計數器加 1 (\addtocpunter{}), 也可以用 \stepcounter{nocounter} 來讓計數器加 1。

    然而這個做法是讓文件顯示出數字, 如果要把數字讓 latex 拿來做內部的處理 (也就是說讓 latex 認得這個數字) 的話, 就要在這個計數器前加上\the, 變成 \thenocounter, 或是用指令 \value{nocounter}。

  3. 用 label 和 ref 將輸入的文字排版到自訂的位置
    這是整篇文章最關鍵的動作, 他可以讓文稿和編譯出來的稿件呈現截然不同的樣式, 用來呈現題目和答案分開的套件還有 answer 套件, 不過因為這個套件的靈活度相對較低, 所以我們自己寫。

    %自我評量解答與標籤(自動) \ansass{<answer>} (放在要填入的位置上) 要有hyperref套件
    \newcounter{assessmentlabel}\setcounter{assessmentlabel}{0}
    \makeatletter
    \DeclareDocumentCommand{\ansass}{m g}{%
    \protected@write \@auxout {}{\string \newlabel {assessment\theassessmentlabel}{{\IfValueTF{#2}{#1, \par #2}{#1}}{\thepage}{\IfValueTF{#2}{#1, \par #2}{#1}}{assessment\theassessmentlabel}{}}}%
    \hypertarget{assessment\theassessmentlabel}{} \stepcounter{assessmentlabel}
    }

    先設定一個計數器 assessmentlabel (因為我們不會在文稿中用到它, 所以故意讓名稱很長, 以免以後定義其他的會重複), 這個計數器比較特別的是他是從 0 開始的。

    定義一個新的指令 \ansass (這裡用 xfithen 套件裡的 \DeclareDocumentCommand 是純粹因為我懶得改, 這裡其實用 \newcommand 就可以了), 這個指令很長, 主要是將 \ansass{} 裡的文字貼上一個標籤 (label)。

    為了不要手動設定標籤, 所以我們利用計數器讓標籤能自動編號 (assessment\theassessmentlabel), 如上一條目所說的, 用 \theassessmentlabel 才能真的讓 latex 看懂這個數字, 所以指令最後就加上 \stepcounter{assessmentlabel} 讓計數器加 1。

    於是, 每當我們輸入一次 \ansass{}, 就會自動貼上標籤 assessment0, assessmnet1, .... 依序下去。而 \ansass{} 這個指令只會貼標籤, 是不會在文稿上顯示出任何文字的, 要顯示文字, 我們要用指令 \ref{}。(例如 \ref{assessmnet1}, 當然這很麻煩, 所以我們用自動計數+指令來簡化它)

    %自我評量答案, 直接在格子中填入 \ansassis 即可
    \newcounter{assessmentans}\setcounter{assessmentans}{0}
    \newcommand{\ansassis}{\ref{assessment\theassessmentans}\stepcounter{assessmentans}}

    接著我們定義一個新的計數器和指令, 讓 ref 也能自動編號的把剛剛所貼的標籤顯示出來。這個指令很單純, \ansassis 就是讓 \ref{} 的 {} 裡自動填上 assessment0, assessmnet1, ....。

  4. 用 loop 讓 latex 自動計算題數來排版解答
    雖然已經簡化了指令, 可是我們還是不想自己手動數我們到底輸入了幾個 \ansass 再重複的輸入 \ansassis 來編輯解答, 所以我們接下來再設定一個迴圈 (loop)。

    \newcounter{assloop}
    \setcounter{assloop}{1}
    \newcommand\ansassloop[2]{
    \loop \ifnum\numexpr\value{assloop}-1 < #1
    #2%
    \stepcounter{assloop}%
    \repeat }

    定義 \ansassloop{}{} 這個指令, 這個指令需要兩個參數, 第一個 {} 放數字, 代表要重複的次數, 第二個 {} 裡放要重複的文字。舉例來說, 如果輸入 \ansassloop{5}{xyz}, 那麼就會輸出 xyzxyzxyzxyzxyz。

    解釋一下指令內部。\ifnum 是條件式, 可用來比較數字大小, \numexpr 用來計算數字 (在 latex 裡直接輸入數字運算像 6+2 是沒有用的), 而條件式就是 assloop 這個計數器的值 \value{assloop} 減 1 < #1 (事實上就是計數器的值小於等於 #1) 的話, 就顯示 #2 並同時將計數器加 1, 然後重複 (\reapeat 回 \loop), 一直重複到計數器的值大於 #1 才停止。

    這個指令解決了重複輸入的問題, 接下來要解決手動數 \ansass 個數的問題。

    \ansassloop{\theassessmentlabel}{(\no) \ansassis ; \;}
    是的, 就這麼一行, 簡單的說, 就是把 \ansassloop 的第一個 {} 裡放上 \theassessmentlabel, 所以只要 assessmentlabel 這個計數器的值為何, (\no) \ansassis ; \; 就會重複幾次。而 assessmentlabel 這個計數器的值恰恰正好是文稿裡 \ansass 的個數了。(其實如果你很仔細的看, 你會發現這句話有點怪怪的, 至於是什麼問題以及為什麼我要這麼做, 這裡就賣個關子吧。)

    另外, 這個方法沒辦法用在 \item 上, 也就是說在 \ansloop 第二個 {} 放 \item 是沒有用的, 如果要用 \item, 必須改用 multido 這個套件。

  5. 結語
    最後簡單的把一些沒有說到的內容再補充一下。首先, 參考解答的方框我是用 tcolorbox 排版的, 可以去找他的介紹, 美化版面相當好用。

    \Z 和 \Q 這個指令是我用來強迫不論任何使用任何字體, 句號和頓號都要用 cwTeX 的字體排版 (也就是置於文章句末的下方)。

    \everymath{\textstyle} 和 \everymath{\displaystyle} 用來改變隨文數學式的大小。

    \CrossOpenShadow 這個十字架符號是來自於 bbding 這個 package。

3 則留言:

  1. 您好:
    先謝謝您的說明文章,對我編輯講義有很大的幫助。

    有一個問題想請問您,現在我在一份講義中,在兩個不同地方作了兩個自我評量,而第二個自我評量參考答案之編號會接續著上一個自我評量參考答案的題目數,例如第一個自我評量有8題(編號1~8),自我評量參考答案就會從1~8,而第二個自我評量有5題(編號1~5),自我評量參考答案會從9~13。想請問這該如何解決?謝謝

    回覆刪除
    回覆
    1. 因為我們是用 nocounter 這個計數器作為參考答案的題號, 所以要在答案之前放入 \setcounter{nocounter}{1} 就可以讓編號從 1 開始了

      刪除
    2. 謝謝您,最後這樣就成功從1開始了,謝謝摟
      \begin{assesssment}
      \setcounter{nocounter}{1}
      \item ...
      \item ...
      \item ...
      \end{assessment}

      刪除